Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
BisimulationDecomposition.cpp
Go to the documentation of this file.
2
3#include <chrono>
4
21
22namespace storm {
23namespace storage {
24
25using namespace bisimulation;
26
27template<typename ModelType, typename BlockDataType>
29 this->preserveSingleFormula(model, formula);
30}
31
32template<typename ModelType, typename BlockDataType>
34 std::vector<std::shared_ptr<storm::logic::Formula const>> const& formulas)
35 : Options() {
36 if (formulas.empty()) {
37 this->respectedAtomicPropositions = model.getStateLabeling().getLabels();
38 this->keepRewards = true;
39 }
40 if (formulas.size() == 1) {
41 this->preserveSingleFormula(model, *formulas.front());
42 } else {
43 for (auto const& formula : formulas) {
44 preserveFormula(*formula);
45 }
46 }
47}
48
49template<typename ModelType, typename BlockDataType>
51 : measureDrivenInitialPartition(false),
52 phiStates(),
53 psiStates(),
54 respectedAtomicPropositions(),
55 buildQuotient(true),
56 keepRewards(false),
58 bounded(false),
59 discounted(false) {
60 // Intentionally left empty.
61}
62
63template<typename ModelType, typename BlockDataType>
65 // Disable the measure driven initial partition.
66 measureDrivenInitialPartition = false;
67 phiStates = boost::none;
68 psiStates = boost::none;
69
70 // Retrieve information about formula.
72
73 // Preserve rewards if necessary.
74 keepRewards = keepRewards || info.containsRewardOperator() || info.containsRewardBoundedFormula();
75
76 // Preserve bounded properties if necessary.
77 bounded = bounded || (info.containsBoundedUntilFormula() || info.containsNextFormula() || info.containsCumulativeRewardFormula());
78
79 // Preserve discounted properties if necessary.
80 discounted = discounted || info.containsDiscountFormula();
82 // Compute the relevant labels and expressions.
83 this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas());
84}
85
86template<typename ModelType, typename BlockDataType>
88 // Retrieve information about formula.
90
91 keepRewards = info.containsRewardOperator() || info.containsRewardBoundedFormula();
92
93 // We need to preserve bounded properties iff the formula contains a bounded until or a next subformula.
95
96 // We need to preserve discounting iff the formula contains a discounted subformula
97 discounted = info.containsDiscountFormula();
98
99 // Compute the relevant labels and expressions.
100 this->addToRespectedAtomicPropositions(formula.getAtomicExpressionFormulas(), formula.getAtomicLabelFormulas());
101
102 // Check whether measure driven initial partition is possible and, if so, set it.
103 this->checkAndSetMeasureDrivenInitialPartition(model, formula);
104}
105
106template<typename ModelType, typename BlockDataType>
108 storm::logic::Formula const& formula) {
109 std::shared_ptr<storm::logic::Formula const> newFormula = formula.asSharedPointer();
110
111 if (formula.isProbabilityOperatorFormula()) {
113 optimalityType = formula.asProbabilityOperatorFormula().getOptimalityType();
114 } else if (formula.asProbabilityOperatorFormula().hasBound()) {
116 if (comparisonType == storm::logic::ComparisonType::Less || comparisonType == storm::logic::ComparisonType::LessEqual) {
117 optimalityType = OptimizationDirection::Maximize;
118 } else {
119 optimalityType = OptimizationDirection::Minimize;
120 }
121 }
123 } else if (formula.isRewardOperatorFormula()) {
125 optimalityType = formula.asRewardOperatorFormula().getOptimalityType();
126 } else if (formula.asRewardOperatorFormula().hasBound()) {
128 if (comparisonType == storm::logic::ComparisonType::Less || comparisonType == storm::logic::ComparisonType::LessEqual) {
129 optimalityType = OptimizationDirection::Maximize;
130 } else {
131 optimalityType = OptimizationDirection::Minimize;
132 }
133 }
134 newFormula = formula.asRewardOperatorFormula().getSubformula().asSharedPointer();
135 }
136
137 std::shared_ptr<storm::logic::Formula const> leftSubformula = std::make_shared<storm::logic::BooleanLiteralFormula>(true);
138 std::shared_ptr<storm::logic::Formula const> rightSubformula;
139 if (newFormula->isUntilFormula()) {
140 leftSubformula = newFormula->asUntilFormula().getLeftSubformula().asSharedPointer();
141 rightSubformula = newFormula->asUntilFormula().getRightSubformula().asSharedPointer();
142 if (leftSubformula->isInFragment(storm::logic::propositional()) && rightSubformula->isInFragment(storm::logic::propositional())) {
143 measureDrivenInitialPartition = true;
144 }
145 } else if (newFormula->isEventuallyFormula()) {
146 rightSubformula = newFormula->asEventuallyFormula().getSubformula().asSharedPointer();
147 if (rightSubformula->isInFragment(storm::logic::propositional())) {
148 measureDrivenInitialPartition = true;
149 }
150 }
151
152 if (measureDrivenInitialPartition) {
154 std::unique_ptr<storm::modelchecker::CheckResult> phiStatesCheckResult = checker.check(*leftSubformula);
155 std::unique_ptr<storm::modelchecker::CheckResult> psiStatesCheckResult = checker.check(*rightSubformula);
156 phiStates = phiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector();
157 psiStates = psiStatesCheckResult->asExplicitQualitativeCheckResult().getTruthValuesVector();
158 } else {
159 optimalityType = boost::none;
160 }
161}
162
163template<typename ModelType, typename BlockDataType>
164void BisimulationDecomposition<ModelType, BlockDataType>::Options::addToRespectedAtomicPropositions(
165 std::vector<std::shared_ptr<storm::logic::AtomicExpressionFormula const>> const& expressions,
166 std::vector<std::shared_ptr<storm::logic::AtomicLabelFormula const>> const& labels) {
167 std::set<std::string> labelsToRespect;
168 for (auto const& labelFormula : labels) {
169 labelsToRespect.insert(labelFormula->getLabel());
170 }
171 for (auto const& expressionFormula : expressions) {
172 labelsToRespect.insert(expressionFormula->toString());
173 }
174 if (!respectedAtomicPropositions) {
175 respectedAtomicPropositions = labelsToRespect;
176 } else {
177 respectedAtomicPropositions.get().insert(labelsToRespect.begin(), labelsToRespect.end());
178 }
179}
180
181template<typename ModelType, typename BlockDataType>
183 : BisimulationDecomposition(model, model.getBackwardTransitions(), options) {
184 // Intentionally left empty.
185}
186
187template<typename ModelType, typename BlockDataType>
189 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
190 Options const& options)
191 : model(model), backwardTransitions(backwardTransitions), options(options), partition(), comparator(options.getTolerance()), quotient(nullptr) {
192 STORM_LOG_THROW(!options.getKeepRewards() || !model.hasRewardModel() || model.hasUniqueRewardModel(), storm::exceptions::IllegalFunctionCallException,
193 "Bisimulation currently only supports models with at most one reward model.");
194 STORM_LOG_THROW(!options.getKeepRewards() || !model.hasRewardModel() || !model.getUniqueRewardModel().hasTransitionRewards(),
195 storm::exceptions::IllegalFunctionCallException,
196 "Bisimulation is currently supported for models with state or action rewards only. Consider converting the transition rewards to state "
197 "rewards (via suitable function calls).");
198 STORM_LOG_THROW(options.getType() != BisimulationType::Weak || !options.getBounded(), storm::exceptions::IllegalFunctionCallException,
199 "Weak bisimulation cannot preserve bounded properties.");
200 STORM_LOG_THROW(options.getType() != BisimulationType::Weak || !options.getDiscounted(), storm::exceptions::IllegalFunctionCallException,
201 "Weak bisimulation cannot preserve discounted properties.");
202
203 // Fix the respected atomic propositions if they were not explicitly given.
205 this->options.respectedAtomicPropositions = model.getStateLabeling().getLabels();
206 }
207}
209template<typename ModelType, typename BlockDataType>
211 std::chrono::high_resolution_clock::time_point totalStart = std::chrono::high_resolution_clock::now();
212
213 std::chrono::high_resolution_clock::time_point initialPartitionStart = std::chrono::high_resolution_clock::now();
214 // initialize the initial partition.
215 if (options.measureDrivenInitialPartition) {
216 STORM_LOG_THROW(options.phiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without phi states.");
217 STORM_LOG_THROW(options.psiStates, storm::exceptions::InvalidOptionException, "Unable to compute measure-driven initial partition without psi states.");
218 this->initializeMeasureDrivenPartition();
219 } else {
220 this->initializeLabelBasedPartition();
221 }
222 STORM_LOG_WARN_COND(partition.size() > 1, "Initial partition consists only of a single block.");
223 std::chrono::high_resolution_clock::duration initialPartitionTime = std::chrono::high_resolution_clock::now() - initialPartitionStart;
225 this->initialize();
226
227 std::chrono::high_resolution_clock::time_point refinementStart = std::chrono::high_resolution_clock::now();
228 this->performPartitionRefinement();
229 std::chrono::high_resolution_clock::duration refinementTime = std::chrono::high_resolution_clock::now() - refinementStart;
230
231 std::chrono::high_resolution_clock::time_point extractionStart = std::chrono::high_resolution_clock::now();
232 this->extractDecompositionBlocks();
233 std::chrono::high_resolution_clock::duration extractionTime = std::chrono::high_resolution_clock::now() - extractionStart;
234
235 std::chrono::high_resolution_clock::time_point quotientBuildStart = std::chrono::high_resolution_clock::now();
236 if (options.buildQuotient) {
237 this->buildQuotient();
238 }
239 std::chrono::high_resolution_clock::duration quotientBuildTime = std::chrono::high_resolution_clock::now() - quotientBuildStart;
240
241 std::chrono::high_resolution_clock::duration totalTime = std::chrono::high_resolution_clock::now() - totalStart;
242
243 if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) {
244 std::chrono::milliseconds initialPartitionTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(initialPartitionTime);
245 std::chrono::milliseconds refinementTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(refinementTime);
246 std::chrono::milliseconds extractionTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(extractionTime);
247 std::chrono::milliseconds quotientBuildTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(quotientBuildTime);
248 std::chrono::milliseconds totalTimeInMilliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(totalTime);
249 std::cout << "\nTime breakdown:\n";
250 std::cout << " * time for initial partition: " << initialPartitionTimeInMilliseconds.count() << "ms\n";
251 std::cout << " * time for partitioning: " << refinementTimeInMilliseconds.count() << "ms\n";
252 std::cout << " * time for extraction: " << extractionTimeInMilliseconds.count() << "ms\n";
253 std::cout << " * time for building quotient: " << quotientBuildTimeInMilliseconds.count() << "ms\n";
254 std::cout << "------------------------------------------\n";
255 std::cout << " * total time: " << totalTimeInMilliseconds.count() << "ms\n\n";
256 }
258
259template<typename ModelType, typename BlockDataType>
261 // Insert all blocks into the splitter queue as a (potential) splitter.
262 std::vector<Block<BlockDataType>*> splitterQueue;
263 std::for_each(partition.getBlocks().begin(), partition.getBlocks().end(), [&](std::unique_ptr<Block<BlockDataType>> const& block) {
264 block->data().setSplitter();
265 splitterQueue.push_back(block.get());
266 });
267
268 // Then perform the actual splitting until there are no more splitters.
269 uint_fast64_t iterations = 0;
270 while (!splitterQueue.empty()) {
271 ++iterations;
272
273 // Get and prepare the next splitter.
274 // Sort the splitters according to their sizes to prefer small splitters. That is just a heuristic, but
275 // tends to work well.
276 std::sort(splitterQueue.begin(), splitterQueue.end(),
277 [](Block<BlockDataType> const* b1, Block<BlockDataType> const* b2) { return b1->getNumberOfStates() > b2->getNumberOfStates(); });
278 Block<BlockDataType>* splitter = splitterQueue.back();
279 splitterQueue.pop_back();
280 splitter->data().setSplitter(false);
281
282 // Now refine the partition using the current splitter.
283 refinePartitionBasedOnSplitter(*splitter, splitterQueue);
284
286 std::cout << "Performed " << iterations << " iterations of partition refinement before abort.\n";
287 STORM_LOG_THROW(false, storm::exceptions::AbortException, "Aborted in bisimulation computation.");
288 break;
289 }
291}
292
293template<typename ModelType, typename BlockDataType>
295 STORM_LOG_THROW(this->quotient != nullptr, storm::exceptions::IllegalFunctionCallException,
296 "Unable to retrieve quotient model from bisimulation decomposition, because it was not built.");
297 return this->quotient;
298}
299
300template<typename ModelType, typename BlockDataType>
302 auto const& rewardModel = model.getUniqueRewardModel();
303 if (rewardModel.hasStateRewards()) {
304 this->splitInitialPartitionBasedOnRewards(rewardModel.getStateRewardVector());
305 }
306 if (rewardModel.hasStateActionRewards()) {
307 if (model.isNondeterministicModel()) {
308 std::vector<std::set<ValueType>> actionRewards;
309 actionRewards.reserve(model.getNumberOfStates());
310 for (storm::storage::sparse::state_type state = 0; state < model.getNumberOfStates(); ++state) {
311 std::set<ValueType> rewardsAtState;
312 for (auto choice = model.getTransitionMatrix().getRowGroupIndices()[state];
313 choice < model.getTransitionMatrix().getRowGroupIndices()[state + 1]; ++choice) {
314 rewardsAtState.insert(rewardModel.getStateActionReward(choice));
315 }
316 actionRewards.push_back(std::move(rewardsAtState));
317 }
318 this->splitInitialPartitionBasedOnActionRewards(actionRewards);
319 } else {
320 this->splitInitialPartitionBasedOnRewards(rewardModel.getStateActionRewardVector());
321 }
322 }
323}
324
325template<typename ModelType, typename BlockDataType>
327 partition.split([&rewardVector](storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) {
328 return rewardVector[a] < rewardVector[b];
329 });
330}
331
332template<typename ModelType, typename BlockDataType>
334 partition.split([&actionRewards](storm::storage::sparse::state_type const& a, storm::storage::sparse::state_type const& b) {
335 return actionRewards[a] < actionRewards[b];
336 });
337}
338
339template<typename ModelType, typename BlockDataType>
341 partition = storm::storage::bisimulation::Partition<BlockDataType>(model.getNumberOfStates());
342
343 for (auto const& label : options.respectedAtomicPropositions.get()) {
344 if (label == "init") {
345 continue;
346 }
347 partition.splitStates(model.getStates(label));
348 }
349
350 // If the model has state rewards, we need to consider them, because otherwise reward properties are not
351 // preserved.
352 if (options.getKeepRewards() && model.hasRewardModel()) {
353 this->splitInitialPartitionBasedOnRewards();
354 }
355}
356
357template<typename ModelType, typename BlockDataType>
359 std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 = this->getStatesWithProbability01();
360
361 boost::optional<storm::storage::sparse::state_type> representativePsiState;
362 if (!options.psiStates.get().empty()) {
363 representativePsiState = *options.psiStates.get().begin();
364 }
365
367 model.getNumberOfStates(), statesWithProbability01.first,
368 options.getBounded() || options.getKeepRewards() ? options.psiStates.get() : statesWithProbability01.second, representativePsiState);
369
370 // If the model has state rewards, we need to consider them, because otherwise reward properties are not
371 // preserved.
372 if (options.getKeepRewards() && model.hasRewardModel()) {
373 this->splitInitialPartitionBasedOnRewards();
374 }
375}
376
377template<typename ModelType, typename BlockDataType>
379 // Intentionally left empty.
380}
381
382template<typename ModelType, typename BlockDataType>
384 // Now move the states from the internal partition into their final place in the decomposition. We do so in
385 // a way that maintains the block IDs as indices.
386 this->blocks.resize(partition.size());
387 for (auto const& blockPtr : partition.getBlocks()) {
388 // We need to sort the states to allow for rapid construction of the blocks.
389 partition.sortBlock(*blockPtr);
390
391 // Convert the state-value-pairs to states only.
392 this->blocks[blockPtr->getId()] = block_type(partition.begin(*blockPtr), partition.end(*blockPtr), true);
393 }
394}
395
399
403
407} // namespace storage
408} // namespace storm
RewardOperatorFormula & asRewardOperatorFormula()
Definition Formula.cpp:484
std::vector< std::shared_ptr< AtomicExpressionFormula const > > getAtomicExpressionFormulas() const
Definition Formula.cpp:500
virtual bool isProbabilityOperatorFormula() const
Definition Formula.cpp:180
ProbabilityOperatorFormula & asProbabilityOperatorFormula()
Definition Formula.cpp:476
std::vector< std::shared_ptr< AtomicLabelFormula const > > getAtomicLabelFormulas() const
Definition Formula.cpp:506
virtual bool isRewardOperatorFormula() const
Definition Formula.cpp:184
std::shared_ptr< Formula const > asSharedPointer()
Definition Formula.cpp:571
FormulaInformation info(bool recurseIntoOperators=true) const
Definition Formula.cpp:209
ComparisonType getComparisonType() const
storm::solver::OptimizationDirection const & getOptimalityType() const
Formula const & getSubformula() const
This class is the superclass of all decompositions of a sparse model into its bisimulation quotient.
virtual void buildQuotient()=0
Builds the quotient model based on the previously computed equivalence classes (stored in the blocks ...
std::shared_ptr< ModelType > getQuotient() const
Retrieves the quotient of the model under the computed bisimulation.
void performPartitionRefinement()
Performs the partition refinement on the model and thereby computes the equivalence classes under str...
virtual void splitInitialPartitionBasedOnRewards()
Splits the initial partition based on the (unique) reward model of the current model.
virtual void splitInitialPartitionBasedOnActionRewards(std::vector< std::set< ValueType > > const &rewardVector)
Splits the initial partition based on the given vector of action rewards.
BisimulationDecomposition(ModelType const &model, Options const &options)
Decomposes the given model into equivalance classes of a bisimulation.
void computeBisimulationDecomposition()
Computes the decomposition of the model into bisimulation equivalence classes.
void extractDecompositionBlocks()
Constructs the blocks of the decomposition object based on the current partition.
virtual void initializeMeasureDrivenPartition()
Creates the measure-driven initial partition for reaching psi states from phi states.
virtual void initializeLabelBasedPartition()
Initializes the initial partition based on all respected labels.
virtual void initialize()
A function that can initialize auxiliary data structures.
A class that holds a possibly non-square matrix in the compressed row storage format.
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:38
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
FragmentSpecification propositional()
bool isTerminate()
Check whether the program should terminate (due to some abort signal).
void preserveFormula(storm::logic::Formula const &formula)
Changes the options in a way that the given formula is preserved.
boost::optional< std::set< std::string > > respectedAtomicPropositions
An optional set of strings that indicate which of the atomic propositions of the model are to be resp...