Conversation
…otients The Dd-format quotient extraction (used, e.g., by the hybrid engine) built the quotient's transition matrix directly from the already-normalized model transition matrix. For Markov automata, this matrix stores probabilities rather than rates for Markovian choices, so passing it unchanged into the quotient's MarkovAutomaton constructor made computeMarkovianInfo() re-derive an incorrect exit rate of 1 for every Markovian state, silently corrupting all time- and reward-based properties (e.g. Tmin/Tmax) after bisimulation minimization. Fixes stormchecker#498. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The original-variable path can still lose Markovian exit rates, and its regression coverage is missing.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
This PR fixes incorrect Markovian exit rates in DD-based bisimulation quotients and adds regression coverage.
Changes:
- Restores rate-weighted Markovian transitions.
- Updates related sanity checks.
- Adds a regression model and
Tmintest.
File summaries
| File | Reviewed change |
|---|---|
src/test/storm/storage/SymbolicBisimulationDecompositionTest.cpp |
Adds exit-rate regression coverage; original-variable mode remains untested. |
src/storm/storage/dd/bisimulation/QuotientExtractor.cpp |
Restores rates during quotient extraction; the original-variable conversion path still requires preserving exit rates. |
resources/examples/testfiles/ma/bisimulation_exit_rates.ma |
Adds the regression Markov automaton model. |
Review details
Suppressed comments (2)
src/storm/storage/dd/bisimulation/QuotientExtractor.cpp:1287
- The new regression test only uses the default
BisimulationOptions::useOriginalVariables = false, so it exercisesextractQuotientUsingBlockVariablesbut never thisextractQuotientUsingOriginalVariablespath. A future regression in the explicitly supported--bisimulation:origvarsmode would therefore remain undetected even though this fix duplicates the rate-scaling logic here. Please parameterize the regression over both extraction modes or add a second run withuseOriginalVariables = true.
storm::dd::Add<DdType, ValueType> transitionMatrix = model.getTransitionMatrix();
if (modelType == storm::models::ModelType::MarkovAutomaton) {
auto const& markovAutomaton = *model.template as<storm::models::symbolic::MarkovAutomaton<DdType, ValueType>>();
transitionMatrix = transitionMatrix * markovAutomaton.getMarkovianMarker().ite(markovAutomaton.getExitRateVector(),
model.getManager().template getAddOne<ValueType>());
src/test/storm/storage/SymbolicBisimulationDecompositionTest.cpp:313
- This regression test uses the default
BisimulationOptions, so it exercises onlyextractQuotientUsingBlockVariables. The PR also changesextractQuotientUsingOriginalVariables, used by--bisimulation:origvars, but there is no test coverage foruseOriginalVariables; add the same time assertion with that option enabled so the second extraction path cannot regress independently.
std::shared_ptr<storm::models::Model<double>> quotient = decomposition.getQuotient(storm::dd::bisimulation::QuotientFormat::Dd);
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Addresses Copilot review feedback on PR stormchecker#1050: the exit-rate regression test previously only exercised extractQuotientUsingBlockVariables (the default). Parameterize it over BisimulationOptions::useOriginalVariables so extractQuotientUsingOriginalVariables (--bisimulation:origvars) is covered as well. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Expression conversion can reintroduce reserved init/deadlock labels and reject valid models in five files.
Review details
Suppressed comments (5)
src/storm/models/symbolic/Ctmc.cpp:105
- This re-adds reserved labels after they were intentionally erased above. A model may legally have an expression label named
deadlock;getStates("deadlock")resolves to the special deadlock BDD, and the BDD-label constructor then rejects the resultingdeadlockentry as an illegal custom label and throws. Skipinit/deadlockentries when materializing expression labels.
for (auto const& labelExpressionPair : this->getLabelToExpressionMap()) {
newLabelToBddMap.emplace(labelExpressionPair.first, this->getStates(labelExpressionPair.first));
}
src/storm/models/symbolic/Dtmc.cpp:61
- This re-adds reserved labels after they were intentionally erased above. A model may legally have an expression label named
deadlock;getStates("deadlock")resolves to the special deadlock BDD, and the BDD-label constructor then rejects the resultingdeadlockentry as an illegal custom label and throws. Skipinit/deadlockentries when materializing expression labels.
for (auto const& labelExpressionPair : this->getLabelToExpressionMap()) {
newLabelToBddMap.emplace(labelExpressionPair.first, this->getStates(labelExpressionPair.first));
}
src/storm/models/symbolic/MarkovAutomaton.cpp:145
- This re-adds reserved labels after they were intentionally erased above. A model may legally have an expression label named
deadlock;getStates("deadlock")resolves to the special deadlock BDD, and the BDD-label constructor then rejects the resultingdeadlockentry as an illegal custom label and throws. Skipinit/deadlockentries when materializing expression labels.
for (auto const& labelExpressionPair : this->getLabelToExpressionMap()) {
newLabelToBddMap.emplace(labelExpressionPair.first, this->getStates(labelExpressionPair.first));
}
src/storm/models/symbolic/Mdp.cpp:57
- This re-adds reserved labels after they were intentionally erased above. A model may legally have an expression label named
deadlock;getStates("deadlock")resolves to the special deadlock BDD, and the BDD-label constructor then rejects the resultingdeadlockentry as an illegal custom label and throws. Skipinit/deadlockentries when materializing expression labels.
for (auto const& labelExpressionPair : this->getLabelToExpressionMap()) {
newLabelToBddMap.emplace(labelExpressionPair.first, this->getStates(labelExpressionPair.first));
}
src/storm/models/symbolic/StochasticTwoPlayerGame.cpp:94
- This re-adds reserved labels after they were intentionally erased above. A model may legally have an expression label named
deadlock;getStates("deadlock")resolves to the special deadlock BDD, and the BDD-label constructor then rejects the resultingdeadlockentry as an illegal custom label and throws. Skipinit/deadlockentries when materializing expression labels.
for (auto const& labelExpressionPair : this->getLabelToExpressionMap()) {
newLabelToBddMap.emplace(labelExpressionPair.first, this->getStates(labelExpressionPair.first));
}
- Files reviewed: 9/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
volkm
left a comment
There was a problem hiding this comment.
LGTM. Thanks for fixing this important issue.
| auto doubleMa = exactMa->template toValueType<double>(); | ||
| ASSERT_TRUE(doubleMa->hasLabel("done")); | ||
|
|
||
| checkExitRatePreserved(*doubleMa); |
There was a problem hiding this comment.
Could we also check the same for exact?
|
The handling of rate matrix vs probability matrix reminded me of #826. If we have a CTMC representation as probability matrix + exit rate we need to be careful of this issue as well. I think for now it is considered a rate matrix. |
Summary
Fixes #498: bisimulation minimization changed model checking results for Markov automata.
Root cause: DD-based bisimulation quotient extraction (the
Ddquotient format, used e.g. by thehybridengine) built the quotient's transition matrix directly from the model's already-normalized transition matrix. For Markov automata, this matrix stores normalized probabilities for Markovian choices — the actual rates live separately in the exit-rate vector. Passing this normalized matrix into the quotient'sMarkovAutomatonconstructor made it re-derive the exit rate from the (already-summing-to-1) matrix, collapsing every Markovian state's exit rate to1. This silently corrupted all time- and reward-based properties (e.g.Tmin/Tmax) after--bisimulation, while probability properties stayed correct — matching the exact symptom reported in the issue.Fix: scale Markovian choices back to their original (unnormalized) rates before building the quotient's transition matrix, so the quotient's re-derived exit rates come out correct. Adjusted the affected sanity-check assertions accordingly (they assumed a fully-normalized matrix, which is no longer the case for Markovian rows).
Test plan
MarkovAutomatonExitRates) with a small MA model (resources/examples/testfiles/ma/bisimulation_exit_rates.ma) whose bisimilar states get merged; verified it fails without the fix (wrong result1instead of0.25) and passes with it.hybrid --bisimulation,hybrid --bisimulation --bisimulation:origvars, anddd-to-sparse --bisimulation:quot ddall now agree with the unminimized result.test-storageandtest-modelchecker-cslsuites pass.🤖 Generated with Claude Code