Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
SparseLTLHelper.cpp
Go to the documentation of this file.
2
14
15namespace storm {
16namespace modelchecker {
17namespace helper {
18
19template<typename ValueType, bool Nondeterministic>
21 : _transitionMatrix(transitionMatrix) {
22 // Intentionally left empty.
23}
24
25template<typename ValueType, bool Nondeterministic>
28 STORM_LOG_ASSERT(this->isProduceSchedulerSet(), "Trying to get the produced optimal choices although no scheduler was requested.");
29 STORM_LOG_ASSERT(this->_schedulerHelper.is_initialized(),
30 "Trying to get the produced optimal choices but none were available. Was there a computation call before?");
31
32 return this->_schedulerHelper.get().extractScheduler(model, this->hasRelevantStates());
33}
34
35template<typename ValueType, bool Nondeterministic>
37 CheckFormulaCallback const& formulaChecker) {
38 // Replace state-subformulae by atomic propositions (APs)
40 std::shared_ptr<storm::logic::Formula> ltlFormula = storm::logic::ExtractMaximalStateFormulasVisitor::extract(formula, extracted);
41 STORM_LOG_ASSERT(ltlFormula->isPathFormula(), "Unexpected formula type.");
42
43 // Compute Satisfaction sets for the APs (which represent the state-subformulae
44 auto apSets = computeApSets(extracted, formulaChecker);
45
46 STORM_LOG_INFO("Computing LTL probabilities for formula with " << apSets.size() << " atomic proposition(s).");
47
48 // Compute the resulting LTL probabilities
49 return computeLTLProbabilities(env, ltlFormula->asPathFormula(), apSets);
50}
51
52template<typename ValueType, bool Nondeterministic>
53std::map<std::string, storm::storage::BitVector> SparseLTLHelper<ValueType, Nondeterministic>::computeApSets(
54 std::map<std::string, std::shared_ptr<storm::logic::Formula const>> const& extracted, CheckFormulaCallback const& formulaChecker) {
55 std::map<std::string, storm::storage::BitVector> apSets;
56 for (auto& p : extracted) {
57 STORM_LOG_DEBUG(" Computing satisfaction set for atomic proposition \"" << p.first << "\" <=> " << *p.second << "...");
58 apSets[p.first] = formulaChecker(*p.second);
59 }
60 return apSets;
61}
62
63template<typename ValueType, bool Nondeterministic>
65 storm::storage::SparseMatrix<ValueType> const& transitionMatrix,
66 storm::storage::SparseMatrix<ValueType> const& backwardTransitions,
68 STORM_LOG_INFO("Computing accepting states for acceptance condition " << *acceptance.getAcceptanceExpression());
69 if (acceptance.getAcceptanceExpression()->isTRUE()) {
70 STORM_LOG_INFO(" TRUE -> all states accepting (assumes no deadlock in the model)");
71 return storm::storage::BitVector(transitionMatrix.getRowGroupCount(), true);
72 } else if (acceptance.getAcceptanceExpression()->isFALSE()) {
73 STORM_LOG_INFO(" FALSE -> all states rejecting");
74 return storm::storage::BitVector(transitionMatrix.getRowGroupCount(), false);
75 }
76
77 std::vector<std::vector<automata::AcceptanceCondition::acceptance_expr::ptr>> dnf = acceptance.extractFromDNF();
78
79 storm::storage::BitVector acceptingStates(transitionMatrix.getRowGroupCount(), false);
80
81 std::size_t accMECs = 0;
82 std::size_t allMECs = 0;
83
84 // All accepting states will be on a MEC. For efficiency, we compute the MECs of the MDP first to restrict the possible candidates.
85 // Compute MECs for the entire MDP
86 storm::storage::MaximalEndComponentDecomposition<ValueType> mecs(transitionMatrix, backwardTransitions);
87 // Maps every state to the MEC it is in, or to InvalidMecIndex if it does not belong to any MEC.
88 std::vector<uint64_t> stateToMec(transitionMatrix.getRowGroupCount(), std::numeric_limits<uint64_t>::max());
89 // Contains states that are on a mec
90 storm::storage::BitVector mecStates(transitionMatrix.getRowGroupCount(), false);
91 for (uint64_t mec_counter = 0; auto const& mec : mecs) {
92 for (auto const& [state, _] : mec) {
93 stateToMec[state] = mec_counter;
94 mecStates.set(state, true);
95 }
96 ++mec_counter;
97 }
98
99 for (auto const& conjunction : dnf) {
100 // get the states of the mdp that (a) are on a MEC, (b) are not already known to be accepting, and (c) don't violate Fins of the conjunction
101 storm::storage::BitVector allowed = mecStates & ~acceptingStates;
102
103 if (allowed.empty()) {
104 break; // no more candidates
105 }
106
107 for (auto const& literal : conjunction) {
108 if (literal->isTRUE()) {
109 // skip
110 } else if (literal->isFALSE()) {
111 allowed.clear();
112 break;
113 } else if (literal->isAtom()) {
114 const cpphoafparser::AtomAcceptance& atom = literal->getAtom();
115 if (atom.getType() == cpphoafparser::AtomAcceptance::TEMPORAL_FIN) {
116 // only deal with FIN, ignore INF here
117 const storm::storage::BitVector& accSet = acceptance.getAcceptanceSet(atom.getAcceptanceSet());
118 if (atom.isNegated()) {
119 // allowed = allowed \ ~accSet = allowed & accSet
120 allowed &= accSet;
121 } else {
122 // allowed = allowed \ accSet = allowed & ~accSet
123 allowed &= ~accSet;
124 }
125 }
126 }
127 }
128
129 if (allowed.empty()) {
130 // skip
131 continue;
132 }
133
134 // Compute MECs in the allowed fragment
135 storm::storage::MaximalEndComponentDecomposition<ValueType> allowedECs(transitionMatrix, backwardTransitions, allowed);
136 allMECs += allowedECs.size();
137 for (const auto& ec : allowedECs) {
138 auto const representativeEcState = ec.begin()->first;
139 if (acceptingStates.get(representativeEcState)) {
140 // The ec is part of a mec that is already known to be accepting
141 continue;
142 }
143
144 bool accepting = true;
145 for (auto const& literal : conjunction) {
146 if (literal->isTRUE()) {
147 // skip
148
149 } else if (literal->isFALSE()) {
150 accepting = false;
151 break;
152 } else if (literal->isAtom()) {
153 const cpphoafparser::AtomAcceptance& atom = literal->getAtom();
154 const storm::storage::BitVector& accSet = acceptance.getAcceptanceSet(atom.getAcceptanceSet());
155 if (atom.getType() == cpphoafparser::AtomAcceptance::TEMPORAL_INF) {
156 if (atom.isNegated()) {
157 if (!ec.containsAnyState(~accSet)) {
158 accepting = false;
159 break;
160 }
161 } else {
162 if (!ec.containsAnyState(accSet)) {
163 accepting = false;
164 break;
165 }
166 }
167
168 } else if (atom.getType() == cpphoafparser::AtomAcceptance::TEMPORAL_FIN) {
169 // Do only sanity checks here.
170 STORM_LOG_ASSERT(atom.isNegated() ? !ec.containsAnyState(~accSet) : !ec.containsAnyState(accSet),
171 "EC contains Fin-states, which should have been removed");
172 }
173 }
174 }
175
176 if (accepting) {
177 accMECs++;
178
179 // get the MEC containing the current EC
180 auto const mec_index = stateToMec[representativeEcState];
181 STORM_LOG_ASSERT(mec_index < mecs.size(), "MEC index out of range.");
182 auto const& mec = mecs[mec_index];
183
184 // All states of the (global) mec are accepting since we can almost surely reach the inner ec
185 for (auto const& [state, _] : mec) {
186 acceptingStates.set(state);
187 }
188
189 if (this->isProduceSchedulerSet()) {
190 // save choices for states that weren't assigned to any other MEC yet.
191 this->_schedulerHelper.get().saveProductEcChoices(acceptance, ec, mec, conjunction, product);
192 }
193 }
194 }
195 }
196
197 STORM_LOG_INFO("Found " << acceptingStates.getNumberOfSetBits() << " states in " << accMECs << " accepting MECs (considered " << allMECs << " MECs).");
198
199 return acceptingStates;
200}
201
202template<typename ValueType, bool Nondeterministic>
203storm::storage::BitVector SparseLTLHelper<ValueType, Nondeterministic>::computeAcceptingBCCs(automata::AcceptanceCondition const& acceptance,
204 storm::storage::SparseMatrix<ValueType> const& transitionMatrix) {
206 transitionMatrix, storage::StronglyConnectedComponentDecompositionOptions().onlyBottomSccs().dropNaiveSccs());
207 storm::storage::BitVector acceptingStates(transitionMatrix.getRowGroupCount(), false);
208
209 std::size_t checkedBSCCs = 0, acceptingBSCCs = 0, acceptingBSCCStates = 0;
210 for (auto& scc : bottomSccs) {
211 checkedBSCCs++;
212 if (acceptance.isAccepting(scc)) {
213 acceptingBSCCs++;
214 for (auto& state : scc) {
215 acceptingStates.set(state);
216 acceptingBSCCStates++;
217 }
218 }
219 }
220 STORM_LOG_INFO("BSCC analysis: " << acceptingBSCCs << " of " << checkedBSCCs << " BSCCs were acceptingStates (" << acceptingBSCCStates
221 << " states in acceptingStates BSCCs).");
222 return acceptingStates;
223}
224
225template<typename ValueType, bool Nondeterministic>
227 Environment const& env, storm::automata::DeterministicAutomaton const& da, std::map<std::string, storm::storage::BitVector>& apSatSets) {
228 const storm::automata::APSet& apSet = da.getAPSet();
229
230 std::vector<storm::storage::BitVector> statesForAP;
231 for (const std::string& ap : apSet.getAPs()) {
232 auto it = apSatSets.find(ap);
233 STORM_LOG_THROW(it != apSatSets.end(), storm::exceptions::InvalidOperationException,
234 "Deterministic automaton has AP " << ap << ", does not appear in formula");
235
236 statesForAP.push_back(std::move(it->second));
237 }
238
239 storm::storage::BitVector statesOfInterest;
240
241 if (this->hasRelevantStates()) {
242 statesOfInterest = this->getRelevantStates();
243 } else {
244 // Product from all model states
245 statesOfInterest = storm::storage::BitVector(this->_transitionMatrix.getRowGroupCount(), true);
246 }
247
248 STORM_LOG_INFO("Building " + (Nondeterministic ? std::string("MDP-DA") : std::string("DTMC-DA")) + " product with deterministic automaton, starting from "
249 << statesOfInterest.getNumberOfSetBits() << " model states...");
250 transformer::DAProductBuilder productBuilder(da, statesForAP);
251
252 auto product = productBuilder.build<productModelType>(this->_transitionMatrix, statesOfInterest);
253
254 STORM_LOG_INFO("Product " + (Nondeterministic ? std::string("MDP-DA") : std::string("DTMC-DA")) + " has "
255 << product->getProductModel().getNumberOfStates() << " states and " << product->getProductModel().getNumberOfTransitions()
256 << " transitions.");
257
258 // Prepare scheduler
259 if (this->isProduceSchedulerSet()) {
260 STORM_LOG_THROW(Nondeterministic, storm::exceptions::InvalidOperationException, "Scheduler export only supported for nondeterministic models.");
261 this->_schedulerHelper.emplace(product->getProductModel().getNumberOfStates());
262 }
263
264 // Compute accepting states
265 storm::storage::BitVector acceptingStates;
266 if (Nondeterministic) {
267 STORM_LOG_INFO("Computing MECs and checking for acceptance...");
268 acceptingStates = computeAcceptingECs(*product->getAcceptance(), product->getProductModel().getTransitionMatrix(),
269 product->getProductModel().getBackwardTransitions(), product);
270
271 } else {
272 STORM_LOG_INFO("Computing BSCCs and checking for acceptance...");
273 acceptingStates = computeAcceptingBCCs(*product->getAcceptance(), product->getProductModel().getTransitionMatrix());
274 }
275
276 if (acceptingStates.empty()) {
277 STORM_LOG_INFO("No accepting states, skipping probability computation.");
278 if (this->isProduceSchedulerSet()) {
279 this->_schedulerHelper.get().setRandom();
280 }
281 std::vector<ValueType> numericResult(this->_transitionMatrix.getRowGroupCount(), storm::utility::zero<ValueType>());
282 return numericResult;
283 }
284
285 STORM_LOG_INFO("Computing probabilities for reaching accepting components...");
286
287 storm::storage::BitVector bvTrue(product->getProductModel().getNumberOfStates(), true);
288 storm::storage::BitVector soiProduct(product->getStatesOfInterest());
289
290 // Create goal for computeUntilProbabilities, always compute maximizing probabilities
292 if (this->isValueThresholdSet()) {
293 solveGoalProduct = storm::solver::SolveGoal<ValueType>(OptimizationDirection::Maximize, this->getValueThresholdComparisonType(),
294 this->getValueThresholdValue(), std::move(soiProduct));
295 } else {
296 solveGoalProduct = storm::solver::SolveGoal<ValueType>(OptimizationDirection::Maximize);
297 solveGoalProduct.setRelevantValues(std::move(soiProduct));
298 }
299
300 std::vector<ValueType> prodNumericResult;
301
302 if (Nondeterministic) {
305 env, std::move(solveGoalProduct), product->getProductModel().getTransitionMatrix(), product->getProductModel().getBackwardTransitions(), bvTrue,
306 acceptingStates, this->isQualitativeSet(),
307 this->isProduceSchedulerSet() // Whether to create memoryless scheduler for the Model-DA Product.
308 );
309 prodNumericResult = std::move(prodCheckResult.values);
310
311 if (this->isProduceSchedulerSet()) {
312 this->_schedulerHelper.get().prepareScheduler(da.getNumberOfStates(), acceptingStates, std::move(prodCheckResult.scheduler), productBuilder,
313 product, statesOfInterest, this->_transitionMatrix);
314 }
315
316 } else {
318 env, std::move(solveGoalProduct), product->getProductModel().getTransitionMatrix(), product->getProductModel().getBackwardTransitions(), bvTrue,
319 acceptingStates, this->isQualitativeSet());
320 }
321
322 std::vector<ValueType> numericResult = product->projectToOriginalModel(this->_transitionMatrix.getRowGroupCount(), prodNumericResult);
323
324 return numericResult;
325}
326
327template<typename ValueType, bool Nondeterministic>
329 std::map<std::string, storm::storage::BitVector>& apSatSets) {
330 std::shared_ptr<storm::logic::Formula const> ltlFormula;
331 STORM_LOG_THROW((!Nondeterministic) || this->isOptimizationDirectionSet(), storm::exceptions::InvalidPropertyException,
332 "Formula needs to specify whether minimal or maximal values are to be computed on nondeterministic model.");
333 if (Nondeterministic && this->getOptimizationDirection() == OptimizationDirection::Minimize) {
334 // negate formula in order to compute 1-Pmax[!formula]
335 ltlFormula = std::make_shared<storm::logic::UnaryBooleanPathFormula>(storm::logic::UnaryBooleanOperatorType::Not, formula.asSharedPointer());
336 STORM_LOG_INFO("Computing Pmin, proceeding with negated LTL formula.");
337 } else {
338 ltlFormula = formula.asSharedPointer();
339 }
340
341 STORM_LOG_INFO("Resulting LTL path formula: " << ltlFormula->toString());
342 STORM_LOG_INFO(" in prefix format: " << ltlFormula->toPrefixString());
343
344 // Convert LTL formula to a deterministic automaton
345 std::shared_ptr<storm::automata::DeterministicAutomaton> da;
346 if (env.modelchecker().isLtl2daToolSet()) {
347 // Use the external tool given via ltl2da
349 } else {
350 // Use the internal tool (Spot)
351 // For nondeterministic models the acceptance condition is transformed into DNF
352 da = storm::automata::LTL2DeterministicAutomaton::ltl2daSpot(*ltlFormula, Nondeterministic);
353 }
354
355 STORM_LOG_INFO("Deterministic automaton for LTL formula has " << da->getNumberOfStates() << " states, " << da->getAPSet().size()
356 << " atomic propositions and " << *da->getAcceptance()->getAcceptanceExpression()
357 << " as acceptance condition.\n");
358
359 std::vector<ValueType> numericResult = computeDAProductProbabilities(env, *da, apSatSets);
360
361 if (Nondeterministic && this->getOptimizationDirection() == OptimizationDirection::Minimize) {
362 // compute 1-Pmax[!fomula]
363 for (auto& value : numericResult) {
364 value = storm::utility::one<ValueType>() - value;
365 }
366 }
367
368 return numericResult;
369}
370
371template class SparseLTLHelper<double, false>;
372template class SparseLTLHelper<double, true>;
373
377
378} // namespace helper
379} // namespace modelchecker
380} // namespace storm
ModelCheckerEnvironment & modelchecker()
std::string const & getLtl2daTool() const
const std::vector< std::string > & getAPs() const
Definition APSet.cpp:44
storm::storage::BitVector & getAcceptanceSet(unsigned int index)
std::vector< std::vector< acceptance_expr::ptr > > extractFromDNF() const
acceptance_expr::ptr getAcceptanceExpression() const
static std::shared_ptr< DeterministicAutomaton > ltl2daExternalTool(storm::logic::Formula const &f, std::string ltl2daTool)
Converts an LTL formula into a deterministic omega-automaton using an external LTL2DA tool.
static std::shared_ptr< DeterministicAutomaton > ltl2daSpot(storm::logic::Formula const &f, bool dnf)
Converts an LTL formula into a deterministic omega-automaton using the internal LTL2DA tool "Spot".
static std::shared_ptr< Formula > extract(PathFormula const &f, ApToFormulaMap &extractedFormulas)
Finds state subformulae in f and replaces them by atomic propositions.
std::map< std::string, std::shared_ptr< Formula const > > ApToFormulaMap
std::shared_ptr< Formula const > asSharedPointer()
Definition Formula.cpp:571
static std::vector< ValueType > computeUntilProbabilities(Environment const &env, storm::solver::SolveGoal< ValueType > &&goal, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool qualitative, ModelCheckerHint const &hint=ModelCheckerHint())
Helper class for LTL model checking.
std::function< storm::storage::BitVector(storm::logic::Formula const &)> CheckFormulaCallback
std::vector< ValueType > computeLTLProbabilities(Environment const &env, storm::logic::PathFormula const &formula, CheckFormulaCallback const &formulaChecker)
Computes the LTL probabilities.
SparseLTLHelper(storm::storage::SparseMatrix< ValueType > const &transitionMatrix)
Initializes the helper for a discrete time model (i.e.
static std::map< std::string, storm::storage::BitVector > computeApSets(std::map< std::string, std::shared_ptr< storm::logic::Formula const > > const &extracted, CheckFormulaCallback const &formulaChecker)
Computes the states that are satisfying the AP.
std::vector< ValueType > computeDAProductProbabilities(Environment const &env, storm::automata::DeterministicAutomaton const &da, std::map< std::string, storm::storage::BitVector > &apSatSets)
Computes the (maximizing) probabilities for the constructed DA product.
typename std::conditional< Nondeterministic, storm::models::sparse::Mdp< ValueType >, storm::models::sparse::Dtmc< ValueType > >::type productModelType
The type of the product model (DTMC or MDP) that is used during the computation.
static MDPSparseModelCheckingHelperReturnType< SolutionType > computeUntilProbabilities(Environment const &env, storm::solver::SolveGoal< ValueType, SolutionType > &&goal, storm::storage::SparseMatrix< ValueType > const &transitionMatrix, storm::storage::SparseMatrix< ValueType > const &backwardTransitions, storm::storage::BitVector const &phiStates, storm::storage::BitVector const &psiStates, bool qualitative, bool produceScheduler, ModelCheckerHint const &hint=ModelCheckerHint())
Base class for all sparse models.
Definition Model.h:32
void setRelevantValues(storm::storage::BitVector &&values)
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
bool empty() const
Retrieves whether no bits are set to true in this bit vector.
void clear()
Removes all set bits from the bit vector.
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
This class represents the decomposition of a nondeterministic model into its maximal end components.
This class defines which action is chosen in a particular state of a non-deterministic model.
Definition Scheduler.h:18
A class that holds a possibly non-square matrix in the compressed row storage format.
index_type getRowGroupCount() const
Returns the number of row groups in the matrix.
This class represents the decomposition of a graph-like structure into its strongly connected compone...
std::shared_ptr< DAProduct< Model > > ptr
Definition DAProduct.h:13
storm::automata::AcceptanceCondition::ptr getAcceptance()
Definition DAProduct.h:19
std::vector< ValueType > projectToOriginalModel(const Model &originalModel, const std::vector< ValueType > &prodValues)
Definition Product.h:70
const storm::storage::BitVector & getStatesOfInterest() const
Definition Product.h:84
#define STORM_LOG_INFO(message)
Definition logging.h:24
#define STORM_LOG_DEBUG(message)
Definition logging.h:18
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
Expression conjunction(std::vector< storm::expressions::Expression > const &expressions)
std::unique_ptr< storm::storage::Scheduler< ValueType > > scheduler