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