Storm
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
IterativeMinMaxLinearEquationSolver.cpp
Go to the documentation of this file.
1#include <functional>
2#include <limits>
3
5
8
22
23namespace storm {
24namespace solver {
25
26template<typename ValueType, typename SolutionType>
28 STORM_LOG_ASSERT(static_cast<bool>(std::is_same_v<storm::Interval, ValueType>),
29 "Only for interval models"); // This constructor is only meant for intervals where we can not pass a good factory yet.
30}
31
32template<typename ValueType, typename SolutionType>
34 std::unique_ptr<LinearEquationSolverFactory<ValueType>>&& linearEquationSolverFactory)
35 : linearEquationSolverFactory(std::move(linearEquationSolverFactory)) {
36 // Intentionally left empty
37}
38
39template<typename ValueType, typename SolutionType>
41 storm::storage::SparseMatrix<ValueType> const& A, std::unique_ptr<LinearEquationSolverFactory<ValueType>>&& linearEquationSolverFactory)
42 : StandardMinMaxLinearEquationSolver<ValueType, SolutionType>(A), linearEquationSolverFactory(std::move(linearEquationSolverFactory)) {
43 // Intentionally left empty.
44}
45
46template<typename ValueType, typename SolutionType>
48 storm::storage::SparseMatrix<ValueType>&& A, std::unique_ptr<LinearEquationSolverFactory<ValueType>>&& linearEquationSolverFactory)
49 : StandardMinMaxLinearEquationSolver<ValueType, SolutionType>(std::move(A)), linearEquationSolverFactory(std::move(linearEquationSolverFactory)) {
50 // Intentionally left empty.
51}
52
53template<typename ValueType, typename SolutionType>
54MinMaxMethod IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::getMethod(Environment const& env, bool isExactMode) const {
55 // Adjust the method if none was specified and we want exact or sound computations.
56 auto method = env.solver().minMax().getMethod();
57
58 if (isExactMode && method != MinMaxMethod::PolicyIteration && method != MinMaxMethod::RationalSearch && method != MinMaxMethod::ViToPi) {
59 if (env.solver().minMax().isMethodSetFromDefault()) {
61 "Selecting 'Policy iteration' as the solution technique to guarantee exact results. If you want to override this, please explicitly specify a "
62 "different method.");
63 method = MinMaxMethod::PolicyIteration;
64 } else {
65 STORM_LOG_WARN("The selected solution method " << toString(method) << " does not guarantee exact results.");
66 }
67 } else if (env.solver().isForceSoundness() && method != MinMaxMethod::SoundValueIteration && method != MinMaxMethod::IntervalIteration &&
68 method != MinMaxMethod::PolicyIteration && method != MinMaxMethod::RationalSearch && method != MinMaxMethod::OptimisticValueIteration) {
69 if (env.solver().minMax().isMethodSetFromDefault()) {
70 method = MinMaxMethod::OptimisticValueIteration;
72 "Selecting '"
73 << toString(method)
74 << "' as the solution technique to guarantee sound results. If you want to override this, please explicitly specify a different method.");
75 } else {
76 STORM_LOG_WARN("The selected solution method does not guarantee sound results.");
77 }
78 }
79 STORM_LOG_THROW(method == MinMaxMethod::ValueIteration || method == MinMaxMethod::PolicyIteration || method == MinMaxMethod::RationalSearch ||
80 method == MinMaxMethod::SoundValueIteration || method == MinMaxMethod::IntervalIteration ||
81 method == MinMaxMethod::OptimisticValueIteration || method == MinMaxMethod::ViToPi,
82 storm::exceptions::InvalidEnvironmentException, "This solver does not support the selected method '" << toString(method) << "'.");
83 return method;
84}
85
86template<typename ValueType, typename SolutionType>
88 std::vector<SolutionType>& x, std::vector<ValueType> const& b) const {
89 bool result = false;
90 switch (getMethod(env, storm::NumberTraits<ValueType>::IsExact || env.solver().isForceExact())) {
91 case MinMaxMethod::ValueIteration:
92 result = solveEquationsValueIteration(env, dir, x, b);
93 break;
94 case MinMaxMethod::OptimisticValueIteration:
95 result = solveEquationsOptimisticValueIteration(env, dir, x, b);
96 break;
97 case MinMaxMethod::PolicyIteration:
98 result = solveEquationsPolicyIteration(env, dir, x, b);
99 break;
100 case MinMaxMethod::RationalSearch:
101 result = solveEquationsRationalSearch(env, dir, x, b);
102 break;
103 case MinMaxMethod::IntervalIteration:
104 result = solveEquationsIntervalIteration(env, dir, x, b);
105 break;
106 case MinMaxMethod::SoundValueIteration:
107 result = solveEquationsSoundValueIteration(env, dir, x, b);
108 break;
109 case MinMaxMethod::ViToPi:
110 result = solveEquationsViToPi(env, dir, x, b);
111 break;
112 default:
113 STORM_LOG_THROW(false, storm::exceptions::InvalidEnvironmentException, "This solver does not implement the selected solution method");
114 }
115
116 return result;
117}
118
119template<typename ValueType, typename SolutionType>
121 if (!viOperator) {
122 viOperator = std::make_shared<helper::ValueIterationOperator<ValueType, false, SolutionType>>();
123 viOperator->setMatrixBackwards(*this->A);
124 }
125 if (this->choiceFixedForRowGroup) {
126 // Ignore those rows that are not selected
127 assert(this->initialScheduler);
128 auto callback = [&](uint64_t groupIndex, uint64_t localRowIndex) {
129 return this->choiceFixedForRowGroup->get(groupIndex) && this->initialScheduler->at(groupIndex) != localRowIndex;
130 };
131 viOperator->setIgnoredRows(true, callback);
132 }
133}
134
135template<typename ValueType, typename SolutionType>
136void IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::extractScheduler(std::vector<SolutionType>& x, std::vector<ValueType> const& b,
137 OptimizationDirection const& dir, bool updateX, bool robust) const {
138 // Make sure that storage for scheduler choices is available
139 if (!this->schedulerChoices) {
140 this->schedulerChoices = std::vector<uint64_t>(x.size(), 0);
141 } else {
142 this->schedulerChoices->resize(x.size(), 0);
143 }
144 // Set the correct choices.
145 STORM_LOG_WARN_COND(viOperator, "Expected VI operator to be initialized for scheduler extraction. Initializing now, but this is inefficient.");
146 if (!viOperator) {
147 setUpViOperator();
148 }
150 schedHelper.computeScheduler(x, b, dir, *this->schedulerChoices, robust, updateX ? &x : nullptr);
151}
152
153template<typename ValueType, typename SolutionType>
154bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveInducedEquationSystem(
155 Environment const& env, std::unique_ptr<LinearEquationSolver<ValueType>>& linearEquationSolver, std::vector<uint64_t> const& scheduler,
156 std::vector<SolutionType>& x, std::vector<ValueType>& subB, std::vector<ValueType> const& originalB) const {
157 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
158 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "We did not implement solving induced equation systems for interval-based models.");
159 // Implementing this requires linear equation systems with different value types and solution types (or some appropriate casting)
160 return false;
161 } else {
162 STORM_LOG_ASSERT(subB.size() == x.size(), "Sizes of subB and x do not coincide.");
163 STORM_LOG_ASSERT(this->linearEquationSolverFactory != nullptr, "Wrong constructor was called.");
164
165 // Resolve the nondeterminism according to the given scheduler.
166 bool convertToEquationSystem = this->linearEquationSolverFactory->getEquationProblemFormat(env) == LinearEquationSolverProblemFormat::EquationSystem;
168
169 submatrix = this->A->selectRowsFromRowGroups(scheduler, convertToEquationSystem);
170 if (convertToEquationSystem) {
171 submatrix.convertToEquationSystem();
172 }
173 storm::utility::vector::selectVectorValues<ValueType>(subB, scheduler, this->A->getRowGroupIndices(), originalB);
174
175 // Check whether the linear equation solver is already initialized
176 if (!linearEquationSolver) {
177 // Initialize the equation solver
178 linearEquationSolver = this->linearEquationSolverFactory->create(env, std::move(submatrix));
179 linearEquationSolver->setBoundsFromOtherSolver(*this);
180 linearEquationSolver->setCachingEnabled(true);
181 } else {
182 // If the equation solver is already initialized, it suffices to update the matrix
183 linearEquationSolver->setMatrix(std::move(submatrix));
184 }
185 // Solve the equation system for the 'DTMC' and return true upon success
186 return linearEquationSolver->solveEquations(env, x, subB);
187 }
188}
189
190template<typename ValueType, typename SolutionType>
191bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsPolicyIteration(Environment const& env, OptimizationDirection dir,
192 std::vector<SolutionType>& x,
193 std::vector<ValueType> const& b) const {
194 std::vector<storm::storage::sparse::state_type> scheduler =
195 this->hasInitialScheduler() ? this->getInitialScheduler() : std::vector<storm::storage::sparse::state_type>(this->A->getRowGroupCount());
196 return performPolicyIteration(env, dir, x, b, std::move(scheduler));
197}
198
199template<typename ValueType, typename SolutionType>
200bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::performPolicyIteration(
201 Environment const& env, OptimizationDirection dir, std::vector<SolutionType>& x, std::vector<ValueType> const& b,
202 std::vector<storm::storage::sparse::state_type>&& initialPolicy) const {
203 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
204 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "We did not implement policy iteration for interval-based models.");
205 return false;
206 } else {
207 std::vector<storm::storage::sparse::state_type> scheduler = std::move(initialPolicy);
208 // Get a vector for storing the right-hand side of the inner equation system.
209 if (!auxiliaryRowGroupVector) {
210 auxiliaryRowGroupVector = std::make_unique<std::vector<ValueType>>(this->A->getRowGroupCount());
211 }
212 std::vector<ValueType>& subB = *auxiliaryRowGroupVector;
213
214 // The solver that we will use throughout the procedure.
215 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> solver;
216 // The linear equation solver should be at least as precise as this solver
217 std::unique_ptr<storm::Environment> environmentOfSolverStorage;
218 auto precOfSolver = env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType());
220 bool changePrecision = precOfSolver.first && precOfSolver.first.get() > env.solver().minMax().getPrecision();
221 bool changeRelative = precOfSolver.second && !precOfSolver.second.get() && env.solver().minMax().getRelativeTerminationCriterion();
222 if (changePrecision || changeRelative) {
223 environmentOfSolverStorage = std::make_unique<storm::Environment>(env);
224 boost::optional<storm::RationalNumber> newPrecision;
225 boost::optional<bool> newRelative;
226 if (changePrecision) {
227 newPrecision = env.solver().minMax().getPrecision();
228 }
229 if (changeRelative) {
230 newRelative = true;
231 }
232 environmentOfSolverStorage->solver().setLinearEquationSolverPrecision(newPrecision, newRelative);
233 }
234 }
235 storm::Environment const& environmentOfSolver = environmentOfSolverStorage ? *environmentOfSolverStorage : env;
236
238 uint64_t iterations = 0;
239 this->startMeasureProgress();
240 do {
241 // Solve the equation system for the 'DTMC'.
242 solveInducedEquationSystem(environmentOfSolver, solver, scheduler, x, subB, b);
243
244 // Go through the multiplication result and see whether we can improve any of the choices.
245 bool schedulerImproved = false;
246 // Group refers to the state number
247 for (uint_fast64_t group = 0; group < this->A->getRowGroupCount(); ++group) {
248 if (!this->choiceFixedForRowGroup || !this->choiceFixedForRowGroup.get()[group]) {
249 // Only update when the choice is not fixed
250 uint_fast64_t currentChoice = scheduler[group];
251 for (uint_fast64_t choice = this->A->getRowGroupIndices()[group]; choice < this->A->getRowGroupIndices()[group + 1]; ++choice) {
252 // If the choice is the currently selected one, we can skip it.
253 if (choice - this->A->getRowGroupIndices()[group] == currentChoice) {
254 continue;
255 }
256
257 // Create the value of the choice.
258 ValueType choiceValue = storm::utility::zero<ValueType>();
259 for (auto const& entry : this->A->getRow(choice)) {
260 choiceValue += entry.getValue() * x[entry.getColumn()];
261 }
262 choiceValue += b[choice];
263
264 // If the value is strictly better than the solution of the inner system, we need to improve the scheduler.
265 // TODO: If the underlying solver is not precise, this might run forever (i.e. when a state has two choices where the (exact) values are
266 // equal). only changing the scheduler if the values are not equal (modulo precision) would make this unsound.
267 if (valueImproved(dir, x[group], choiceValue)) {
268 schedulerImproved = true;
269 scheduler[group] = choice - this->A->getRowGroupIndices()[group];
270 x[group] = std::move(choiceValue);
271 }
272 }
273 }
274 }
275
276 // If the scheduler did not improve, we are done.
277 if (!schedulerImproved) {
279 }
280
281 // Update environment variables.
282 ++iterations;
283 status =
284 this->updateStatus(status, x, dir == storm::OptimizationDirection::Minimize ? SolverGuarantee::GreaterOrEqual : SolverGuarantee::LessOrEqual,
285 iterations, env.solver().minMax().getMaximalNumberOfIterations());
286
287 // Potentially show progress.
288 this->showProgressIterative(iterations);
289 } while (status == SolverStatus::InProgress);
290
291 STORM_LOG_INFO("Number of iterations: " << iterations);
292 this->reportStatus(status, iterations);
293
294 // If requested, we store the scheduler for retrieval.
295 if (this->isTrackSchedulerSet()) {
296 this->schedulerChoices = std::move(scheduler);
297 }
298
299 if (!this->isCachingEnabled()) {
300 clearCache();
301 }
302
303 return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly;
304 }
305}
306
307template<typename ValueType, typename SolutionType>
308bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::valueImproved(OptimizationDirection dir, ValueType const& value1,
309 ValueType const& value2) const {
310 if (dir == OptimizationDirection::Minimize) {
311 return value2 < value1;
312 } else {
313 return value2 > value1;
314 }
315}
316
317template<typename ValueType, typename SolutionType>
319 Environment const& env, boost::optional<storm::solver::OptimizationDirection> const& direction, bool const& hasInitialScheduler) const {
320 auto method = getMethod(env, storm::NumberTraits<ValueType>::IsExact || env.solver().isForceExact());
321
322 // Check whether a linear equation solver is needed and potentially start with its requirements
323 bool needsLinEqSolver = false;
324 needsLinEqSolver |= method == MinMaxMethod::PolicyIteration;
325 needsLinEqSolver |= method == MinMaxMethod::ValueIteration && (this->hasInitialScheduler() || hasInitialScheduler);
326 needsLinEqSolver |= method == MinMaxMethod::ViToPi;
327
329 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
330 STORM_LOG_ASSERT(!needsLinEqSolver, "Intervals should not require a linear equation solver.");
331 // nothing to be done;
332 } else if (needsLinEqSolver) {
333 requirements = MinMaxLinearEquationSolverRequirements(this->linearEquationSolverFactory->getRequirements(env));
334 } else {
335 // nothing to be done.
336 }
337
338 if (method == MinMaxMethod::ValueIteration) {
339 if (!this->hasUniqueSolution()) { // Traditional value iteration has no requirements if the solution is unique.
340 // Computing a scheduler is only possible if the solution is unique
341 if (env.solver().minMax().isForceRequireUnique() || this->isTrackSchedulerSet()) {
342 requirements.requireUniqueSolution();
343 } else {
344 // As we want the smallest (largest) solution for maximizing (minimizing) equation systems, we have to approach the solution from below (above).
345 if (!direction || direction.get() == OptimizationDirection::Maximize) {
346 requirements.requireLowerBounds();
347 }
348 if (!direction || direction.get() == OptimizationDirection::Minimize) {
349 requirements.requireUpperBounds();
350 }
351 }
352 }
353 } else if (method == MinMaxMethod::OptimisticValueIteration) {
354 // OptimisticValueIteration always requires lower bounds and a unique solution.
355 if (!this->hasUniqueSolution()) {
356 requirements.requireUniqueSolution();
357 }
358 requirements.requireLowerBounds();
359
360 } else if (method == MinMaxMethod::IntervalIteration) {
361 // Interval iteration requires a unique solution and lower+upper bounds
362 if (!this->hasUniqueSolution()) {
363 requirements.requireUniqueSolution();
364 }
365 requirements.requireBounds();
366 } else if (method == MinMaxMethod::RationalSearch) {
367 // Rational search needs to approach the solution from below.
368 requirements.requireLowerBounds();
369 // The solution needs to be unique in case of minimizing or in cases where we want a scheduler.
370 if (!this->hasUniqueSolution()) {
371 // RationalSearch guesses and verifies a fixpoint and terminates once a fixpoint is found. To ensure that the guessed fixpoint is the
372 // correct one, we enforce uniqueness.
373 requirements.requireUniqueSolution();
374 }
375 } else if (method == MinMaxMethod::PolicyIteration) {
376 // The initial scheduler shall not select an end component
377 if (!this->hasUniqueSolution() && env.solver().minMax().isForceRequireUnique()) {
378 requirements.requireUniqueSolution();
379 }
380 if (!this->hasNoEndComponents() && !this->hasInitialScheduler()) {
381 requirements.requireValidInitialScheduler();
382 }
383 } else if (method == MinMaxMethod::SoundValueIteration) {
384 if (!this->hasUniqueSolution()) {
385 requirements.requireUniqueSolution();
386 }
387 requirements.requireBounds(false);
388 } else if (method == MinMaxMethod::ViToPi) {
389 // Since we want to use value iteration to extract an initial scheduler, the solution has to be unique.
390 if (!this->hasUniqueSolution()) {
391 requirements.requireUniqueSolution();
392 }
393 } else {
394 STORM_LOG_THROW(false, storm::exceptions::InvalidEnvironmentException, "Unsupported technique for iterative MinMax linear equation solver.");
395 }
396 return requirements;
397}
398
399template<typename ValueType, typename SolutionType>
400ValueType computeMaxAbsDiff(std::vector<ValueType> const& allValues, storm::storage::BitVector const& relevantValues, std::vector<ValueType> const& oldValues) {
401 ValueType result = storm::utility::zero<ValueType>();
402 auto oldValueIt = oldValues.begin();
403 for (auto value : relevantValues) {
404 result = storm::utility::max<ValueType>(result, storm::utility::abs<ValueType>(allValues[value] - *oldValueIt));
405 ++oldValueIt;
406 }
407 return result;
408}
409
410template<typename ValueType, typename SolutionType>
411ValueType computeMaxAbsDiff(std::vector<ValueType> const& allOldValues, std::vector<ValueType> const& allNewValues,
412 storm::storage::BitVector const& relevantValues) {
413 ValueType result = storm::utility::zero<ValueType>();
414 for (auto value : relevantValues) {
415 result = storm::utility::max<ValueType>(result, storm::utility::abs<ValueType>(allNewValues[value] - allOldValues[value]));
416 }
417 return result;
418}
419
420template<typename ValueType, typename SolutionType>
421bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsOptimisticValueIteration(Environment const& env, OptimizationDirection dir,
422 std::vector<SolutionType>& x,
423 std::vector<ValueType> const& b) const {
424 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
425 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "We did not implement optimistic value iteration for interval-based models.");
426 return false;
427 } else {
429 // If all entries are zero, OVI might run in an endless loop. However, the result is easy in this case.
430 x.assign(x.size(), storm::utility::zero<SolutionType>());
431 if (this->isTrackSchedulerSet()) {
432 this->schedulerChoices = std::vector<uint_fast64_t>(x.size(), 0);
433 }
434 return true;
435 }
436
437 setUpViOperator();
438
439 helper::OptimisticValueIterationHelper<ValueType, false> oviHelper(viOperator);
440 auto prec = storm::utility::convertNumber<ValueType>(env.solver().minMax().getPrecision());
441 std::optional<ValueType> lowerBound, upperBound;
442 if (this->hasLowerBound()) {
443 lowerBound = this->getLowerBound(true);
444 }
445 if (this->hasUpperBound()) {
446 upperBound = this->getUpperBound(true);
447 }
448 uint64_t numIterations{0};
449 auto oviCallback = [&](SolverStatus const& current, std::vector<ValueType> const& v) {
450 this->showProgressIterative(numIterations);
451 return this->updateStatus(current, v, SolverGuarantee::LessOrEqual, numIterations, env.solver().minMax().getMaximalNumberOfIterations());
452 };
453 this->createLowerBoundsVector(x);
454 std::optional<ValueType> guessingFactor;
455 if (env.solver().ovi().getUpperBoundGuessingFactor()) {
456 guessingFactor = storm::utility::convertNumber<ValueType>(*env.solver().ovi().getUpperBoundGuessingFactor());
457 }
458 this->startMeasureProgress();
459 auto status = oviHelper.OVI(x, b, numIterations, env.solver().minMax().getRelativeTerminationCriterion(), prec, dir, guessingFactor, lowerBound,
460 upperBound, oviCallback);
461 this->reportStatus(status, numIterations);
462
463 // If requested, we store the scheduler for retrieval.
464 if (this->isTrackSchedulerSet()) {
465 this->extractScheduler(x, b, dir, this->isUncertaintyRobust());
466 }
467
468 if (!this->isCachingEnabled()) {
469 clearCache();
470 }
471
472 return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly;
473 }
474}
475
476template<typename ValueType, typename SolutionType>
477bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsValueIteration(Environment const& env, OptimizationDirection dir,
478 std::vector<SolutionType>& x,
479 std::vector<ValueType> const& b) const {
480 setUpViOperator();
481 // By default, we can not provide any guarantee
483
484 if (this->hasInitialScheduler()) {
485 if (!auxiliaryRowGroupVector) {
486 auxiliaryRowGroupVector = std::make_unique<std::vector<ValueType>>(this->A->getRowGroupCount());
487 }
488 // Solve the equation system induced by the initial scheduler.
489 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> linEqSolver;
490 // The linear equation solver should be at least as precise as this solver
491 std::unique_ptr<storm::Environment> environmentOfSolverStorage;
492 auto precOfSolver = env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType());
494 bool changePrecision = precOfSolver.first && precOfSolver.first.get() > env.solver().minMax().getPrecision();
495 bool changeRelative = precOfSolver.second && !precOfSolver.second.get() && env.solver().minMax().getRelativeTerminationCriterion();
496 if (changePrecision || changeRelative) {
497 environmentOfSolverStorage = std::make_unique<storm::Environment>(env);
498 boost::optional<storm::RationalNumber> newPrecision;
499 boost::optional<bool> newRelative;
500 if (changePrecision) {
501 newPrecision = env.solver().minMax().getPrecision();
502 }
503 if (changeRelative) {
504 newRelative = true;
505 }
506 environmentOfSolverStorage->solver().setLinearEquationSolverPrecision(newPrecision, newRelative);
507 }
508 }
509 storm::Environment const& environmentOfSolver = environmentOfSolverStorage ? *environmentOfSolverStorage : env;
510
511 solveInducedEquationSystem(environmentOfSolver, linEqSolver, this->getInitialScheduler(), x, *auxiliaryRowGroupVector, b);
512 // If we were given an initial scheduler and are maximizing (minimizing), our current solution becomes
513 // always less-or-equal (greater-or-equal) than the actual solution.
515 } else if (!this->hasUniqueSolution()) {
516 if (maximize(dir)) {
517 this->createLowerBoundsVector(x);
519 } else {
520 this->createUpperBoundsVector(x);
522 }
523 } else if (this->hasCustomTerminationCondition()) {
524 if (this->getTerminationCondition().requiresGuarantee(SolverGuarantee::LessOrEqual) && this->hasLowerBound()) {
525 this->createLowerBoundsVector(x);
527 } else if (this->getTerminationCondition().requiresGuarantee(SolverGuarantee::GreaterOrEqual) && this->hasUpperBound()) {
528 this->createUpperBoundsVector(x);
530 }
531 }
532
534 uint64_t numIterations{0};
535 auto viCallback = [&](SolverStatus const& current) {
536 this->showProgressIterative(numIterations);
537 return this->updateStatus(current, x, guarantee, numIterations, env.solver().minMax().getMaximalNumberOfIterations());
538 };
539 this->startMeasureProgress();
540 auto status = viHelper.VI(x, b, numIterations, env.solver().minMax().getRelativeTerminationCriterion(),
541 storm::utility::convertNumber<SolutionType>(env.solver().minMax().getPrecision()), dir, viCallback,
542 env.solver().minMax().getMultiplicationStyle(), this->isUncertaintyRobust());
543 this->reportStatus(status, numIterations);
544
545 // If requested, we store the scheduler for retrieval.
546 if (this->isTrackSchedulerSet()) {
547 this->extractScheduler(x, b, dir, this->isUncertaintyRobust());
548 }
549
550 if (!this->isCachingEnabled()) {
551 clearCache();
552 }
553
554 return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly;
555}
556
557template<typename ValueType, typename SolutionType>
558void preserveOldRelevantValues(std::vector<ValueType> const& allValues, storm::storage::BitVector const& relevantValues, std::vector<ValueType>& oldValues) {
559 storm::utility::vector::selectVectorValues(oldValues, relevantValues, allValues);
560}
561
568template<typename ValueType, typename SolutionType>
569bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsIntervalIteration(Environment const& env, OptimizationDirection dir,
570 std::vector<SolutionType>& x,
571 std::vector<ValueType> const& b) const {
572 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
573 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "We did not implement intervaliteration for interval-based models");
574 return false;
575 } else {
576 setUpViOperator();
577 helper::IntervalIterationHelper<ValueType, false> iiHelper(viOperator);
578 auto prec = storm::utility::convertNumber<ValueType>(env.solver().minMax().getPrecision());
579 auto lowerBoundsCallback = [&](std::vector<SolutionType>& vector) { this->createLowerBoundsVector(vector); };
580 auto upperBoundsCallback = [&](std::vector<SolutionType>& vector) { this->createUpperBoundsVector(vector); };
581
582 uint64_t numIterations{0};
583 auto iiCallback = [&](helper::IIData<ValueType> const& data) {
584 this->showProgressIterative(numIterations);
585 bool terminateEarly = this->hasCustomTerminationCondition() && this->getTerminationCondition().terminateNow(data.x, SolverGuarantee::LessOrEqual) &&
586 this->getTerminationCondition().terminateNow(data.y, SolverGuarantee::GreaterOrEqual);
587 return this->updateStatus(data.status, terminateEarly, numIterations, env.solver().minMax().getMaximalNumberOfIterations());
588 };
589 std::optional<storm::storage::BitVector> optionalRelevantValues;
590 if (this->hasRelevantValues()) {
591 optionalRelevantValues = this->getRelevantValues();
592 }
593 this->startMeasureProgress();
594 auto status = iiHelper.II(x, b, numIterations, env.solver().minMax().getRelativeTerminationCriterion(), prec, lowerBoundsCallback, upperBoundsCallback,
595 dir, iiCallback, optionalRelevantValues);
596 this->reportStatus(status, numIterations);
597
598 // If requested, we store the scheduler for retrieval.
599 if (this->isTrackSchedulerSet()) {
600 this->extractScheduler(x, b, dir, this->isUncertaintyRobust());
601 }
602
603 if (!this->isCachingEnabled()) {
604 clearCache();
605 }
606
607 return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly;
608 }
609}
610
611template<typename ValueType, typename SolutionType>
612bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsSoundValueIteration(Environment const& env, OptimizationDirection dir,
613 std::vector<SolutionType>& x,
614 std::vector<ValueType> const& b) const {
615 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
616 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "SoundVI does not handle interval-based models");
617 return false;
618 } else {
619 // Prepare the solution vectors and the helper.
620 assert(x.size() == this->A->getRowGroupCount());
621
622 std::optional<ValueType> lowerBound, upperBound;
623 if (this->hasLowerBound()) {
624 lowerBound = this->getLowerBound(true);
625 }
626 if (this->hasUpperBound()) {
627 upperBound = this->getUpperBound(true);
628 }
629
630 setUpViOperator();
631
632 auto precision = storm::utility::convertNumber<ValueType>(env.solver().minMax().getPrecision());
633 uint64_t numIterations{0};
634 auto sviCallback = [&](typename helper::SoundValueIterationHelper<ValueType, false>::SVIData const& current) {
635 this->showProgressIterative(numIterations);
636 return this->updateStatus(current.status,
637 this->hasCustomTerminationCondition() && current.checkCustomTerminationCondition(this->getTerminationCondition()),
638 numIterations, env.solver().minMax().getMaximalNumberOfIterations());
639 };
640 this->startMeasureProgress();
641 helper::SoundValueIterationHelper<ValueType, false> sviHelper(viOperator);
642 std::optional<storm::storage::BitVector> optionalRelevantValues;
643 if (this->hasRelevantValues()) {
644 optionalRelevantValues = this->getRelevantValues();
645 }
646 auto status = sviHelper.SVI(x, b, numIterations, env.solver().minMax().getRelativeTerminationCriterion(), precision, dir, lowerBound, upperBound,
647 sviCallback, optionalRelevantValues);
648
649 // If requested, we store the scheduler for retrieval.
650 if (this->isTrackSchedulerSet()) {
651 this->extractScheduler(x, b, dir, this->isUncertaintyRobust());
652 }
653
654 this->reportStatus(status, numIterations);
655
656 if (!this->isCachingEnabled()) {
657 clearCache();
658 }
659
660 return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly;
661 }
662}
663
664template<typename ValueType, typename SolutionType>
665bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsViToPi(Environment const& env, OptimizationDirection dir,
666 std::vector<SolutionType>& x, std::vector<ValueType> const& b) const {
667 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
668 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "ViToPi does not handle interval-based models");
669 return false;
670 }
671 // First create an (inprecise) vi solver to get a good initial strategy for the (potentially precise) policy iteration solver.
672 std::vector<storm::storage::sparse::state_type> initialSched;
673 {
674 Environment viEnv = env;
675 viEnv.solver().minMax().setMethod(MinMaxMethod::ValueIteration);
676 viEnv.solver().setForceExact(false);
677 viEnv.solver().setForceSoundness(false);
678 auto impreciseSolver = GeneralMinMaxLinearEquationSolverFactory<double>().create(viEnv, this->A->template toValueType<double>());
679 impreciseSolver->setHasUniqueSolution(this->hasUniqueSolution());
680 impreciseSolver->setTrackScheduler(true);
681 if (this->hasInitialScheduler()) {
682 auto initSched = this->getInitialScheduler();
683 impreciseSolver->setInitialScheduler(std::move(initSched));
684 }
685 auto impreciseSolverReq = impreciseSolver->getRequirements(viEnv, dir);
686 STORM_LOG_THROW(!impreciseSolverReq.hasEnabledCriticalRequirement(), storm::exceptions::UnmetRequirementException,
687 "The value-iteration based solver has an unmet requirement: " << impreciseSolverReq.getEnabledRequirementsAsString());
688 impreciseSolver->setRequirementsChecked(true);
689 auto xVi = storm::utility::vector::convertNumericVector<double>(x);
690 auto bVi = storm::utility::vector::convertNumericVector<double>(b);
691 impreciseSolver->solveEquations(viEnv, dir, xVi, bVi);
692 initialSched = impreciseSolver->getSchedulerChoices();
693 }
694 STORM_LOG_INFO("Found initial policy using Value Iteration. Starting Policy iteration now.");
695 return performPolicyIteration(env, dir, x, b, std::move(initialSched));
696}
697
698template<typename ValueType, typename SolutionType>
699bool IterativeMinMaxLinearEquationSolver<ValueType, SolutionType>::solveEquationsRationalSearch(Environment const& env, OptimizationDirection dir,
700 std::vector<SolutionType>& x,
701 std::vector<ValueType> const& b) const {
702 if constexpr (std::is_same_v<ValueType, storm::Interval>) {
703 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Rational search does not handle interval-based models");
704 return false;
705 } else {
706 // Set up two value iteration operators. One for exact and one for imprecise computations
707 setUpViOperator();
708 std::shared_ptr<helper::ValueIterationOperator<storm::RationalNumber, false>> exactOp;
709 std::shared_ptr<helper::ValueIterationOperator<double, false>> impreciseOp;
710 std::function<bool(uint64_t, uint64_t)> fixedChoicesCallback;
711 if (this->choiceFixedForRowGroup) {
712 // Ignore those rows that are not selected
713 assert(this->initialScheduler);
714 fixedChoicesCallback = [&](uint64_t groupIndex, uint64_t localRowIndex) {
715 return this->choiceFixedForRowGroup->get(groupIndex) && this->initialScheduler->at(groupIndex) != localRowIndex;
716 };
717 }
718
719 if constexpr (std::is_same_v<ValueType, storm::RationalNumber>) {
720 exactOp = viOperator;
721 impreciseOp = std::make_shared<helper::ValueIterationOperator<double, false>>();
722 impreciseOp->setMatrixBackwards(this->A->template toValueType<double>(), &this->A->getRowGroupIndices());
723 if (this->choiceFixedForRowGroup) {
724 impreciseOp->setIgnoredRows(true, fixedChoicesCallback);
725 }
726 } else if constexpr (std::is_same_v<ValueType, double>) {
727 impreciseOp = viOperator;
728 exactOp = std::make_shared<helper::ValueIterationOperator<storm::RationalNumber, false>>();
729 exactOp->setMatrixBackwards(this->A->template toValueType<storm::RationalNumber>(), &this->A->getRowGroupIndices());
730 if (this->choiceFixedForRowGroup) {
731 exactOp->setIgnoredRows(true, fixedChoicesCallback);
732 }
733 }
734
736 uint64_t numIterations{0};
737 auto rsCallback = [&](SolverStatus const& current) {
738 this->showProgressIterative(numIterations);
739 return this->updateStatus(current, x, SolverGuarantee::None, numIterations, env.solver().minMax().getMaximalNumberOfIterations());
740 };
741 this->startMeasureProgress();
742 auto status = rsHelper.RS(x, b, numIterations, storm::utility::convertNumber<ValueType>(env.solver().minMax().getPrecision()), dir, rsCallback);
743
744 this->reportStatus(status, numIterations);
745
746 // If requested, we store the scheduler for retrieval.
747 if (this->isTrackSchedulerSet()) {
748 this->extractScheduler(x, b, dir, this->isUncertaintyRobust());
749 }
750
751 if (!this->isCachingEnabled()) {
752 clearCache();
753 }
754
755 return status == SolverStatus::Converged || status == SolverStatus::TerminatedEarly;
756 }
757}
758
759template<typename ValueType, typename SolutionType>
765
769
770} // namespace solver
771} // namespace storm
SolverEnvironment & solver()
uint64_t const & getMaximalNumberOfIterations() const
storm::RationalNumber const & getPrecision() const
storm::solver::MinMaxMethod const & getMethod() const
bool const & getRelativeTerminationCriterion() const
std::optional< storm::RationalNumber > const & getUpperBoundGuessingFactor() const
OviSolverEnvironment const & ovi() const
MinMaxSolverEnvironment & minMax()
virtual void clearCache() const override
Clears the currently cached data that has been stored during previous calls of the solver.
virtual bool internalSolveEquations(Environment const &env, OptimizationDirection dir, std::vector< SolutionType > &x, std::vector< ValueType > const &b) const override
virtual MinMaxLinearEquationSolverRequirements getRequirements(Environment const &env, boost::optional< storm::solver::OptimizationDirection > const &direction=boost::none, bool const &hasInitialScheduler=false) const override
Retrieves the requirements of this solver for solving equations with the current settings.
virtual void clearCache() const
Clears the currently cached data that has been stored during previous calls of the solver.
MinMaxLinearEquationSolverRequirements & requireBounds(bool critical=true)
MinMaxLinearEquationSolverRequirements & requireUniqueSolution(bool critical=true)
MinMaxLinearEquationSolverRequirements & requireLowerBounds(bool critical=true)
MinMaxLinearEquationSolverRequirements & requireValidInitialScheduler(bool critical=true)
MinMaxLinearEquationSolverRequirements & requireUpperBounds(bool critical=true)
Helper class to extract optimal scheduler choices from a MinMax equation system solution.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:18
A class that holds a possibly non-square matrix in the compressed row storage format.
void convertToEquationSystem()
Transforms the matrix into an equation system.
SparseMatrix selectRowsFromRowGroups(std::vector< index_type > const &rowGroupToRowIndexMapping, bool insertDiagonalEntries=true) const
Selects exactly one row from each row group of this matrix and returns the resulting matrix.
#define STORM_LOG_INFO(message)
Definition logging.h:29
#define STORM_LOG_WARN(message)
Definition logging.h:30
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:38
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
SFTBDDChecker::ValueType ValueType
bool constexpr maximize(OptimizationDirection d)
void preserveOldRelevantValues(std::vector< ValueType > const &allValues, storm::storage::BitVector const &relevantValues, std::vector< ValueType > &oldValues)
std::string toString(GurobiSolverMethod const &method)
Yields a string representation of the GurobiSolverMethod.
ValueType computeMaxAbsDiff(std::vector< ValueType > const &allValues, storm::storage::BitVector const &relevantValues, std::vector< ValueType > const &oldValues)
void selectVectorValues(std::vector< T > &vector, storm::storage::BitVector const &positions, std::vector< T > const &values)
Selects the elements from a vector at the specified positions and writes them consecutively into anot...
Definition vector.h:189
bool hasNonZeroEntry(std::vector< T > const &v)
Definition vector.h:1252
LabParser.cpp.
Definition cli.cpp:18