Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
JaniLocalEliminator.cpp
Go to the documentation of this file.
9
11
12namespace storm {
13namespace jani {
14
15JaniLocalEliminator::JaniLocalEliminator(Model const &original, storm::jani::Property &property, bool addMissingGuards)
16 : original(original), addMissingGuards(addMissingGuards) {
17 setProperty(property);
19}
20
21JaniLocalEliminator::JaniLocalEliminator(const Model &original, std::vector<storm::jani::Property> &properties, bool addMissingGuards)
22 : original(original), addMissingGuards(addMissingGuards) {
23 if (properties.size() > 1) {
24 STORM_LOG_WARN("Only the first property will be used for local elimination.");
25 }
26 STORM_LOG_THROW(!properties.empty(), storm::exceptions::InvalidArgumentException, "Local elimination requires at least one property");
27
28 setProperty(properties[0]);
29
31}
32
33Model JaniLocalEliminator::eliminateAutomatically(const Model &model, std::vector<jani::Property> properties, uint64_t locationHeuristic,
34 uint64_t edgesHeuristic) {
35 auto eliminator = storm::jani::JaniLocalEliminator(model, properties);
36 eliminator.scheduler.addAction(std::make_unique<elimination_actions::AutomaticAction>(locationHeuristic, edgesHeuristic));
37 eliminator.eliminate();
38 return eliminator.getResult();
39}
40
42 newModel = original;
43
44 Session session = Session(newModel, property, flatten);
45
46 if (addMissingGuards) {
47 session.addMissingGuards(session.getModel().getAutomaton(0).getName());
48 }
49
50 for (auto &automaton : session.getModel().getAutomata()) {
51 bool hasTransientAssignments = false;
52 for (auto loc : automaton.getLocations()) {
53 if (loc.getAssignments().hasTransientAssignment()) {
54 hasTransientAssignments = true;
55 }
56 }
57 if (hasTransientAssignments) {
58 STORM_LOG_TRACE("Pushing transient location assignments to edge destinations");
59 automaton.pushTransientRealLocationAssignmentsToEdges();
60 automaton.pushEdgeAssignmentsToDestinations();
61 }
62 }
63
64 while (!session.getFinished()) {
65 std::unique_ptr<Action> action = scheduler.getNextAction();
66 action->doAction(session);
67 }
68
69 newModel = session.getModel();
70 newModel.finalize();
71}
72
74 return newModel;
75}
76
77bool JaniLocalEliminator::Session::isEliminable(const std::string &automatonName, std::string const &locationName) {
78 return !isPossiblyInitial(automatonName, locationName) && !hasLoops(automatonName, locationName) && !isPartOfProp(automatonName, locationName);
79}
80bool JaniLocalEliminator::Session::hasLoops(const std::string &automatonName, std::string const &locationName) {
81 Automaton &automaton = model.getAutomaton(automatonName);
82 uint64_t locationIndex = automaton.getLocationIndex(locationName);
83 for (Edge edge : automaton.getEdgesFromLocation(locationIndex)) {
84 for (const EdgeDestination &dest : edge.getDestinations()) {
85 if (dest.getLocationIndex() == locationIndex)
86 return true;
87 }
88 }
89 return false;
90}
91bool JaniLocalEliminator::Session::hasNamedActions(const std::string &automatonName, std::string const &locationName) {
92 Automaton &automaton = model.getAutomaton(automatonName);
93 uint64_t locationIndex = automaton.getLocationIndex(locationName);
94 for (const Edge &edge : automaton.getEdgesFromLocation(locationIndex)) {
95 if (!edge.hasSilentAction()) {
96 return true;
97 }
98 }
99 return false;
100}
101
102bool JaniLocalEliminator::Session::isPossiblyInitial(const std::string &automatonName, std::string const &locationName) {
103 Automaton &automaton = model.getAutomaton(automatonName);
104 auto location = automaton.getLocation(automaton.getLocationIndex(locationName));
105 for (const auto &asg : location.getAssignments()) {
106 if (!asg.isTransient())
107 continue;
108 if (asg.getAssignedExpression().containsVariables() ||
109 (asg.getVariable().hasInitExpression() && asg.getVariable().getInitExpression().containsVariables()))
110 continue;
111 if (asg.getVariable().getType().isBoundedType() && asg.getVariable().getType().asBoundedType().isIntegerType()) {
112 if (asg.getVariable().hasInitExpression()) {
113 int initValue = asg.getVariable().getInitExpression().evaluateAsInt();
114 int currentValue = asg.getAssignedExpression().evaluateAsInt();
115 if (initValue != currentValue)
116 return false;
117 } else {
118 STORM_LOG_WARN("Variable " + asg.getVariable().getName() + " has no init expression. The result may not be correct.");
119 }
120 } else if (asg.getVariable().getType().isBasicType() && asg.getVariable().getType().asBasicType().isBooleanType()) {
121 if (asg.getVariable().hasInitExpression()) {
122 bool initValue = asg.getVariable().getInitExpression().evaluateAsBool();
123 bool currentValue = asg.getAssignedExpression().evaluateAsBool();
124 if (initValue != currentValue)
125 return false;
126 } else {
127 STORM_LOG_WARN("Variable " + asg.getVariable().getName() + " has no init expression. The result may not be correct.");
128 }
129 }
130 }
131
132 return true;
133}
134
135bool JaniLocalEliminator::Session::isPartOfProp(const std::string &automatonName, std::string const &locationName) {
136 uint64_t locationIndex = model.getAutomaton(automatonName).getLocationIndex(locationName);
137 return isPartOfProp(automatonName, locationIndex);
138}
139
140bool JaniLocalEliminator::Session::isPartOfProp(const std::string &automatonName, uint64_t locationIndex) {
141 AutomatonInfo &autInfo = automataInfo[automatonName];
142 return autInfo.potentiallyPartOfProp.count(locationIndex) == 1;
143}
144
145bool JaniLocalEliminator::Session::computeIsPartOfProp(const std::string &automatonName, const std::string &locationName) {
146 Automaton &automaton = model.getAutomaton(automatonName);
147 uint64_t locationIndex = automaton.getLocationIndex(locationName);
148 return computeIsPartOfProp(automatonName, locationIndex);
149}
150
151bool JaniLocalEliminator::Session::computeIsPartOfProp(const std::string &automatonName, uint64_t locationIndex) {
152 Automaton &automaton = model.getAutomaton(automatonName);
153 auto location = automaton.getLocation(locationIndex);
154 std::map<expressions::Variable, expressions::Expression> substitutionMap;
155 for (auto &asg : location.getAssignments()) {
156 if (!asg.isTransient())
157 continue;
158 substitutionMap.insert(std::pair<expressions::Variable, expressions::Expression>(asg.getExpressionVariable(), asg.getAssignedExpression()));
159 }
160 return computeIsPartOfProp(substitutionMap);
161}
162
163bool JaniLocalEliminator::Session::computeIsPartOfProp(const std::map<expressions::Variable, expressions::Expression> &substitutionMap) {
164 storm::solver::Z3SmtSolver solver(model.getExpressionManager());
165 auto propertyFormula = property.getRawFormula()->substitute(substitutionMap);
166 auto expression = model.getExpressionManager().boolean(false);
167 if (propertyFormula->isProbabilityOperatorFormula() || propertyFormula->isRewardOperatorFormula()) {
168 auto subformula = &propertyFormula->asUnaryStateFormula().getSubformula();
169 if (subformula->isEventuallyFormula()) {
170 expression = subformula->asEventuallyFormula().getSubformula().toExpression(model.getExpressionManager());
171 } else if (subformula->isUntilFormula()) {
172 const auto &untilFormula = subformula->asUntilFormula();
173 if (untilFormula.getLeftSubformula().isTrueFormula() && untilFormula.getRightSubformula().isAtomicExpressionFormula()) {
174 expression = untilFormula.getRightSubformula().toExpression(model.getExpressionManager());
175 } else {
176 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Until formulas are only supported if the left subformula is \"true\"");
177 }
178 } else {
179 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "This type of formula is not supported");
180 }
181 }
182 auto simplified = expression.simplify();
183 if (simplified.isLiteral()) {
184 return simplified.evaluateAsBool();
185 }
186 solver.add(simplified);
187
188 auto result = solver.check();
190}
191
192void JaniLocalEliminator::Session::setPartOfProp(const std::string &automatonName, const std::string &locationName, bool isPartOfProp) {
193 uint64_t locationIndex = model.getAutomaton(automatonName).getLocationIndex(locationName);
194 return setPartOfProp(automatonName, locationIndex, isPartOfProp);
195}
196
197void JaniLocalEliminator::Session::setPartOfProp(const std::string &automatonName, uint64_t locationIndex, bool isPartOfProp) {
198 AutomatonInfo &autInfo = automataInfo[automatonName];
199 if (autInfo.potentiallyPartOfProp.count(locationIndex) == 1) {
200 if (!isPartOfProp) {
201 autInfo.potentiallyPartOfProp.erase(locationIndex);
202 }
203 } else {
204 if (isPartOfProp) {
205 autInfo.potentiallyPartOfProp.insert(locationIndex);
206 }
207 }
208}
209
210void JaniLocalEliminator::Session::clearIsPartOfProp(const std::string &automatonName) {
211 AutomatonInfo &autInfo = automataInfo[automatonName];
212 autInfo.potentiallyPartOfProp.clear();
213}
214
215void JaniLocalEliminator::setProperty(storm::jani::Property &newProperty) {
216 auto raw = newProperty.getRawFormula();
217 bool supported = false;
218
219 if (raw->isProbabilityOperatorFormula()) {
220 auto subformula = &raw->asProbabilityOperatorFormula().getSubformula();
221 if (subformula->isEventuallyFormula()) {
222 supported = true;
223 } else if (subformula->isUntilFormula()) {
224 const auto &untilFormula = subformula->asUntilFormula();
225 if (untilFormula.getLeftSubformula().isTrueFormula()) {
226 supported = true;
227 }
228 }
229 }
230 if (raw->isRewardOperatorFormula()) {
231 auto subformula = &raw->asRewardOperatorFormula().getSubformula();
232 if (subformula->isEventuallyFormula()) {
233 supported = true;
234 } else if (subformula->isUntilFormula()) {
235 const auto &untilFormula = subformula->asUntilFormula();
236 if (untilFormula.getLeftSubformula().isTrueFormula()) {
237 supported = true;
238 }
239 }
240 }
241
242 STORM_LOG_THROW(supported, storm::exceptions::NotSupportedException, "This type of property is not supported for location elimination");
243
244 this->property = newProperty;
245}
246
248
249std::unique_ptr<JaniLocalEliminator::Action> JaniLocalEliminator::EliminationScheduler::getNextAction() {
250 if (actionQueue.empty()) {
251 return std::make_unique<elimination_actions::FinishAction>();
252 }
253 std::unique_ptr<JaniLocalEliminator::Action> val = std::move(actionQueue.front());
254 actionQueue.pop();
255 return val;
256}
257
258void JaniLocalEliminator::EliminationScheduler::addAction(std::unique_ptr<JaniLocalEliminator::Action> action) {
259 actionQueue.push(std::move(action));
260}
261
262JaniLocalEliminator::Session::Session(Model model, Property property, bool flatten) : model(model), property(property), finished(false) {
263 if (flatten && model.getNumberOfAutomata() > 1) {
265 }
266
268
269 if (property.getRawFormula()->isRewardOperatorFormula()) {
270 isRewardFormula = true;
271 rewardModels = property.getRawFormula()->getReferencedRewardModels();
272 } else if (property.getRawFormula()->isProbabilityOperatorFormula()) {
273 isRewardFormula = false;
274 } else {
275 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "This type of property is currently not supported");
276 }
277
278 for (auto &var : property.getUsedVariablesAndConstants()) {
279 expressionVarsInProperty.insert(var.getIndex());
280 }
281}
282
286
288 this->model = model;
289}
290
294
296 return finished;
297}
298
300 this->finished = finished;
301}
302
307
311
313 std::set<std::string> &rewardVariables) {
314 STORM_LOG_THROW(!first.usesAssignmentLevels() && !then.usesAssignmentLevels(), storm::exceptions::NotImplementedException,
315 "Assignment levels are currently not supported");
316
317 OrderedAssignments newOa;
318
319 // This method takes two OrderedAssignments and returns an OrderedAssignments that is equivalent to executing the two in sequence.
320 // This is done by removing those assignments from the first set that also occur in the second set (because that first assignment would be
321 // overwritten by the second one). We then modify the second assignment to that variable so that we still get the same end result.
322
323 // Collect variables that occur in the second set of assignments. This will be used to decide which first assignments to keep and which to discard.
324 std::set<expressions::Variable> thenVariables;
325 for (const auto &assignment : then.getOrderedAssignments()) {
326 thenVariables.emplace(assignment.getExpressionVariable());
327 }
328
329 // Add the remaining assignments from the first OrderedAssignments to the new assignment.
330 for (const auto &assignment : first.getOrderedAssignments()) {
331 if (thenVariables.find(assignment.getExpressionVariable()) != thenVariables.end()) {
332 continue;
333 }
334 newOa.add(assignment);
335 }
336
337 // Finally add all assignments from the second OrderedAssignments to the new OrderedAssignments. While doing this, we need to replace all variables
338 // that were updated in the first assignment with the expression assigned to them.
339 std::map<expressions::Variable, expressions::Expression> substitutionMap = first.getAsVariableToExpressionMap();
340 for (const auto &assignment : then.getOrderedAssignments()) {
341 bool isReward = rewardVariables.count(assignment.getExpressionVariable().getName());
342 auto firstAssignment = substitutionMap.find(assignment.getExpressionVariable());
343 if (isReward && firstAssignment != substitutionMap.end()) {
344 auto newAssignment = (*firstAssignment).second + assignment.getAssignedExpression().substitute(substitutionMap);
345
346 newOa.add(Assignment(assignment.getVariable(), newAssignment));
347 } else {
348 newOa.add(Assignment(assignment.getVariable(), assignment.getAssignedExpression().substitute(substitutionMap).simplify()));
349 }
350 }
351 return newOa;
352}
353
354bool JaniLocalEliminator::Session::isVariablePartOfProperty(const std::string &expressionVariableName) {
355 auto expressionVariable = model.getExpressionManager().getVariable(expressionVariableName);
356 uint_fast64_t expressionVariableIndex = expressionVariable.getIndex();
357 return expressionVarsInProperty.count(expressionVariableIndex) != 0;
358}
359
361 model = model.flattenComposition();
362 automataInfo.clear();
363 buildAutomataInfo();
364}
365
366void JaniLocalEliminator::Session::addMissingGuards(const std::string &automatonName) {
367 auto &automaton = model.getAutomaton(automatonName);
368
369 std::string sinkName = "sink_location";
370 while (automaton.hasLocation(sinkName)) {
371 sinkName += "_";
372 }
373 Location sink(sinkName, OrderedAssignments());
374 automaton.addLocation(sink);
375 uint64_t sinkIndex = automaton.getNumberOfLocations() - 1;
376
377 automataInfo[automatonName].hasSink = true;
378 automataInfo[automatonName].sinkIndex = sinkIndex;
379
380 for (uint64_t i = 0; i < automaton.getNumberOfLocations(); i++) {
381 if (i == sinkIndex)
382 continue;
383 auto outgoingEdges = automaton.getEdgesFromLocation(i);
384 expressions::Expression allGuards;
385 allGuards = model.getExpressionManager().boolean(false);
386 for (const auto &edge : outgoingEdges) {
387 allGuards = edge.getGuard() || allGuards;
388 }
389 expressions::Expression newGuard = !allGuards;
390
391 // Before we add the edge, check whether it is satisfiable:
392 auto variables = newGuard.getVariables();
393 storm::solver::Z3SmtSolver solver(model.getExpressionManager());
394 solver.add(newGuard);
395 for (const auto &var : model.getGlobalVariables()) {
396 if (var.getType().isBoundedType() && var.getType().asBoundedType().isIntegerType() && variables.count(var.getExpressionVariable()) > 0) {
397 auto &biVariable = var.getType().asBoundedType();
398 solver.add(var.getExpressionVariable().getExpression() >= biVariable.getLowerBound());
399 solver.add(var.getExpressionVariable().getExpression() <= biVariable.getUpperBound());
400 }
401 }
402 for (const auto &var : automaton.getVariables()) {
403 if (var.getType().isBoundedType() && var.getType().asBoundedType().isIntegerType() && variables.count(var.getExpressionVariable()) > 0) {
404 auto &biVariable = var.getType().asBoundedType();
405 solver.add(var.getExpressionVariable().getExpression() >= biVariable.getLowerBound());
406 solver.add(var.getExpressionVariable().getExpression() <= biVariable.getUpperBound());
407 }
408 }
409 auto result = solver.check();
410
412 STORM_LOG_TRACE("\tAdding missing guard from location " + automaton.getLocation(i).getName());
414 STORM_LOG_TRACE("\t\tThe guard was satisfiable with assignment\n"
415 << ([&] {
416 auto satisfyingAssignment = solver.getModel();
417 std::string message;
418 for (auto &var : variables) {
419 if (var.hasIntegerType()) {
420 message += "\t\t\t" + var.getName() + ": " + std::to_string(satisfyingAssignment->getIntegerValue(var));
421 } else if (var.hasBooleanType()) {
422 message += "\t\t\t" + var.getName() + ": " + std::to_string(satisfyingAssignment->getBooleanValue(var));
423 } else if (var.hasRationalType()) {
424 message += "\t\t\t" + var.getName() + ": " + std::to_string(satisfyingAssignment->getRationalValue(var));
425 }
426 }
427 return message;
428 })());
429 } else {
430 STORM_LOG_TRACE("\t\tThe solver could not determine whether the guard was satisfiable");
431 }
432 std::vector<std::pair<uint64_t, storm::expressions::Expression>> destinationLocationsAndProbabilities;
433 std::shared_ptr<storm::jani::TemplateEdge> templateEdge = std::make_shared<storm::jani::TemplateEdge>(newGuard);
435 templateEdge->addDestination(ted);
436 destinationLocationsAndProbabilities.emplace_back(sinkIndex, model.getExpressionManager().rational(1.0));
437
438 automaton.addEdge(storm::jani::Edge(i, 0, boost::none, templateEdge, destinationLocationsAndProbabilities));
439 } else {
440 STORM_LOG_TRACE("\tLocation " + automaton.getLocation(i).getName() + " has no missing guard");
441 }
442 }
443}
444
446 for (auto &aut : model.getAutomata()) {
447 automataInfo[aut.getName()] = AutomatonInfo();
448 for (auto &loc : aut.getLocations()) {
449 bool isPartOfProp = computeIsPartOfProp(aut.getName(), loc.getName());
450 setPartOfProp(aut.getName(), loc.getName(), isPartOfProp);
451 }
452 }
453}
454
455JaniLocalEliminator::AutomatonInfo &JaniLocalEliminator::Session::getAutomatonInfo(const std::string &name) {
456 return automataInfo[name];
457}
458
459JaniLocalEliminator::AutomatonInfo::AutomatonInfo() : hasSink(false), sinkIndex(0) {}
460} // namespace jani
461} // namespace storm
Expression simplify() const
Simplifies the expression according to some basic rules.
std::set< storm::expressions::Variable > getVariables() const
Retrieves the set of all variables that appear in the expression.
Expression substitute(std::map< Variable, Expression > const &variableToExpressionMap) const
Substitutes all occurrences of the variables according to the given map.
Location const & getLocation(uint64_t index) const
Retrieves the location with the given index.
Edges getEdgesFromLocation(std::string const &name)
Retrieves the edges of the location with the given name.
std::string const & getName() const
Retrieves the name of the automaton.
Definition Automaton.cpp:47
uint64_t getLocationIndex(std::string const &name) const
std::map< storm::expressions::Variable, storm::expressions::Expression > getAsVariableToExpressionMap() const
Retrieves the mapping from variables to their assigned expressions that corresponds to the assignment...
bool usesAssignmentLevels() const
Retrieves whether the edge uses an assignment level other than zero.
storm::expressions::Expression const & getProbability() const
Retrieves the probability of choosing this destination.
OrderedAssignments const & getOrderedAssignments() const
Retrieves the assignments to make when choosing this destination.
storm::expressions::Expression const & getGuard() const
Retrieves the guard of this edge.
Definition Edge.cpp:65
bool computeIsPartOfProp(const std::string &automatonName, const std::string &locationName)
Session(Model model, Property property, bool flatten=true)
expressions::Expression getNewGuard(const Edge &edge, const EdgeDestination &dest, const Edge &outgoing)
bool hasLoops(const std::string &automatonName, std::string const &locationName)
void setPartOfProp(const std::string &automatonName, const std::string &locationName, bool isPartOfProp)
expressions::Expression getProbability(const EdgeDestination &first, const EdgeDestination &then)
bool hasNamedActions(const std::string &automatonName, std::string const &locationName)
bool isPartOfProp(const std::string &automatonName, std::string const &locationName)
bool isPossiblyInitial(const std::string &automatonName, std::string const &locationName)
bool isVariablePartOfProperty(const std::string &expressionVariableName)
void addMissingGuards(const std::string &automatonName)
AutomatonInfo & getAutomatonInfo(const std::string &name)
void clearIsPartOfProp(const std::string &automatonName)
bool isEliminable(const std::string &automatonName, std::string const &locationName)
OrderedAssignments executeInSequence(const EdgeDestination &first, const EdgeDestination &then, std::set< std::string > &rewardVariables)
static Model eliminateAutomatically(const Model &model, std::vector< jani::Property > properties, uint64_t locationHeuristic, uint64_t edgesHeuristic)
JaniLocalEliminator(Model const &original, storm::jani::Property &property, bool addMissingGuards=false)
Jani Location:
Definition Location.h:15
std::vector< Automaton > & getAutomata()
Retrieves the automata of the model.
Definition Model.cpp:872
std::size_t getNumberOfAutomata() const
Retrieves the number of automata in this model.
Definition Model.cpp:914
Automaton & getAutomaton(std::string const &name)
Retrieves the automaton with the given name.
Definition Model.cpp:888
void finalize()
After adding all components to the model, this method has to be called.
Definition Model.cpp:1399
void clear()
Removes all assignments from this set.
bool add(Assignment const &assignment, bool addToExisting=false)
Adds the given assignment to the set of assignments.
std::shared_ptr< storm::logic::Formula const > getRawFormula() const
Definition Property.cpp:92
virtual void add(storm::expressions::Expression const &assertion) override
Adds an assertion to the solver's stack.
virtual std::shared_ptr< SmtSolver::ModelReference > getModel() override
If the last call to check() or checkWithAssumptions() returned Sat, this method retrieves a model tha...
virtual CheckResult check() override
Checks whether the conjunction of assertions that are currently on the solver's stack is satisfiable.
#define STORM_LOG_WARN(message)
Definition logging.h:30
#define STORM_LOG_TRACE(message)
Definition logging.h:17
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
LabParser.cpp.
Definition cli.cpp:18