Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
RobustParameterLifter.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <cmath>
5#include <map>
6#include <memory>
7#include <optional>
8#include <set>
9#include <type_traits>
10#include <vector>
11
12#include <carl/core/rootfinder/RootFinder.h>
13
34
35std::unordered_map<storm::RationalFunction, storm::transformer::Annotation> storm::transformer::BigStep::lastSavedAnnotations;
36
37namespace storm {
38namespace transformer {
39
41
42template<typename ParametricType, typename ConstantType>
44 std::vector<ParametricType> const& pVector,
45 storm::storage::BitVector const& selectedRows,
46 storm::storage::BitVector const& selectedColumns, bool generateRowLabels,
47 bool useMonotonicity) {
48 oldToNewColumnIndexMapping = std::vector<uint64_t>(selectedColumns.size(), selectedColumns.size());
49 uint64_t newIndexColumns = 0;
50 for (auto const& oldColumn : selectedColumns) {
51 oldToNewColumnIndexMapping[oldColumn] = newIndexColumns++;
52 }
53
54 oldToNewRowIndexMapping = std::vector<uint64_t>(selectedRows.size(), selectedRows.size());
55 uint64_t newIndexRows = 0;
56 for (auto const& oldRow : selectedRows) {
57 oldToNewRowIndexMapping[oldRow] = newIndexRows++;
58 }
59
60 // Stores which entries of the original matrix/vector are non-constant. Entries for non-selected rows/columns are omitted
61 auto nonConstMatrixEntries = storm::storage::BitVector(pMatrix.getEntryCount(), false); // this vector has to be resized later
62 auto nonConstVectorEntries = storm::storage::BitVector(selectedRows.getNumberOfSetBits(), false);
63 // Counters for selected entries in the pMatrix and the pVector
64 uint64_t pMatrixEntryCount = 0;
65 uint64_t pVectorEntryCount = 0;
66
67 // The matrix builder for the new matrix. The correct number of rows and entries is not known yet.
68 storm::storage::SparseMatrixBuilder<Interval> builder(newIndexRows, newIndexColumns, 0, true, false);
69
70 this->occurringVariablesAtState.resize(pMatrix.getRowCount());
71
72 for (uint64_t row = 0; row < pMatrix.getRowCount(); row++) {
73 if (!selectedRows.get(row)) {
74 continue;
75 }
76 std::set<VariableType> occurringVariables;
77 for (auto const& entry : pMatrix.getRow(row)) {
78 auto column = entry.getColumn();
79 if (!selectedColumns.get(column)) {
80 continue;
81 }
82
83 auto transition = entry.getValue();
84
85 auto variables = transition.gatherVariables();
86 occurringVariables.insert(variables.begin(), variables.end());
87
88 if (storm::utility::isConstant(transition)) {
89 builder.addNextValue(oldToNewColumnIndexMapping[row], oldToNewColumnIndexMapping[column], utility::convertNumber<double>(transition));
90 } else {
91 nonConstMatrixEntries.set(pMatrixEntryCount, true);
92 auto valuation = RobustAbstractValuation(transition);
93 builder.addNextValue(oldToNewColumnIndexMapping[row], oldToNewColumnIndexMapping[column], Interval());
94 Interval& placeholder = functionValuationCollector.add(valuation);
95 matrixAssignment.push_back(std::pair<typename storm::storage::SparseMatrix<Interval>::iterator, Interval&>(
97 }
98 pMatrixEntryCount++;
99 }
100
101 // Save the occuringVariables of a state, needed if we want to use monotonicity
102 for (auto& var : occurringVariables) {
103 occuringStatesAtVariable[var].insert(row);
104 }
105 occurringVariablesAtState[row] = std::move(occurringVariables);
106 }
107
108 for (uint64_t i = 0; i < pVector.size(); i++) {
109 auto const transition = pVector[i];
110 if (!selectedRows.get(i)) {
111 continue;
112 }
113 if (storm::utility::isConstant(transition)) {
114 vector.push_back(utility::convertNumber<double>(transition));
115 } else {
116 nonConstVectorEntries.set(pVectorEntryCount, true);
117 auto valuation = RobustAbstractValuation(transition);
118 vector.push_back(Interval());
119 Interval& placeholder = functionValuationCollector.add(valuation);
120 vectorAssignment.push_back(std::pair<typename std::vector<Interval>::iterator, Interval&>(typename std::vector<Interval>::iterator(), placeholder));
121 for (auto const& var : valuation.getParameters()) {
122 occuringStatesAtVariable[var].insert(i);
123 occurringVariablesAtState[i].emplace(var);
124 }
125 }
126 pVectorEntryCount++;
127 }
128
129 matrix = builder.build();
130 vector.shrink_to_fit();
131 matrixAssignment.shrink_to_fit();
132 vectorAssignment.shrink_to_fit();
133 nonConstMatrixEntries.resize(pMatrixEntryCount);
134
135 // Now insert the correct iterators for the matrix and vector assignment
136 auto matrixAssignmentIt = matrixAssignment.begin();
137 uint64_t startEntryOfRow = 0;
138 for (uint64_t group = 0; group < matrix.getRowGroupCount(); ++group) {
139 uint64_t startEntryOfNextRow = startEntryOfRow + matrix.getRow(group, 0).getNumberOfEntries();
140 for (uint64_t matrixRow = matrix.getRowGroupIndices()[group]; matrixRow < matrix.getRowGroupIndices()[group + 1]; ++matrixRow) {
141 auto matrixEntryIt = matrix.getRow(matrixRow).begin();
142 for (uint64_t nonConstEntryIndex = nonConstMatrixEntries.getNextSetIndex(startEntryOfRow); nonConstEntryIndex < startEntryOfNextRow;
143 nonConstEntryIndex = nonConstMatrixEntries.getNextSetIndex(nonConstEntryIndex + 1)) {
144 matrixAssignmentIt->first = matrixEntryIt + (nonConstEntryIndex - startEntryOfRow);
145 ++matrixAssignmentIt;
146 }
147 }
148 startEntryOfRow = startEntryOfNextRow;
149 }
150 STORM_LOG_ASSERT(matrixAssignmentIt == matrixAssignment.end(), "Unexpected number of entries in the matrix assignment.");
151
152 auto vectorAssignmentIt = vectorAssignment.begin();
153 for (auto const& nonConstVectorEntry : nonConstVectorEntries) {
154 for (uint64_t vectorIndex = matrix.getRowGroupIndices()[nonConstVectorEntry]; vectorIndex != matrix.getRowGroupIndices()[nonConstVectorEntry + 1];
155 ++vectorIndex) {
156 vectorAssignmentIt->first = vector.begin() + vectorIndex;
157 ++vectorAssignmentIt;
158 }
159 }
160 STORM_LOG_ASSERT(vectorAssignmentIt == vectorAssignment.end(), "Unexpected number of entries in the vector assignment.");
161}
162
163template<typename ParametricType, typename ConstantType>
165 storm::solver::OptimizationDirection const& dirForParameters) {
166 // write the evaluation result of each function,evaluation pair into the placeholders
167 this->currentRegionAllIllDefined = functionValuationCollector.evaluateCollectedFunctions(region, dirForParameters);
168
169 // TODO Return if currentRegionAllIllDefined? Or write to matrix?
170
171 // apply the matrix and vector assignments to write the contents of the placeholder into the matrix/vector
172 for (auto& assignment : matrixAssignment) {
173 assignment.first->setValue(assignment.second);
174 }
175
176 for (auto& assignment : vectorAssignment) {
177 *assignment.first = assignment.second;
178 }
179}
180
181template<typename ParametricType, typename ConstantType>
182const std::vector<std::set<typename RobustParameterLifter<ParametricType, ConstantType>::VariableType>>&
186
187template<typename ParametricType, typename ConstantType>
188std::map<typename RobustParameterLifter<ParametricType, ConstantType>::VariableType, std::set<uint_fast64_t>> const&
192
193template<typename ParametricType, typename ConstantType>
194std::optional<std::set<typename storm::utility::parametric::CoefficientType<ParametricType>::type>>
197 std::shared_ptr<storm::expressions::ExpressionManager> expressionManager = std::make_shared<storm::expressions::ExpressionManager>();
198
200 auto smtSolver = factory.create(*expressionManager);
201
203
204 auto expression = rfte.toExpression(function) == expressionManager->rational(0);
205
206 auto variables = expressionManager->getVariables();
207 // Sum the summands together directly in the expression so we pass this info to the solver
208 expressions::Expression exprBounds = expressionManager->boolean(true);
209 for (auto const& var : variables) {
210 exprBounds = exprBounds && expressionManager->rational(0) <= var && var <= expressionManager->rational(1);
211 }
212
213 smtSolver->setTimeout(50);
214
215 smtSolver->add(exprBounds);
216 smtSolver->add(expression);
217
218 std::set<CoefficientType> zeroes = {};
219
220 while (true) {
221 auto checkResult = smtSolver->check();
222
223 if (checkResult == solver::SmtSolver::CheckResult::Sat) {
224 auto model = smtSolver->getModel();
225
226 STORM_LOG_ERROR_COND(variables.size() == 1, "Should be one variable.");
227 if (variables.size() != 1) {
228 return {};
229 }
230 auto const var = *variables.begin();
231
232 double value = model->getRationalValue(var);
233
234 zeroes.emplace(utility::convertNumber<CoefficientType>(value));
235
236 // Add new constraint so we search for the next zero in the polynomial
237 // Get another model (or unsat)
238 // For some reason, this only really works when we then make a new
239 smtSolver->addNotCurrentModel();
240 } else if (checkResult == solver::SmtSolver::CheckResult::Unknown) {
241 return std::nullopt;
242 break;
243 } else {
244 // Unsat => found all zeroes :)
245 break;
246 }
247 }
248 return zeroes;
249}
250
251template<typename ParametricType, typename ConstantType>
252std::optional<std::set<typename storm::utility::parametric::CoefficientType<ParametricType>::type>>
253RobustParameterLifter<ParametricType, ConstantType>::RobustAbstractValuation::zeroesCarl(
256 auto const& carlRoots = carl::rootfinder::realRoots<CoefficientType, CoefficientType>(
257 polynomial, carl::Interval<CoefficientType>(utility::zero<CoefficientType>(), utility::one<CoefficientType>()),
258 carl::rootfinder::SplittingStrategy::ABERTH);
259 std::set<CoefficientType> zeroes = {};
260 for (carl::RealAlgebraicNumber<CoefficientType> const& root : carlRoots) {
261 CoefficientType rootCoefficient;
262 if (root.isNumeric()) {
263 rootCoefficient = CoefficientType(root.value());
264 } else {
265 rootCoefficient = CoefficientType((root.upper() + root.lower()) / 2);
266 }
267 zeroes.emplace(rootCoefficient);
268 }
269 return zeroes;
270}
271
272template<typename ParametricType, typename ConstantType>
273std::set<typename storm::utility::parametric::CoefficientType<ParametricType>::type>
274RobustParameterLifter<ParametricType, ConstantType>::RobustAbstractValuation::cubicEquationZeroes(
276 if (polynomial.isConstant()) {
277 return {};
278 }
279 STORM_LOG_ERROR_COND(polynomial.gatherVariables().size() == 1, "Multi-variate polynomials currently not supported");
280 // Polynomial is a*p^3 + b*p^2 + c*p + d
281
282 // Recover factors from polynomial
283 CoefficientType a = utility::zero<CoefficientType>(), b = a, c = a, d = a;
284 utility::convertNumber<ConstantType>(a);
285 for (auto const& term : polynomial.getTerms()) {
286 STORM_LOG_ASSERT(term.getNrVariables() <= 1, "No terms with more than one variable allowed but " << term << " has " << term.getNrVariables());
287 if (!term.isConstant() && term.getSingleVariable() != parameter) {
288 continue;
289 }
290 CoefficientType coefficient = term.coeff();
291 STORM_LOG_ASSERT(term.tdeg() < 4, "Transitions are only allowed to have a maximum degree of four.");
292 switch (term.tdeg()) {
293 case 0:
294 d = coefficient;
295 break;
296 case 1:
297 c = coefficient;
298 break;
299 case 2:
300 b = coefficient;
301 break;
302 case 3:
303 a = coefficient;
304 break;
305 }
306 }
307 // Translated from https://stackoverflow.com/questions/27176423/function-to-solve-cubic-equation-analytically
308
309 // Quadratic case
310 if (utility::isZero(a)) {
311 a = b;
312 b = c;
313 c = d;
314 // Linear case
315 if (utility::isZero(a)) {
316 a = b;
317 b = c;
318 // Constant case
319 if (utility::isZero(a)) {
320 return {};
321 }
322 return {-b / a};
323 }
324
325 CoefficientType D = b * b - 4 * a * c;
326 if (utility::isZero(D)) {
327 return {-b / (2 * a)};
328 } else if (D > 0) {
329 return {(-b + utility::sqrt(D)) / (2 * a), (-b - utility::sqrt(D)) / (2 * a)};
330 }
331 return {};
332 }
333 std::set<CoefficientType> roots;
334
335 // Convert to depressed cubic t^3+pt+q = 0 (subst x = t - b/3a)
336 CoefficientType p = (3 * a * c - b * b) / (3 * a * a);
337 CoefficientType q = (2 * b * b * b - 9 * a * b * c + 27 * a * a * d) / (27 * a * a * a);
338 double pDouble = utility::convertNumber<ConstantType>(p);
339 double qDouble = utility::convertNumber<ConstantType>(q);
340
341 if (utility::isZero(p)) { // p = 0 -> t^3 = -q -> t = -q^1/3
342 roots = {utility::convertNumber<CoefficientType>(std::cbrt(-qDouble))};
343 } else if (utility::isZero(q)) { // q = 0 -> t^3 + pt = 0 -> t(t^2+p)=0
344 roots = {0};
345 if (p < 0) {
346 roots.emplace(utility::convertNumber<CoefficientType>(utility::sqrt(-pDouble)));
347 roots.emplace(utility::convertNumber<CoefficientType>(-utility::sqrt(-pDouble)));
348 }
349 } else {
350 // These are all coefficients (we also plug the values into RationalFunctions later), i.e., they are rational numbers,
351 // but some of these operations are strictly real, so we convert to double and back (i.e., approximate).
352 CoefficientType D = q * q / 4 + p * p * p / 27;
353 if (utility::isZero(D)) { // D = 0 -> two roots
354 roots = {-3 * q / (p * 2), 3 * q / p};
355 } else if (D > 0) { // Only one real root
356 double Ddouble = utility::convertNumber<ConstantType>(D);
357 CoefficientType u = utility::convertNumber<CoefficientType>(std::cbrt(-qDouble / 2 - utility::sqrt(Ddouble)));
358 roots = {u - p / (3 * u)};
359 } else { // D < 0, three roots, but needs to use complex numbers/trigonometric solution
360 double u = 2 * utility::sqrt(-pDouble / 3);
361 double t = std::acos(3 * qDouble / pDouble / u) / 3; // D < 0 implies p < 0 and acos argument in [-1..1]
362 double k = 2 * M_PI / 3;
363
364 roots = {utility::convertNumber<CoefficientType>(u * std::cos(t)), utility::convertNumber<CoefficientType>(u * std::cos(t - k)),
365 utility::convertNumber<CoefficientType>(u * std::cos(t - 2 * k))};
366 }
367 }
368
369 return roots;
370}
371
372template<typename ParametricType, typename ConstantType>
374 : transition(transition) {
375 STORM_LOG_ERROR_COND(transition.denominator().isConstant(), "Robust PLA only supports transitions with constant denominators.");
376 transition.simplify();
377 std::set<VariableType> occurringVariables;
378 storm::utility::parametric::gatherOccurringVariables(transition, occurringVariables);
379 for (auto const& var : occurringVariables) {
380 parameters.emplace(var);
381 }
382}
383
384template<typename ParametricType, typename ConstantType>
388
389template<typename ParametricType, typename ConstantType>
391 return vector;
392}
393
394template<typename ParametricType, typename ConstantType>
396 return currentRegionAllIllDefined;
397}
398
399template<typename ParametricType, typename ConstantType>
401 return this->transition == other.transition;
402}
403
404template<typename ParametricType, typename ConstantType>
405std::set<typename RobustParameterLifter<ParametricType, ConstantType>::VariableType> const&
409
410template<typename ParametricType, typename ConstantType>
414
415template<typename ParametricType, typename ConstantType>
417 // TODO This function is a mess
418 if (this->extrema || this->annotation) {
419 // Extrema already initialized
420 return;
421 }
422
423 if (BigStep::lastSavedAnnotations.count(transition)) {
424 auto& annotation = BigStep::lastSavedAnnotations.at(transition);
425
426 auto const& terms = annotation.getTerms();
427
428 // Try to find all zeroes of all derivatives with the SMT solver.
429 // TODO: Are we even using that this is a sum of terms?
430
431 std::optional<std::set<CoefficientType>> carlResult;
432
433 if (terms.size() < 5) {
434 carlResult = zeroesCarl(annotation.getProbability().derivative(), annotation.getParameter());
435 }
436
437 if (carlResult) {
438 // Hooray, we found the zeroes with the SMT solver / CARL
439 this->extrema = std::map<VariableType, std::set<CoefficientType>>();
440 (*this->extrema)[annotation.getParameter()];
441 for (auto const& root : *carlResult) {
442 (*this->extrema).at(annotation.getParameter()).emplace(utility::convertNumber<CoefficientType>(root));
443 }
444 } else {
445 // TODO make evaluation depth configurable
446 annotation.computeDerivative(4);
447 }
448 this->annotation.emplace(annotation);
449 } else {
450 this->extrema = std::map<VariableType, std::set<CoefficientType>>();
451
452 for (auto const& p : transition.gatherVariables()) {
453 (*this->extrema)[p] = {};
454
455 auto const& derivative = transition.derivative(p);
456
457 if (derivative.isConstant()) {
458 continue;
459 }
460
461 // There is no annotation for this transition:
462 auto nominatorAsUnivariate = derivative.nominator().toUnivariatePolynomial();
463 // Constant denominator is now distributed in the factors, not in the denominator of the rational function
464 nominatorAsUnivariate /= derivative.denominator().coefficient();
465
466 // Compute zeros of derivative (= maxima/minima of function) and emplace those between 0 and 1 into the maxima set
467 std::optional<std::set<CoefficientType>> zeroes;
468 // Find zeroes with straight-forward method for degrees <4, find them with SMT for degrees above that
469 if (derivative.nominator().totalDegree() < 4) {
470 zeroes = cubicEquationZeroes(RawPolynomial(derivative.nominator()), p);
471 } else {
472 zeroes = zeroesSMT(derivative, p);
473 }
474 STORM_LOG_ERROR_COND(zeroes, "Zeroes of " << derivative << " could not be found.");
475 for (auto const& zero : *zeroes) {
476 if (zero >= utility::zero<CoefficientType>() && zero <= utility::one<CoefficientType>()) {
477 this->extrema->at(p).emplace(zero);
478 }
479 }
480 }
481 }
482}
483
484template<typename ParametricType, typename ConstantType>
485std::optional<std::map<typename RobustParameterLifter<ParametricType, ConstantType>::VariableType,
486 std::set<typename storm::utility::parametric::CoefficientType<ParametricType>::type>>> const&
490
491template<typename ParametricType, typename ConstantType>
493 return this->annotation;
494}
495
496template<typename ParametricType, typename ConstantType>
498 std::size_t seed = 0;
499 carl::hash_add(seed, transition);
500 return seed;
501}
502
503template<typename ParametricType, typename ConstantType>
505 // If no valuation like this is present in the collectedValuations, initialize the extrema
506 if (!collectedValuations.count(valuation)) {
507 valuation.initialize();
508 this->regionsAndBounds.emplace(valuation, std::vector<std::pair<Interval, Interval>>());
509 }
510 // insert the function and the valuation
511 // Note that references to elements of an unordered map remain valid after calling unordered_map::insert.
512 auto insertionRes = collectedValuations.insert(std::pair<RobustAbstractValuation, Interval>(std::move(valuation), storm::Interval(0, 1)));
513 return insertionRes.first->second;
514}
515
516Interval evaluateExtremaAnnotations(std::map<UniPoly, std::set<double>> extremaAnnotations, Interval input) {
517 Interval sumOfTerms(0.0, 0.0);
518 for (auto const& [poly, roots] : extremaAnnotations) {
519 std::set<double> potentialExtrema = {input.lower(), input.upper()};
520 for (auto const& root : roots) {
521 if (root >= input.lower() && root <= input.upper()) {
522 potentialExtrema.emplace(root);
523 }
524 }
525
526 double minValue = utility::infinity<double>();
527 double maxValue = -utility::infinity<double>();
528
529 for (auto const& potentialExtremum : potentialExtrema) {
530 auto value = utility::convertNumber<double>(poly.evaluate(utility::convertNumber<RationalFunctionCoefficient>(potentialExtremum)));
531 if (value > maxValue) {
532 maxValue = value;
533 }
534 if (value < minValue) {
535 minValue = value;
536 }
537 }
538 sumOfTerms += Interval(minValue, maxValue);
539 }
540 return sumOfTerms;
541}
542
543template<typename ParametricType, typename ConstantType>
544bool RobustParameterLifter<ParametricType, ConstantType>::FunctionValuationCollector::evaluateCollectedFunctions(
545 storm::storage::ParameterRegion<ParametricType> const& region, storm::solver::OptimizationDirection const& dirForUnspecifiedParameters) {
546 std::unordered_map<RobustAbstractValuation, Interval, RobustAbstractValuationHash> insertThese;
547 for (auto& [abstrValuation, placeholder] : collectedValuations) {
548 // Results of our computations go here, we use different methods
549 ConstantType lowerBound = utility::infinity<ConstantType>();
550 ConstantType upperBound = -utility::infinity<ConstantType>();
551
552 if (abstrValuation.getExtrema()) {
553 // We know the extrema of this abstract valuation => we can get the exact bounds easily
554
555 // If an annotation exists:
556 // Evaluating the annotation is cheaper than evaluating the RationalFunction, which isn't prime-factorized
557 // If no annotation exists:
558 // The RationalFunction is hopefully prime-factorized
559 auto const& maybeAnnotation = abstrValuation.getAnnotation();
560
561 if (maybeAnnotation) {
562 // We only have one parameter and can evaluate the annotation directly
563 auto p = maybeAnnotation->getParameter();
564
565 CoefficientType lowerP = region.getLowerBoundary(p);
566 CoefficientType upperP = region.getUpperBoundary(p);
567 std::set<CoefficientType> potentialExtrema = {lowerP, upperP};
568 for (auto const& maximum : abstrValuation.getExtrema()->at(p)) {
569 if (maximum >= lowerP && maximum <= upperP) {
570 potentialExtrema.emplace(maximum);
571 }
572 }
573
574 for (auto const& potentialExtremum : potentialExtrema) {
575 // Possible optimization: evaluate all transitions together, keeping track of intermediate results
576 auto value = maybeAnnotation->evaluate(utility::convertNumber<double>(potentialExtremum));
577 if (value > upperBound) {
578 upperBound = value;
579 }
580 if (value < lowerBound) {
581 lowerBound = value;
582 }
583 }
584 } else {
585 // We may have multiple parameters, but the derivatives w.r.t. each parameter only contain that parameter
586 // We first figure out the positions of the lower and upper bounds per parameter
587 // Lower/upper bound of every parameter is independent because the transitions are sums of terms with one parameter each
588 // At the end, we compute the value
589 std::map<VariableType, CoefficientType> lowerPositions;
590 std::map<VariableType, CoefficientType> upperPositions;
591
592 for (auto const& p : abstrValuation.getParameters()) {
593 CoefficientType lowerP = region.getLowerBoundary(p);
594 CoefficientType upperP = region.getUpperBoundary(p);
595
596 std::set<CoefficientType> potentialExtrema = {lowerP, upperP};
597 for (auto const& maximum : abstrValuation.getExtrema()->at(p)) {
598 if (maximum >= lowerP && maximum <= upperP) {
599 potentialExtrema.emplace(maximum);
600 }
601 }
602
603 CoefficientType minPosP;
604 CoefficientType maxPosP;
605 CoefficientType minValue = utility::infinity<CoefficientType>();
606 CoefficientType maxValue = -utility::infinity<CoefficientType>();
607
608 auto instantiation = std::map<VariableType, CoefficientType>(region.getLowerBoundaries());
609
610 for (auto const& potentialExtremum : potentialExtrema) {
611 // We modify the instantiation to have value potentialExtremum at p, keeping other parameters the same
612 instantiation[p] = potentialExtremum;
613 auto value = abstrValuation.getTransition().evaluate(instantiation);
614 if (value > maxValue) {
615 maxValue = value;
616 maxPosP = potentialExtremum;
617 }
618 if (value < minValue) {
619 minValue = value;
620 minPosP = potentialExtremum;
621 }
622 }
623
624 lowerPositions[p] = minPosP;
625 upperPositions[p] = maxPosP;
626 }
627
628 // Compute function values at left and right ends
629 lowerBound = utility::convertNumber<ConstantType>(abstrValuation.getTransition().evaluate(lowerPositions));
630 upperBound = utility::convertNumber<ConstantType>(abstrValuation.getTransition().evaluate(upperPositions));
631 }
632
633 if (upperBound < utility::zero<ConstantType>() || lowerBound > utility::one<ConstantType>()) {
634 // Current region is entirely ill-defined (partially ill-defined is fine:)
635 return true;
636 }
637 } else {
638 STORM_LOG_ASSERT(abstrValuation.getAnnotation(), "Needs to have annotation if no zeroes");
639 auto& regionsAndBounds = this->regionsAndBounds.at(abstrValuation);
640 auto const& annotation = *abstrValuation.getAnnotation();
641
642 auto plaRegion = Interval(region.getLowerBoundary(annotation.getParameter()), region.getUpperBoundary(annotation.getParameter()));
643
644 bool refine = false;
645 do {
646 lowerBound = 1.0;
647 upperBound = 0.0;
648 std::vector<uint64_t> regionsInPLARegion;
649 for (uint64_t i = 0; i < regionsAndBounds.size(); i++) {
650 auto const& [region, bound] = regionsAndBounds[i];
652 i == 0 ? true : (!(region.upper() < regionsAndBounds[i - 1].first.lower() || region.lower() > regionsAndBounds[i - 1].first.upper())),
653 "regions next to each other need to intersect");
654 if (region.upper() <= plaRegion.lower() || region.lower() >= plaRegion.upper()) {
655 if (regionsInPLARegion.empty()) {
656 continue;
657 } else {
658 // Regions are sorted => we've walked past the interesting part
659 break;
660 }
661 }
662 lowerBound = utility::min(lowerBound, bound.lower());
663 upperBound = utility::max(upperBound, bound.upper());
664 regionsInPLARegion.push_back(i);
665 }
666
667 // TODO make this configurable
668 uint64_t regionsRefine = std::max((uint64_t)10, annotation.maxDegree());
669 refine = regionsInPLARegion.size() < regionsRefine;
670
671 if (refine) {
672 std::vector<Interval> newIntervals;
673 auto diameter = plaRegion.diameter();
674 // If we have no regions at all, initialize with the entire region
675 if (regionsAndBounds.empty()) {
676 regionsAndBounds.emplace_back(plaRegion, Interval(0, 1));
677 regionsInPLARegion.push_back(0);
678 }
679 // Add start (old regions might be larger than currently considered region)
680 if (regionsAndBounds[regionsInPLARegion.front()].first.lower() < plaRegion.lower()) {
681 newIntervals.push_back(Interval(regionsAndBounds[regionsInPLARegion.front()].first.lower(), plaRegion.lower()));
682 }
683 // Split up considered region
684 for (uint64_t i = 0; i < regionsRefine; i++) {
685 newIntervals.push_back(Interval(plaRegion.lower() + ((double)i / (double)regionsRefine) * diameter,
686 plaRegion.lower() + ((double)(i + 1) / (double)regionsRefine) * diameter));
687 }
688 // Add end
689 if (regionsAndBounds[regionsInPLARegion.back()].first.upper() > plaRegion.upper()) {
690 newIntervals.push_back(Interval(plaRegion.upper(), regionsAndBounds[regionsInPLARegion.back()].first.upper()));
691 }
692 // Remember everything that comes after what we changed
693 std::vector<std::pair<Interval, Interval>> regionsAndBoundsAfter;
694 for (uint64_t i = regionsInPLARegion.back() + 1; i < regionsAndBounds.size(); i++) {
695 regionsAndBoundsAfter.push_back(regionsAndBounds[i]);
696 }
697 // Remove previous results
698 regionsAndBounds.erase(regionsAndBounds.begin() + *regionsInPLARegion.begin(), regionsAndBounds.end());
699
700 // Compute region results using interval arithmetic
701 for (auto const& region : newIntervals) {
702 regionsAndBounds.emplace_back(region, annotation.evaluateOnIntervalMidpointTheorem(region));
703 }
704 // Emplace back remembered stuff
705 for (auto const& item : regionsAndBoundsAfter) {
706 regionsAndBounds.emplace_back(item);
707 }
708 }
709 } while (refine);
710 }
711
712 // bool graphPreserving = true;
713 // // const ConstantType epsilon =
714 // // graphPreserving ? utility::convertNumber<ConstantType>(storm::settings::getModule<storm::settings::modules::GeneralSettings>().getPrecision())
715 // // : utility::zero<ConstantType>();
716 const ConstantType epsilon = 0;
717 // We want to check in the realm of feasible instantiations, even if our not our entire parameter space is feasible
718 lowerBound = utility::max(utility::min(lowerBound, utility::one<ConstantType>() - epsilon), epsilon);
719 upperBound = utility::max(utility::min(upperBound, utility::one<ConstantType>() - epsilon), epsilon);
720
721 STORM_LOG_ASSERT(lowerBound <= upperBound, "Whoops");
722
723 placeholder = Interval(lowerBound, upperBound);
724 }
725 for (auto& key : insertThese) {
726 this->collectedValuations.insert(std::move(insertThese.extract(key.first)));
727 }
728 return false;
729}
730
731template class RobustParameterLifter<storm::RationalFunction, double>;
732} // namespace transformer
733} // namespace storm
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
uint64_t getNumberOfSetBits() const
Returns the number of bits that are set to true in this bit vector.
size_t size() const
Retrieves the number of bits this bit vector can store.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
Valuation const & getLowerBoundaries() const
CoefficientType const & getLowerBoundary(VariableType const &variable) const
CoefficientType const & getUpperBoundary(VariableType const &variable) const
A class that can be used to build a sparse matrix by adding value by value.
void addNextValue(index_type row, index_type column, value_type const &value)
Sets the matrix entry at the given row and column to the given value.
SparseMatrix< value_type > build(index_type overriddenRowCount=0, index_type overriddenColumnCount=0, index_type overriddenRowGroupCount=0)
A class that holds a possibly non-square matrix in the compressed row storage format.
const_rows getRow(index_type row) const
Returns an object representing the given row.
index_type getEntryCount() const
Returns the number of entries in the matrix.
std::vector< MatrixEntry< index_type, value_type > >::iterator iterator
index_type getRowCount() const
Returns the number of rows of the matrix.
static std::unordered_map< RationalFunction, Annotation > lastSavedAnnotations
Definition BigStep.h:199
std::optional< std::map< VariableType, std::set< CoefficientType > > > const & getExtrema() const
This class lifts parameter choices to nondeterminism: For each row in the given matrix that considerd...
storm::storage::SparseMatrix< Interval > const & getMatrix() const
std::map< VariableType, std::set< uint_fast64_t > > const & getOccuringStatesAtVariable() const
std::vector< Interval > const & getVector() const
storm::utility::parametric::CoefficientType< ParametricType >::type CoefficientType
std::vector< std::set< VariableType > > const & getOccurringVariablesAtState() const
void specifyRegion(storm::storage::ParameterRegion< ParametricType > const &region, storm::solver::OptimizationDirection const &dirForParameters)
storm::utility::parametric::VariableType< ParametricType >::type VariableType
RobustParameterLifter(storm::storage::SparseMatrix< ParametricType > const &pMatrix, std::vector< ParametricType > const &pVector, storm::storage::BitVector const &selectedRows, storm::storage::BitVector const &selectedColumns, bool generateRowLabels=false, bool useMonotonicity=false)
Lifts the parameter choices to nondeterminisim.
virtual std::unique_ptr< storm::solver::SmtSolver > create(storm::expressions::ExpressionManager &manager) const
Creates a new SMT solver instance.
Definition solver.cpp:146
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_ERROR_COND(cond, message)
Definition macros.h:52
std::set< storm::RationalFunctionVariable > getParameters(DFT< storm::RationalFunction > const &dft)
Get all rate/probability parameters occurring in the DFT.
Definition DFT.cpp:825
storm::utility::parametric::CoefficientType< storm::RationalFunction >::type CoefficientType
carl::UnivariatePolynomial< RationalFunctionCoefficient > UniPoly
Definition BigStep.cpp:39
Interval evaluateExtremaAnnotations(std::map< UniPoly, std::set< double > > extremaAnnotations, Interval input)
void gatherOccurringVariables(FunctionType const &function, std::set< typename VariableType< FunctionType >::type > &variableSet)
Add all variables that occur in the given function to the the given set.
ValueType max(ValueType const &first, ValueType const &second)
bool isConstant(ValueType const &)
ValueType min(ValueType const &first, ValueType const &second)
bool isZero(ValueType const &a)
Definition constants.cpp:38
ValueType sqrt(ValueType const &number)
carl::Interval< double > Interval
Interval type.
carl::MultivariatePolynomial< RationalFunctionCoefficient > RawPolynomial