Storm 1.14.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) {
131 if (uint64BucketsPerInteger == 0) {
132 STORM_LOG_ASSERT(value == storm::utility::zero<IntegerType>(), "Unexpected non-zero value for zero bucket size.");
133 // nothing to do
134 } else if constexpr (Signed) {
135 if (value < 0) {
136 // Two's complement representation (e.g. -3 is 1111...1101)
137 // We encode the corresponding positive value and then invert the bits.
138 appendEncodedInteger<true>(result, -(value + 1), uint64BucketsPerInteger);
139 // Invert the bits
140 for (auto& v : std::span<uint64_t>(result.end() - uint64BucketsPerInteger, result.end())) {
141 v = ~v;
142 }
143 } else {
144 // We encode the non-negative value as if it were unsigned.
145 appendEncodedInteger<false>(result, value, uint64BucketsPerInteger);
146 // Special case: the most significant bit must not be set. Otherwise, it would indicate a negative number in two's complement.
147 STORM_LOG_ASSERT((result.back() & (1ull << 63)) == 0ull,
148 "Encoding error for positive signed integer: most significant bit is set. Not enough uint64 buckets allocated?");
149 }
150 } else {
151 auto const twoTo64 = storm::utility::pow<IntegerType>(2, 64);
152 // We assume a little endian representation, so we start with the least significant bits.
153
154 STORM_LOG_ASSERT(value >= 0, "Value must be non-negative for unsigned encoding.");
155 auto divisionResult = storm::utility::divide<IntegerType>(value, twoTo64);
156 result.push_back(storm::utility::convertNumber<uint64_t, storm::RationalNumber>(divisionResult.second));
157 uint64_t buckets = 1;
158 while (divisionResult.first != 0) {
159 divisionResult = storm::utility::divide<IntegerType>(divisionResult.first, twoTo64);
160 result.push_back(storm::utility::convertNumber<uint64_t, storm::RationalNumber>(divisionResult.second));
161 ++buckets;
162 }
163 // fill remaining buckets with zeros
164 STORM_LOG_ASSERT(uint64BucketsPerInteger >= buckets, "Not enough uint64 buckets allocated for encoding integer value "
165 << value << " (" << uint64BucketsPerInteger << " buckets allocated but " << buckets
166 << " needed).");
167 result.resize(result.size() + (uint64BucketsPerInteger - buckets), 0ull);
168 }
169 }
170
171 static void appendEncodedRational(std::vector<uint64_t>& result, storm::RationalNumber const& value, uint64_t uint64BucketsPerInteger) {
172 // We may assume that the denominator is always positive as this is a requirement for both GMP and CLN
173 static_assert(storm::RationalNumberDenominatorAlwaysPositive);
174 appendEncodedInteger<true>(result, storm::utility::numerator(value), uint64BucketsPerInteger); // signed
175 appendEncodedInteger<false>(result, storm::utility::denominator(value), uint64BucketsPerInteger); // unsigned
176 }
177
178 template<std::ranges::input_range InputRange>
179 requires std::same_as<std::ranges::range_value_t<InputRange>, storm::RationalNumber>
180 static std::vector<uint64_t> createUint64FromRationalRange(InputRange&& input, uint64_t const numberSize) {
181 STORM_LOG_ASSERT(numberSize % 128ull == 0ull && numberSize > 0ull, "Type size must be a positive multiple of 128 for rational representation.");
182 auto const uint64BucketsPerNumber = numberSize / 64ull;
183 auto const size = std::ranges::size(input) * uint64BucketsPerNumber;
184
185 std::vector<uint64_t> values;
186 values.reserve(size);
187 for (auto const& r : input) {
188 appendEncodedRational(values, r, numberSize / 128ull);
189 }
190 values.shrink_to_fit();
191 STORM_LOG_ASSERT(values.size() == size, "Unexpected size of encoded rational values: " << values.size() << " vs. " << size);
192 return values;
193 }
194
195 template<std::ranges::input_range InputRange>
197 static std::vector<uint64_t> createUint64FromRationalIntervalRange(InputRange&& input, uint64_t const numberSize) {
198 STORM_LOG_ASSERT(numberSize % 256ull == 0ull && numberSize > 0ull,
199 "Type size must be a positive multiple of 256 for rational interval representation.");
201 }
202
203 template<std::ranges::input_range InputRange>
204 requires std::same_as<std::ranges::range_value_t<InputRange>, double>
205 static auto doubleToIntervalRangeView(InputRange&& input) {
206 STORM_LOG_ASSERT(std::ranges::size(input) % 2 == 0, "Input size is not even: " << std::ranges::size(input));
207 return std::ranges::iota_view(0ull, std::ranges::size(input) / 2) |
208 std::views::transform([&input](auto i) -> storm::Interval { return storm::Interval{input[2 * i], input[2 * i + 1]}; });
209 }
210
211 template<typename BaseType, std::ranges::input_range InputRange>
213 static auto intervalToBaseRangeView(InputRange&& input) {
214 return std::ranges::iota_view(0ull, std::ranges::size(input) * 2) | std::views::transform([&input](auto i) -> BaseType {
215 if (i % 2 == 0) {
216 return storm::utility::convertNumber<BaseType>(input[i / 2].lower());
217 } else {
218 return storm::utility::convertNumber<BaseType>(input[i / 2].upper());
219 }
220 });
221 }
222
231 template<typename ValueType>
232 static auto applyDecodedVector(auto&& func, storm::umb::GenericVector const& input, storm::umb::SizedType const& sourceType) {
233 STORM_LOG_ASSERT(input.hasValue(), "Input vector is not set.");
234 auto conversionView = [](auto&& input) {
235 using SourceType = std::ranges::range_value_t<decltype(input)>;
236 if constexpr (std::same_as<ValueType, SourceType>) {
237 return input;
238 } else {
239 return input | std::ranges::views::transform(
240 [](SourceType const& value) -> ValueType { return storm::utility::convertNumber<ValueType, SourceType>(value); });
241 }
242 };
243
244 using enum storm::umb::Type;
245
246 // find out how to interpret the input values
247 switch (sourceType.type) {
248 case Double:
251 "Some values are given in type double but will be converted to an exact (arbitrary precision) type. Rounding errors may occur.");
252 STORM_LOG_ASSERT(input.template isType<double>(), "Unexpected type for values. Expected double.");
253 STORM_LOG_ASSERT(sourceType.bitSize() == 64, "Unexpected source type size for double representation. Expected 64.");
254 return func(conversionView(input.template get<double>()));
255 case Rational:
257 "Some values are given in an exact type but converted to an inexact type. Rounding errors may occur.");
258 if (input.template isType<storm::RationalNumber>()) {
259 return func(conversionView(input.template get<storm::RationalNumber>()));
260 } else {
261 STORM_LOG_ASSERT(input.template isType<uint64_t>(), "Unexpected type for rational representation. Expected uint64.");
262 return func(conversionView(uint64ToRationalRangeView(input.template get<uint64_t>(), sourceType.bitSize())));
263 }
264 case DoubleInterval:
265 // For intervals, there is no suitable value conversion to non-interval types since we would drop the uncertainty
266 if constexpr (!storm::IsIntervalType<ValueType>) {
267 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
268 "Some values are given as double intervals but a model with a non-interval type is requested.");
269 return func(std::ranges::empty_view<ValueType>{});
270 } else {
273 "Some values are given in type double but will be converted to an exact (arbitrary precision) type. Rounding errors may occur.");
274 STORM_LOG_ASSERT(sourceType.bitSize() == 128ull, "Unexpected source type size for double interval representation. Expected 128.");
275 if (input.template isType<storm::Interval>()) {
276 return func(conversionView(input.template get<storm::Interval>()));
277 } else {
278 STORM_LOG_ASSERT(input.template isType<double>(), "Unexpected type for double interval representation. Expected double.");
279 return func(conversionView(doubleToIntervalRangeView(input.template get<double>())));
280 }
281 }
282 case RationalInterval:
283 // For intervals, there is no suitable value conversion to non-interval types since we would drop the uncertainty
284 if constexpr (!storm::IsIntervalType<ValueType>) {
285 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
286 "Some values are given as rational intervals but a model with a non-interval type is requested.");
287 return func(std::ranges::empty_view<ValueType>{});
288 } else {
290 "Some values are given in an exact type but converted to an inexact type. Rounding errors may occur.");
291 if (input.template isType<storm::RationalInterval>()) {
292 return func(conversionView(input.template get<storm::RationalInterval>()));
293 } else {
294 STORM_LOG_ASSERT(input.template isType<uint64_t>(), "Unexpected type for rational interval representation. Expected uint64.");
295 return func(conversionView(uint64ToRationalIntervalRangeView(input.template get<uint64_t>(), sourceType.bitSize())));
296 }
297 }
298 default:
299 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Values have unsupported type " << sourceType.toString() << ".");
300 }
301 }
302
303 template<typename ValueType>
304 static std::vector<ValueType> createDecodedVector(storm::umb::GenericVector const& input, storm::umb::SizedType const& sourceType) {
306 [](auto&& decodedInput) {
307 std::vector<ValueType> v;
308 v.reserve(std::ranges::size(decodedInput));
309 for (auto&& value : decodedInput) {
310 v.push_back(value);
311 }
312 return v;
313 },
314 input, sourceType);
315 }
316};
317} // 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:9
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:36
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:28
Import and export of umb files.
NumberTraits< RationalType >::IntegerType denominator(RationalType const &number)
NumberTraits< RationalType >::IntegerType numerator(RationalType const &number)
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 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