Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
JaniNextStateGenerator.cpp
Go to the documentation of this file.
2
3#include <memory>
4
33
34namespace storm {
35namespace generator {
36
41template<typename ValueType, typename StateType>
44
45 std::vector<uint64_t> locations;
47 std::vector<typename Generator::AutomataEdgeSets> automataEdgeSets; // one entry for each element of 'edges'
48 std::vector<typename Generator::EdgeSetWithIndices::const_iterator> iteratorList;
49 std::vector<typename Generator::EdgeSetWithIndices const*> edgeSets;
50 std::vector<typename Generator::EdgeSetWithIndices::const_iterator> firstEnabledEdgeIterators;
52 std::vector<storm::jani::EdgeDestination const*> destinations;
53 std::vector<LocationVariableInformation const*> locationVars;
55 // The state whose (non-transient) variable values are currently stored in the evaluator while a state is expanded. See
56 // JaniNextStateGenerator::setEvaluatorState.
58
59 // The expressions of the (non-transient) variables (location, boolean, integer variables in that order).
60 // Only used by evaluatorHoldsState (i.e., in assertions). Caching them avoids that the evaluator has to compile the expressions over and over again.
61 std::vector<storm::expressions::Expression> variableExpressionsForAssertions;
62};
63
64template<typename ValueType, typename StateType>
66 : JaniNextStateGenerator(model.substituteConstantsFunctionsTranscendentals(), options, false) {
67 // Intentionally left empty.
68}
69
70template<typename ValueType, typename StateType>
72 : NextStateGenerator<ValueType, StateType>(model.getExpressionManager(), options),
73 model(model),
74 rewardExpressions(),
75 hasStateActionRewards(false),
76 evaluateRewardExpressionsAtEdges(false),
77 evaluateRewardExpressionsAtDestinations(false),
78 scratch(std::make_unique<JaniNextStateGeneratorScratchMemory<ValueType, StateType>>()) {
79 auto features = this->model.getModelFeatures();
84 // Eliminate arrays if necessary.
85 if (features.hasArrays()) {
86 arrayEliminatorData = this->model.eliminateArrays(true);
87 this->options.substituteExpressions([this](storm::expressions::Expression const& exp) { return arrayEliminatorData.transformExpression(exp); });
88 features.remove(storm::jani::ModelFeature::Arrays);
89 }
90 STORM_LOG_THROW(features.empty(), storm::exceptions::NotSupportedException,
91 "The explicit next-state generator does not support the following model feature(s): " << features.toString() << ".");
92 // Simplify the system compositions so that we can exclude the case where automata appear in the composition multiple times.
93 this->model.simplifyComposition();
94
95 // Get the reward expressions to be build. Also find out whether there is a non-trivial one.
96 bool hasNonTrivialRewardExpressions = false;
97 if (this->options.isBuildAllRewardModelsSet()) {
98 rewardExpressions = this->model.getAllRewardModelExpressions();
99 hasNonTrivialRewardExpressions = this->model.hasNonTrivialRewardExpression();
100 } else {
101 // Extract the reward models from the model based on the names we were given.
102 for (auto const& rewardModelName : this->options.getRewardModelNames()) {
103 rewardExpressions.emplace_back(rewardModelName, this->model.getRewardModelExpression(rewardModelName));
104 hasNonTrivialRewardExpressions = hasNonTrivialRewardExpressions || this->model.isNonTrivialRewardModelExpression(rewardModelName);
105 }
106 }
107 // If a transient variable has a non-zero default value, we also consider that non-trivial.
108 // In those cases, lifting edge destination assignments to the edges would mean that reward is collected twice:
109 // once at the edge (assigned value), once at the edge destinations (default value).
110 if (!hasNonTrivialRewardExpressions) {
111 for (auto const& rewExpr : rewardExpressions) {
112 STORM_LOG_ASSERT(rewExpr.second.isVariable(), "Expected trivial reward expression to be a variable. Got " << rewExpr.second << " instead.");
113 auto const& var = this->model.getGlobalVariables().getVariable(rewExpr.second.getBaseExpression().asVariableExpression().getVariable());
114 if (var.isTransient() && var.hasInitExpression() && !storm::utility::isZero(var.getInitExpression().evaluateAsRational())) {
115 hasNonTrivialRewardExpressions = true;
116 break;
117 }
118 }
119 }
120
121 // We try to lift the edge destination assignments to the edges as this reduces the number of evaluator calls.
122 // However, this will only be helpful if there are no assignment levels and only trivial reward expressions.
123 if (hasNonTrivialRewardExpressions || this->model.usesAssignmentLevels()) {
125 } else {
126 this->model.liftTransientEdgeDestinationAssignments(storm::jani::AssignmentLevelFinder().getLowestAssignmentLevel(this->model));
127 evaluateRewardExpressionsAtEdges = true;
128 }
129
130 // Create all synchronization-related information, e.g. the automata that are put in parallel.
131 this->createSynchronizationInformation();
132
133 // Now we are ready to initialize the variable information.
134 this->checkValid();
135 this->variableInformation =
136 VariableInformation(this->model, this->parallelAutomata, options.getReservedBitsForUnboundedVariables(), options.isAddOutOfBoundsStateSet());
137 this->variableInformation.registerArrayVariableReplacements(arrayEliminatorData);
138 this->transientVariableInformation = TransientVariableInformation<ValueType>(this->model, this->parallelAutomata);
139 this->transientVariableInformation.registerArrayVariableReplacements(arrayEliminatorData);
140 this->initializeSpecialStates();
141
142 // Create a proper evaluator.
143 this->evaluator = std::make_unique<storm::expressions::ExpressionEvaluator<ValueType>>(this->model.getManager());
144 this->transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
145
146 // Build the information structs for the reward models.
147 buildRewardModelInformation();
148
149 // If there are terminal states we need to handle, we now need to translate all labels to expressions.
150 if (this->options.hasTerminalStates()) {
151 for (auto const& expressionOrLabelAndBool : this->options.getTerminalStates()) {
152 if (expressionOrLabelAndBool.first.isExpression()) {
153 this->terminalStates.emplace_back(expressionOrLabelAndBool.first.getExpression(), expressionOrLabelAndBool.second);
154 } else {
155 // If it's a label, i.e. refers to a transient boolean variable we do some sanity checks first
156 if (!this->isSpecialLabel(expressionOrLabelAndBool.first.getLabel())) {
157 STORM_LOG_THROW(this->model.getGlobalVariables().hasVariable(expressionOrLabelAndBool.first.getLabel()),
158 storm::exceptions::InvalidArgumentException,
159 "Terminal states refer to illegal label '" << expressionOrLabelAndBool.first.getLabel() << "'.");
160
161 storm::jani::Variable const& variable = this->model.getGlobalVariables().getVariable(expressionOrLabelAndBool.first.getLabel());
163 storm::exceptions::InvalidArgumentException,
164 "Terminal states refer to non-boolean variable '" << expressionOrLabelAndBool.first.getLabel() << "'.");
165 STORM_LOG_THROW(variable.isTransient(), storm::exceptions::InvalidArgumentException,
166 "Terminal states refer to non-transient variable '" << expressionOrLabelAndBool.first.getLabel() << "'.");
167
168 this->terminalStates.emplace_back(variable.getExpressionVariable().getExpression(), expressionOrLabelAndBool.second);
169 }
170 }
171 }
172 }
173}
174
175// Declared (instead of implicitly defaulted) in the header, because JaniNextStateGeneratorScratchMemory is incomplete there; defined here, where it is
176// complete, so that std::unique_ptr<JaniNextStateGeneratorScratchMemory<...>> can be destructed.
177template<typename ValueType, typename StateType>
178JaniNextStateGenerator<ValueType, StateType>::~JaniNextStateGenerator() = default;
179
180template<typename ValueType, typename StateType>
188 // We do not add Functions as these should ideally be substituted before creating this generator.
189 // This is because functions may also occur in properties and the user of this class should take care of that.
190 return features;
191}
192
193template<typename ValueType, typename StateType>
195 auto features = model.getModelFeatures();
196 features.remove(storm::jani::ModelFeature::Arrays);
198 features.remove(storm::jani::ModelFeature::Functions); // can be substituted
202 if (!features.empty()) {
203 STORM_LOG_INFO("The model can not be build as it contains these unsupported features: " << features.toString());
204 return false;
205 }
206 // There probably are more cases where the model is unsupported. However, checking these is more involved.
207 // As this method is supposed to be a quick check, we just return true at this point.
208 return true;
209}
210
211template<typename ValueType, typename StateType>
213 switch (model.getModelType()) {
215 return ModelType::DTMC;
217 return ModelType::CTMC;
219 return ModelType::MDP;
221 return ModelType::MA;
222 default:
223 STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, "Invalid model type.");
224 }
225}
226
227template<typename ValueType, typename StateType>
229 return model.isDeterministicModel();
230}
231
232template<typename ValueType, typename StateType>
234 return model.isDiscreteTimeModel();
235}
236
237template<typename ValueType, typename StateType>
241
242template<typename ValueType, typename StateType>
243uint64_t JaniNextStateGenerator<ValueType, StateType>::getLocation(CompressedState const& state, LocationVariableInformation const& locationVariable) const {
244 if (locationVariable.bitWidth == 0) {
245 return 0;
246 } else {
247 return state.getAsInt(locationVariable.bitOffset, locationVariable.bitWidth);
248 }
249}
250
251template<typename ValueType, typename StateType>
252void JaniNextStateGenerator<ValueType, StateType>::setLocation(CompressedState& state, LocationVariableInformation const& locationVariable,
253 uint64_t locationIndex) const {
254 if (locationVariable.bitWidth != 0) {
255 state.setFromInt(locationVariable.bitOffset, locationVariable.bitWidth, locationIndex);
256 }
257}
258
259template<typename ValueType, typename StateType>
260std::vector<uint64_t> JaniNextStateGenerator<ValueType, StateType>::getLocations(CompressedState const& state) const {
261 std::vector<uint64_t> result;
262 getLocations(state, result);
263 return result;
264}
265
266template<typename ValueType, typename StateType>
267void JaniNextStateGenerator<ValueType, StateType>::getLocations(CompressedState const& state, std::vector<uint64_t>& result) const {
268 result.resize(this->variableInformation.locationVariables.size());
269
270 auto resultIt = result.begin();
271 for (auto it = this->variableInformation.locationVariables.begin(), ite = this->variableInformation.locationVariables.end(); it != ite; ++it, ++resultIt) {
272 if (it->bitWidth == 0) {
273 *resultIt = 0;
274 } else {
275 *resultIt = state.getAsInt(it->bitOffset, it->bitWidth);
276 }
277 }
278}
279
280template<typename ValueType, typename StateType>
282 std::vector<StateType> initialStateIndices;
283
284 if (this->model.hasNonTrivialInitialStates()) {
285 // Prepare an SMT solver to enumerate all initial states.
287 std::unique_ptr<storm::solver::SmtSolver> solver = factory.create(model.getExpressionManager());
288
289 std::vector<storm::expressions::Expression> rangeExpressions = model.getAllRangeExpressions(this->parallelAutomata);
290 for (auto const& expression : rangeExpressions) {
291 solver->add(expression);
292 }
293 solver->add(model.getInitialStatesExpression(this->parallelAutomata));
294
295 // Proceed as long as the solver can still enumerate initial states.
297 // Create fresh state.
298 CompressedState initialState(this->variableInformation.getTotalBitOffset(true));
299
300 // Read variable assignment from the solution of the solver. Also, create an expression we can use to
301 // prevent the variable assignment from being enumerated again.
302 storm::expressions::Expression blockingExpression;
303 std::shared_ptr<storm::solver::SmtSolver::ModelReference> model = solver->getModel();
304 for (auto const& booleanVariable : this->variableInformation.booleanVariables) {
305 bool variableValue = model->getBooleanValue(booleanVariable.variable);
306 storm::expressions::Expression localBlockingExpression = variableValue ? !booleanVariable.variable : booleanVariable.variable;
307 blockingExpression = blockingExpression.isInitialized() ? blockingExpression || localBlockingExpression : localBlockingExpression;
308 initialState.set(booleanVariable.bitOffset, variableValue);
309 }
310 for (auto const& integerVariable : this->variableInformation.integerVariables) {
311 int_fast64_t variableValue = model->getIntegerValue(integerVariable.variable);
312 if (integerVariable.forceOutOfBoundsCheck || this->getOptions().isExplorationChecksSet()) {
313 STORM_LOG_THROW(variableValue >= integerVariable.lowerBound, storm::exceptions::WrongFormatException,
314 "The initial value for variable " << integerVariable.variable.getName() << " is lower than the lower bound.");
315 STORM_LOG_THROW(variableValue <= integerVariable.upperBound, storm::exceptions::WrongFormatException,
316 "The initial value for variable " << integerVariable.variable.getName() << " is higher than the upper bound.");
317 }
318 storm::expressions::Expression localBlockingExpression = integerVariable.variable != model->getManager().integer(variableValue);
319 blockingExpression = blockingExpression.isInitialized() ? blockingExpression || localBlockingExpression : localBlockingExpression;
320 initialState.setFromInt(integerVariable.bitOffset, integerVariable.bitWidth,
321 static_cast<uint_fast64_t>(variableValue - integerVariable.lowerBound));
322 }
323
324 // Gather iterators to the initial locations of all the automata.
325 std::vector<std::set<uint64_t>::const_iterator> initialLocationsIts;
326 std::vector<std::set<uint64_t>::const_iterator> initialLocationsItes;
327 for (auto const& automatonRef : this->parallelAutomata) {
328 auto const& automaton = automatonRef.get();
329 initialLocationsIts.push_back(automaton.getInitialLocationIndices().cbegin());
330 initialLocationsItes.push_back(automaton.getInitialLocationIndices().cend());
331 }
333 initialLocationsIts, initialLocationsItes,
334 [this, &initialState](uint64_t index, uint64_t value) { setLocation(initialState, this->variableInformation.locationVariables[index], value); },
335 [&stateToIdCallback, &initialStateIndices, &initialState]() {
336 // Register initial state.
337 StateType id = stateToIdCallback(initialState);
338 initialStateIndices.push_back(id);
339 return true;
340 });
341
342 // Block the current initial state to search for the next one.
343 if (!blockingExpression.isInitialized()) {
344 break;
345 }
346 solver->add(blockingExpression);
347 }
348
349 STORM_LOG_DEBUG("Enumerated " << initialStateIndices.size() << " initial states using SMT solving.");
350 } else {
351 // Create vectors holding all possible values
352 std::vector<std::vector<uint64_t>> allValues;
353 for (auto const& aRef : this->parallelAutomata) {
354 auto const& aInitLocs = aRef.get().getInitialLocationIndices();
355 allValues.emplace_back(aInitLocs.begin(), aInitLocs.end());
356 }
357 uint64_t locEndIndex = allValues.size();
358 for (auto const& intVar : this->variableInformation.integerVariables) {
359 STORM_LOG_ASSERT(intVar.lowerBound <= intVar.upperBound, "Expecting variable with non-empty set of possible values.");
360 // The value of integer variables is shifted so that 0 is always the smallest possible value
361 allValues.push_back(storm::utility::vector::buildVectorForRange<uint64_t>(static_cast<uint64_t>(0), intVar.upperBound + 1 - intVar.lowerBound));
362 }
363 uint64_t intEndIndex = allValues.size();
364 // For boolean variables we consider the values 0 and 1.
365 allValues.resize(allValues.size() + this->variableInformation.booleanVariables.size(),
366 std::vector<uint64_t>({static_cast<uint64_t>(0), static_cast<uint64_t>(1)}));
367
368 std::vector<std::vector<uint64_t>::const_iterator> its;
369 std::vector<std::vector<uint64_t>::const_iterator> ites;
370 for (auto const& valVec : allValues) {
371 its.push_back(valVec.cbegin());
372 ites.push_back(valVec.cend());
373 }
374
375 // Now create an initial state for each combination of values
376 CompressedState initialState(this->variableInformation.getTotalBitOffset(true));
378 its, ites,
379 [this, &initialState, &locEndIndex, &intEndIndex](uint64_t index, uint64_t value) {
380 // Set the value for the variable corresponding to the given index
381 if (index < locEndIndex) {
382 // Location variable
383 setLocation(initialState, this->variableInformation.locationVariables[index], value);
384 } else if (index < intEndIndex) {
385 // Integer variable
386 auto const& intVar = this->variableInformation.integerVariables[index - locEndIndex];
387 initialState.setFromInt(intVar.bitOffset, intVar.bitWidth, value);
388 } else {
389 // Boolean variable
390 STORM_LOG_ASSERT(index - intEndIndex < this->variableInformation.booleanVariables.size(), "Unexpected index.");
391 auto const& boolVar = this->variableInformation.booleanVariables[index - intEndIndex];
392 STORM_LOG_ASSERT(value <= 1u, "Unexpected value for boolean variable.");
393 initialState.set(boolVar.bitOffset, static_cast<bool>(value));
394 }
395 },
396 [&stateToIdCallback, &initialStateIndices, &initialState]() {
397 // Register initial state.
398 StateType id = stateToIdCallback(initialState);
399 initialStateIndices.push_back(id);
400 return true; // Keep on exploring
401 });
402 STORM_LOG_DEBUG("Enumerated " << initialStateIndices.size() << " initial states using brute force enumeration.");
403 }
404 return initialStateIndices;
405}
406
407template<typename ValueType, typename StateType>
408void JaniNextStateGenerator<ValueType, StateType>::applyUpdate(CompressedState& state, storm::jani::EdgeDestination const& destination,
409 storm::generator::LocationVariableInformation const& locationVariable, int64_t assignmentLevel,
410 storm::expressions::ExpressionEvaluator<ValueType> const& expressionEvaluator) {
411 // Update the location of the state.
412 setLocation(state, locationVariable, destination.getLocationIndex());
413
414 // Then perform the assignments.
415 auto const& assignments = destination.getOrderedAssignments().getNonTransientAssignments(assignmentLevel);
416 auto assignmentIt = assignments.begin();
417 auto assignmentIte = assignments.end();
418
419 // Iterate over all boolean assignments and carry them out.
420 auto boolIt = this->variableInformation.booleanVariables.begin();
421 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsVariable() && assignmentIt->getExpressionVariable().hasBooleanType(); ++assignmentIt) {
422 while (assignmentIt->getExpressionVariable() != boolIt->variable) {
423 ++boolIt;
424 }
425 state.set(boolIt->bitOffset, expressionEvaluator.asBool(assignmentIt->getAssignedExpression()));
426 }
427
428 // Iterate over all integer assignments and carry them out.
429 auto integerIt = this->variableInformation.integerVariables.begin();
430 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsVariable() && assignmentIt->getExpressionVariable().hasIntegerType(); ++assignmentIt) {
431 while (assignmentIt->getExpressionVariable() != integerIt->variable) {
432 ++integerIt;
433 }
434 int_fast64_t assignedValue = expressionEvaluator.asInt(assignmentIt->getAssignedExpression());
435 if (this->options.isAddOutOfBoundsStateSet()) {
436 if (assignedValue < integerIt->lowerBound || assignedValue > integerIt->upperBound) {
437 state = this->outOfBoundsState;
438 }
439 } else if (integerIt->forceOutOfBoundsCheck || this->options.isExplorationChecksSet()) {
440 STORM_LOG_THROW(assignedValue >= integerIt->lowerBound, storm::exceptions::WrongFormatException,
441 "The update " << assignmentIt->getExpressionVariable().getName() << " := " << assignmentIt->getAssignedExpression()
442 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
443 << assignmentIt->getExpressionVariable().getName() << "'.");
444 STORM_LOG_THROW(assignedValue <= integerIt->upperBound, storm::exceptions::WrongFormatException,
445 "The update " << assignmentIt->getExpressionVariable().getName() << " := " << assignmentIt->getAssignedExpression()
446 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
447 << assignmentIt->getExpressionVariable().getName() << "'.");
448 }
449 state.setFromInt(integerIt->bitOffset, integerIt->bitWidth, assignedValue - integerIt->lowerBound);
450 STORM_LOG_ASSERT(static_cast<int_fast64_t>(state.getAsInt(integerIt->bitOffset, integerIt->bitWidth)) + integerIt->lowerBound == assignedValue,
451 "Writing to the bit vector bucket failed (read " << state.getAsInt(integerIt->bitOffset, integerIt->bitWidth) << " but wrote "
452 << assignedValue << ").");
453 }
454 // Iterate over all array access assignments and carry them out.
455 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsArrayAccess(); ++assignmentIt) {
456 auto const& arrayIndicesAsExpr = assignmentIt->getLValue().getArrayIndexVector();
457 std::vector<uint64_t> arrayIndices;
458 arrayIndices.reserve(arrayIndicesAsExpr.size());
459 for (auto const& i : arrayIndicesAsExpr) {
460 arrayIndices.push_back(static_cast<uint64_t>(expressionEvaluator.asInt(i)));
461 }
462 if (assignmentIt->getAssignedExpression().hasIntegerType()) {
463 IntegerVariableInformation const& intInfo =
464 this->variableInformation.getIntegerArrayVariableReplacement(assignmentIt->getLValue().getVariable().getExpressionVariable(), arrayIndices);
465 int_fast64_t assignedValue = expressionEvaluator.asInt(assignmentIt->getAssignedExpression());
466
467 if (this->options.isAddOutOfBoundsStateSet()) {
468 if (assignedValue < intInfo.lowerBound || assignedValue > intInfo.upperBound) {
469 state = this->outOfBoundsState;
470 }
471 } else if (this->options.isExplorationChecksSet()) {
472 STORM_LOG_THROW(assignedValue >= intInfo.lowerBound, storm::exceptions::WrongFormatException,
473 "The update " << assignmentIt->getLValue() << " := " << assignmentIt->getAssignedExpression()
474 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
475 << assignmentIt->getExpressionVariable().getName() << "'.");
476 STORM_LOG_THROW(assignedValue <= intInfo.upperBound, storm::exceptions::WrongFormatException,
477 "The update " << assignmentIt->getLValue() << " := " << assignmentIt->getAssignedExpression()
478 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
479 << assignmentIt->getExpressionVariable().getName() << "'.");
480 }
481 state.setFromInt(intInfo.bitOffset, intInfo.bitWidth, assignedValue - intInfo.lowerBound);
482 STORM_LOG_ASSERT(static_cast<int_fast64_t>(state.getAsInt(intInfo.bitOffset, intInfo.bitWidth)) + intInfo.lowerBound == assignedValue,
483 "Writing to the bit vector bucket failed (read " << state.getAsInt(intInfo.bitOffset, intInfo.bitWidth) << " but wrote "
484 << assignedValue << ").");
485 } else if (assignmentIt->getAssignedExpression().hasBooleanType()) {
486 BooleanVariableInformation const& boolInfo =
487 this->variableInformation.getBooleanArrayVariableReplacement(assignmentIt->getLValue().getVariable().getExpressionVariable(), arrayIndices);
488 state.set(boolInfo.bitOffset, expressionEvaluator.asBool(assignmentIt->getAssignedExpression()));
489 } else {
490 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unhandled type of base variable.");
491 }
492 }
493
494 // Check that we processed all assignments.
495 STORM_LOG_ASSERT(assignmentIt == assignmentIte, "Not all assignments were consumed.");
496}
497
498template<typename ValueType, typename StateType>
499void JaniNextStateGenerator<ValueType, StateType>::applyTransientUpdate(TransientVariableValuation<ValueType>& transientValuation,
500 storm::jani::detail::ConstAssignments const& transientAssignments,
501 storm::expressions::ExpressionEvaluator<ValueType> const& expressionEvaluator) const {
502 auto assignmentIt = transientAssignments.begin();
503 auto assignmentIte = transientAssignments.end();
504
505 // Iterate over all boolean assignments and carry them out.
506 auto boolIt = this->transientVariableInformation.booleanVariableInformation.begin();
507 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsVariable() && assignmentIt->getExpressionVariable().hasBooleanType(); ++assignmentIt) {
508 while (assignmentIt->getExpressionVariable() != boolIt->variable) {
509 ++boolIt;
510 }
511 transientValuation.booleanValues.emplace_back(&(*boolIt), expressionEvaluator.asBool(assignmentIt->getAssignedExpression()));
512 }
513 // Iterate over all integer assignments and carry them out.
514 auto integerIt = this->transientVariableInformation.integerVariableInformation.begin();
515 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsVariable() && assignmentIt->getExpressionVariable().hasIntegerType(); ++assignmentIt) {
516 while (assignmentIt->getExpressionVariable() != integerIt->variable) {
517 ++integerIt;
518 }
519 int64_t assignedValue = expressionEvaluator.asInt(assignmentIt->getAssignedExpression());
520 if (this->options.isExplorationChecksSet()) {
521 STORM_LOG_THROW(!integerIt->lowerBound || assignedValue >= integerIt->lowerBound.get(), storm::exceptions::WrongFormatException,
522 "The update " << assignmentIt->getExpressionVariable().getName() << " := " << assignmentIt->getAssignedExpression()
523 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
524 << assignmentIt->getExpressionVariable().getName() << "'.");
525 STORM_LOG_THROW(!integerIt->upperBound || assignedValue <= integerIt->upperBound.get(), storm::exceptions::WrongFormatException,
526 "The update " << assignmentIt->getExpressionVariable().getName() << " := " << assignmentIt->getAssignedExpression()
527 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
528 << assignmentIt->getExpressionVariable().getName() << "'.");
529 }
530 transientValuation.integerValues.emplace_back(&(*integerIt), assignedValue);
531 }
532 // Iterate over all rational assignments and carry them out.
533 auto rationalIt = this->transientVariableInformation.rationalVariableInformation.begin();
534 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsVariable() && assignmentIt->getExpressionVariable().hasRationalType(); ++assignmentIt) {
535 while (assignmentIt->getExpressionVariable() != rationalIt->variable) {
536 ++rationalIt;
537 }
538 transientValuation.rationalValues.emplace_back(&(*rationalIt), expressionEvaluator.asRational(assignmentIt->getAssignedExpression()));
539 }
540
541 // Iterate over all array access assignments and carry them out.
542 for (; assignmentIt != assignmentIte && assignmentIt->lValueIsArrayAccess(); ++assignmentIt) {
543 auto const& arrayIndicesAsExpr = assignmentIt->getLValue().getArrayIndexVector();
544 std::vector<uint64_t> arrayIndices;
545 arrayIndices.reserve(arrayIndicesAsExpr.size());
546 for (auto const& i : arrayIndicesAsExpr) {
547 arrayIndices.push_back(static_cast<uint64_t>(expressionEvaluator.asInt(i)));
548 }
549 storm::expressions::Type const& baseType = assignmentIt->getLValue().getVariable().getExpressionVariable().getType();
550 if (baseType.isIntegerType()) {
551 auto const& intInfo = this->transientVariableInformation.getIntegerArrayVariableReplacement(
552 assignmentIt->getLValue().getVariable().getExpressionVariable(), arrayIndices);
553 int64_t assignedValue = expressionEvaluator.asInt(assignmentIt->getAssignedExpression());
554 if (this->options.isExplorationChecksSet()) {
555 STORM_LOG_THROW(assignedValue >= intInfo.lowerBound, storm::exceptions::WrongFormatException,
556 "The update " << assignmentIt->getLValue() << " := " << assignmentIt->getAssignedExpression()
557 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
558 << assignmentIt->getExpressionVariable().getName() << "'.");
559 STORM_LOG_THROW(assignedValue <= intInfo.upperBound, storm::exceptions::WrongFormatException,
560 "The update " << assignmentIt->getLValue() << " := " << assignmentIt->getAssignedExpression()
561 << " leads to an out-of-bounds value (" << assignedValue << ") for the variable '"
562 << assignmentIt->getExpressionVariable().getName() << "'.");
563 }
564 transientValuation.integerValues.emplace_back(&intInfo, assignedValue);
565 } else if (baseType.isBooleanType()) {
566 auto const& boolInfo = this->transientVariableInformation.getBooleanArrayVariableReplacement(
567 assignmentIt->getLValue().getVariable().getExpressionVariable(), arrayIndices);
568 transientValuation.booleanValues.emplace_back(&boolInfo, expressionEvaluator.asBool(assignmentIt->getAssignedExpression()));
569 } else if (baseType.isRationalType()) {
570 auto const& rationalInfo = this->transientVariableInformation.getRationalArrayVariableReplacement(
571 assignmentIt->getLValue().getVariable().getExpressionVariable(), arrayIndices);
572 transientValuation.rationalValues.emplace_back(&rationalInfo, expressionEvaluator.asRational(assignmentIt->getAssignedExpression()));
573 } else {
574 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Unhandled type of base variable.");
575 }
576 }
577
578 // Check that we processed all assignments.
579 STORM_LOG_ASSERT(assignmentIt == assignmentIte, "Not all assignments were consumed.");
580}
581
582template<typename ValueType, typename StateType>
583TransientVariableValuation<ValueType> JaniNextStateGenerator<ValueType, StateType>::getTransientVariableValuationAtLocations(
584 std::vector<uint64_t> const& locations, storm::expressions::ExpressionEvaluator<ValueType> const& evaluator) const {
585 TransientVariableValuation<ValueType> transientVariableValuation;
586 getTransientVariableValuationAtLocations(locations, evaluator, transientVariableValuation);
587 return transientVariableValuation;
588}
589
590template<typename ValueType, typename StateType>
591void JaniNextStateGenerator<ValueType, StateType>::getTransientVariableValuationAtLocations(
592 std::vector<uint64_t> const& locations, storm::expressions::ExpressionEvaluator<ValueType> const& evaluator,
593 TransientVariableValuation<ValueType>& transientVariableValuation) const {
594 transientVariableValuation.clear();
595 uint64_t automatonIndex = 0;
596 for (auto const& automatonRef : this->parallelAutomata) {
597 auto const& automaton = automatonRef.get();
598 uint64_t currentLocationIndex = locations[automatonIndex];
599 storm::jani::Location const& location = automaton.getLocation(currentLocationIndex);
600 STORM_LOG_ASSERT(!location.getAssignments().hasMultipleLevels(true), "Indexed assignments at locations are not supported in the jani standard.");
601 applyTransientUpdate(transientVariableValuation, location.getAssignments().getTransientAssignments(), evaluator);
602 ++automatonIndex;
603 }
604}
605
606template<typename ValueType, typename StateType>
609 transientVariableInformation.setDefaultValuesInEvaluator(evaluator);
610 auto transientVariableValuation = getTransientVariableValuationAtLocations(getLocations(state), evaluator);
611 transientVariableValuation.setInEvaluator(evaluator, this->getOptions().isExplorationChecksSet());
612}
613
614template<typename ValueType, typename StateType>
617 if (this->variableInformation.hasOutOfBoundsBit()) {
618 builder.addBooleanVariable(this->variableInformation.outOfBoundsBit->variable);
619 }
620 for (auto const& v : this->variableInformation.locationVariables) {
621 builder.addIntegerVariable(v.variable, 0, v.highestValue);
622 }
623 for (auto const& v : this->variableInformation.booleanVariables) {
624 builder.addBooleanVariable(v.variable);
625 }
626 for (auto const& v : this->variableInformation.integerVariables) {
627 builder.addIntegerVariable(v.variable, v.lowerBound, v.upperBound);
628 }
629 // Also add information for transient variables
630 for (auto const& varInfo : transientVariableInformation.booleanVariableInformation) {
631 builder.addBooleanVariable(varInfo.variable);
632 }
633 for (auto const& varInfo : transientVariableInformation.integerVariableInformation) {
634 builder.addIntegerVariable(varInfo.variable, varInfo.lowerBound.value_or(std::numeric_limits<int64_t>::min()),
635 varInfo.upperBound.value_or(std::numeric_limits<int64_t>::max()));
636 }
637 for (auto const& varInfo : transientVariableInformation.rationalVariableInformation) {
638 if (std::is_same_v<ValueType, storm::RationalNumber>) {
639 uint64_t const bitSize = std::max<uint64_t>(64, this->getOptions().getReservedBitsForUnboundedVariables());
640 builder.addRationalVariable(varInfo.variable, bitSize * 2); // reserve bits for numerator and denominator
641 } else {
642 STORM_LOG_THROW((std::is_same_v<ValueType, double>), storm::exceptions::NotSupportedException,
643 "State valuations for transient variables of the given value type are not supported.");
644 builder.addDoubleVariable(varInfo.variable);
645 }
646 }
647 return storm::storage::sparse::Valuations(builder.buildClassDescription(), builder.getManager().shared_from_this());
648}
649
650template<typename ValueType, typename StateType>
652 storm::storage::sparse::Valuations& valuations) const {
653 // Add values for non-transient variables
654 unpackStateAppendToValuations(*this->state, this->variableInformation, valuations.getStorage());
655
656 auto transientVariableValuation = getTransientVariableValuationAtLocations(getLocations(*this->state), *this->evaluator);
657 transientVariableValuation.setInValuations(currentStateIndex, transientVariableInformation, valuations.getStorage());
658}
659
660template<typename ValueType, typename StateType>
661bool JaniNextStateGenerator<ValueType, StateType>::evaluatorHoldsState(CompressedState const& state) const {
662 auto const& info = this->variableInformation;
663 // Compiling expressions is expensive, so we do it only once (the compiled expressions are stored within the Expression objects)
664 if (scratch->variableExpressionsForAssertions.empty()) {
665 for (auto const& v : info.locationVariables) {
666 scratch->variableExpressionsForAssertions.push_back(v.variable.getExpression());
667 }
668 for (auto const& v : info.booleanVariables) {
669 scratch->variableExpressionsForAssertions.push_back(v.variable.getExpression());
670 }
671 for (auto const& v : info.integerVariables) {
672 scratch->variableExpressionsForAssertions.push_back(v.variable.getExpression());
673 }
674 }
675 auto const expected = unpackStateIntoValuation(state, info, *this->expressionManager);
676 // Values that can not be represented exactly by the evaluator (e.g., double-based) are not compared.
677 auto const exactlyRepresentable = [](int64_t value) { return value > -(int64_t(1) << 52) && value < (int64_t(1) << 52); };
678 auto const holdsInteger = [&](storm::expressions::Variable const& variable, storm::expressions::Expression const& expression) {
679 int64_t const expectedValue = expected.getIntegerValue(variable);
680 return !exactlyRepresentable(expectedValue) || this->evaluator->asInt(expression) == expectedValue;
681 };
682 auto expressionIt = scratch->variableExpressionsForAssertions.begin();
683 for (auto const& v : info.locationVariables) {
684 if (!holdsInteger(v.variable, *expressionIt++)) {
685 return false;
686 }
687 }
688 for (auto const& v : info.booleanVariables) {
689 if (this->evaluator->asBool(*expressionIt++) != expected.getBooleanValue(v.variable)) {
690 return false;
691 }
692 }
693 for (auto const& v : info.integerVariables) {
694 if (!holdsInteger(v.variable, *expressionIt++)) {
695 return false;
696 }
697 }
698 return true;
699}
700
701template<typename ValueType, typename StateType>
702void JaniNextStateGenerator<ValueType, StateType>::setEvaluatorState(CompressedState const& state) {
703 STORM_LOG_ASSERT(state.size() == scratch->evaluatorState.size(), "Unexpected state size.");
704 STORM_LOG_ASSERT(evaluatorHoldsState(scratch->evaluatorState), "The evaluator does not hold the expected state.");
705 unpackStateDifferenceIntoEvaluator(state, scratch->evaluatorState, this->variableInformation, *this->evaluator);
706 scratch->evaluatorState = state;
707}
708
709template<typename ValueType, typename StateType>
711 // The evaluator should have the default values of the transient variables right now.
712
713 // Prepare the result, in case we return early.
715 result.clear();
716
717 // The evaluator currently holds the values of the state that is expanded (see NextStateGenerator::load)
718 scratch->evaluatorState = *this->state;
719 STORM_LOG_ASSERT(evaluatorHoldsState(*this->state), "The state to expand must be loaded into the evaluator (see load).");
720
721 // Retrieve the locations from the state.
722 std::vector<uint64_t>& locations = scratch->locations;
723 getLocations(*this->state, locations);
724
725 // First, construct the state rewards, as we may return early if there are no choices later and we already
726 // need the state rewards then.
727 getTransientVariableValuationAtLocations(locations, *this->evaluator, scratch->transientValuation);
728 scratch->transientValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
729 evaluateRewardExpressions(result.getStateRewards());
730
731 // If a terminal expression was set and we must not expand this state, return now.
732 // Terminal state expressions do not consider transient variables.
733 if (!this->terminalStates.empty()) {
734 for (auto const& expressionBool : this->terminalStates) {
735 if (this->evaluator->asBool(expressionBool.first) == expressionBool.second) {
736 // Set back transient variables to default values so we are ready to process the next state
737 this->transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
738 return result;
739 }
740 }
741 }
742
743 // Set back transient variables to default values so we are ready to process the transition assignments
744 this->transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
745
746 // Get all choices for the state.
747 result.setExpanded();
748 if (this->getOptions().isApplyMaximalProgressAssumptionSet()) {
749 // First explore only edges without a rate
750 getActionChoices(locations, *this->state, stateToIdCallback, EdgeFilter::WithoutRate, result);
751 if (result.empty()) {
752 // Expand the Markovian edges if there are no probabilistic ones.
753 getActionChoices(locations, *this->state, stateToIdCallback, EdgeFilter::WithRate, result);
754 }
755 } else {
756 getActionChoices(locations, *this->state, stateToIdCallback, EdgeFilter::All, result);
757 }
758 std::size_t totalNumberOfChoices = result.getNumberOfChoices();
759
760 // If there is not a single choice, we return immediately, because the state has no behavior (other than
761 // the state reward).
762 if (totalNumberOfChoices == 0) {
763 return result;
764 }
765
766 // If the model is a deterministic model, we need to fuse the choices into one.
767 if (this->isDeterministicModel() && totalNumberOfChoices > 1) {
768 Choice<ValueType> globalChoice;
769
770 if (this->options.isAddOverlappingGuardLabelSet()) {
771 this->overlappingGuardStates->push_back(stateToIdCallback(*this->state));
772 }
773
774 // For CTMCs, we need to keep track of the total exit rate to scale the action rewards later. For DTMCs
775 // this is equal to the number of choices, which is why we initialize it like this here.
776 ValueType totalExitRate = this->isDiscreteTimeModel() ? static_cast<ValueType>(totalNumberOfChoices) : storm::utility::zero<ValueType>();
777
778 // Iterate over all choices and combine the probabilities/rates into one choice.
779 for (auto const& choice : result) {
780 for (auto const& stateProbabilityPair : choice) {
781 if (this->isDiscreteTimeModel()) {
782 globalChoice.addProbability(stateProbabilityPair.first, stateProbabilityPair.second / totalNumberOfChoices);
783 } else {
784 globalChoice.addProbability(stateProbabilityPair.first, stateProbabilityPair.second);
785 }
786 }
787
788 if (hasStateActionRewards && !this->isDiscreteTimeModel()) {
789 totalExitRate += choice.getTotalMass();
790 }
791 }
792
793 std::vector<ValueType> stateActionRewards(rewardExpressions.size(), storm::utility::zero<ValueType>());
794 for (auto const& choice : result) {
795 if (hasStateActionRewards) {
796 for (uint_fast64_t rewardVariableIndex = 0; rewardVariableIndex < rewardExpressions.size(); ++rewardVariableIndex) {
797 stateActionRewards[rewardVariableIndex] += choice.getRewards()[rewardVariableIndex] * choice.getTotalMass() / totalExitRate;
798 }
799 }
800
801 if (this->options.isBuildChoiceOriginsSet() && choice.hasOriginData()) {
802 globalChoice.addOriginData(choice.getOriginData());
803 }
804 }
805 globalChoice.addRewards(std::move(stateActionRewards));
806
807 // Move the newly fused choice in place.
808 result.clearChoices();
809 result.addChoice(std::move(globalChoice));
810 }
811
812 this->postprocess(result);
813
814 return result;
815}
816
817template<typename ValueType, typename StateType>
818Choice<ValueType>& JaniNextStateGenerator<ValueType, StateType>::expandNonSynchronizingEdge(storm::jani::Edge const& edge, uint64_t outputActionIndex,
819 uint64_t automatonIndex, CompressedState const& state,
820 StateToIdCallback const& stateToIdCallback,
822 // Determine the exit rate if it's a Markovian edge.
823 boost::optional<ValueType> exitRate = boost::none;
824 if (edge.hasRate()) {
825 exitRate = this->evaluator->asRational(edge.getRate());
826 }
827
828 Choice<ValueType>& choice = behavior.startNewChoice(outputActionIndex, static_cast<bool>(exitRate));
829 std::vector<ValueType>& stateActionRewards = choice.getRewards();
830
831 // Perform the transient edge assignments and create the state action rewards
832 TransientVariableValuation<ValueType>& transientVariableValuation = scratch->transientValuation;
833 transientVariableValuation.clear();
834 if (!evaluateRewardExpressionsAtEdges || edge.getAssignments().empty()) {
835 stateActionRewards.resize(rewardModelInformation.size(), storm::utility::zero<ValueType>());
836 } else {
837 for (int64_t assignmentLevel = edge.getAssignments().getLowestLevel(true); assignmentLevel <= edge.getAssignments().getHighestLevel(true);
838 ++assignmentLevel) {
839 transientVariableValuation.clear();
840 applyTransientUpdate(transientVariableValuation, edge.getAssignments().getTransientAssignments(assignmentLevel), *this->evaluator);
841 transientVariableValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
842 }
843 evaluateRewardExpressions(stateActionRewards);
844 transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
845 }
846
847 // Iterate over all updates of the current command.
849 for (auto const& destination : edge.getDestinations()) {
850 ValueType probability = this->evaluator->asRational(destination.getProbability());
851
852 if (probability != storm::utility::zero<ValueType>()) {
853 bool evaluatorChanged = false;
854 // Obtain target state index and add it to the list of known states. If it has not yet been
855 // seen, we also add it to the set of states that have yet to be explored.
856 int64_t assignmentLevel = edge.getLowestAssignmentLevel(); // Might be the largest possible integer, if there is no assignment
857 int64_t const& highestLevel = edge.getHighestAssignmentLevel();
858 bool hasTransientAssignments = destination.hasTransientAssignment();
859 CompressedState& newState = scratch->successorState;
860 newState = state;
861 applyUpdate(newState, destination, this->variableInformation.locationVariables[automatonIndex], assignmentLevel, *this->evaluator);
862 if (hasTransientAssignments) {
864 "Transition rewards are not supported and scaling to action rewards is disabled.");
865 transientVariableValuation.clear();
866 applyTransientUpdate(transientVariableValuation, destination.getOrderedAssignments().getTransientAssignments(assignmentLevel),
867 *this->evaluator);
868 transientVariableValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
869 evaluatorChanged = true;
870 }
871 if (assignmentLevel < highestLevel) {
872 while (assignmentLevel < highestLevel) {
873 ++assignmentLevel;
874 setEvaluatorState(newState);
875 evaluatorChanged = true;
876 applyUpdate(newState, destination, this->variableInformation.locationVariables[automatonIndex], assignmentLevel, *this->evaluator);
877 if (hasTransientAssignments) {
878 transientVariableValuation.clear();
879 applyTransientUpdate(transientVariableValuation, destination.getOrderedAssignments().getTransientAssignments(assignmentLevel),
880 *this->evaluator);
881 transientVariableValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
882 evaluatorChanged = true;
883 }
884 }
885 }
886 if (evaluateRewardExpressionsAtDestinations) {
887 setEvaluatorState(newState);
888 evaluatorChanged = true;
889 addEvaluatedRewardExpressions(stateActionRewards, probability);
890 }
891
892 if (evaluatorChanged) {
893 // Restore the old variable valuation
894 setEvaluatorState(state);
895 if (hasTransientAssignments) {
896 this->transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
897 }
898 }
899
900 StateType stateIndex = stateToIdCallback(newState);
901
902 // Update the choice by adding the probability/target state to it.
903 probability = exitRate ? exitRate.get() * probability : probability;
904 choice.addProbability(stateIndex, probability);
905
906 if (this->options.isExplorationChecksSet()) {
907 probabilitySum += probability;
908 }
909 }
910 }
911
912 if (this->options.isExplorationChecksSet()) {
913 // Check that the resulting distribution is in fact a distribution.
914 STORM_LOG_THROW(!this->isDiscreteTimeModel() || (!storm::utility::isConstant(probabilitySum) || this->comparator.isOne(probabilitySum)),
915 storm::exceptions::WrongFormatException, "Probabilities do not sum to one for edge (actually sum to " << probabilitySum << ").");
916 }
917
918 return choice;
919}
920
921template<typename ValueType, typename StateType>
922void JaniNextStateGenerator<ValueType, StateType>::generateSynchronizedDistribution(storm::storage::BitVector const& state,
923 AutomataEdgeSets const& edgeCombination,
924 std::vector<EdgeSetWithIndices::const_iterator> const& iteratorList,
925 storm::generator::Distribution<StateType, ValueType>& distribution,
926 std::vector<ValueType>& stateActionRewards, EdgeIndexSet& edgeIndices,
927 StateToIdCallback const& stateToIdCallback) {
928 // Collect some information of the edges.
929 int64_t lowestDestinationAssignmentLevel = std::numeric_limits<int64_t>::max();
930 int64_t highestDestinationAssignmentLevel = std::numeric_limits<int64_t>::min();
931 int64_t lowestEdgeAssignmentLevel = std::numeric_limits<int64_t>::max();
932 int64_t highestEdgeAssignmentLevel = std::numeric_limits<int64_t>::min();
933 uint64_t numDestinations = 1;
934 for (uint_fast64_t i = 0; i < iteratorList.size(); ++i) {
935 if (this->getOptions().isBuildChoiceOriginsSet()) {
936 auto automatonIndex = model.getAutomatonIndex(parallelAutomata[edgeCombination[i].first].get().getName());
937 edgeIndices.insert(storm::jani::Model::encodeAutomatonAndEdgeIndices(automatonIndex, iteratorList[i]->first));
938 }
939 storm::jani::Edge const& edge = *iteratorList[i]->second;
940 lowestDestinationAssignmentLevel = std::min(lowestDestinationAssignmentLevel, edge.getLowestAssignmentLevel());
941 highestDestinationAssignmentLevel = std::max(highestDestinationAssignmentLevel, edge.getHighestAssignmentLevel());
942 if (!edge.getAssignments().empty(true)) {
943 lowestEdgeAssignmentLevel = std::min(lowestEdgeAssignmentLevel, edge.getAssignments().getLowestLevel(true));
944 highestEdgeAssignmentLevel = std::max(highestEdgeAssignmentLevel, edge.getAssignments().getHighestLevel(true));
945 }
946 numDestinations *= edge.getNumberOfDestinations();
947 }
948
949 // Perform the edge assignments (if there are any)
950 TransientVariableValuation<ValueType>& transientVariableValuation = scratch->transientValuation;
951 transientVariableValuation.clear();
952 if (evaluateRewardExpressionsAtEdges && lowestEdgeAssignmentLevel <= highestEdgeAssignmentLevel) {
953 for (int64_t assignmentLevel = lowestEdgeAssignmentLevel; assignmentLevel <= highestEdgeAssignmentLevel; ++assignmentLevel) {
954 transientVariableValuation.clear();
955 for (uint_fast64_t i = 0; i < iteratorList.size(); ++i) {
956 storm::jani::Edge const& edge = *iteratorList[i]->second;
957 applyTransientUpdate(transientVariableValuation, edge.getAssignments().getTransientAssignments(assignmentLevel), *this->evaluator);
958 }
959 transientVariableValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
960 }
961 addEvaluatedRewardExpressions(stateActionRewards, storm::utility::one<ValueType>());
962 transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
963 }
964
965 std::vector<storm::jani::EdgeDestination const*>& destinations = scratch->destinations;
966 std::vector<LocationVariableInformation const*>& locationVars = scratch->locationVars;
967
968 for (uint64_t destinationId = 0; destinationId < numDestinations; ++destinationId) {
969 // First assignment level
970 destinations.clear();
971 locationVars.clear();
972 transientVariableValuation.clear();
973 CompressedState& successorState = scratch->successorState;
974 successorState = state;
975 ValueType successorProbability = storm::utility::one<ValueType>();
976
977 uint64_t destinationIndex = destinationId;
978 for (uint64_t i = 0; i < iteratorList.size(); ++i) {
979 storm::jani::Edge const& edge = *iteratorList[i]->second;
980 STORM_LOG_ASSERT(edge.getNumberOfDestinations() > 0, "Found an edge with zero destinations. This is not expected.");
981 uint64_t localDestinationIndex = destinationIndex % edge.getNumberOfDestinations();
982 destinations.push_back(&edge.getDestination(localDestinationIndex));
983 locationVars.push_back(&this->variableInformation.locationVariables[edgeCombination[i].first]);
984 destinationIndex /= edge.getNumberOfDestinations();
985 ValueType probability = this->evaluator->asRational(destinations.back()->getProbability());
986 if (edge.hasRate()) {
987 successorProbability *= probability * this->evaluator->asRational(edge.getRate());
988 } else {
989 successorProbability *= probability;
990 }
991 if (storm::utility::isZero(successorProbability)) {
992 break;
993 }
994
995 applyUpdate(successorState, *destinations.back(), *locationVars.back(), lowestDestinationAssignmentLevel, *this->evaluator);
996 applyTransientUpdate(transientVariableValuation,
997 destinations.back()->getOrderedAssignments().getTransientAssignments(lowestDestinationAssignmentLevel), *this->evaluator);
998 }
999
1000 if (!storm::utility::isZero(successorProbability)) {
1001 bool evaluatorChanged = false;
1002 // remaining assignment levels (if there are any)
1003 for (int64_t assignmentLevel = lowestDestinationAssignmentLevel + 1; assignmentLevel <= highestDestinationAssignmentLevel; ++assignmentLevel) {
1004 setEvaluatorState(successorState);
1005 transientVariableValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
1006 transientVariableValuation.clear();
1007 evaluatorChanged = true;
1008 auto locationVarIt = locationVars.begin();
1009 for (auto const& destPtr : destinations) {
1010 applyUpdate(successorState, *destPtr, **locationVarIt, assignmentLevel, *this->evaluator);
1011 applyTransientUpdate(transientVariableValuation, destinations.back()->getOrderedAssignments().getTransientAssignments(assignmentLevel),
1012 *this->evaluator);
1013 ++locationVarIt;
1014 }
1015 }
1016 if (!transientVariableValuation.empty()) {
1017 evaluatorChanged = true;
1018 transientVariableValuation.setInEvaluator(*this->evaluator, this->getOptions().isExplorationChecksSet());
1019 }
1020 if (evaluateRewardExpressionsAtDestinations) {
1021 setEvaluatorState(successorState);
1022 evaluatorChanged = true;
1023 addEvaluatedRewardExpressions(stateActionRewards, successorProbability);
1024 }
1025 if (evaluatorChanged) {
1026 // Restore the old state information
1027 setEvaluatorState(state);
1028 this->transientVariableInformation.setDefaultValuesInEvaluator(*this->evaluator);
1029 }
1030
1031 StateType id = stateToIdCallback(successorState);
1032 distribution.add(id, successorProbability);
1033 }
1034 }
1035}
1036
1037template<typename ValueType, typename StateType>
1038void JaniNextStateGenerator<ValueType, StateType>::expandSynchronizingEdgeCombination(AutomataEdgeSets const& edgeCombination, uint64_t outputActionIndex,
1039 CompressedState const& state, StateToIdCallback const& stateToIdCallback,
1040 StateBehavior<ValueType, StateType>& behavior) {
1041 if (this->options.isExplorationChecksSet()) {
1042 // Check whether a global variable is written multiple times in any combination.
1043 checkGlobalVariableWritesValid(edgeCombination);
1044 }
1045
1046 std::vector<EdgeSetWithIndices::const_iterator>& iteratorList = scratch->iteratorList;
1047 iteratorList.resize(edgeCombination.size());
1048
1049 // Initialize the list of iterators.
1050 for (size_t i = 0; i < edgeCombination.size(); ++i) {
1051 iteratorList[i] = edgeCombination[i].second.cbegin();
1052 }
1053
1054 storm::generator::Distribution<StateType, ValueType>& distribution = scratch->distribution;
1055
1056 // As long as there is one feasible combination of commands, keep on expanding it.
1057 bool done = false;
1058 while (!done) {
1059 distribution.clear();
1060
1061 EdgeIndexSet edgeIndices;
1062 Choice<ValueType>& choice = behavior.startNewChoice(outputActionIndex);
1063 std::vector<ValueType>& stateActionRewards = choice.getRewards();
1064 stateActionRewards.assign(rewardExpressions.size(), storm::utility::zero<ValueType>());
1065 // old version without assignment levels generateSynchronizedDistribution(state, storm::utility::one<ValueType>(), 0, edgeCombination, iteratorList,
1066 // distribution, stateActionRewards, edgeIndices, stateToIdCallback);
1067 generateSynchronizedDistribution(state, edgeCombination, iteratorList, distribution, stateActionRewards, edgeIndices, stateToIdCallback);
1068 distribution.compress();
1069
1070 // At this point, we applied all commands of the current command combination and the distribution
1071 // contains all target states and their respective probabilities. The choice (which has already been added
1072 // to the behavior above) and its rewards are already set. That means we are now ready to
1073 // fill in the origin data, the probabilities/rates and the labels of the choice.
1074
1075 // Add the edge indices if requested.
1076 if (this->getOptions().isBuildChoiceOriginsSet()) {
1077 choice.addOriginData(boost::any(std::move(edgeIndices)));
1078 }
1079
1080 // Add the probabilities/rates to the newly created choice.
1081 ValueType probabilitySum = storm::utility::zero<ValueType>();
1082 choice.reserve(std::distance(distribution.begin(), distribution.end()));
1083 for (auto const& stateProbability : distribution) {
1084 choice.addProbability(stateProbability.getState(), stateProbability.getValue());
1085
1086 if (this->options.isExplorationChecksSet()) {
1087 probabilitySum += stateProbability.getValue();
1088 }
1089 }
1090
1091 if (this->options.isExplorationChecksSet()) {
1092 // Check that the resulting distribution is in fact a distribution.
1093 STORM_LOG_THROW(!this->isDiscreteTimeModel() || !storm::utility::isConstant(probabilitySum) || this->comparator.isOne(probabilitySum),
1094 storm::exceptions::WrongFormatException,
1095 "Sum of update probabilities do not sum to one for some edge (actually sum to " << probabilitySum << ").");
1096 }
1097
1098 if (this->options.isBuildChoiceLabelsSet()) {
1099 if (outputActionIndex != storm::jani::Model::SILENT_ACTION_INDEX) {
1100 choice.addLabel(model.getAction(outputActionIndex).getName());
1101 }
1102 }
1103
1104 // Now, check whether there is one more command combination to consider.
1105 bool movedIterator = false;
1106 for (uint64_t j = 0; !movedIterator && j < iteratorList.size(); ++j) {
1107 ++iteratorList[j];
1108 if (iteratorList[j] != edgeCombination[j].second.end()) {
1109 movedIterator = true;
1110 } else {
1111 // Reset the iterator to the beginning of the list.
1112 iteratorList[j] = edgeCombination[j].second.begin();
1113 }
1114 }
1115
1116 done = !movedIterator;
1117 }
1118}
1119
1120template<typename ValueType, typename StateType>
1121void JaniNextStateGenerator<ValueType, StateType>::getActionChoices(std::vector<uint64_t> const& locations, CompressedState const& state,
1122 StateToIdCallback const& stateToIdCallback, EdgeFilter const& edgeFilter,
1123 StateBehavior<ValueType, StateType>& behavior) {
1124 scratch->automataEdgeSets.resize(edges.size());
1125
1126 // To avoid reallocations, we reuse some scratch memory.
1127 // This vector will store for each automaton the set of edges with the current output and the current source location
1128 std::vector<EdgeSetWithIndices const*>& edgeSetsMemory = scratch->edgeSets;
1129 // This vector will store the 'first' combination of edges that is productive.
1130 std::vector<typename EdgeSetWithIndices::const_iterator>& edgeIteratorMemory = scratch->firstEnabledEdgeIterators;
1131
1132 uint64_t outputAndEdgesIndex = 0;
1133 for (OutputAndEdges const& outputAndEdges : edges) {
1134 auto const& edges = outputAndEdges.second;
1135 AutomataEdgeSets& automataEdgeSets = scratch->automataEdgeSets[outputAndEdgesIndex++];
1136 if (edges.size() == 1) {
1137 // If the synch consists of just one element, it's non-synchronizing.
1138 auto const& nonsychingEdges = edges.front();
1139 uint64_t automatonIndex = nonsychingEdges.first;
1140
1141 auto edgesIt = nonsychingEdges.second.find(locations[automatonIndex]);
1142 if (edgesIt != nonsychingEdges.second.end()) {
1143 for (auto const& indexAndEdge : edgesIt->second) {
1144 if (edgeFilter != EdgeFilter::All) {
1145 STORM_LOG_ASSERT(edgeFilter == EdgeFilter::WithRate || edgeFilter == EdgeFilter::WithoutRate, "Unexpected edge filter.");
1146 if ((edgeFilter == EdgeFilter::WithRate) != indexAndEdge.second->hasRate()) {
1147 continue;
1148 }
1149 }
1150 if (!this->evaluator->asBool(indexAndEdge.second->getGuard())) {
1151 continue;
1152 }
1153
1154 uint64_t actionIndex = outputAndEdges.first ? outputAndEdges.first.get() : indexAndEdge.second->getActionIndex();
1155 Choice<ValueType>& choice =
1156 expandNonSynchronizingEdge(*indexAndEdge.second, actionIndex, automatonIndex, state, stateToIdCallback, behavior);
1157
1158 if (this->getOptions().isBuildChoiceOriginsSet()) {
1159 auto modelAutomatonIndex = model.getAutomatonIndex(parallelAutomata[automatonIndex].get().getName());
1160 EdgeIndexSet edgeIndex{storm::jani::Model::encodeAutomatonAndEdgeIndices(modelAutomatonIndex, indexAndEdge.first)};
1161 choice.addOriginData(boost::any(std::move(edgeIndex)));
1162 }
1163
1164 if (this->getOptions().isBuildChoiceLabelsSet()) {
1165 if (actionIndex != storm::jani::Model::SILENT_ACTION_INDEX) {
1166 choice.addLabel(model.getAction(actionIndex).getName());
1167 }
1168 }
1169 }
1170 }
1171 } else {
1172 // If the element has more than one set of edges, we need to perform a synchronization.
1173 // We require that some output action for the synchronisation must have been set before.
1174 // This might be the silent action, if the Jani model does not specify an output action.
1175 STORM_LOG_ASSERT(outputAndEdges.first, "Need output action index for synchronization.");
1176
1177 uint64_t outputActionIndex = outputAndEdges.first.get();
1178
1179 // Find out whether this combination is productive
1180 bool productiveCombination = true;
1181 // First check, whether each automaton has at least one edge with the current output and the current source location
1182 // We will also store the edges of each automaton with the current outputAction
1183 edgeSetsMemory.clear();
1184 for (auto const& automatonAndEdges : outputAndEdges.second) {
1185 uint64_t automatonIndex = automatonAndEdges.first;
1186 LocationsAndEdges const& locationsAndEdges = automatonAndEdges.second;
1187 auto edgesIt = locationsAndEdges.find(locations[automatonIndex]);
1188 if (edgesIt == locationsAndEdges.end()) {
1189 productiveCombination = false;
1190 break;
1191 }
1192 edgeSetsMemory.push_back(&edgesIt->second);
1193 }
1194
1195 if (productiveCombination) {
1196 // second, check whether each automaton has at least one enabled action
1197 edgeIteratorMemory.clear(); // Store the first enabled edge in each automaton.
1198 for (auto const& edgesIt : edgeSetsMemory) {
1199 bool atLeastOneEdge = false;
1200 EdgeSetWithIndices const& edgeSetWithIndices = *edgesIt;
1201 for (auto indexAndEdgeIt = edgeSetWithIndices.begin(), indexAndEdgeIte = edgeSetWithIndices.end(); indexAndEdgeIt != indexAndEdgeIte;
1202 ++indexAndEdgeIt) {
1203 // check whether we do not consider this edge
1204 if (edgeFilter != EdgeFilter::All) {
1205 STORM_LOG_ASSERT(edgeFilter == EdgeFilter::WithRate || edgeFilter == EdgeFilter::WithoutRate, "Unexpected edge filter.");
1206 if ((edgeFilter == EdgeFilter::WithRate) != indexAndEdgeIt->second->hasRate()) {
1207 continue;
1208 }
1209 }
1210
1211 if (!this->evaluator->asBool(indexAndEdgeIt->second->getGuard())) {
1212 continue;
1213 }
1214
1215 // If we reach this point, the edge is considered enabled.
1216 atLeastOneEdge = true;
1217 edgeIteratorMemory.push_back(indexAndEdgeIt);
1218 break;
1219 }
1220
1221 // If there is no enabled edge of this automaton, the whole combination is not productive.
1222 if (!atLeastOneEdge) {
1223 productiveCombination = false;
1224 break;
1225 }
1226 }
1227 }
1228
1229 // produce the combination
1230 if (productiveCombination) {
1231 // Reuse the memory of the edge sets (resizing is a no-op except for the first time this synchronization is reached)
1232 automataEdgeSets.resize(outputAndEdges.second.size());
1233 STORM_LOG_ASSERT(edgeSetsMemory.size() == outputAndEdges.second.size(), "Unexpected number of edge sets stored.");
1234 STORM_LOG_ASSERT(edgeIteratorMemory.size() == outputAndEdges.second.size(), "Unexpected number of edge iterators stored.");
1235 auto edgeSetIt = edgeSetsMemory.begin();
1236 auto edgeIteratorIt = edgeIteratorMemory.begin();
1237 auto automatonEdgeSetsIt = automataEdgeSets.begin();
1238 for (auto const& automatonAndEdges : outputAndEdges.second) {
1239 EdgeSetWithIndices& enabledEdgesOfAutomaton = automatonEdgeSetsIt->second;
1240 enabledEdgesOfAutomaton.clear();
1241 automatonEdgeSetsIt->first = automatonAndEdges.first;
1242 EdgeSetWithIndices const& edgeSetWithIndices = **edgeSetIt;
1243 auto indexAndEdgeIt = *edgeIteratorIt;
1244 // The first edge where the edgeIterator points to is always enabled.
1245 enabledEdgesOfAutomaton.emplace_back(*indexAndEdgeIt);
1246 auto indexAndEdgeIte = edgeSetWithIndices.end();
1247 for (++indexAndEdgeIt; indexAndEdgeIt != indexAndEdgeIte; ++indexAndEdgeIt) {
1248 // check whether we do not consider this edge
1249 if (edgeFilter != EdgeFilter::All) {
1250 STORM_LOG_ASSERT(edgeFilter == EdgeFilter::WithRate || edgeFilter == EdgeFilter::WithoutRate, "Unexpected edge filter.");
1251 if ((edgeFilter == EdgeFilter::WithRate) != indexAndEdgeIt->second->hasRate()) {
1252 continue;
1253 }
1254 }
1255
1256 if (!this->evaluator->asBool(indexAndEdgeIt->second->getGuard())) {
1257 continue;
1258 }
1259 // If we reach this point, the edge is considered enabled.
1260 enabledEdgesOfAutomaton.emplace_back(*indexAndEdgeIt);
1261 }
1262 ++automatonEdgeSetsIt;
1263 ++edgeSetIt;
1264 ++edgeIteratorIt;
1265 }
1266 // insert choices in the result vector.
1267 expandSynchronizingEdgeCombination(automataEdgeSets, outputActionIndex, state, stateToIdCallback, behavior);
1268 }
1269 }
1270 }
1271}
1272
1273template<typename ValueType, typename StateType>
1274void JaniNextStateGenerator<ValueType, StateType>::checkGlobalVariableWritesValid(AutomataEdgeSets const& enabledEdges) const {
1275 // Todo: this also throws if the writes are on different assignment level
1276 // Todo: this also throws if the writes are on different elements of the same array
1277 std::map<storm::expressions::Variable, uint64_t> writtenGlobalVariables;
1278 for (auto edgeSetIt = enabledEdges.begin(), edgeSetIte = enabledEdges.end(); edgeSetIt != edgeSetIte; ++edgeSetIt) {
1279 for (auto const& indexAndEdge : edgeSetIt->second) {
1280 for (auto const& globalVariable : indexAndEdge.second->getWrittenGlobalVariables()) {
1281 auto it = writtenGlobalVariables.find(globalVariable);
1282
1283 auto index = std::distance(enabledEdges.begin(), edgeSetIt);
1284 if (it != writtenGlobalVariables.end()) {
1285 STORM_LOG_THROW(it->second == static_cast<uint64_t>(index), storm::exceptions::WrongFormatException,
1286 "Multiple writes to global variable '" << globalVariable.getName() << "' in synchronizing edges.");
1287 } else {
1288 writtenGlobalVariables.emplace(globalVariable, index);
1289 }
1290 }
1291 }
1292 }
1293}
1294
1295template<typename ValueType, typename StateType>
1297 return rewardExpressions.size();
1298}
1299
1300template<typename ValueType, typename StateType>
1302 return rewardModelInformation[index];
1303}
1304
1305template<typename ValueType, typename StateType>
1307 std::vector<StateType> const& initialStateIndices,
1308 std::vector<StateType> const& deadlockStateIndices,
1309 std::vector<StateType> const& unexploredStateIndices) {
1310 // As in JANI we can use transient boolean variable assignments in locations to identify states, we need to
1311 // create a list of boolean transient variables and the expressions that define them.
1312 std::vector<std::pair<std::string, storm::expressions::Expression>> transientVariableExpressions;
1313 for (auto const& variable : model.getGlobalVariables().getTransientVariables()) {
1314 if (variable.getType().isBasicType() && variable.getType().asBasicType().isBooleanType()) {
1315 if (this->options.isBuildAllLabelsSet() || this->options.getLabelNames().find(variable.getName()) != this->options.getLabelNames().end()) {
1316 transientVariableExpressions.emplace_back(variable.getName(), variable.getExpressionVariable().getExpression());
1317 }
1318 }
1319 }
1320 return NextStateGenerator<ValueType, StateType>::label(stateStorage, initialStateIndices, deadlockStateIndices, unexploredStateIndices,
1321 transientVariableExpressions);
1322}
1323
1324template<typename ValueType, typename StateType>
1325std::vector<ValueType> JaniNextStateGenerator<ValueType, StateType>::evaluateRewardExpressions() const {
1326 std::vector<ValueType> result;
1327 evaluateRewardExpressions(result);
1328 return result;
1329}
1330
1331template<typename ValueType, typename StateType>
1332void JaniNextStateGenerator<ValueType, StateType>::evaluateRewardExpressions(std::vector<ValueType>& result) const {
1333 result.clear();
1334 result.reserve(rewardExpressions.size());
1335 for (auto const& rewardExpression : rewardExpressions) {
1336 result.push_back(this->evaluator->asRational(rewardExpression.second));
1337 }
1338}
1339
1340template<typename ValueType, typename StateType>
1341void JaniNextStateGenerator<ValueType, StateType>::addEvaluatedRewardExpressions(std::vector<ValueType>& rewards, ValueType const& factor) const {
1342 STORM_LOG_ASSERT(rewards.size() == rewardExpressions.size(), "Reward count mismatch.");
1343 auto rewIt = rewards.begin();
1344 for (auto const& rewardExpression : rewardExpressions) {
1345 (*rewIt) += factor * this->evaluator->asRational(rewardExpression.second);
1346 ++rewIt;
1347 }
1348}
1349
1350template<typename ValueType, typename StateType>
1351void JaniNextStateGenerator<ValueType, StateType>::buildRewardModelInformation() {
1352 for (auto const& rewardModel : rewardExpressions) {
1353 storm::jani::RewardModelInformation info(this->model, rewardModel.second);
1354 rewardModelInformation.emplace_back(rewardModel.first, info.hasStateRewards(), false, false);
1355 STORM_LOG_THROW(this->options.isScaleAndLiftTransitionRewardsSet() || !info.hasTransitionRewards(), storm::exceptions::NotSupportedException,
1356 "Transition rewards are not supported and a reduction to action-based rewards was not possible.");
1357 if (info.hasTransitionRewards()) {
1358 evaluateRewardExpressionsAtDestinations = true;
1359 }
1360 if (info.hasActionRewards() || (this->options.isScaleAndLiftTransitionRewardsSet() && info.hasTransitionRewards())) {
1361 hasStateActionRewards = true;
1362 rewardModelInformation.back().setHasStateActionRewards();
1363 }
1364 }
1365 if (!hasStateActionRewards) {
1366 evaluateRewardExpressionsAtDestinations = false;
1367 evaluateRewardExpressionsAtEdges = false;
1368 }
1369}
1370
1371template<typename ValueType, typename StateType>
1372void JaniNextStateGenerator<ValueType, StateType>::createSynchronizationInformation() {
1373 // Create synchronizing edges information.
1374 storm::jani::Composition const& topLevelComposition = this->model.getSystemComposition();
1375 if (topLevelComposition.isAutomatonComposition()) {
1376 auto const& automaton = this->model.getAutomaton(topLevelComposition.asAutomatonComposition().getAutomatonName());
1377 this->parallelAutomata.push_back(automaton);
1378
1379 LocationsAndEdges locationsAndEdges;
1380 uint64_t edgeIndex = 0;
1381 for (auto const& edge : automaton.getEdges()) {
1382 locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(std::make_pair(edgeIndex, &edge));
1383 ++edgeIndex;
1384 }
1385
1386 AutomataAndEdges automataAndEdges;
1387 automataAndEdges.emplace_back(std::make_pair(0, std::move(locationsAndEdges)));
1388
1389 this->edges.emplace_back(std::make_pair(boost::none, std::move(automataAndEdges)));
1390 } else {
1391 STORM_LOG_THROW(topLevelComposition.isParallelComposition(), storm::exceptions::WrongFormatException, "Expected parallel composition.");
1392 storm::jani::ParallelComposition const& parallelComposition = topLevelComposition.asParallelComposition();
1393
1394 uint64_t automatonIndex = 0;
1395 for (auto const& composition : parallelComposition.getSubcompositions()) {
1396 STORM_LOG_THROW(composition->isAutomatonComposition(), storm::exceptions::WrongFormatException, "Expected flat parallel composition.");
1397 STORM_LOG_THROW(composition->asAutomatonComposition().getInputEnabledActions().empty(), storm::exceptions::NotSupportedException,
1398 "Input-enabled actions are not supported right now.");
1399
1400 this->parallelAutomata.push_back(this->model.getAutomaton(composition->asAutomatonComposition().getAutomatonName()));
1401
1402 // Add edges with silent action.
1403 LocationsAndEdges locationsAndEdges;
1404 uint64_t edgeIndex = 0;
1405 for (auto const& edge : parallelAutomata.back().get().getEdges()) {
1407 locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(std::make_pair(edgeIndex, &edge));
1408 }
1409 ++edgeIndex;
1410 }
1411
1412 if (!locationsAndEdges.empty()) {
1413 AutomataAndEdges automataAndEdges;
1414 automataAndEdges.emplace_back(std::make_pair(automatonIndex, std::move(locationsAndEdges)));
1415 this->edges.emplace_back(std::make_pair(boost::none, std::move(automataAndEdges)));
1416 }
1417 ++automatonIndex;
1418 }
1419
1420 for (auto const& vector : parallelComposition.getSynchronizationVectors()) {
1421 uint64_t outputActionIndex = this->model.getActionIndex(vector.getOutput());
1422
1423 AutomataAndEdges automataAndEdges;
1424 bool atLeastOneEdge = true;
1425 uint64_t automatonIndex = 0;
1426 for (auto const& element : vector.getInput()) {
1428 LocationsAndEdges locationsAndEdges;
1429 uint64_t actionIndex = this->model.getActionIndex(element);
1430 uint64_t edgeIndex = 0;
1431 for (auto const& edge : parallelAutomata[automatonIndex].get().getEdges()) {
1432 if (edge.getActionIndex() == actionIndex) {
1433 locationsAndEdges[edge.getSourceLocationIndex()].emplace_back(std::make_pair(edgeIndex, &edge));
1434 }
1435 ++edgeIndex;
1436 }
1437 if (locationsAndEdges.empty()) {
1438 atLeastOneEdge = false;
1439 break;
1440 }
1441 automataAndEdges.emplace_back(std::make_pair(automatonIndex, std::move(locationsAndEdges)));
1442 }
1443 ++automatonIndex;
1444 }
1445
1446 if (atLeastOneEdge) {
1447 this->edges.emplace_back(std::make_pair(outputActionIndex, std::move(automataAndEdges)));
1448 }
1449 }
1450 }
1451
1452 STORM_LOG_TRACE("Number of synchronizations: " << this->edges.size() << ".");
1453}
1454
1455template<typename ValueType, typename StateType>
1456std::shared_ptr<storm::storage::sparse::ChoiceOrigins> JaniNextStateGenerator<ValueType, StateType>::generateChoiceOrigins(
1457 std::vector<boost::any>& dataForChoiceOrigins) const {
1458 if (!this->getOptions().isBuildChoiceOriginsSet()) {
1459 return nullptr;
1460 }
1461
1462 std::vector<uint_fast64_t> identifiers;
1463 identifiers.reserve(dataForChoiceOrigins.size());
1464
1465 std::map<EdgeIndexSet, uint_fast64_t> edgeIndexSetToIdentifierMap;
1466 // The empty edge set (i.e., the choices without origin) always has to get identifier getIdentifierForChoicesWithNoOrigin() -- which is assumed to be 0
1467 STORM_LOG_ASSERT(storm::storage::sparse::ChoiceOrigins::getIdentifierForChoicesWithNoOrigin() == 0, "The no origin identifier is assumed to be zero.");
1468 edgeIndexSetToIdentifierMap.insert(std::make_pair(EdgeIndexSet(), 0));
1469 uint_fast64_t currentIdentifier = 1;
1470 for (boost::any& originData : dataForChoiceOrigins) {
1471 STORM_LOG_ASSERT(originData.empty() || boost::any_cast<EdgeIndexSet>(&originData) != nullptr,
1472 "Origin data has unexpected type: " << originData.type().name() << ".");
1473
1474 EdgeIndexSet currentEdgeIndexSet = originData.empty() ? EdgeIndexSet() : boost::any_cast<EdgeIndexSet>(std::move(originData));
1475 auto insertionRes = edgeIndexSetToIdentifierMap.emplace(std::move(currentEdgeIndexSet), currentIdentifier);
1476 identifiers.push_back(insertionRes.first->second);
1477 if (insertionRes.second) {
1478 ++currentIdentifier;
1479 }
1480 }
1481
1482 std::vector<EdgeIndexSet> identifierToEdgeIndexSetMapping(currentIdentifier);
1483 for (auto const& setIdPair : edgeIndexSetToIdentifierMap) {
1484 identifierToEdgeIndexSetMapping[setIdPair.second] = setIdPair.first;
1485 }
1486
1487 return std::make_shared<storm::storage::sparse::JaniChoiceOrigins>(std::make_shared<storm::jani::Model>(model), std::move(identifiers),
1488 std::move(identifierToEdgeIndexSetMapping));
1489}
1490
1491template<typename ValueType, typename StateType>
1492storm::storage::BitVector JaniNextStateGenerator<ValueType, StateType>::evaluateObservationLabels(CompressedState const& /*state*/) const {
1493 STORM_LOG_WARN("There are no observation labels in JANI currenty");
1494 return storm::storage::BitVector(0);
1495}
1496
1497template<typename ValueType, typename StateType>
1498void JaniNextStateGenerator<ValueType, StateType>::checkValid() const {
1499 // If the program still contains undefined constants and we are not in a parametric setting, assemble an appropriate error message.
1500 if (!std::is_same<ValueType, storm::RationalFunction>::value && model.hasUndefinedConstants()) {
1501 std::vector<std::reference_wrapper<storm::jani::Constant const>> undefinedConstants = model.getUndefinedConstants();
1502 std::stringstream stream;
1503 bool printComma = false;
1504 for (auto const& constant : undefinedConstants) {
1505 if (printComma) {
1506 stream << ", ";
1507 } else {
1508 printComma = true;
1509 }
1510 stream << constant.get().getName() << " (" << constant.get().getType() << ")";
1511 }
1512 stream << ".";
1513 STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Program still contains these undefined constants: " + stream.str() + ".");
1514 } else if (std::is_same<ValueType, storm::RationalFunction>::value && !model.undefinedConstantsAreGraphPreserving()) {
1515 STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException,
1516 "The input model contains undefined constants that influence the graph structure of the underlying model, which is not allowed.");
1517 }
1518}
1519
1520template class JaniNextStateGenerator<double>;
1521template class JaniNextStateGenerator<storm::RationalNumber>;
1522template class JaniNextStateGenerator<storm::RationalFunction>;
1523} // namespace generator
1524} // namespace storm
uint64_t getReservedBitsForUnboundedVariables() const
std::set< std::string > const & getRewardModelNames() const
Which reward models are built.
std::vector< std::pair< LabelOrExpression, bool > > const & getTerminalStates() const
ExpressionManager const & getManager() const
Retrieves the manager responsible for this expression.
bool isInitialized() const
Checks whether the object encapsulates a base-expression.
Expression integer(int_fast64_t value) const
Creates an expression that characterizes the given integer literal.
bool isBooleanType() const
Checks whether this type is a boolean type.
Definition Type.cpp:194
bool isIntegerType() const
Checks whether this type is an integral type.
Definition Type.cpp:198
bool isRationalType() const
Checks whether this type is a rational type.
Definition Type.cpp:234
Type const & getType() const
Retrieves the type of the variable.
Definition Variable.cpp:50
storm::expressions::Expression getExpression() const
Retrieves an expression that represents the variable.
Definition Variable.cpp:34
std::string const & getName() const
Retrieves the name of the variable.
Definition Variable.cpp:46
void clear()
Clears this distribution.
ContainerType::iterator begin()
Access to iterators over the entries of the distribution.
void compress()
Compresses the internal storage by summing the values of entries which agree on the index.
ContainerType::iterator end()
void add(DistributionEntry< IndexType, ValueType > const &entry)
Adds the given entry to the distribution.
storm::storage::FlatSet< uint_fast64_t > EdgeIndexSet
virtual ModelType getModelType() const override
static storm::jani::ModelFeatures getSupportedJaniFeatures()
Returns the jani features with which this builder can deal natively.
virtual std::shared_ptr< storm::storage::sparse::ChoiceOrigins > generateChoiceOrigins(std::vector< boost::any > &dataForChoiceOrigins) const override
virtual std::vector< StateType > getInitialStates(StateToIdCallback const &stateToIdCallback) override
JaniNextStateGenerator(storm::jani::Model const &model, NextStateGeneratorOptions const &options=NextStateGeneratorOptions())
virtual storm::builder::RewardModelInformation getRewardModelInformation(uint64_t const &index) const override
virtual StateBehavior< ValueType, StateType > const & expand(StateToIdCallback const &stateToIdCallback) override
virtual bool isPartiallyObservable() const override
virtual std::size_t getNumberOfRewardModels() const override
static bool canHandle(storm::jani::Model const &model)
A quick check to detect whether the given model is not supported.
NextStateGenerator< ValueType, StateType >::StateToIdCallback StateToIdCallback
virtual storm::models::sparse::StateLabeling label(storm::storage::sparse::StateStorage< StateType > const &stateStorage, std::vector< StateType > const &initialStateIndices={}, std::vector< StateType > const &deadlockStateIndices={}, std::vector< StateType > const &unexploredStateIndices={}) override
virtual void addStateValuation(storm::storage::sparse::state_type const &currentStateIndex, storm::storage::sparse::Valuations &valuations) const override
Adds the valuation for the currently loaded state to the given builder.
virtual void unpackTransientVariableValuesIntoEvaluator(CompressedState const &state, storm::expressions::ExpressionEvaluator< ValueType > &evaluator) const override
Sets the values of all transient variables in the current state to the given evaluator.
virtual storm::storage::sparse::Valuations initializeStateValuations() const override
Initializes state valuations by adding the appropriate variables.
std::unique_ptr< storm::expressions::ExpressionEvaluator< BaseValueType > > evaluator
void postprocess(StateBehavior< ValueType, uint32_t > &result)
boost::optional< std::vector< uint64_t > > overlappingGuardStates
std::vector< std::pair< storm::expressions::Expression, bool > > terminalStates
virtual storm::models::sparse::StateLabeling label(storm::storage::sparse::StateStorage< StateType > const &stateStorage, std::vector< StateType > const &initialStateIndices={}, std::vector< StateType > const &deadlockStateIndices={}, std::vector< StateType > const &unexploredStateIndices={})=0
bool empty() const
Retrieves whether the behavior is empty in the sense that there are no available choices.
std::vector< ValueType > const & getStateRewards() const
Retrieves the list of state rewards under selected reward models.
void clear()
Resets this to an empty behavior, i.e.
std::size_t getNumberOfChoices() const
Retrieves the number of choices in the behavior.
void setExpanded(bool newValue=true)
Sets whether the state was expanded.
Choice< ValueType, StateType > & startNewChoice(uint_fast64_t actionIndex=0, bool markovian=false)
Adds a new empty choice with the given action index and returns a reference to it.
std::string const & getName() const
Returns the name of the location.
Definition Action.cpp:9
std::string const & getAutomatonName() const
Retrieves the name of the automaton this composition element refers to.
bool isBooleanType() const
Definition BasicType.cpp:20
virtual bool isAutomatonComposition() const
virtual bool isParallelComposition() const
AutomatonComposition const & asAutomatonComposition() const
ParallelComposition const & asParallelComposition() const
storm::expressions::Expression const & getProbability() const
Retrieves the probability of choosing this destination.
bool hasTransientAssignment() const
Retrieves whether this destination has transient assignments.
OrderedAssignments const & getOrderedAssignments() const
Retrieves the assignments to make when choosing this destination.
uint64_t getLocationIndex() const
Retrieves the id of the destination location.
uint64_t getActionIndex() const
Retrieves the id of the action with which this edge is labeled.
Definition Edge.cpp:45
std::vector< EdgeDestination > const & getDestinations() const
Retrieves the destinations of this edge.
Definition Edge.cpp:77
bool hasRate() const
Retrieves whether this edge has an associated rate.
Definition Edge.cpp:49
uint64_t getSourceLocationIndex() const
Retrieves the index of the source location.
Definition Edge.cpp:41
OrderedAssignments const & getAssignments() const
Retrieves the assignments of this edge.
Definition Edge.cpp:89
storm::expressions::Expression const & getRate() const
Retrieves the rate of this edge.
Definition Edge.cpp:53
int64_t const & getHighestAssignmentLevel() const
Retrieves the highest assignment level occurring in a destination assignment If no assignment exists,...
Definition Edge.cpp:147
std::size_t getNumberOfDestinations() const
Retrieves the number of destinations of this edge.
Definition Edge.cpp:85
int64_t const & getLowestAssignmentLevel() const
Retrieves the lowest assignment level occurring in a destination assignment.
Definition Edge.cpp:143
EdgeDestination const & getDestination(uint64_t index) const
Retrieves the destination with the given index.
Definition Edge.cpp:73
virtual bool isBasicType() const
Definition JaniType.cpp:11
BasicType const & asBasicType() const
Definition JaniType.cpp:31
OrderedAssignments const & getAssignments() const
Retrieves the assignments of this location.
Definition Location.cpp:23
ModelFeatures & add(ModelFeature const &modelFeature)
void remove(ModelFeature const &modelFeature)
bool hasUndefinedConstants() const
Retrieves whether the model still has undefined constants.
Definition Model.cpp:1079
storm::expressions::ExpressionManager & getManager() const
Retrieves the expression manager responsible for the expressions in the model.
Definition Model.cpp:109
VariableSet & getGlobalVariables()
Retrieves the variables of this automaton.
Definition Model.cpp:717
storm::expressions::Expression getRewardModelExpression(std::string const &identifier) const
Retrieves the defining reward expression of the reward model with the given identifier.
Definition Model.cpp:809
static uint64_t encodeAutomatonAndEdgeIndices(uint64_t automatonIndex, uint64_t edgeIndex)
Encode and decode a tuple of automaton and edge index in one 64-bit index.
Definition Model.cpp:1600
Composition const & getSystemComposition() const
Retrieves the system composition expression.
Definition Model.cpp:945
void liftTransientEdgeDestinationAssignments(int64_t maxLevel=0)
Lifts the common edge destination assignments of transient variables to edge assignments.
Definition Model.cpp:1556
static const uint64_t SILENT_ACTION_INDEX
The index of the silent action.
Definition Model.h:658
bool hasNonTrivialRewardExpression() const
Returns true iff there is a non-trivial reward model, i.e., a reward model that does not consist of a...
Definition Model.cpp:789
bool undefinedConstantsAreGraphPreserving() const
Checks that undefined constants (parameters) of the model preserve the graph of the underlying model.
Definition Model.cpp:1497
void pushEdgeAssignmentsToDestinations()
Definition Model.cpp:1550
Action const & getAction(uint64_t index) const
Retrieves the action with the given index.
Definition Model.cpp:624
std::vector< std::pair< std::string, storm::expressions::Expression > > getAllRewardModelExpressions() const
Retrieves all available reward model names and expressions of the model.
Definition Model.cpp:837
bool isNonTrivialRewardModelExpression(std::string const &identifier) const
Returns true iff the given identifier corresponds to a non-trivial reward expression i....
Definition Model.cpp:793
void simplifyComposition()
Attempts to simplify the composition.
Definition Model.cpp:981
ModelFeatures const & getModelFeatures() const
Retrieves the enabled model features.
Definition Model.cpp:125
Automaton & getAutomaton(std::string const &name)
Retrieves the automaton with the given name.
Definition Model.cpp:884
bool hasNonTrivialInitialStates() const
Retrieves whether there are non-trivial initial states in the model or any of the contained automata.
Definition Model.cpp:1303
uint64_t getActionIndex(std::string const &name) const
Get the index of the action.
Definition Model.cpp:632
bool usesAssignmentLevels(bool onlyTransient=false) const
Retrieves whether the model uses an assignment level other than zero.
Definition Model.cpp:1571
uint64_t getAutomatonIndex(std::string const &name) const
Retrieves the index of the given automaton.
Definition Model.cpp:904
std::vector< std::reference_wrapper< Constant const > > getUndefinedConstants() const
Retrieves all undefined constants of the model.
Definition Model.cpp:1088
bool hasMultipleLevels(bool onlyTransient=false) const
Checks whether the assignments have several levels.
bool empty(bool onlyTransient=false) const
Retrieves whether this set of assignments is empty.
int64_t getLowestLevel(bool onlyTransient=false) const
Retrieves the lowest level among all assignments.
detail::ConstAssignments getNonTransientAssignments() const
Returns all non-transient assignments in this set of assignments.
detail::ConstAssignments getTransientAssignments() const
Returns all transient assignments in this set of assignments.
int64_t getHighestLevel(bool onlyTransient=false) const
Retrieves the highest level among all assignments.
std::vector< SynchronizationVector > const & getSynchronizationVectors() const
Retrieves the synchronization vectors of the parallel composition.
std::vector< std::shared_ptr< Composition > > const & getSubcompositions() const
Retrieves the subcompositions of the parallel composition.
static bool isNoActionInput(std::string const &action)
storm::expressions::Variable const & getExpressionVariable() const
Retrieves the associated expression variable.
Definition Variable.cpp:26
JaniType & getType()
Definition Variable.cpp:67
bool isTransient() const
Definition Variable.cpp:42
bool hasVariable(std::string const &name) const
Retrieves whether this variable set contains a variable with the given name.
Variable const & getVariable(std::string const &name) const
Retrieves the variable with the given name.
This class manages the labeling of the state space with a number of (atomic) labels.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void setFromInt(uint64_t bitIndex, uint64_t numberOfBits, uint64_t value)
Sets the selected number of lowermost bits of the provided value at the given bit index.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
uint64_t getAsInt(uint64_t bitIndex, uint64_t numberOfBits) const
Retrieves the content of the current bit vector at the given index for the given number of bits as an...
static uint_fast64_t getIdentifierForChoicesWithNoOrigin()
Helper to incrementally build a ValuationClassDescription, i.e.
Provides access to valuations of variables for a set of entities (e.g.
Definition Valuations.h:28
ValuationsStorage const & getStorage() const
virtual std::unique_ptr< storm::solver::SmtSolver > create(storm::expressions::ExpressionManager &manager) const
Creates a new SMT solver instance.
Definition solver.cpp:173
#define STORM_LOG_INFO(message)
Definition logging.h:34
#define STORM_LOG_WARN(message)
Definition logging.h:35
#define STORM_LOG_DEBUG(message)
Definition logging.h:28
#define STORM_LOG_TRACE(message)
Definition logging.h:22
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
SFTBDDChecker::ValueType ValueType
void unpackStateAppendToValuations(CompressedState const &state, VariableInformation const &variableInformation, storm::storage::sparse::ValuationsStorage &valuations)
Appends the values of the variables in the given state to the valuations object.
storm::expressions::SimpleValuation unpackStateIntoValuation(CompressedState const &state, VariableInformation const &variableInformation, storm::expressions::ExpressionManager const &manager)
Converts the compressed state into an explicit representation in the form of a valuation.
void unpackStateDifferenceIntoEvaluator(CompressedState const &newState, CompressedState const &oldState, VariableInformation const &variableInformation, storm::expressions::ExpressionEvaluator< ValueType > &evaluator)
Updates the (non-transient) variable values in the given evaluator from the values of oldState to the...
storm::storage::BitVector CompressedState
storm::builder::BuilderOptions NextStateGeneratorOptions
storm::adapters::DereferenceIteratorAdapter< std::vector< std::shared_ptr< Assignment > > const > ConstAssignments
bool isDiscreteTimeModel(ModelType const &modelType)
Definition ModelType.cpp:89
void forEach(std::vector< IteratorType > const &its, std::vector< IteratorType > const &ites, std::function< void(uint64_t, decltype(*std::declval< IteratorType >()))> const &setValueCallback, std::function< bool()> const &newCombinationCallback)
std::vector< T > buildVectorForRange(T min, T max)
Constructs a vector [min, min+1, ...., max-1].
Definition vector.h:130
bool isConstant(ValueType const &)
bool isZero(ValueType const &a)
Definition constants.cpp:44
ValueType zero()
Definition constants.cpp:26
ValueType one()
Definition constants.cpp:21
void addOriginData(boost::any const &data)
Adds the given data that specifies the origin of this choice w.r.t.
Definition Choice.cpp:129
void addRewards(std::vector< ValueType > &&values)
Adds the given choices rewards to this choice.
Definition Choice.cpp:182
void addProbability(StateType const &state, ValueType const &value)
Adds the given probability value to the given state in the underlying distribution.
Definition Choice.cpp:171
The actual (private) implementation of JaniNextStateGeneratorScratchMemory, see the corresponding mem...
TransientVariableValuation< ValueType > transientValuation
std::vector< typename Generator::EdgeSetWithIndices const * > edgeSets
std::vector< typename Generator::AutomataEdgeSets > automataEdgeSets
JaniNextStateGenerator< ValueType, StateType > Generator
std::vector< typename Generator::EdgeSetWithIndices::const_iterator > iteratorList
std::vector< typename Generator::EdgeSetWithIndices::const_iterator > firstEnabledEdgeIterators
std::vector< LocationVariableInformation const * > locationVars
storm::generator::Distribution< StateType, ValueType > distribution
std::vector< storm::jani::EdgeDestination const * > destinations
std::vector< storm::expressions::Expression > variableExpressionsForAssertions