37 auto const stateLabelPreservation = resolveStateLabelPreservationOption(options.stateLabelPreservation, !formulas.empty());
40 for (
auto const& f : formulas) {
48 for (
auto const& l : f->getAtomicLabelFormulas()) {
51 for (
auto const& e : f->getAtomicExpressionFormulas()) {
56 [
this](std::string
const& label) { return model.getStateLabeling().containsLabel(label); }),
57 "Formula " << *f <<
" uses a label that is not known in the model.");
59 [
this](std::string
const& rew) { return model.getRewardModels().contains(rew); }),
60 "Formula " << *f <<
" uses a reward model that is not known in the model.");
64 for (
auto const& label : model.getStateLabeling().getLabels()) {
65 if (label ==
"init") {
73 if (options.preserveAllRewards.value_or(formulas.empty())) {
74 for (
auto const& [name, rm] : model.getRewardModels()) {
79 if (options.preserveChoiceLabels && model.hasChoiceLabeling()) {
80 for (
auto const& label : model.getChoiceLabeling().getLabels()) {
89 std::vector<std::shared_ptr<storm::logic::Formula const>>
const& formulas)
90 : model(model), options(options), formulas(formulas) {
93 STORM_LOG_THROW(model.isOfType(storm::models::ModelType::Dtmc) || model.isOfType(storm::models::ModelType::Ctmc),
94 storm::exceptions::NotSupportedException,
95 "Weak bisimulation is only implemented for DTMCs and CTMCs, but the given model is of type " << model.getType() <<
".");
97 storm::logic::FragmentSpecification preservedFragment = storm::logic::propositional();
98 preservedFragment.setProbabilityOperatorsAllowed(true)
99 .setUntilFormulasAllowed(true)
100 .setReachabilityProbabilityFormulasAllowed(true)
101 .setGloballyFormulasAllowed(true)
102 .setRewardOperatorsAllowed(true)
103 .setReachabilityRewardFormulasAllowed(true);
104 if (model.isOfType(storm::models::ModelType::Ctmc)) {
107 preservedFragment.setBoundedUntilFormulasAllowed(true)
108 .setTimeBoundedUntilFormulasAllowed(true)
109 .setStepBoundedUntilFormulasAllowed(false)
110 .setRewardOperatorsAllowed(false)
111 .setTimeOperatorsAllowed(true)
112 .setReachbilityTimeFormulasAllowed(true);
114 std::string unsupportedFormulas;
115 for (
auto const& f : this->formulas) {
116 if (!f->isInFragment(preservedFragment)) {
117 unsupportedFormulas += (unsupportedFormulas.empty() ?
"" :
", ") + f->toString();
120 STORM_LOG_THROW(unsupportedFormulas.empty(), storm::exceptions::IllegalFunctionCallException,
121 "The formula(s) " << unsupportedFormulas <<
" are not known to be preserved by weak bisimulation.");
124 auto const preservationInformation = getPreservationInformation();
125 auto const stateLabelPreservation = resolveStateLabelPreservationOption(options.stateLabelPreservation, !formulas.empty());
130 for (
auto const& label : preservationInformation.preservedStateLabels) {
131 preservedStateAnnotations.addBoolean(model.getStateLabeling().getStates(label));
136 PropositionalChecker propositionalChecker(model);
137 auto const collectPropositionalStates = [
this, &propositionalChecker](
storm::logic::Formula const& subformula) {
138 if (!propositionalChecker.canHandle(subformula)) {
141 }
else if (subformula.isBooleanLiteralFormula()) {
145 auto states = propositionalChecker.check(subformula)
146 ->template asExplicitQualitativeCheckResult<typename PropositionalChecker::SolutionType>()
147 .getTruthValuesVector();
149 if (!states.empty() && !states.full() &&
150 std::ranges::none_of(preservedStateAnnotations.getBooleans(), [&states](
auto const& b) { return b == states; })) {
151 preservedStateAnnotations.addBoolean(std::move(states));
155 for (
auto const& f : this->formulas) {
156 f->traverse(collectPropositionalStates);
161 if (preservationInformation.preservedStateLabels.contains(
"init")) {
162 preservedStateAnnotations.addBoolean(model.getStateLabeling().getStates(
"init"));
165 for (
auto const& label : preservationInformation.preservedChoiceLabels) {
166 STORM_LOG_ASSERT(model.hasChoiceLabeling(),
"Preserving choice labels is only possible if the model has a choice labeling.");
167 preservedChoiceAnnotations.addBoolean(model.getChoiceLabeling().getChoices(label));
169 if (model.hasChoiceOrigins() && options.preserveChoiceOrigins) {
170 preservedChoiceAnnotations.integers.emplace_back(model.getChoiceOrigins()->getIdentifiers());
172 for (
auto const& rewName : preservationInformation.preservedRewardModels) {
173 auto const& rewardModel = model.getRewardModel(rewName);
174 STORM_LOG_THROW(!rewardModel.hasTransitionRewards(), storm::exceptions::NotSupportedException,
175 "Bisimulation initialization does not handle transition rewards of reward model '" << rewName <<
"'.");
180 storm::exceptions::NotSupportedException,
181 "Weak bisimulation on CTMCs does not preserve the state-action rewards of reward model '" << rewName <<
"'.");
182 if (rewardModel.hasStateRewards()) {
183 preservedStateAnnotations.values.emplace_back(rewardModel.getStateRewardVector());
185 if (rewardModel.hasStateActionRewards()) {
186 preservedChoiceAnnotations.values.emplace_back(rewardModel.getStateActionRewardVector());
190 if (model.isOfType(MarkovAutomaton)) {
191 auto const& ma = model.template as<storm::models::sparse::MarkovAutomaton<ValueType>>();
192 preservedStateAnnotations.addBoolean(ma->getMarkovianStates());
193 preservedStateAnnotations.values.emplace_back(ma->getExitRates());
194 if (!ma->isClosed()) {
197 storm::storage::BitVector markovianChoices(model.getNumberOfChoices(),
false);
198 for (uint64_t
const markovianState : ma->getMarkovianStates()) {
199 markovianChoices.set(model.getTransitionMatrix().getRowGroupIndices()[markovianState]);
201 preservedChoiceAnnotations.addBoolean(std::move(markovianChoices));
204 STORM_LOG_THROW(model.isOfType(
Dtmc) || model.isOfType(Ctmc) || model.isOfType(
Mdp), storm::exceptions::NotSupportedException,
205 "Bisimulation initialization is not implemented for model type '" << model.getType() <<
"'.");
288 if (preservedChoiceAnnotations.empty() && (!options.actionSensitive || !model.isNondeterministicModel())) {
292 std::vector<uint64_t> auxVector;
295 Partition choicePartition(model.getNumberOfChoices());
296 if (options.actionSensitive && model.isNondeterministicModel()) {
297 auxVector.reserve(model.getNumberOfChoices());
299 for (uint64_t state = 0; state < model.getNumberOfStates(); ++state) {
300 for (uint64_t act = 0; act < model.getTransitionMatrix().getRowGroupSize(state); ++act) {
301 auxVector.push_back(act);
314 auxVector.resize(model.getNumberOfChoices());
315 uint64_t choiceClass = 0;
316 choicePartition.
forEachBlock([&choiceClass, &auxVector](
auto const& block) {
317 for (uint64_t
const choice : block) {
318 auxVector[choice] = choiceClass;
322 return std::optional<std::vector<uint64_t>>(std::move(auxVector));
328 Partition statePartition(model.getNumberOfStates());
329 if (choiceClasses && model.isNondeterministicModel()) {
330 auto const& groupIndices = model.getTransitionMatrix().getRowGroupIndices();
333 using Signature = std::vector<uint64_t>;
334 std::vector<uint64_t> stateActionSignature;
335 stateActionSignature.reserve(model.getNumberOfStates());
336 std::map<Signature, uint64_t> actionSignatureToIndex;
337 for (uint64_t state = 0; state < model.getNumberOfStates(); ++state) {
339 Signature actionSignature(choiceClasses->begin() + groupIndices[state], choiceClasses->begin() + groupIndices[state + 1]);
340 std::sort(actionSignature.begin(), actionSignature.end());
341 actionSignature.erase(std::unique(actionSignature.begin(), actionSignature.end()), actionSignature.end());
343 uint64_t
const signatureIndex = actionSignatureToIndex.emplace(actionSignature, actionSignatureToIndex.size()).first->second;
345 stateActionSignature.push_back(signatureIndex);
348 preservedStateAnnotations.applySplit(statePartition, tolerance, stateActionSignature);
349 }
else if (choiceClasses && !model.isNondeterministicModel()) {
350 STORM_LOG_ASSERT(choiceClasses->size() == model.getNumberOfStates(),
"Unexpected number of choice classes.");
352 preservedStateAnnotations.applySplit(statePartition, tolerance, choiceClasses.value());
354 preservedStateAnnotations.applySplit(statePartition, tolerance);
356 return statePartition;
417 STORM_LOG_ASSERT(!model.isNondeterministicModel(),
"Weak bisimulation is only supported for deterministic models.");
418 uint64_t
const numberOfStates = model.getNumberOfStates();
423 if (model.isDiscreteTimeModel()) {
425 stepSensitiveStates |= ~model.getRewardModel(rewardModelName).getStatesWithZeroReward(model.getTransitionMatrix());
431 [
this](std::string
const& rewardModelName) {
432 auto const& rewardModel = model.getRewardModel(rewardModelName);
433 return !rewardModel.hasStateActionRewards() && !rewardModel.hasTransitionRewards();
435 "Weak bisimulation on a continuous-time model preserves a reward model with state-action or transition rewards, which the "
436 "constructor should have rejected.");
443 if (!divergentStates.
empty()) {
445 if (block.size() > 1) {
446 partition.
splitBlockByPredicate(block, [&divergentStates](uint64_t
const state) {
return divergentStates.
get(state); });