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