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