Skip to content

merging of equivalent states as preprocessing - #1016

Open
tquatmann wants to merge 11 commits into
stormchecker:masterfrom
tquatmann:feature/goal-state-merge
Open

merging of equivalent states as preprocessing#1016
tquatmann wants to merge 11 commits into
stormchecker:masterfrom
tquatmann:feature/goal-state-merge

Conversation

@tquatmann

Copy link
Copy Markdown
Contributor
  • extends the GoalStateMerger so that it can now merge states based on an input formula (e.g. all prob0/prob1 states
  • adds a CLI option --merge-equivalent-states to invoke that as a preprocessing
  • Simplifies SparseParametricModelSimplifier by calling the new GoalStateMerger

Relates to #996

* extends the GoalStateMerger so that it can now merge states based on an input formula (e.g. all prob0/prob1 states
* adds a CLI option --merge-equivalent-states to invoke that as a preprocessing
* Simplifies SparseParametricModelSimplifier by calling the new GoalStateMerger
Comment thread src/storm-pars/transformer/SparseParametricModelSimplifier.cpp Outdated
@tquatmann tquatmann added this to the 1.15 milestone Aug 17, 2026
…merge

# Conflicts:
#	src/storm-pars/transformer/SparseParametricDtmcSimplifier.cpp
#	src/storm-pars/transformer/SparseParametricMdpSimplifier.cpp
#	src/storm/transformer/GoalStateMerger.cpp
@tquatmann
tquatmann requested a balanced review from Copilot September 4, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Extends goal/sink state merging to work as a formula-driven preprocessing step and consolidates parametric model simplification around the updated GoalStateMerger.

Changes:

  • Generalize GoalStateMerger to merge equivalent states based on a supported input formula and expose it via API/CLI preprocessing.
  • Add a new CLI option --merge-equivalent-states and wire it into the preprocessing pipeline.
  • Consolidate parametric DTMC/MDP simplifiers into SparseParametricModelSimplifier, updating tests and call sites accordingly.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/test/storm/transformer/GoalStateMergerTest.cpp Adds regression tests validating formula-driven merging preserves results.
src/test/storm-pars/modelchecker/region/monotonicity/OrderExtenderTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/modelchecker/region/monotonicity/MonotonicityHelperTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/modelchecker/region/monotonicity/MonotonicityCheckerTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/modelchecker/region/monotonicity/AssumptionMakerTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/modelchecker/region/monotonicity/AssumptionCheckerTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/modelchecker/SparseDtmcParameterLiftingMonotonicityTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/derivative/SparseDerivativeInstantiationModelCheckerTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/test/storm-pars/derivative/GradientDescentInstantiationSearcherTest.cpp Switches to consolidated SparseParametricModelSimplifier.
src/storm/utility/graph.cpp Adds missing template instantiation for performProb1A (rational reward model).
src/storm/transformer/GoalStateMerger.h Refactors merger to be value-type based and adds mergeForFormula.
src/storm/transformer/GoalStateMerger.cpp Implements formula-driven merging and model-component rebuild incl. valuations/labels/origins.
src/storm/settings/modules/TransformationSettings.h Declares new merge-equivalent-states setting.
src/storm/settings/modules/TransformationSettings.cpp Registers new CLI option and getter.
src/storm/modelchecker/multiobjective/pcaa/StandardPcaaWeightVectorChecker.cpp Adapts to new GoalStateMerger<ValueType> result model type.
src/storm/modelchecker/multiobjective/constraintbased/SparseCbQuery.cpp Adapts to new GoalStateMerger<ValueType> result model type.
src/storm/api/transformation.h Adds mergeEquivalentStatesForFormula API helper around GoalStateMerger.
src/storm-pars/transformer/SparseParametricModelSimplifier.h Removes virtual interface pieces; consolidates simplifier roles.
src/storm-pars/transformer/SparseParametricModelSimplifier.cpp Re-implements simplification pipeline using GoalStateMerger + eliminations.
src/storm-pars/transformer/SparseParametricMdpSimplifier.h Removed (functionality consolidated).
src/storm-pars/transformer/SparseParametricMdpSimplifier.cpp Removed (functionality consolidated).
src/storm-pars/transformer/SparseParametricDtmcSimplifier.h Removed (functionality consolidated).
src/storm-pars/transformer/SparseParametricDtmcSimplifier.cpp Removed (functionality consolidated).
src/storm-pars/modelchecker/region/ValidatingSparseParameterLiftingModelChecker.cpp Uses consolidated simplifier instead of DTMC/MDP variants.
src/storm-pars/modelchecker/region/SparseMdpParameterLiftingModelChecker.cpp Uses consolidated simplifier instead of MDP variant.
src/storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.cpp Uses consolidated simplifier instead of DTMC variant.
src/storm-pars-cli/storm-pars.cpp Uses consolidated simplifier in CLI model simplification path.
src/storm-pars-cli/sampling.h Removes includes of removed DTMC/MDP simplifiers.
src/storm-cli-utilities/model-handling.h Wires --merge-equivalent-states preprocessing before bisimulation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/storm-pars/transformer/SparseParametricModelSimplifier.cpp Outdated
Comment thread src/storm/transformer/GoalStateMerger.cpp Outdated
Comment thread src/storm/transformer/GoalStateMerger.cpp Outdated
Comment thread src/storm/transformer/GoalStateMerger.cpp Outdated
Comment thread src/storm/api/transformation.h
Comment thread src/test/storm/transformer/GoalStateMergerTest.cpp Outdated
Comment thread src/storm/transformer/GoalStateMerger.cpp Outdated

@volkm volkm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

I only have minor mostly code-style comments. Feel free to ignore some of them.

}

/*!
* Identifies states of the given model and formula that are equivalent and merges them into a single state, yielding a (potentially smaller) model on which the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Identifies states of the given model and formula that are equivalent and merges them into a single state, yielding a (potentially smaller) model on which the
* Identifies states of the given model that are equivalent w.r.t. to the given formula and merges them into a single state, yielding a (potentially smaller) model on which the

template<typename ValueType>
std::shared_ptr<storm::models::sparse::Model<ValueType>> mergeEquivalentStatesForFormula(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model,
storm::logic::Formula const& formula) {
// storm::transformer::GoalStateMerger is only instantiated for double, rational number, and rational function value types.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the instantiations are limited, maybe move the function to a cpp and explicitly instantiate it. This would avoid including RationalFunctionForward (which might be slightly better for build times?).

Comment on lines +164 to +165
if (model->isOfType(storm::models::ModelType::Dtmc) || model->isOfType(storm::models::ModelType::Ctmc) ||
model->isOfType(storm::models::ModelType::Mdp) || model->isOfType(storm::models::ModelType::MarkovAutomaton)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not throw a NotSupportedException instead of returning a nullptr?

Comment on lines +744 to +745
} else if (auto mergedModel = storm::api::mergeEquivalentStatesForFormula<ValueType>(result.first, *formulas.front())) {
STORM_LOG_INFO("Merged target/sink states relevant for the considered property '" << *formulas.front() << "'.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this works, but I personally dislike assignments in conditions. Could we maybe explicitly check for the nullptr?

* * (if rewardModelName is given) the reward collected at the state is constant.
*
* The resulting model will only have the rewardModel with the provided name (or no reward model at all if no name was given).
* Labelings of eliminated states will be lost

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This contradictory when elimination only happens if no state label is defined. Do you mean choice labels here?

Comment on lines +46 to +49
bool const ok = modelVal == 0.0 ? mergedVal == 0 : std::abs((mergedVal - modelVal) / modelVal) <= 1e-6;
EXPECT_TRUE(ok) << "Relative difference between original model result (" << modelVal << ") and merged model result (" << mergedVal
<< ") is too high.\nFailed for model " << prismModelFile << " and formula " << *formulas.front()
<< " (dropUnreachableFromInit=" << dropUnreachableFromInit << ").";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also distinguish between modelVal == 0.0 and else, and then use EXPECT_NEAR(mergedVal, modelVal, 1e-6) (but this uses absolute precision)

std::optional<uint64_t> representative(storm::storage::BitVector const& b) {
if (auto it = b.begin(); it != b.end()) {
return *it;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use getNextSetIndex(0)? Or check for empty and otherwise return b.begin()? The current versions is not immediately understandable.

if (originalModel.hasStateValuations()) {
std::vector<uint64_t> newToOldStateIndices(maybeStates.begin(), maybeStates.end());
if (result.first.targetState.has_value()) {
STORM_LOG_ASSERT(result.first.targetState.value() == newToOldStateIndices.size(), "unexpected position of target state.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
STORM_LOG_ASSERT(result.first.targetState.value() == newToOldStateIndices.size(), "unexpected position of target state.");
STORM_LOG_ASSERT(result.first.targetState.value() == newToOldStateIndices.size(), "Unexpected position of target state.");

newToOldStateIndices.push_back(representativeTargetState.value_or(*targetStates.begin()));
}
if (result.first.sinkState.has_value()) {
STORM_LOG_ASSERT(result.first.sinkState.value() == newToOldStateIndices.size(), "unexpected position of sink state.");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
STORM_LOG_ASSERT(result.first.sinkState.value() == newToOldStateIndices.size(), "unexpected position of sink state.");
STORM_LOG_ASSERT(result.first.sinkState.value() == newToOldStateIndices.size(), "Unexpected position of sink state.");

std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01;
if (!originalModel.isNondeterministicModel()) {
statesWithProbability01 = storm::utility::graph::performProb01(backwardTransitions, *phiStates, *psiStates);
} else if (std::optional<bool> const minimizing = isMinimizing(formula); !minimizing.has_value()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

!isMinimizing(formula).has_value() would not work?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants