Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ValueEncoding.h
Go to the documentation of this file.
1#pragma once
2#include <ranges>
3#include <span>
4#include <vector>
5
13
15
16namespace storm::umb {
17
19 public:
20 template<bool Signed, std::ranges::input_range InputRange>
21 requires std::same_as<std::ranges::range_value_t<InputRange>, uint64_t>
24 auto const twoTo64 = storm::utility::pow<IntegerType>(2, 64);
25
26 STORM_LOG_ASSERT(std::ranges::size(input) > 0, "Input range must not be empty.");
27 // We assume a little endian representation, so we reverse the input to start with the most significant bits.
28 auto reverse_input = std::ranges::reverse_view(std::forward<InputRange>(input));
29
30 // Helper function to decode from an unsigned range (with the most significant bits first).
31 auto decodeFromUnsignedRange = [](auto&& input) -> IntegerType {
32 auto const twoTo64 = storm::utility::pow<IntegerType>(2, 64);
33 auto inputIt = std::ranges::begin(input);
34 auto const inputEnd = std::ranges::end(input);
35 auto result = storm::utility::convertNumber<IntegerType>(*inputIt);
36 for (++inputIt; inputIt != inputEnd; ++inputIt) {
37 result *= twoTo64;
39 }
40 return result;
41 };
42
43 if constexpr (Signed) {
44 // Find out if the number is negative, in which case we would compute the two's complement.
45 uint64_t constexpr mostSignificantBitMask = 1ull << 63;
46 if (*std::ranges::begin(reverse_input) & mostSignificantBitMask) {
47 // Two's complement (e.g. 1111...1101 is -3)
48 return -decodeFromUnsignedRange(reverse_input | std::ranges::views::transform([](uint64_t value) { return ~value; })) - 1;
49 }
50 }
51 // Reaching this point means that the number is positive (i.e. signed and unsigned representations coincide).
52 return decodeFromUnsignedRange(reverse_input);
53 }
54
55 template<std::ranges::input_range InputRange>
56 requires std::same_as<std::ranges::range_value_t<InputRange>, uint64_t>
57 static auto uint64ToRationalRangeView(InputRange&& input, uint64_t const numberSize) {
58 STORM_LOG_ASSERT(numberSize % 128ull == 0ull && numberSize > 0ull, "Type size must be a positive multiple of 128 for rational representation.");
59 auto const uint64ValuesPerNumber = numberSize / 64ull;
60 auto const size = std::ranges::size(input) / uint64ValuesPerNumber;
61 return std::ranges::iota_view(0ull, size) | std::ranges::views::transform([&input, uint64ValuesPerNumber](auto i) -> storm::RationalNumber {
62 auto const left = uint64ValuesPerNumber * i;
63 auto const right = left + uint64ValuesPerNumber;
64 auto mid = left + uint64ValuesPerNumber / 2;
65
66 std::ranges::subrange numeratorRange{std::ranges::begin(input) + left, std::ranges::begin(input) + mid};
67 storm::RationalNumber numerator = decodeArbitraryPrecisionInteger<true>(numeratorRange); // signed
68
69 std::ranges::subrange denominatorRange{std::ranges::begin(input) + mid, std::ranges::begin(input) + right};
70 storm::RationalNumber denominator = decodeArbitraryPrecisionInteger<false>(denominatorRange); // unsigned
71
72 return numerator / denominator;
73 });
74 }
75
76 template<std::ranges::input_range InputRange>
77 requires std::same_as<std::ranges::range_value_t<InputRange>, uint64_t>
78 static auto uint64ToRationalIntervalRangeView(InputRange&& input, uint64_t const numberSize) {
79 STORM_LOG_ASSERT(numberSize % 256ull == 0ull && numberSize > 0ull,
80 "Type size must be a positive multiple of 256 for rational interval representation.");
81 auto rationalView = uint64ToRationalRangeView(input, numberSize / 2);
82 return std::ranges::iota_view(0ull, std::ranges::size(rationalView) / 2) | std::views::transform([rationalView](auto i) -> storm::RationalInterval {
83 return storm::RationalInterval{rationalView[2 * i], rationalView[2 * i + 1]};
84 });
85 }
86
87 template<bool Signed>
89 // The bitsize method returns the smallest n with -2^n <= x < 2^n.
90 if constexpr (Signed) {
91 // For signed integers, we need one additional bit for the sign.
92 // the only exception is when value is equal to -2^n, in which case we don't need that extra bit.
93 if (value < 0) {
95 }
96 return storm::utility::bitsize(value) + 1;
97 } else {
98 // For unsigned integers, we get n with 2^{n-1} <= x < 2^n.
99 return storm::utility::bitsize(value);
100 }
101 }
102
103 template<std::ranges::input_range InputRange>
104 requires std::same_as<std::ranges::range_value_t<InputRange>, storm::RationalNumber>
105 static uint64_t getMinimalRationalSize(InputRange&& input, bool multiplesOf64) {
106 // We may assume that the denominator is always positive as this is a requirement for both GMP and CLN
107 static_assert(storm::RationalNumberDenominatorAlwaysPositive);
108 uint64_t minimalIntegerSize = 1;
109 for (auto const& r : input) {
110 minimalIntegerSize = std::max(minimalIntegerSize, getSizeOfIntegerEncoding<true>(storm::utility::numerator(r)));
111 minimalIntegerSize = std::max(minimalIntegerSize, getSizeOfIntegerEncoding<false>(storm::utility::denominator(r)));
112 }
113 if (multiplesOf64) {
114 // Round up to the next multiple of 64
115 minimalIntegerSize = ((minimalIntegerSize + 63ull) / 64ull) * 64ull;
116 }
117 // Each rational number consists of two integers (numerator and denominator)
118 return minimalIntegerSize * 2ull;
119 }
120
121 template<std::ranges::input_range InputRange>
122 requires std::same_as<std::ranges::range_value_t<InputRange>, storm::RationalInterval>
123 static uint64_t getMinimalRationalIntervalSize(InputRange&& input, bool multiplesOf64) {
125 }
126
127 template<bool Signed>
128 static void appendEncodedInteger(std::vector<uint64_t>& result, typename storm::NumberTraits<storm::RationalNumber>::IntegerType const& value,
129 uint64_t uint64BucketsPerInteger) {
130 if constexpr (Signed) {
131 if (value < 0) {
132 // Two's complement representation (e.g. -3 is 1111...1101)
133 // We encode the corresponding positive value and then invert the bits.
134 appendEncodedInteger<true>(result, -(value + 1), uint64BucketsPerInteger);
135 // Invert the bits
136 for (auto& v : std::span<uint64_t>(result.end() - uint64BucketsPerInteger, result.end())) {
137 v = ~v;
138 }
139 } else {
140 // We encode the non-negative value as if it were unsigned.
141 appendEncodedInteger<false>(result, value, uint64BucketsPerInteger);
142 // Special case: the most significant bit must not be set. Otherwise, it would indicate a negative number in two's complement.
143 STORM_LOG_ASSERT((result.back() & (1ull << 63)) == 0ull,
144 "Encoding error for positive signed integer: most significant bit is set. Not enough uint64 buckets allocated?");
145 }
146 } else {
148 auto const twoTo64 = storm::utility::pow<IntegerType>(2, 64);
149 // We assume a little endian representation, so we start with the least significant bits.
150
151 STORM_LOG_ASSERT(value >= 0, "Value must be non-negative for unsigned encoding.");
152 auto divisionResult = storm::utility::divide<IntegerType>(value, twoTo64);
153 result.push_back(storm::utility::convertNumber<uint64_t, storm::RationalNumber>(divisionResult.second));
154 uint64_t buckets = 1;
155 while (divisionResult.first != 0) {
156 divisionResult = storm::utility::divide<IntegerType>(divisionResult.first, twoTo64);
157 result.push_back(storm::utility::convertNumber<uint64_t, storm::RationalNumber>(divisionResult.second));
158 ++buckets;
159 }
160 // fill remaining buckets with zeros
161 result.resize(result.size() + (uint64BucketsPerInteger - buckets), 0ull);
162 }
163 }
164
165 static void appendEncodedRational(std::vector<uint64_t>& result, storm::RationalNumber const& value, uint64_t uint64BucketsPerInteger) {
166 // We may assume that the denominator is always positive as this is a requirement for both GMP and CLN
167 static_assert(storm::RationalNumberDenominatorAlwaysPositive);
168 appendEncodedInteger<true>(result, storm::utility::numerator(value), uint64BucketsPerInteger); // signed
169 appendEncodedInteger<false>(result, storm::utility::denominator(value), uint64BucketsPerInteger); // unsigned
170 }
171
172 template<std::ranges::input_range InputRange>
173 requires std::same_as<std::ranges::range_value_t<InputRange>, storm::RationalNumber>
174 static std::vector<uint64_t> createUint64FromRationalRange(InputRange&& input, uint64_t const numberSize) {
175 STORM_LOG_ASSERT(numberSize % 128ull == 0ull && numberSize > 0ull, "Type size must be a positive multiple of 128 for rational representation.");
176 auto const uint64BucketsPerNumber = numberSize / 64ull;
177 auto const size = std::ranges::size(input) * uint64BucketsPerNumber;
178
179 std::vector<uint64_t> values;
180 values.reserve(size);
181 for (auto const& r : input) {
182 appendEncodedRational(values, r, numberSize / 128ull);
183 }
184 values.shrink_to_fit();
185 STORM_LOG_ASSERT(values.size() == size, "Unexpected size of encoded rational values: " << values.size() << " vs. " << size);
186 return values;
187 }
188
189 template<std::ranges::input_range InputRange>
191 static std::vector<uint64_t> createUint64FromRationalIntervalRange(InputRange&& input, uint64_t const numberSize) {
192 STORM_LOG_ASSERT(numberSize % 256ull == 0ull && numberSize > 0ull,
193 "Type size must be a positive multiple of 256 for rational interval representation.");
195 }
196
197 template<std::ranges::input_range InputRange>
198 requires std::same_as<std::ranges::range_value_t<InputRange>, double>
199 static auto doubleToIntervalRangeView(InputRange&& input) {
200 STORM_LOG_ASSERT(std::ranges::size(input) % 2 == 0, "Input size is not even: " << std::ranges::size(input));
201 return std::ranges::iota_view(0ull, std::ranges::size(input) / 2) |
202 std::views::transform([&input](auto i) -> storm::Interval { return storm::Interval{input[2 * i], input[2 * i + 1]}; });
203 }
204
205 template<typename BaseType, std::ranges::input_range InputRange>
207 static auto intervalToBaseRangeView(InputRange&& input) {
208 return std::ranges::iota_view(0ull, std::ranges::size(input) * 2) | std::views::transform([&input](auto i) -> BaseType {
209 if (i % 2 == 0) {
210 return storm::utility::convertNumber<BaseType>(input[i / 2].lower());
211 } else {
212 return storm::utility::convertNumber<BaseType>(input[i / 2].upper());
213 }
214 });
215 }
216
225 template<typename ValueType>
226 static auto applyDecodedVector(auto&& func, storm::umb::GenericVector const& input, storm::umb::SizedType const& sourceType) {
227 STORM_LOG_ASSERT(input.hasValue(), "Input vector is not set.");
228 auto conversionView = [](auto&& input) {
229 using SourceType = std::ranges::range_value_t<decltype(input)>;
230 if constexpr (std::same_as<ValueType, SourceType>) {
231 return input;
232 } else {
233 return input | std::ranges::views::transform(
234 [](SourceType const& value) -> ValueType { return storm::utility::convertNumber<ValueType, SourceType>(value); });
235 }
236 };
237
238 using enum storm::umb::Type;
239
240 // find out how to interpret the input values
241 switch (sourceType.type) {
242 case Double:
245 "Some values are given in type double but will be converted to an exact (arbitrary precision) type. Rounding errors may occur.");
246 STORM_LOG_ASSERT(input.template isType<double>(), "Unexpected type for values. Expected double.");
247 STORM_LOG_ASSERT(sourceType.bitSize() == 64, "Unexpected source type size for double representation. Expected 64.");
248 return func(conversionView(input.template get<double>()));
249 case Rational:
251 "Some values are given in an exact type but converted to an inexact type. Rounding errors may occur.");
252 if (input.template isType<storm::RationalNumber>()) {
253 return func(conversionView(input.template get<storm::RationalNumber>()));
254 } else {
255 STORM_LOG_ASSERT(input.template isType<uint64_t>(), "Unexpected type for rational representation. Expected uint64.");
256 return func(conversionView(uint64ToRationalRangeView(input.template get<uint64_t>(), sourceType.bitSize())));
257 }
258 case DoubleInterval:
259 // For intervals, there is no suitable value conversion to non-interval types since we would drop the uncertainty
260 if constexpr (!storm::IsIntervalType<ValueType>) {
261 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
262 "Some values are given as double intervals but a model with a non-interval type is requested.");
263 return func(std::ranges::empty_view<ValueType>{});
264 } else {
267 "Some values are given in type double but will be converted to an exact (arbitrary precision) type. Rounding errors may occur.");
268 STORM_LOG_ASSERT(sourceType.bitSize() == 128ull, "Unexpected source type size for double interval representation. Expected 128.");
269 if (input.template isType<storm::Interval>()) {
270 return func(conversionView(input.template get<storm::Interval>()));
271 } else {
272 STORM_LOG_ASSERT(input.template isType<double>(), "Unexpected type for double interval representation. Expected double.");
273 return func(conversionView(doubleToIntervalRangeView(input.template get<double>())));
274 }
275 }
276 case RationalInterval:
277 // For intervals, there is no suitable value conversion to non-interval types since we would drop the uncertainty
278 if constexpr (!storm::IsIntervalType<ValueType>) {
279 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
280 "Some values are given as rational intervals but a model with a non-interval type is requested.");
281 return func(std::ranges::empty_view<ValueType>{});
282 } else {
284 "Some values are given in an exact type but converted to an inexact type. Rounding errors may occur.");
285 if (input.template isType<storm::RationalInterval>()) {
286 return func(conversionView(input.template get<storm::RationalInterval>()));
287 } else {
288 STORM_LOG_ASSERT(input.template isType<uint64_t>(), "Unexpected type for rational interval representation. Expected uint64.");
289 return func(conversionView(uint64ToRationalIntervalRangeView(input.template get<uint64_t>(), sourceType.bitSize())));
290 }
291 }
292 default:
293 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Values have unsupported type " << sourceType.toString() << ".");
294 }
295 }
296
297 template<typename ValueType>
298 static std::vector<ValueType> createDecodedVector(storm::umb::GenericVector const& input, storm::umb::SizedType const& sourceType) {
300 [](auto&& decodedInput) {
301 std::vector<ValueType> v;
302 v.reserve(std::ranges::size(decodedInput));
303 for (auto&& value : decodedInput) {
304 v.push_back(value);
305 }
306 return v;
307 },
308 input, sourceType);
309 }
310};
311} // namespace storm::umb
static auto applyDecodedVector(auto &&func, storm::umb::GenericVector const &input, storm::umb::SizedType const &sourceType)
returns func(<decoded_input>) where <decoded_input> is a range that (if necessary) decodes and conver...
static uint64_t getMinimalRationalSize(InputRange &&input, bool multiplesOf64)
static auto intervalToBaseRangeView(InputRange &&input)
static uint64_t getMinimalRationalIntervalSize(InputRange &&input, bool multiplesOf64)
static void appendEncodedInteger(std::vector< uint64_t > &result, typename storm::NumberTraits< storm::RationalNumber >::IntegerType const &value, uint64_t uint64BucketsPerInteger)
static void appendEncodedRational(std::vector< uint64_t > &result, storm::RationalNumber const &value, uint64_t uint64BucketsPerInteger)
static std::vector< uint64_t > createUint64FromRationalRange(InputRange &&input, uint64_t const numberSize)
static storm::NumberTraits< storm::RationalNumber >::IntegerType decodeArbitraryPrecisionInteger(InputRange &&input)
static auto doubleToIntervalRangeView(InputRange &&input)
static std::vector< ValueType > createDecodedVector(storm::umb::GenericVector const &input, storm::umb::SizedType const &sourceType)
static auto uint64ToRationalIntervalRangeView(InputRange &&input, uint64_t const numberSize)
static auto uint64ToRationalRangeView(InputRange &&input, uint64_t const numberSize)
static uint64_t getSizeOfIntegerEncoding(typename storm::NumberTraits< storm::RationalNumber >::IntegerType const &value)
static std::vector< uint64_t > createUint64FromRationalIntervalRange(InputRange &&input, uint64_t const numberSize)
#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
NumberTraits< RationalType >::IntegerType denominator(RationalType const &number)
NumberTraits< RationalType >::IntegerType numerator(RationalType const &number)
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 pow(ValueType const &value, int_fast64_t exponent)
uint64_t bitsize(ValueType const &number)
Returns the minimum number of bits to represent the given number.
TargetType convertNumber(SourceType const &number)
carl::Interval< storm::RationalNumber > RationalInterval
carl::Interval< double > Interval
Interval type.
constexpr bool IsIntervalType
Helper to check if a type is an interval.
static const bool IsExact
uint64_t bitSize() const
Definition Type.cpp:87
std::string toString() const
Definition Type.cpp:91
storm::SerializedEnum< storm::umb::TypeDeclaration > type
Definition Type.h:59