Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
MonotonicityHelper.cpp
Go to the documentation of this file.
2
5
10
13
16
17namespace storm {
18namespace analysis {
19/*** Constructor ***/
20template<typename ValueType, typename ConstantType>
22 std::vector<std::shared_ptr<logic::Formula const>> formulas,
23 std::vector<storage::ParameterRegion<ValueType>> regions, uint_fast64_t numberOfSamples,
24 double const& precision, bool dotOutput)
25 : env(env), assumptionMaker(model->getTransitionMatrix()) {
26 STORM_LOG_ASSERT(model != nullptr, "Model should not be null.");
27
28 this->model = model;
29 this->formulas = formulas;
30 this->precision = utility::convertNumber<ConstantType>(precision);
31 this->matrix = model->getTransitionMatrix();
32 this->dotOutput = dotOutput;
33
34 if (regions.size() == 1) {
35 this->region = *(regions.begin());
36 } else {
39 std::set<VariableType> vars;
41 for (auto var : vars) {
44 lowerBoundaries.insert(std::make_pair(var, lb));
45 upperBoundaries.insert(std::make_pair(var, ub));
46 }
47 this->region = storage::ParameterRegion<ValueType>(std::move(lowerBoundaries), std::move(upperBoundaries));
48 }
49
50 if (numberOfSamples > 2) {
51 // sampling
52 if (model->isOfType(models::ModelType::Dtmc)) {
53 checkMonotonicityOnSamples(env, model->template as<models::sparse::Dtmc<ValueType>>(), numberOfSamples);
54 } else if (model->isOfType(models::ModelType::Mdp)) {
55 checkMonotonicityOnSamples(env, model->template as<models::sparse::Mdp<ValueType>>(), numberOfSamples);
56 }
57 checkSamples = true;
58 } else {
59 if (numberOfSamples > 0) {
60 STORM_LOG_WARN("At least 3 sample points are needed to check for monotonicity on samples, not using samples for now");
61 }
62 checkSamples = false;
63 }
64
65 this->extender = std::make_unique<analysis::OrderExtender<ValueType, ConstantType>>(model, formulas[0]);
66
67 for (uint_fast64_t i = 0; i < matrix.getRowCount(); ++i) {
68 std::set<VariableType> occurringVariables;
69
70 for (auto& entry : matrix.getRow(i)) {
71 storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables);
72 }
73 for (auto& var : occurringVariables) {
74 occuringStatesAtVariable[var].push_back(i);
75 }
76 }
77}
78
79/*** Public methods ***/
80template<typename ValueType, typename ConstantType>
81std::vector<typename MonotonicityHelper<ValueType, ConstantType>::ExpectedMonotonicityResult>
82MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityInBuild(std::ostream& outfile, bool usePLA, std::string dotOutfileName) {
83 if (usePLA) {
84 storm::utility::Stopwatch plaWatch(true);
85 this->extender->initializeMinMaxValues(env, region);
86 plaWatch.stop();
87 STORM_LOG_STATISTICS("\nTotal time for pla checking: " << plaWatch << ".\n\n");
88 }
89 createOrder();
90
91 // output of results
92 for (auto itr : monResults) {
93 if (itr.first != nullptr) {
94 std::cout << "Number of done states: " << itr.first->getNumberOfDoneStates() << '\n';
95 }
96 if (checkSamples) {
97 for (auto& entry : resultCheckOnSamples.getMonotonicityResult()) {
98 if (entry.second == Monotonicity::Not) {
99 itr.second.first->updateMonotonicityResult(entry.first, entry.second, true);
100 }
101 }
102 }
103 std::string temp = itr.second.first->toString();
104 bool first = true;
105 for (auto& assumption : itr.second.second) {
106 if (!first) {
107 outfile << " & ";
108 } else {
109 outfile << "Assumptions: \n"
110 << " ";
111 first = false;
112 }
113 outfile << assumption;
114 }
115 if (!first) {
116 outfile << '\n';
117 } else {
118 outfile << "No Assumptions\n";
119 }
120 outfile << "Monotonicity Result: \n"
121 << " " << temp << "\n\n";
122 }
123
124 if (monResults.size() == 0) {
125 outfile << "No monotonicity found, as the order is insufficient\n";
126 if (checkSamples) {
127 outfile << "Monotonicity Result on samples: " << resultCheckOnSamples.toString() << '\n';
128 }
129 }
130
131 // dotoutput
132 if (dotOutput) {
133 STORM_LOG_WARN_COND(monResults.size() <= 10, "Too many Reachability Orders. Dot Output will only be created for 10.");
134 int i = 0;
135 auto orderItr = monResults.begin();
136 while (i < 10 && orderItr != monResults.end()) {
137 std::ofstream dotOutfile;
138 std::string name = dotOutfileName + std::to_string(i);
139 storm::io::openFile(name, dotOutfile);
140 dotOutfile << "Assumptions:\n";
141 auto assumptionItr = orderItr->second.second.begin();
142 while (assumptionItr != orderItr->second.second.end()) {
143 dotOutfile << *assumptionItr << '\n';
144 dotOutfile << '\n';
145 assumptionItr++;
146 }
147 dotOutfile << '\n';
148 orderItr->first->dotOutputToFile(dotOutfile);
149 storm::io::closeFile(dotOutfile);
150 i++;
151 orderItr++;
152 }
153 }
154 std::vector<ExpectedMonotonicityResult> result;
155 result.reserve(monResults.size());
156 for (auto const& [order, entry] : monResults) {
157 result.push_back({order, entry.first, entry.second});
158 }
159 return result;
160}
161
162template<typename ValueType, typename ConstantType>
163std::shared_ptr<LocalMonotonicityResult<typename MonotonicityHelper<ValueType, ConstantType>::VariableType>>
165 LocalMonotonicityResult<VariableType> localMonRes(model->getNumberOfStates());
166 for (uint_fast64_t state = 0; state < model->getNumberOfStates(); ++state) {
167 for (auto& var : extender->getVariablesOccuringAtState()[state]) {
168 localMonRes.setMonotonicity(state, var, extender->getMonotoncityChecker().checkLocalMonotonicity(order, state, var, region));
169 }
170 }
171 localMonRes.setDone(order->getDoneBuilding());
172 return std::make_shared<LocalMonotonicityResult<VariableType>>(localMonRes);
173}
174
175/*** Private methods ***/
176template<typename ValueType, typename ConstantType>
177void MonotonicityHelper<ValueType, ConstantType>::createOrder() {
178 // Transform to Orders
179 std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> criticalTuple;
180
181 // Create initial order
182 auto monRes = std::make_shared<MonotonicityResult<VariableType>>(MonotonicityResult<VariableType>());
183 criticalTuple = extender->toOrder(region, monRes);
184 auto order = std::get<0>(criticalTuple);
185 if (order->isInvalid()) {
186 // The initial order already turned out to be inconsistent. It carries the same "done"
187 // sentinel as a successfully built order, so we must discard it explicitly: deriving
188 // monotonicity from a contradictory order would be unsound.
189 STORM_LOG_INFO(" The initial order is invalid, so no monotonicity results are produced");
190 return;
191 }
192 // Continue based on not (yet) sorted states
193 auto val1 = std::get<1>(criticalTuple);
194 auto val2 = std::get<2>(criticalTuple);
195 auto numberOfStates = model->getNumberOfStates();
196 std::vector<Assumption> assumptions;
197
198 if (val1 == numberOfStates && val2 == numberOfStates) {
199 monResults.insert({order, {monRes, assumptions}});
200 } else if (val1 != numberOfStates && val2 != numberOfStates) {
201 extendOrderWithAssumptions(order, val1, val2, assumptions, monRes);
202 } else {
203 STORM_LOG_ASSERT(false, "Unreachable code reached.");
204 }
205}
206
207template<typename ValueType, typename ConstantType>
208void MonotonicityHelper<ValueType, ConstantType>::extendOrderWithAssumptions(std::shared_ptr<Order> order, uint_fast64_t val1, uint_fast64_t val2,
209 std::vector<Assumption> assumptions,
210 std::shared_ptr<MonotonicityResult<VariableType>> monRes) {
211 // Algorithm 2 spawns up to three branches per unresolved (val1, val2) pair, one per candidate
212 // assumption. The worklist keeps this iterative so that pMCs chaining many such ambiguities
213 // cannot overflow the call stack. Branches are pushed in reverse so popping (LIFO) visits
214 // them depth-first, left to right.
215 struct PendingBranch {
216 std::shared_ptr<Order> order;
217 uint_fast64_t val1;
218 uint_fast64_t val2;
219 std::vector<Assumption> assumptions;
220 std::shared_ptr<MonotonicityResult<VariableType>> monRes;
221 };
222 std::vector<PendingBranch> worklist;
223 worklist.push_back({std::move(order), val1, val2, std::move(assumptions), std::move(monRes)});
224
225 auto const numberOfStates = model->getNumberOfStates();
226 while (!worklist.empty()) {
227 PendingBranch current = std::move(worklist.back());
228 worklist.pop_back();
229
230 if (current.order->isInvalid()) {
231 // We don't add anything as the order we created with assumptions turns out to be invalid
232 STORM_LOG_INFO(" The order was invalid, so we stop here");
233 continue;
234 }
235 if (current.val1 == numberOfStates || current.val2 == numberOfStates) {
236 STORM_LOG_ASSERT(current.val1 == current.val2, "The order is done, so both states must be unset.");
237 STORM_LOG_ASSERT(current.order->getNumberOfAddedStates() == current.order->getNumberOfStates(), "All states must have been added.");
238 monResults.insert({current.order, {current.monRes, current.assumptions}});
239 continue;
240 }
241
242 // Make the three assumptions
243 STORM_LOG_INFO("Creating assumptions for " << current.val1 << " and " << current.val2 << ". ");
244 auto newAssumptions = assumptionMaker.createAndCheckAssumptions(current.val1, current.val2, current.order, region);
245 STORM_LOG_ASSERT(newAssumptions.size() <= 3, "At most three candidate assumptions can be created.");
246
247 if (newAssumptions.empty()) {
248 auto fallbackMonRes = std::make_shared<MonotonicityResult<VariableType>>(MonotonicityResult<VariableType>());
249 for (auto& entry : occuringStatesAtVariable) {
250 for (auto& state : entry.second) {
251 extender->checkParOnStateMonRes(state, current.order, entry.first, fallbackMonRes);
252 if (fallbackMonRes->getMonotonicity(entry.first) == Monotonicity::Unknown) {
253 break;
254 }
255 }
256 fallbackMonRes->setDoneForVar(entry.first);
257 }
258 monResults.insert({current.order, {fallbackMonRes, current.assumptions}});
259 STORM_LOG_INFO(" None of the assumptions were valid, we stop exploring the current order");
260 continue;
261 }
262 STORM_LOG_INFO(" Created " << newAssumptions.size() << " assumptions, we continue extending the current order");
263
264 // Candidates 0..size-2 each get an independent copy of current.order/monRes, so extending
265 // one cannot affect another; the last candidate reuses current.order/monRes in place.
266 // That in-place mutation must happen last, after every other candidate has already taken
267 // its copy from the still-pristine original.
268 std::vector<PendingBranch> preparedBranches;
269 preparedBranches.reserve(newAssumptions.size());
270 for (size_t i = 0; i < newAssumptions.size(); ++i) {
271 auto const& assumption = newAssumptions[i];
272 bool const isLastCandidate = (i == newAssumptions.size() - 1);
273
274 std::shared_ptr<Order> branchOrder;
275 if (isLastCandidate) {
276 branchOrder = current.order;
277 } else {
278 branchOrder = current.order->copy();
279 // The copy must inherit the extender's per-order bookkeeping (PLA bounds, usePLA,
280 // continuation state); otherwise extending it would start from a fresh default
281 // context and silently drop those values.
282 extender->copyContext(current.order, branchOrder);
283 }
284 std::shared_ptr<MonotonicityResult<VariableType>> branchMonRes = isLastCandidate ? current.monRes : current.monRes->copy();
285 std::vector<Assumption> branchAssumptions = current.assumptions;
286
287 if (assumption.second == AssumptionStatus::UNKNOWN) {
288 // only add assumption to the set of assumptions if it is unknown whether it holds or not
289 branchAssumptions.push_back(assumption.first);
290 }
291 auto criticalTuple = extender->extendOrder(branchOrder, region, branchMonRes, assumption.first);
292 preparedBranches.push_back(
293 {std::get<0>(criticalTuple), std::get<1>(criticalTuple), std::get<2>(criticalTuple), std::move(branchAssumptions), branchMonRes});
294 }
295 // Pushed in reverse so the first-prepared branch ends up on top of the stack and is
296 // explored first, i.e., depth-first, left to right.
297 for (auto it = preparedBranches.rbegin(); it != preparedBranches.rend(); ++it) {
298 worklist.push_back(std::move(*it));
299 }
300 }
301}
302
303template<typename ValueType, typename ConstantType>
304void MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityOnSamples(Environment const& env, std::shared_ptr<models::sparse::Dtmc<ValueType>> model,
305 uint_fast64_t numberOfSamples) {
306 STORM_LOG_ASSERT(numberOfSamples > 2, "Expected at least 3 samples.");
307
308 auto instantiator = utility::ModelInstantiator<models::sparse::Dtmc<ValueType>, models::sparse::Dtmc<ConstantType>>(*model);
309 std::set<VariableType> variables = models::sparse::getProbabilityParameters(*model);
310 std::vector<std::vector<ConstantType>> samples;
311 // For each of the variables create a model in which we only change the value for this specific variable
312 for (auto itr = variables.begin(); itr != variables.end(); ++itr) {
313 ConstantType previous = -1;
314 bool monDecr = true;
315 bool monIncr = true;
316
317 // Check monotonicity in variable (*itr) by instantiating the model
318 // all other variables fixed on lb, only increasing (*itr)
319 for (uint_fast64_t i = 0; (monDecr || monIncr) && i < numberOfSamples; ++i) {
320 // Create valuation
322 for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) {
323 // Only change value for current variable
324 if ((*itr) == (*itr2)) {
325 auto lb = region.getLowerBoundary(itr->name());
326 auto ub = region.getUpperBoundary(itr->name());
327 // Creates samples between lb and ub, that is: lb, lb + (ub-lb)/(#samples -1), lb + 2* (ub-lb)/(#samples -1), ..., ub
328 valuation[*itr2] = (lb + utility::convertNumber<CoefficientType>(i / (numberOfSamples - 1)) * (ub - lb));
329 } else {
330 auto lb = region.getLowerBoundary(itr2->name());
331 valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb);
332 }
333 }
334
335 // Instantiate model and get result
336 models::sparse::Dtmc<ConstantType> sampleModel = instantiator.instantiate(valuation);
337 auto checker = modelchecker::SparseDtmcPrctlModelChecker<models::sparse::Dtmc<ConstantType>>(sampleModel);
338 std::unique_ptr<modelchecker::CheckResult> checkResult;
339 auto formula = formulas[0];
340 if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) {
341 const modelchecker::CheckTask<logic::UntilFormula, ConstantType> checkTask =
342 modelchecker::CheckTask<logic::UntilFormula, ConstantType>(formula->asProbabilityOperatorFormula().getSubformula().asUntilFormula());
343 checkResult = checker.computeUntilProbabilities(env, checkTask);
344 } else if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) {
345 const modelchecker::CheckTask<logic::EventuallyFormula, ConstantType> checkTask =
346 modelchecker::CheckTask<logic::EventuallyFormula, ConstantType>(
347 formula->asProbabilityOperatorFormula().getSubformula().asEventuallyFormula());
348 checkResult = checker.computeReachabilityProbabilities(env, checkTask);
349 } else {
350 STORM_LOG_THROW_UNCONDITIONALLY(exceptions::NotSupportedException, "Expecting until or eventually formula.");
351 }
352
353 auto const& quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<ConstantType>();
354 std::vector<ConstantType> values = quantitativeResult.getFiniteValueVector();
355 auto initialStates = model->getInitialStates();
356 ConstantType initial = 0;
357 // Get total probability from initial states
358 for (auto j = initialStates.getNextSetIndex(0); j < model->getNumberOfStates(); j = initialStates.getNextSetIndex(j + 1)) {
359 initial += values[j];
360 }
361 // Calculate difference with result for previous valuation
362 STORM_LOG_ASSERT(initial >= 0 - precision && initial <= 1 + precision, "Initial value out of [0,1] range.");
363 ConstantType diff = previous - initial;
364 STORM_LOG_ASSERT(previous == -1 || (diff >= -1 - precision && diff <= 1 + precision), "Diff out of [-1,1] range.");
365
366 if (previous != -1 && (diff > precision || diff < -precision)) {
367 monDecr &= diff > precision; // then previous value is larger than the current value from the initial states
368 monIncr &= diff < -precision;
369 }
370 previous = initial;
371 samples.push_back(std::move(values));
372 }
374 resultCheckOnSamples.addMonotonicityResult(*itr, res);
375 }
376 assumptionMaker.setSampleValues(std::move(samples));
377}
378
379template<typename ValueType, typename ConstantType>
380void MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityOnSamples(Environment const&, std::shared_ptr<models::sparse::Mdp<ValueType>> model,
381 uint_fast64_t numberOfSamples) {
382 STORM_LOG_THROW_UNCONDITIONALLY(storm::exceptions::NotImplementedException, "Checking monotonicity on samples not implemented for mdps.");
383}
384
387} // namespace analysis
388} // namespace storm
void setMonotonicity(uint_fast64_t state, VariableType var, Monotonicity mon)
Sets the local Monotonicity of a parameter at a given state.
MonotonicityHelper(Environment const &env, std::shared_ptr< models::sparse::Model< ValueType > > model, std::vector< std::shared_ptr< logic::Formula const > > formulas, std::vector< storage::ParameterRegion< ValueType > > regions, uint_fast64_t numberOfSamples=0, double const &precision=0.000001, bool dotOutput=false)
Constructor of MonotonicityHelper.
std::vector< ExpectedMonotonicityResult > checkMonotonicityInBuild(std::ostream &outfile, bool usePLA=false, std::string dotOutfileName="dotOutput")
Builds Reachability Orders for the given model and simultaneously uses them to check for Monotonicity...
std::shared_ptr< LocalMonotonicityResult< VariableType > > createLocalMonotonicityResult(std::shared_ptr< Order > order, storage::ParameterRegion< ValueType > region)
Builds Reachability Orders for the given model and simultaneously uses them to check for Monotonicity...
This class represents a discrete-time Markov chain.
Definition Dtmc.h:16
This class represents a (discrete-time) Markov decision process.
Definition Mdp.h:16
Base class for all sparse models.
Definition Model.h:34
storm::utility::parametric::CoefficientType< ParametricType >::type CoefficientType
storm::utility::parametric::Valuation< ParametricType > Valuation
A class that provides convenience operations to display run times.
Definition Stopwatch.h:13
void stop()
Stop stopwatch and add measured time to total time.
Definition Stopwatch.cpp:42
#define STORM_LOG_INFO(message)
Definition logging.h:34
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_STATISTICS(message)
Definition logging.h:48
#define STORM_LOG_THROW_UNCONDITIONALLY(exception, message)
Definition macros.h:48
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:54
@ Unknown
the monotonicity result is unknown
typename utility::parametric::VariableType< FunctionType >::type VariableType
void closeFile(std::ofstream &stream)
Close the given file after writing.
Definition file.h:47
void openFile(std::string const &filepath, std::ofstream &filestream, bool append=false, bool silent=false)
Open the given file for writing.
Definition file.h:18
std::set< storm::RationalFunctionVariable > getProbabilityParameters(Model< storm::RationalFunction > const &model)
Get all probability parameters occurring on transitions.
Definition Model.cpp:732
std::map< typename VariableType< FunctionType >::type, typename CoefficientType< FunctionType >::type > Valuation
Definition parametric.h:43
void gatherOccurringVariables(FunctionType const &function, std::set< typename VariableType< FunctionType >::type > &variableSet)
Add all variables that occur in the given function to the the given set.
TargetType convertNumber(SourceType const &number)