Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
RationalApproximation.cpp
Go to the documentation of this file.
1
3
7
8namespace storm::utility {
9
10storm::RationalNumber findRational(storm::RationalNumber const& lowerBound, bool lowerInclusive, storm::RationalNumber const& upperBound, bool upperInclusive) {
12 STORM_LOG_ASSERT(lowerBound < upperBound || (lowerBound == upperBound && lowerInclusive && upperInclusive), "Invalid interval for rational approximation.");
13
14 // Handle negative numbers
15 if (auto const zero = storm::utility::zero<storm::RationalNumber>(); lowerBound < zero) {
16 // check if zero is in the interval
17 if (upperBound > zero || (upperBound == zero && upperInclusive)) {
19 } else {
20 // all numbers in the interval are negative. We translate that to a positive problem and negate the result
21 return -findRational(-upperBound, upperInclusive, -lowerBound, lowerInclusive);
22 }
23 }
24 // At this point, the solution is known to be non-negative
25
26 // We compute a path in the Stern-Brocot tree from the root to the node representing the simplest rational in the closed interval [lowerBound, upperBound]
27 // If the input interval is open on one or both sides, we traverse the tree further down until a suitable rational number is found
28 // @see https://en.wikipedia.org/wiki/Stern–Brocot_tree#A_tree_of_continued_fractions
29 // @see https://mathoverflow.net/a/424509
30 // The path is encoded using a simple continued fraction representation.
31 // We take path[0] times the right child, path[1] times the left child, path[2] times the right child, etc, using path.back()-1 steps in the last direction.
32 std::vector<Integer> path; // in simple continued fraction representation
33 auto l = lowerBound;
34 auto u = upperBound;
35 while (true) {
36 auto l_den = storm::utility::denominator(l);
37 auto u_den = storm::utility::denominator(u);
38 auto const [l_i, l_rem] = storm::utility::divide(storm::utility::numerator(l), l_den);
39 auto const [u_i, u_rem] = storm::utility::divide(storm::utility::numerator(u), u_den);
40
41 path.push_back(std::min(l_i, u_i)); // insert tree traversal information
42 if (l_i == u_i && !storm::utility::isZero(l_rem) && !storm::utility::isZero(u_rem)) {
43 // continue traversing the tree
46 continue;
47 }
48 // Reaching this point means that we have found a node in the Stern-Brocot tree where the paths for lower and upper bound diverge.
49 // If there still is a remainder, we need to add one to the last entry of the path so that it correctly encodes the node we are referring to.
50 if (l_i != u_i && !storm::utility::isZero(l_i < u_i ? l_rem : u_rem)) {
51 path.back() += Integer(1);
52 }
53
54 // Find out if we hit an interval boundary and whether we need to adapt this due to open intervals
55 bool const needAdjustLower = !lowerInclusive && path.back() == l_i && storm::utility::isZero(l_rem);
56 bool const needAdjustUpper = !upperInclusive && path.back() == u_i && storm::utility::isZero(u_rem);
57 if (needAdjustLower || needAdjustUpper) {
58 // handle for values of the "other" bound that does not need adjustment
59 auto const& o_i = needAdjustLower ? u_i : l_i;
60 auto const& o_rem = needAdjustLower ? u_rem : l_rem;
61 auto const& o_den = needAdjustLower ? u_den : l_den;
62 auto const& o_inclusive = needAdjustLower ? upperInclusive : lowerInclusive;
63
64 // When adjusting lower bounds, we need to explore the right subtree to obtain a larger value than the current lower bound
65 // When adjusting upper bounds, we need to explore the left subtree to obtain a smaller value than the current upper bound
66 // Whether we currently look at left or right subtrees is determined by the parity of the index in the path:
67 // Path entries at even indices correspond to right moves (increasing value) and entries at odd indices correspond to left moves (decreasing value)
68 bool const currentDirectionIsIncreasing = (path.size() - 1) % 2 == 0;
69 bool const adjustInCurrentDirection = (needAdjustLower && currentDirectionIsIncreasing) || (needAdjustUpper && !currentDirectionIsIncreasing);
70 // Below, we navigate through the Stern-Brocot tree by adapting the path
71 // path.back() += 1; extends the path to a child in the "current direction"
72 // path.back() -= 1; path.emplace_back(2); extends the path to a child in the "counter direction"
73 if (adjustInCurrentDirection) {
74 STORM_LOG_ASSERT(path.back() <= o_i, "Unexpected case when navigating the Stern-Brocot tree.");
75 if (path.back() + Integer(1) < o_i || (path.back() + Integer(1) == o_i && !storm::utility::isZero(o_rem))) {
76 // In this case, the next child (in the current direction) is inside the interval, so we can just take that
77 path.back() += Integer(1);
78 } else if (path.back() + Integer(1) == o_i && storm::utility::isZero(o_rem)) {
79 // In this case, the next child coincides with the other boundary
80 if (o_inclusive) {
81 path.back() += Integer(1); // add next child
82 } else {
83 // We first take one child in the current direction and then one child in the counter direction.
84 // path.back() += 1; path.back() -= 1; // cancels out
85 path.emplace_back(2);
86 }
87 } else {
88 // The following assertion holds because path.back() > o_i is not possible due to the way we constructed the path above
89 // and if there would be no remainder, the other boundary would be hit as well (i.e. we would have an empty interval (a,a).
90 STORM_LOG_ASSERT(path.back() == o_i && !storm::utility::isZero(o_rem), "Unexpected case when navigating the Stern-Brocot tree.");
91 // In this case, we need to append one child in the current direction and multiple children in the counter direction based on the continued
92 // fraction representation of the other boundary
93 auto const [o_i2, o_rem2] = storm::utility::divide(o_den, o_rem);
94 // path.back() += 1; path.back() -= 1; // cancels out
95 path.push_back(o_i2);
96 if (!storm::utility::isZero(o_rem2)) {
97 // If there still is a remainder, we add one to the last entry of the path so that it correctly encodes the node we are referring to.
98 path.back() += Integer(1);
99 } else if (!o_inclusive) {
100 // If there is no remainder, we are exactly on the other boundary. If that boundary is also excluded, we need to add one more step.
101 path.back() += Integer(1);
102 }
103 }
104 } else {
105 // Adjusting a bound in the counter direction can only happen if the other bound still has a remainder
106 // Otherwise, we would have also hit that bound
107 STORM_LOG_ASSERT(o_i == path.back() - Integer(1), "Unexpected case when navigating the Stern-Brocot tree.");
108 STORM_LOG_ASSERT(!storm::utility::isZero(o_rem), "Unexpected case when navigating the Stern-Brocot tree.");
109 auto const [o_i2, o_rem2] = storm::utility::divide(o_den, o_rem);
110 path.back() -= Integer(1); // necessary in all cases
111 if (o_i2 > Integer(2) || (o_i2 == Integer(2) && !storm::utility::isZero(o_rem2))) {
112 // In this case, the next child (in the counter direction) is inside the interval, so we can just take that
113 path.emplace_back(2);
114 } else if (o_i2 == Integer(2) && storm::utility::isZero(o_rem2)) {
115 // In this case, the next child in counter direction coincides with the other boundary
116 if (o_inclusive) {
117 path.emplace_back(2);
118 } else {
119 // We first take one child in the counter direction and then one child in the current direction.
120 path.emplace_back(1);
121 path.emplace_back(2);
122 }
123 } else {
124 STORM_LOG_ASSERT(o_i2 == Integer(1) && !storm::utility::isZero(o_rem2), "Unexpected case when navigating the Stern-Brocot tree.");
125 // In this case, we need to append one child in the counter direction and multiple children in the current direction based on the continued
126 // fraction representation of the other boundary
127 auto const [o_i3, o_rem3] = storm::utility::divide(o_rem, o_rem2);
128 path.emplace_back(1);
129 path.push_back(o_i3);
130 if (!storm::utility::isZero(o_rem3)) {
131 // If there still is a remainder, we add one to the last entry of the path so that it correctly encodes the node we are referring to.
132 path.back() += Integer(1);
133 } else if (!o_inclusive) {
134 // If there is no remainder, we are exactly on the other boundary. If that boundary is also excluded, we need to add one more step.
135 path.back() += Integer(1);
136 }
137 }
138 }
139 }
140 break;
141 }
142
143 // Now, construct the rational number from the path
144 auto it = path.rbegin();
146 for (++it; it != path.rend(); ++it) {
148 }
149 return result;
150
151 STORM_LOG_ASSERT(result > lowerBound || (lowerInclusive && result == lowerBound), "Result is below lower bound.");
152 STORM_LOG_ASSERT(result < upperBound || (upperInclusive && result == upperBound), "Result is above upper bound.");
153 return result;
154}
155
156} // namespace storm::utility
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
NumberTraits< RationalType >::IntegerType denominator(RationalType const &number)
NumberTraits< RationalType >::IntegerType numerator(RationalType const &number)
bool isZero(ValueType const &a)
Definition constants.cpp:39
storm::RationalNumber findRational(storm::RationalNumber const &lowerBound, bool lowerInclusive, storm::RationalNumber const &upperBound, bool upperInclusive)
Finds the "simplest" rational number in the given interval, where "simplest" means having the smalles...
ValueType zero()
Definition constants.cpp:24
std::pair< IntegerType, IntegerType > divide(IntegerType const &dividend, IntegerType const &divisor)
(Integer-)Divides the dividend by the divisor and returns the result plus the remainder.
ValueType one()
Definition constants.cpp:19
TargetType convertNumber(SourceType const &number)