Storm 1.13.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
Valuations.h
Go to the documentation of this file.
1#pragma once
2
3#include <bit>
4#include <bitset>
5#include <cstdint>
6#include <cstring>
7#include <memory>
8#include <optional>
9#include <ranges>
10#include <span>
11
18
21
22namespace storm::umb {
23
25 public:
26 Valuations(std::vector<ValuationClassDescription> const& descriptions, std::vector<char> valuations, std::vector<uint64_t> stringMapping,
27 std::vector<char> strings, std::optional<std::vector<uint32_t>> classes = {},
28 std::vector<std::shared_ptr<storm::expressions::ExpressionManager>> expressionManagers = {})
29 : valuations(std::move(valuations)), stringMapping(std::move(stringMapping)), strings(std::move(strings)) {
30 if (expressionManagers.empty()) {
31 expressionManagers.emplace_back(std::make_shared<storm::expressions::ExpressionManager>());
32 }
33 STORM_LOG_ASSERT(descriptions.size() == expressionManagers.size() || expressionManagers.size() == 1,
34 "Mismatch between number of descriptions and expression managers.");
35 for (uint64_t i = 0; i < descriptions.size(); ++i) {
36 // We either have a separate manager for each class or all classes share the same manager.
37 // In the latter case, variable (names,types) pairs need to be unique among all possible classes
38 auto& manager = expressionManagers.size() == 1 ? *expressionManagers.front() : *expressionManagers[i];
39 variableClasses.push_back(createVariablesInformation(manager, descriptions[i]));
40 }
41 if (classes.has_value()) {
42 STORM_LOG_ASSERT(classes->size() == descriptions.size(), "Mismatch between number of descriptions and class mapping.");
43 uniqueSizeInBytes = std::numeric_limits<uint64_t>::max(); // not unique
44 this->classes = {std::move(*classes), std::vector<uint64_t>{1, 0}};
45 std::vector<uint64_t> classSizesInBytes;
46 for (auto const& descr : descriptions) {
47 classSizesInBytes.push_back(descr.sizeInBits() / 8);
48 }
49 uint64_t pos = 0;
50 this->classes->toValuationsMapping.reserve(this->classes->toClassMapping.size() + 1);
51 for (uint64_t entity = 0; entity < this->classes->toClassMapping.size(); ++entity) {
52 pos += classSizesInBytes[this->classes->toClassMapping[entity]];
53 this->classes->toValuationsMapping.push_back(pos);
54 }
55 STORM_LOG_ASSERT(valuations.size() == pos, "Valuation data size does not match class mapping.");
56 } else {
57 STORM_LOG_ASSERT(descriptions.size() == 1, "Valuation descriptions must be unique if no class mapping is given.");
58 uniqueSizeInBytes = descriptions.front().sizeInBits() / 8;
59 STORM_LOG_ASSERT(valuations.size() % uniqueSizeInBytes == 0, "Valuation data size is not a multiple of the unique valuation size.");
60 }
61 }
62
63 Valuations(std::vector<ValuationClassDescription> descriptions, std::vector<char> valuations, std::optional<std::vector<uint32_t>> classes = {},
64 std::vector<std::shared_ptr<storm::expressions::ExpressionManager>> expressionManagers = {})
65 : Valuations(std::move(descriptions), std::move(valuations), {}, {}, std::move(classes), std::move(expressionManagers)) {
66 // Intentionally empty
67 }
68
69 uint64_t size() const {
70 if (classes) {
71 return classes->toClassMapping.size();
72 } else {
73 return valuations.size() / uniqueSizeInBytes;
74 }
75 }
76
77 uint64_t numStrings() const {
78 return stringMapping.size() > 0 ? stringMapping.size() - 1 : 0;
79 }
80
81 void read(uint64_t entity, auto const& callback) const {
82 for (auto const& varInfo : info(entity).variables) {
83 read(entity, varInfo, callback);
84 }
85 }
86
87 void read(auto const& callback) const {
88 for (uint64_t entity = 0; entity < size(); ++entity) {
89 read(entity, callback);
90 }
91 }
92
93 private:
95
96 // Variable information
97 struct VariableInformation {
98 storm::expressions::Variable const expressionVariable;
100 uint64_t const bitOffset; // The first bit holding the variable's data within the valuation.
101 // If the variable is optional, the optional bit is located at bitOffset - 1
102 bool const fits64Bit;
103 };
104 struct VariablesInformation {
105 std::vector<VariableInformation> variables;
106 std::shared_ptr<storm::expressions::ExpressionManager> expressionManager;
107 };
108 std::vector<VariablesInformation> variableClasses;
109
110 // Classes information
111 struct ClassData {
112 std::vector<uint32_t> toClassMapping;
113 std::vector<uint64_t> toValuationsMapping;
114 };
115 std::optional<ClassData> classes; // present iff there are multiple classes
116 uint64_t uniqueSizeInBytes; // if there is only a single class, this is the size of the valuation in bytes. 2^64-1 iff classes are present.
117
118 // Data
119 std::vector<char> valuations;
120 std::vector<uint64_t> stringMapping;
121 std::vector<char> strings;
122
127 static bool fits64Bit(ValuationClassDescription::Variable const& varDesc) {
128 using enum storm::umb::Type;
129 if (varDesc.type.bitSize() > 64) {
130 return false;
131 }
132 switch (varDesc.type.type) {
133 case Bool:
134 return true;
135 case Uint: {
136 if (varDesc.offset.value_or(0) == 0) {
137 return true;
138 }
139 // check if adding the (non-zero) offset still fits into 64 bits
140 uint64_t const maxValue = varDesc.upper.has_value() ? static_cast<uint64_t>(varDesc.upper.value())
141 : (varDesc.type.bitSize() == 64) ? std::numeric_limits<uint64_t>::max()
142 : (static_cast<uint64_t>(1) << varDesc.type.bitSize()) - 1;
143 // the minValue equals the offset (given as int64_t and therefore always fits into 64 bits
144 if (varDesc.offset.value() < 0) {
145 // maxValue + offset = maxValue - (-offset) must fit into output type int64_t
146 return maxValue - static_cast<uint64_t>(-varDesc.offset.value()) <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
147 } else { // positive offset
148 // maxValue + offset must fit into output type uint64_t
149 return maxValue <= std::numeric_limits<uint64_t>::max() - static_cast<uint64_t>(varDesc.offset.value());
150 }
151 }
152 case Int: {
153 if (varDesc.offset.value_or(0) == 0) {
154 return true;
155 }
156 // check if adding the (non-zero) offset still fits into 64 bits
157 int64_t const maxValue = varDesc.upper.value_or(varDesc.type.bitSize() == 64 ? std::numeric_limits<int64_t>::max()
158 : (static_cast<int64_t>(1) << (varDesc.type.bitSize() - 1)) - 1);
159 int64_t const minValue = varDesc.lower.value_or(varDesc.type.bitSize() == 64 ? std::numeric_limits<int64_t>::min()
160 : -(static_cast<int64_t>(1) << (varDesc.type.bitSize() - 1)));
161 if (varDesc.offset.value() < 0) {
162 // minValue + offset must fit into output type int64_t
163 return minValue >= std::numeric_limits<int64_t>::min() - varDesc.offset.value();
164 } else { // positive offset
165 // maxValue + offset must fit into output type int64_t
166 return maxValue <= std::numeric_limits<int64_t>::max() - varDesc.offset.value();
167 }
168 }
169 case Double:
170 return true; // double values are always stored as 64 bit IEEE 754 values
171 case Rational:
172 return false; //
173 case String:
174 return true; // string indices are always stored as uint64_t
175 default:
176 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
177 "Valuations for variable type '" << varDesc.type.toString() << "' are not supported.");
178 }
179 }
180
181 static VariablesInformation createVariablesInformation(storm::expressions::ExpressionManager& expressionManager,
182 ValuationClassDescription const& description) {
183 VariablesInformation result{.variables = {}, .expressionManager = expressionManager.shared_from_this()};
184 uint64_t currentOffset = 0;
185 for (auto const& varVariant : description.variables) {
186 if (std::holds_alternative<ValuationClassDescription::Variable>(varVariant)) {
187 auto const& varDesc = std::get<ValuationClassDescription::Variable>(varVariant);
188 storm::expressions::Type variableType;
189 using enum storm::umb::Type;
190 switch (varDesc.type.type) {
191 case Bool:
192 variableType = expressionManager.getBooleanType();
193 break;
194 case Uint:
195 case Int:
196 variableType = expressionManager.getIntegerType();
197 break;
198 case Double:
199 case Rational:
200 variableType = expressionManager.getRationalType();
201 break;
202 case String:
203 variableType = expressionManager.getStringType();
204 break;
205 default:
206 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
207 "Valuations for variable type '" << varDesc.type.toString() << "' are not supported.");
208 }
209 if (varDesc.isOptional.value_or(false)) {
210 ++currentOffset; // optional variables have a preceding presence bit
211 }
212 result.variables.emplace_back(VariableInformation{.expressionVariable = expressionManager.declareOrGetVariable(varDesc.name, variableType),
213 .description = varDesc,
214 .bitOffset = currentOffset,
215 .fits64Bit = fits64Bit(varDesc)});
216 currentOffset += result.variables.back().description.type.bitSize();
217 } else {
218 auto const& padding = std::get<ValuationClassDescription::Padding>(varVariant);
219 currentOffset += padding.padding;
220 }
221 }
222 STORM_LOG_ASSERT(currentOffset == description.sizeInBits(), "Computed size does not match description size.");
223 STORM_LOG_ASSERT(currentOffset % 8 == 0, "Invalid valuation description detected: size in bits must be a multiple of 8.");
224 return result;
225 }
226
227 VariablesInformation const& info(uint64_t entity) const {
228 STORM_LOG_ASSERT(entity < size(), "Entity index out of bounds: " << entity << " >= " << size() << ".");
229 if (classes) {
230 return variableClasses[classes->toClassMapping[entity]];
231 } else {
232 return variableClasses.front();
233 }
234 }
235
236 std::span<char const> bytes(uint64_t entity) const {
237 STORM_LOG_ASSERT(entity < size(), "Entity index out of bounds: " << entity << " >= " << size() << ".");
238 if (classes) {
239 auto const start = classes->toValuationsMapping[entity];
240 auto const end = classes->toValuationsMapping[entity + 1];
241 return std::span<char const>(&valuations[start], end - start);
242 } else {
243 auto const start = entity * uniqueSizeInBytes;
244 return std::span<char const>(&valuations[start], uniqueSizeInBytes);
245 }
246 }
247
248 std::span<char> bytes(uint64_t entity) {
249 if (classes) {
250 auto const start = classes->toValuationsMapping[entity];
251 auto const end = classes->toValuationsMapping[entity + 1];
252 return std::span<char>(&valuations[start], end - start);
253 } else {
254 auto const start = entity * uniqueSizeInBytes;
255 return std::span<char>(&valuations[start], uniqueSizeInBytes);
256 }
257 }
258
259 uint64_t readUint64(std::span<char const> bytes, uint64_t const bitOffset, uint64_t const bitSize) const {
260 STORM_LOG_ASSERT(bitOffset < bytes.size() * 8, "Variable offset exceeds valuation size.");
261 STORM_LOG_ASSERT(bitSize <= 64, "Invalid bit range.");
262 auto const firstByte = bitOffset / 8;
263 auto const bitOffsetWithinByte = bitOffset % 8;
264 auto const numBytes = (bitOffsetWithinByte + bitSize + 7) / 8;
265 STORM_LOG_ASSERT(numBytes <= 9, "Invalid number of bytes computed: " << numBytes);
266 uint64_t result;
267 // set the first (up to) 8 bytes
268 std::memcpy(&result, &bytes[firstByte], std::min<uint64_t>(numBytes, 8ull));
269 result >>= bitOffsetWithinByte;
270 // if necessary, set the most significant bits by reading a 9th byte
271 if (numBytes == 9ull) {
272 uint64_t upperBits = std::bit_cast<uint8_t>(bytes[firstByte + 8]);
273 upperBits <<= (64 - bitOffsetWithinByte);
274 result |= upperBits;
275 }
276 // Set irrelevant bits to zero
277 if (bitSize < 64) {
278 uint64_t const relevantBitMask = (1ull << bitSize) - 1;
279 result &= relevantBitMask;
280 }
281 return result;
282 }
283
284 template<bool Signed>
285 Integer readInteger(std::span<char const> bytes, uint64_t const bitOffset, uint64_t const bitSize) const {
286 auto const num64BitChunks = (bitSize + 63) / 64;
287 auto chunksView =
288 std::ranges::iota_view(0ull, num64BitChunks) | std::ranges::views::transform([this, &bytes, &bitOffset, &bitSize](auto i) -> uint64_t {
289 return readUint64(bytes, bitOffset + i * 64, std::min<uint64_t>(64, bitSize - i * 64));
290 });
292 if constexpr (Signed) {
293 // Check if this number is supposed to be negative
294 if (result >= storm::utility::pow<Integer>(2, bitSize - 1)) {
295 return result - storm::utility::pow<Integer>(2, bitSize);
296 }
297 }
298 return result;
299 }
300
301 template<typename... AllowedTypes>
302 void read(uint64_t entity, VariableInformation const& varInfo, auto const& callback) const {
303 bool constexpr allowAny = (sizeof...(AllowedTypes) == 0);
304 bool constexpr allowNullopt = allowAny || std::disjunction_v<std::is_same<std::nullopt_t, AllowedTypes>...>;
305 bool constexpr allowBool = allowAny || std::disjunction_v<std::is_same<bool, AllowedTypes>...>;
306 bool constexpr allowUint64 = allowAny || std::disjunction_v<std::is_same<uint64_t, AllowedTypes>...>;
307 bool constexpr allowInt64 = allowAny || std::disjunction_v<std::is_same<int64_t, AllowedTypes>...>;
308 bool constexpr allowDouble = allowAny || std::disjunction_v<std::is_same<double, AllowedTypes>...>;
309 bool constexpr allowRational = allowAny || std::disjunction_v<std::is_same<storm::RationalNumber, AllowedTypes>...>;
310 bool constexpr allowInteger = allowAny || std::disjunction_v<std::is_same<Integer, AllowedTypes>...>;
311 bool constexpr allowString = allowAny || std::disjunction_v<std::is_same<std::string_view, AllowedTypes>...>;
312
313 if (varInfo.description.isOptional.value_or(false)) {
314 if constexpr (allowNullopt) {
315 callback(entity, varInfo.expressionVariable, std::nullopt);
316 }
317 }
318 auto const bitSize = varInfo.description.type.bitSize();
319 using enum storm::umb::Type;
320 if (varInfo.fits64Bit) {
321 STORM_LOG_ASSERT(bitSize <= 64, "Invalid bit size for 64 bit fast path.");
322 uint64_t rawContent = readUint64(bytes(entity), varInfo.bitOffset, bitSize);
323 switch (varInfo.description.type.type) {
324 case Bool:
325 if constexpr (allowBool) {
326 callback(entity, varInfo.expressionVariable, rawContent != 0);
327 return;
328 }
329 case Uint:
330 if (int64_t offset = varInfo.description.offset.value_or(0); offset < 0) {
331 // negative offset, output type is int64_t
332 if constexpr (allowInt64) {
333 callback(entity, varInfo.expressionVariable, static_cast<int64_t>(rawContent) + offset);
334 return;
335 }
336 } else {
337 // non-negative offset, output type is uint64_t
338 uint64_t value = rawContent + offset;
339 if constexpr (allowUint64) {
340 callback(entity, varInfo.expressionVariable, value);
341 return;
342 } else if constexpr (allowInt64) {
343 if (value <= static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
344 callback(entity, varInfo.expressionVariable, static_cast<int64_t>(value));
345 return;
346 }
347 }
348 }
349 case Int:
350 if constexpr (allowInt64) {
351 uint64_t const mostSignificantBitMask = 1ull << (bitSize - 1);
352 if (rawContent & mostSignificantBitMask) {
353 // Negative value: Two's complement (e.g. 1111...1101 is -3)
354 callback(entity, varInfo.expressionVariable, -static_cast<int64_t>(~rawContent & (mostSignificantBitMask - 1)) - 1);
355 return;
356 } else {
357 // Positive value
358 callback(entity, varInfo.expressionVariable, static_cast<int64_t>(rawContent));
359 return;
360 }
361 }
362 case Double:
363 if constexpr (allowDouble) {
364 callback(entity, varInfo.expressionVariable, static_cast<double>(std::bit_cast<double>(rawContent)));
365 return;
366 }
367 case Rational:
368 // Reaching this part should not be possible as varInfo.fits64Bit would be false
369 STORM_LOG_ASSERT(false, "Handling of rational values in 64 bit fast path is not implemented.");
370 case String:
371 if constexpr (allowString) {
372 callback(entity, varInfo.expressionVariable, stringVectorView(strings, stringMapping)[rawContent]);
373 return;
374 }
375 default:
376 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
377 "Valuations for variable type '" << varInfo.description.type.toString() << "' are not supported.");
378 }
379 }
380 // reaching this point means that we could not handle the value in the fast path
381 switch (varInfo.description.type.type) {
382 case Bool:
383 if constexpr (allowBool) {
384 // Bools could be encoded with more than 64 bits (which is not reasonable, but possible..)
385 callback(entity, varInfo.expressionVariable, readInteger<false>(bytes(entity), varInfo.bitOffset, bitSize) != Integer(0));
386 return;
387 }
388 case Uint:
389 case Int:
390 if constexpr (allowInteger) {
391 Integer value = varInfo.description.type.type == Int ? readInteger<true>(bytes(entity), varInfo.bitOffset, bitSize)
392 : readInteger<false>(bytes(entity), varInfo.bitOffset, bitSize);
393 value += storm::utility::convertNumber<Integer>(varInfo.description.offset.value_or(0));
394 callback(entity, varInfo.expressionVariable, value);
395 return;
396 }
397 case Double:
398 // Reaching this part should not be possible as varInfo.fits64Bit would be true
399 STORM_LOG_ASSERT(false, "double variables with more than 64 bits are not compliant.");
400 case Rational:
401 if constexpr (allowRational) {
402 STORM_LOG_ASSERT(bitSize % 2 == 0, "Rational number bit size must be even.");
403 uint64_t b = bitSize / 2;
404 storm::RationalNumber numerator = readInteger<true>(bytes(entity), varInfo.bitOffset, b);
405 storm::RationalNumber denominator = readInteger<false>(bytes(entity), varInfo.bitOffset + b, b);
406 callback(entity, varInfo.expressionVariable, storm::RationalNumber(numerator / denominator));
407 return;
408 }
409 case String: {
410 // Reaching this part should not be possible as varInfo.fits64Bit would be true
411 STORM_LOG_ASSERT(false, "String variables with more than 64 bits are not compliant.");
412 }
413 default:
414 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
415 "Valuations for variable type '" << varInfo.description.type.toString() << "' are not supported.");
416 }
417
418 STORM_LOG_THROW(false, storm::exceptions::UnexpectedException,
419 "Variable " << varInfo.description.name << " of type " << varInfo.description.type.toString() << " is not handled.");
420 }
421};
422} // namespace storm::umb
Variable declareOrGetVariable(std::string const &name, storm::expressions::Type const &variableType, bool auxiliary=false)
Declares a variable with the given name if it does not yet exist.
Type const & getBooleanType() const
Retrieves the boolean type.
Type const & getIntegerType() const
Retrieves the unbounded integer type.
Type const & getStringType() const
Retrieves the string type.
Type const & getRationalType() const
Retrieves the rational type.
Valuations(std::vector< ValuationClassDescription > descriptions, std::vector< char > valuations, std::optional< std::vector< uint32_t > > classes={}, std::vector< std::shared_ptr< storm::expressions::ExpressionManager > > expressionManagers={})
Definition Valuations.h:63
void read(uint64_t entity, auto const &callback) const
Definition Valuations.h:81
Valuations(std::vector< ValuationClassDescription > const &descriptions, std::vector< char > valuations, std::vector< uint64_t > stringMapping, std::vector< char > strings, std::optional< std::vector< uint32_t > > classes={}, std::vector< std::shared_ptr< storm::expressions::ExpressionManager > > expressionManagers={})
Definition Valuations.h:26
uint64_t size() const
Definition Valuations.h:69
void read(auto const &callback) const
Definition Valuations.h:87
uint64_t numStrings() const
Definition Valuations.h:77
static storm::NumberTraits< storm::RationalNumber >::IntegerType decodeArbitraryPrecisionInteger(InputRange &&input)
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:11
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
auto stringVectorView(SEQ< char > const &strings, CSR const &stringMapping)
NumberTraits< RationalType >::IntegerType denominator(RationalType const &number)
NumberTraits< RationalType >::IntegerType numerator(RationalType const &number)
ValueType pow(ValueType const &value, int_fast64_t exponent)
TargetType convertNumber(SourceType const &number)