Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
ValuationsStorage.cpp
Go to the documentation of this file.
2
3#include <bitset>
4#include <cstring>
5#include <ranges>
6
7#include <boost/functional/hash.hpp>
8
13
17
18namespace storm::storage::sparse {
19namespace detail {
20
22 using enum storm::umb::Type;
23 if (varDesc.type.bitSize() > 64) {
24 return false;
25 }
26 switch (varDesc.type.type) {
27 case Bool:
28 return true;
29 case Uint: {
30 // The smallest possible value is 0 + offset. As the offset is given as int64_t, it always fits into 64 bits.
31 // We have to check if the largest possible value also fits.
32 if (varDesc.offset.value_or(0) == 0) {
33 // The offset is 0 and the bitSize is <= 64, so this always fits
34 return true;
35 } else if (varDesc.upper.has_value()) {
36 // If the upper bound is given (as int64_t), then the largest value to represent is max(upper, upper - offset).
37 // Since all numbers involved are given as int64_t, the max value will fit into 64 bits as well.
38 return true;
39 } else if (varDesc.type.bitSize() == 64) {
40 // The largest actual value is 2^64 - 1 + offset.
41 // This never fits into 64 bits for positive offset.
42 // For negative offsets, the actual value type would be int64_t. Then the above number only fits if offset = -2^63
43 // Since offset != 0 in this branch, that is the only valid case where the values fit into 64 bits.
44 return varDesc.offset.value() == std::numeric_limits<int64_t>::min();
45 } else {
46 // The largest actual value is at most 2^63 - 1 + offset
47 // For positive offset, the actual type is uint64_t and we can upper bound the above number by 2^63 - 1 + 2^63 - 1 = 2^64 - 2 < uint64_t max
48 // For negative offset, the actual type is int64_t and we can upper bound the above number by 2^63 - 1 <= int64_t max
49 return true;
50 }
51 }
52 case Int: {
53 if (varDesc.offset.value_or(0) == 0) {
54 return true;
55 } else if (varDesc.offset.value() < 0) {
56 // negative offset. We might have trouble representing the smallest actual value
57 int64_t const minValueStored =
58 varDesc.type.bitSize() == 64 ? std::numeric_limits<int64_t>::min() : -(static_cast<int64_t>(1) << (varDesc.type.bitSize() - 1));
59 return varDesc.lower.has_value() || minValueStored >= std::numeric_limits<int64_t>::min() - varDesc.offset.value();
60 } else {
61 // positive offset. We might have trouble representing the largest actual value
62 int64_t const maxValueStored =
63 varDesc.type.bitSize() == 64 ? std::numeric_limits<int64_t>::max() : (static_cast<int64_t>(1) << (varDesc.type.bitSize() - 1)) - 1;
64 return varDesc.upper.has_value() || maxValueStored <= std::numeric_limits<int64_t>::max() - varDesc.offset.value();
65 }
66 }
67 case Double:
68 return true; // double values are always stored as 64 bit IEEE 754 values
69 case Rational:
70 return false;
71 case String:
72 return true; // string indices are always stored as uint64_t
73 default:
74 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
75 "ValuationsStorage for variable type '" << varDesc.type.toString() << "' are not supported.");
76 }
77}
78
79template<typename ManagerType>
81 std::vector<typename ValuationsStorage::VariableInformation> variables;
82 uint64_t currentOffset = 0;
83 for (auto const& varVariant : description.variables) {
84 if (std::holds_alternative<ValuationClassDescription::Variable>(varVariant)) {
85 auto const& varDesc = std::get<ValuationClassDescription::Variable>(varVariant);
87 if constexpr (std::is_const_v<ManagerType>) {
88 exprVar = expressionManager.getVariable(varDesc.name);
89 } else {
90 storm::expressions::Type variableType;
91 using enum storm::umb::Type;
92 switch (varDesc.type.type) {
93 case Bool:
94 variableType = expressionManager.getBooleanType();
95 break;
96 case Uint:
97 case Int:
98 variableType = expressionManager.getIntegerType();
99 break;
100 case Double:
101 case Rational:
102 variableType = expressionManager.getRationalType();
103 break;
104 case String:
105 variableType = expressionManager.getStringType();
106 break;
107 default:
108 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException,
109 "ValuationsStorage for variable type '" << varDesc.type.toString() << "' are not supported.");
110 }
111 exprVar = expressionManager.declareOrGetVariable(varDesc.name, variableType);
112 }
113 if (varDesc.isOptional.value_or(false)) {
114 ++currentOffset; // optional variables have a preceding presence bit
115 }
116 variables.emplace_back(typename ValuationsStorage::VariableInformation{
117 .expressionVariable = exprVar, .description = varDesc, .bitOffset = currentOffset, .fits64Bit = fits64Bit(varDesc)});
118 currentOffset += variables.back().description.type.bitSize();
119 } else {
120 auto const& padding = std::get<ValuationClassDescription::Padding>(varVariant);
121 currentOffset += padding.padding;
122 }
123 }
124 STORM_LOG_ASSERT(currentOffset == description.sizeInBits(), "Computed size does not match description size.");
125 STORM_LOG_ASSERT(currentOffset % 8 == 0, "Invalid valuation description detected: size in bits must be a multiple of 8.");
127 .variables = std::move(variables), .expressionManager = expressionManager.shared_from_this(), .sizeInBytes = currentOffset / 8};
128}
129
130} // namespace detail
131
132ValuationsStorage::ValuationsStorage(uint64_t const numEntities, std::vector<ValuationClassDescription> const& descriptions, std::vector<char> valuations,
133 std::vector<uint64_t> stringMapping, std::vector<char> strings, std::optional<std::vector<uint32_t>> classes,
134 std::vector<std::shared_ptr<storm::expressions::ExpressionManager const>> expressionManagers)
135 : numEntities(numEntities), valuations(std::move(valuations)), stringMapping(std::move(stringMapping)), strings(std::move(strings)) {
136 STORM_LOG_ASSERT(descriptions.size() == expressionManagers.size() || expressionManagers.size() <= 1,
137 "Mismatch between number of descriptions and expression managers.");
138 // First set up the variable classes.
139 // We either have a separate manager for each class or all classes share the same manager.
140 // Furthermore, we might create a new manager for a class, if no manager was given explicitly.
141 auto sharedManager = std::make_shared<storm::expressions::ExpressionManager>();
142 for (uint64_t i = 0; i < descriptions.size(); ++i) {
143 if (expressionManagers.empty() || (expressionManagers.size() == 1 && expressionManagers.front() == nullptr)) {
144 // Shared manager for all classes, not given explicitly
145 variableClasses.push_back(detail::createVariablesInformation(*sharedManager, descriptions[i]));
146 } else if (expressionManagers.size() == 1) {
147 // Shared manager for all classes, given explicitly
148 variableClasses.push_back(detail::createVariablesInformation(*expressionManagers.front(), descriptions[i]));
149 } else if (expressionManagers[i] == nullptr) {
150 // Separate manager for each class, not given explicitly
151 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
152 variableClasses.push_back(detail::createVariablesInformation(*manager, descriptions[i]));
153 } else {
154 // Separate manager for each class, given explicitly
155 variableClasses.push_back(detail::createVariablesInformation(*expressionManagers[i], descriptions[i]));
156 }
157 }
158 // Initialize string mappings. It should be given iff there is a string variable
159 bool const hasStringVariable = std::any_of(descriptions.begin(), descriptions.end(), [](auto const& classDescr) { return classDescr.hasStringVariable(); });
160 if (hasStringVariable && this->stringMapping.empty()) {
161 this->stringMapping.push_back(0);
162 }
163 STORM_LOG_ASSERT(hasStringVariable || this->stringMapping.empty(), "Non-empty string mapping given but there is no string variable.");
164 STORM_LOG_ASSERT(this->stringMapping.empty() || this->stringMapping.back() == this->strings.size(),
165 "String mapping should end with the total size of the string data.");
166
167 // Enable quick access to the right byte span for each entity.
168 if (classes.has_value() && this->variableClasses.size() > 1) {
169 STORM_LOG_ASSERT(numEntities == classes->size(), "Number of entities does not match class mapping size.");
170 this->entityClassMappings = {std::move(*classes), std::vector<uint64_t>({0ull})};
171 uint64_t pos = 0;
172 this->entityClassMappings->toValuationsMapping.reserve(this->entityClassMappings->toClassMapping.size() + 1);
173 for (uint64_t entity = 0; entity < this->entityClassMappings->toClassMapping.size(); ++entity) {
175 this->entityClassMappings->toClassMapping[entity] < this->variableClasses.size(),
176 "Class index " << this->entityClassMappings->toClassMapping[entity] << " out of bounds. Only " << descriptions.size() << "classes known.");
177 pos += this->variableClasses[this->entityClassMappings->toClassMapping[entity]].sizeInBytes;
178 this->entityClassMappings->toValuationsMapping.push_back(pos);
179 }
180 STORM_LOG_ASSERT(this->valuations.size() == pos, "Valuation data size does not match class mapping.");
181 } else {
182 STORM_LOG_ASSERT(this->variableClasses.size() == 1, "Valuation descriptions must be unique if no class mapping is given.");
183 STORM_LOG_ASSERT(!classes.has_value() || std::all_of(classes->begin(), classes->end(), [&](auto classIndex) { return classIndex == 0; }),
184 "A single description is given but the class mapping is not unique.");
185 STORM_LOG_ASSERT(this->variableClasses.front().sizeInBytes == 0 || this->valuations.size() % this->variableClasses.front().sizeInBytes == 0,
186 "Valuation data size is not a multiple of the unique valuation size.");
187 STORM_LOG_ASSERT(numEntities * this->variableClasses.front().sizeInBytes == this->valuations.size(),
188 "Valuation data size (" << this->valuations.size() << ") does not match number of entities (" << this->numEntities
189 << ") times valuation size (" << this->variableClasses.front().sizeInBytes << ").");
190 }
191}
192
193ValuationsStorage::ValuationsStorage(uint64_t const numEntities, ValuationClassDescription const& description, std::vector<char> valuations,
194 std::shared_ptr<storm::expressions::ExpressionManager const> expressionManager)
195 : ValuationsStorage(numEntities, {description}, std::move(valuations), {}, {}, std::nullopt, {expressionManager}) {
196 STORM_LOG_ASSERT(!description.hasStringVariable(), "String mapping must be given for descriptions with string variables.");
197}
198
199ValuationsStorage::ValuationsStorage(std::vector<ValuationClassDescription> const& descriptions,
200 std::vector<std::shared_ptr<storm::expressions::ExpressionManager const>> expressionManagers)
201 : ValuationsStorage(0, descriptions, {}, {}, {}, std::vector<uint32_t>{}, std::move(expressionManagers)) {}
202
204 std::shared_ptr<storm::expressions::ExpressionManager const> expressionManager)
205 : ValuationsStorage(0, {description}, {}, {}, {}, std::nullopt, {expressionManager}) {}
206
207ValuationsStorage::ValuationsStorage(std::vector<VariablesInformation> const& variableClasses) : numEntities(0), variableClasses(variableClasses) {}
208
209uint64_t ValuationsStorage::size() const {
210 return numEntities;
211}
212
214 return variableClasses.size();
215}
216
218 return stringMapping.size() > 0 ? stringMapping.size() - 1 : 0;
219}
220
222 return !stringMapping.empty();
223}
224
225uint64_t ValuationsStorage::getClassOfEntity(uint64_t entity) const {
226 STORM_LOG_ASSERT(entity < size(), "Entity index out of bounds: " << entity << " >= " << size() << ".");
227 if (entityClassMappings) {
228 return entityClassMappings->toClassMapping[entity];
229 } else {
230 STORM_LOG_ASSERT(variableClasses.size() == 1, "No class mapping given but multiple classes exist.");
231 return 0;
232 }
233}
234
236 STORM_LOG_ASSERT(classIndex < numClasses(), "Class index " << classIndex << " out of bounds. Only " << variableClasses.size() << "classes known.");
238 uint64_t currBit = 0;
239 for (auto const& varInfo : variableClasses[classIndex].variables) {
240 uint64_t padding = varInfo.bitOffset - currBit;
241 if (varInfo.description.isOptional.value_or(false)) {
242 STORM_LOG_ASSERT(padding >= 1, "Optional variables must have at least 1 bit preceding its offset.");
243 --padding;
244 }
245 if (padding > 0) {
246 res.variables.push_back(ValuationClassDescription::Padding{.padding = padding});
247 }
248 res.variables.push_back(varInfo.description);
249 currBit = varInfo.bitOffset + varInfo.description.type.bitSize();
250 STORM_LOG_ASSERT(currBit == res.sizeInBits(), "Unexpected bit offset for variable " << varInfo.description.name << " in class " << classIndex
251 << ". Expected " << res.sizeInBits() << ", got "
252 << varInfo.bitOffset << ".");
253 }
254 if (uint64_t padding = currBit % 8; padding > 0) {
255 res.variables.push_back(ValuationClassDescription::Padding{.padding = 8 - padding});
256 }
257 return res;
258}
259
261 STORM_LOG_ASSERT(!variableClasses.empty(), "No variable classes given, cannot determine expression manager.");
262 auto const& manager = variableClasses.front().expressionManager;
264 std::all_of(variableClasses.begin() + 1, variableClasses.end(), [&manager](auto const& varClass) { return varClass.expressionManager == manager; }),
265 storm::exceptions::IllegalFunctionCallException, "Expression manager is not unique.");
266 return *manager;
267}
268
270 STORM_LOG_ASSERT(classIndex < variableClasses.size(),
271 "Class index " << classIndex << " out of bounds. Only " << variableClasses.size() << "classes known.");
272 return *variableClasses[classIndex].expressionManager;
273}
274
276 auto const& vars = info(entity).variables;
277 auto varInfoIt = std::find_if(vars.begin(), vars.end(), [&variable](auto const& varInfo) { return varInfo.expressionVariable == variable; });
278 STORM_LOG_ASSERT(varInfoIt != vars.end(), "Can not find unknown variable " << variable.getName() << ".");
279 return *varInfoIt;
280}
281
283 STORM_LOG_ASSERT(numClasses() == 1, "Trying to get variable information but the class is not unique among entities.");
284 return getVariableInformation(0, variable);
285}
286
287std::set<storm::expressions::Variable> ValuationsStorage::getAllVariables() const {
288 [[maybe_unused]] auto const& manager = getManager();
289 std::set<storm::expressions::Variable> result;
290 for (auto const& varClass : variableClasses) {
291 for (auto const& varInfo : varClass.variables) {
292 STORM_LOG_ASSERT(varInfo.expressionVariable.getManager() == manager,
293 "Expression manager of variable " << varInfo.expressionVariable.getName() << " does not match the one of the valuation.");
294 result.insert(varInfo.expressionVariable);
295 }
296 }
297 return result;
298}
299
300bool ValuationsStorage::entityHasVariable(uint64_t entity, storm::expressions::Variable const& variable) const {
301 auto const& vars = info(entity).variables;
302 return std::any_of(vars.begin(), vars.end(), [&variable](auto const& varInfo) { return varInfo.expressionVariable == variable; });
303}
304
307 if (entityClassMappings.has_value()) {
308 result.valuationToClass = entityClassMappings->toClassMapping;
309 }
310 result.valuations = valuations;
311 if (hasStrings()) {
312 result.stringMapping = stringMapping;
313 result.strings = strings;
314 }
315 return result;
316}
317
318void ValuationsStorage::resize(uint64_t newEntityCount, uint64_t const classIndex) {
319 if (newEntityCount > size()) {
320 // Initialize one new entity with default values. This is required to ensure that valuation data is consistent (e.g. avoid 0/0 for rationals).
321 emplaceBack<true>(classIndex, [](auto&&...) {});
322 // For the remaining entities, we can be a bit quicker by copying the values of the last initialized entity.
323 if (newEntityCount > size()) {
324 uint64_t const classSize = variableClasses[classIndex].sizeInBytes;
325 valuations.resize(valuations.size() + (newEntityCount - size()) * classSize);
326 if (entityClassMappings) {
327 entityClassMappings->toClassMapping.resize(newEntityCount, classIndex);
328 for (uint64_t valEnd = entityClassMappings->toValuationsMapping.back() + classSize; valEnd < valuations.size(); valEnd += classSize) {
329 entityClassMappings->toValuationsMapping.push_back(valEnd);
330 }
331 }
332 auto const srcBytes = getRawBytes(numEntities); // the bytes of the entry we initialized using emplaceBack
333 for (uint64_t newEntityIndex = numEntities + 1; newEntityIndex < newEntityCount; ++newEntityIndex) {
334 auto destBytes = getRawBytes(newEntityIndex);
335 std::copy(srcBytes.begin(), srcBytes.end(), destBytes.begin());
336 }
337 numEntities = newEntityCount;
338 }
339 } else if (newEntityCount < size()) {
340 uint64_t const newValuationsSize =
341 entityClassMappings.has_value() ? entityClassMappings->toValuationsMapping[newEntityCount] : newEntityCount * variableClasses.front().sizeInBytes;
342 valuations.resize(newValuationsSize);
343 if (entityClassMappings) {
344 entityClassMappings->toClassMapping.resize(newEntityCount);
345 entityClassMappings->toValuationsMapping.resize(newEntityCount + 1);
346 }
347 numEntities = newEntityCount;
348 }
349}
350
351template<typename RationalValueType>
353 readCallback(entity, [&evaluator](auto, auto const& var, auto const& value) {
354 using ValueType = std::remove_cvref_t<decltype(value)>;
355 if constexpr (std::is_same_v<ValueType, bool>) {
356 evaluator.setBooleanValue(var, value);
357 } else if constexpr (std::is_same_v<ValueType, int64_t> || std::is_same_v<ValueType, uint64_t>) {
358 evaluator.setIntegerValue(var, value);
359 // evaluator has no support for arbitrary-precision integers.
360 } else if constexpr (std::is_same_v<ValueType, double> || std::is_same_v<ValueType, storm::RationalNumber>) {
361 evaluator.setRationalValue(var, storm::utility::convertNumber<RationalValueType>(value));
362 } else {
364 (std::is_same_v<ValueType, std::string_view> || std::is_same_v<ValueType, std::string> || std::is_same_v<ValueType, std::nullopt_t>),
365 storm::exceptions::NotSupportedException, "Unsupported variable value type when reading state values: " << typeid(ValueType).name() << ".");
366 }
367 });
368}
369
373
374ValuationsStorage::VariablesInformation const& ValuationsStorage::info(uint64_t entity) const {
375 return variableClasses[getClassOfEntity(entity)];
376}
377
378std::span<char const> ValuationsStorage::getRawBytes(uint64_t entity) const {
379 STORM_LOG_ASSERT(entity < size(), "Entity index out of bounds: " << entity << " >= " << size() << ".");
380 if (entityClassMappings) {
381 auto const start = entityClassMappings->toValuationsMapping[entity];
382 auto const end = entityClassMappings->toValuationsMapping[entity + 1];
383 return std::span<char const>(&valuations[start], end - start);
384 } else {
385 auto const start = entity * variableClasses.front().sizeInBytes;
386 return std::span<char const>(&valuations[start], variableClasses.front().sizeInBytes);
387 }
388}
389
390std::span<char> ValuationsStorage::getRawBytes(uint64_t entity) {
391 if (entityClassMappings) {
392 auto const start = entityClassMappings->toValuationsMapping[entity];
393 auto const end = entityClassMappings->toValuationsMapping[entity + 1];
394 return std::span<char>(&valuations[start], end - start);
395 } else {
396 auto const start = entity * variableClasses.front().sizeInBytes;
397 return std::span<char>(&valuations[start], variableClasses.front().sizeInBytes);
398 }
399}
400
401bool ValuationsStorage::readBit(std::span<char const> bytes, uint64_t const position) const {
402 STORM_LOG_ASSERT(position < bytes.size() * 8, "Bit position exceeds valuation size.");
403 return bytes[position / 8] & (1 << (position % 8));
404}
405
406void ValuationsStorage::writeBit(std::span<char> bytes, uint64_t const position, bool value) const {
407 STORM_LOG_ASSERT(position < bytes.size() * 8, "Bit position exceeds valuation size.");
408 char& byte = bytes[position / 8];
409 char const pos = (1 << (position % 8));
410 if (value) {
411 byte |= pos;
412 } else {
413 byte &= ~pos;
414 }
415}
416
417uint64_t ValuationsStorage::readUint64(std::span<char const> bytes, uint64_t const bitOffset, uint64_t const bitSize) const {
418 STORM_LOG_ASSERT(bitOffset < bytes.size() * 8, "Variable offset exceeds valuation size.");
419 STORM_LOG_ASSERT(bitSize <= 64, "Invalid bit range.");
420 auto const firstByte = bitOffset / 8;
421 auto const bitOffsetWithinByte = bitOffset % 8;
422 auto const numBytes = (bitOffsetWithinByte + bitSize + 7) / 8;
423 STORM_LOG_ASSERT(numBytes <= 9, "Invalid number of bytes computed: " << numBytes);
424 uint64_t result;
425 // set the first (up to) 8 bytes
426 std::memcpy(&result, &bytes[firstByte], std::min<uint64_t>(numBytes, 8ull));
427 result >>= bitOffsetWithinByte;
428 // if necessary, set the most significant bits by reading a 9th byte
429 if (numBytes == 9ull) {
430 uint64_t upperBits = std::bit_cast<uint8_t>(bytes[firstByte + 8]);
431 upperBits <<= (64 - bitOffsetWithinByte);
432 result |= upperBits;
433 }
434 // Set irrelevant bits to zero
435 if (bitSize < 64) {
436 uint64_t const relevantBitMask = (1ull << bitSize) - 1;
437 result &= relevantBitMask;
438 }
439 return result;
440}
441
442void ValuationsStorage::writeUint64(std::span<char> bytes, uint64_t const bitOffset, uint64_t const bitSize, uint64_t const value) const {
443 STORM_LOG_ASSERT(bitOffset < bytes.size() * 8, "Variable offset exceeds valuation size.");
444 STORM_LOG_ASSERT(bitSize <= 64, "Invalid bit range.");
445 STORM_LOG_THROW(bitSize == 64 || value < (1ull << bitSize), storm::exceptions::OutOfRangeException,
446 "Invalid value " << value << " for bit size " << bitSize << ".");
447 uint64_t const firstByte = bitOffset / 8;
448 uint8_t const bitOffsetWithinByte = bitOffset % 8;
449 uint8_t const numBytes = (bitOffsetWithinByte + bitSize + 7) / 8;
450 uint8_t const numFullBytes = (bitOffsetWithinByte + bitSize) / 8;
451 STORM_LOG_ASSERT(numBytes <= 9, "Invalid number of bytes computed: " << numBytes);
452 if (numFullBytes == 0) {
453 // We only have to write into a single byte
454 char& byte = bytes[firstByte];
455 uint8_t const relevantBitsMask = ((1 << bitSize) - 1) << bitOffsetWithinByte; // e.g. 0000 1110 for bitOffsetWithinByte=1 and bitSize=3
456 byte &= static_cast<char>(~relevantBitsMask); // set relevant bits to zero
457 byte |= static_cast<char>((value << bitOffsetWithinByte) & relevantBitsMask); // set relevant bits to the value bits
458 } else {
459 // First write all full bytes
460 if (bitOffsetWithinByte == 0) {
461 // Fast path: variable is byte-aligned, so we can directly write all full bytes without bit shifts
462 std::memcpy(&bytes[firstByte], &value, numFullBytes);
463 } else {
464 uint64_t const shiftedValue = (static_cast<uint64_t>(bytes[firstByte]) & ((1ull << bitOffsetWithinByte) - 1)) | (value << bitOffsetWithinByte);
465 std::memcpy(&bytes[firstByte], &shiftedValue, numFullBytes);
466 }
467 // Then write the last byte if necessary
468 if (numFullBytes != numBytes) {
469 // we have to write a partial byte at the end, so we need to read the existing byte and only overwrite the relevant bits
470 char& lastByte = bytes[firstByte + numFullBytes];
471 uint8_t const numBitsUsedInLastByte = (bitOffsetWithinByte + bitSize) % 8;
472 lastByte &= static_cast<char>((1 << numBitsUsedInLastByte) - 1); // set relevant bits to zero
473 lastByte |= static_cast<char>(value >> (numFullBytes * 8 - bitOffsetWithinByte)); // set relevant bits to the value bits
474 }
475 }
476}
477
478template<bool Signed>
479ValuationsStorage::Integer ValuationsStorage::readInteger(std::span<char const> bytes, uint64_t const bitOffset, uint64_t const bitSize) const {
480 auto const num64BitChunks = (bitSize + 63) / 64;
481 auto chunksView = std::ranges::iota_view(0ull, num64BitChunks) | std::ranges::views::transform([this, &bytes, &bitOffset, &bitSize](auto i) -> uint64_t {
482 return readUint64(bytes, bitOffset + i * 64, std::min<uint64_t>(64, bitSize - i * 64));
483 });
485 if constexpr (Signed) {
486 // Check if this number is supposed to be negative
487 if (result >= storm::utility::pow<Integer>(2, bitSize - 1)) {
488 return result - storm::utility::pow<Integer>(2, bitSize);
489 }
490 }
491 return result;
492}
493
494template ValuationsStorage::Integer ValuationsStorage::readInteger<false>(std::span<char const>, uint64_t, uint64_t) const;
495template ValuationsStorage::Integer ValuationsStorage::readInteger<true>(std::span<char const>, uint64_t, uint64_t) const;
496
497template<bool Signed>
498void ValuationsStorage::writeInteger(std::span<char> bytes, uint64_t bitOffset, uint64_t bitSize, Integer const& value) const {
499 STORM_LOG_THROW(storm::umb::ValueEncoding::getSizeOfIntegerEncoding<Signed>(value) <= bitSize, storm::exceptions::OutOfRangeException,
500 "Value " << value << " cannot be encoded in " << bitSize << " bits.");
501 auto const num64BitChunks = (bitSize + 63) / 64;
502 std::vector<uint64_t> uint64Encoding;
503 storm::umb::ValueEncoding::appendEncodedInteger<Signed>(uint64Encoding, value, num64BitChunks);
504 STORM_LOG_ASSERT(uint64Encoding.size() == num64BitChunks, "Encoding does not fit into the specified bit size.");
505 for (auto v : uint64Encoding) {
506 if (bitSize >= 64) {
507 writeUint64(bytes, bitOffset, 64, v);
508 bitOffset += 64;
509 bitSize -= 64;
510 } else {
511 uint64_t const relevantBitMask = (1ull << bitSize) - 1;
512 // Check if the number is negative by looking at the sign bit
513 if (Signed && ((v & (1ull << (bitSize - 1))) != 0)) {
514 STORM_LOG_ASSERT(value < 0, "Value " << value << " is non-negative but the sign bit is set.");
515 // Assert that all irrelevant bits are set
516 STORM_LOG_ASSERT((~relevantBitMask & v) == ~relevantBitMask,
517 "Value " << value << " does not fit into the specified bit size of " << bitSize << " bits.");
518 // For negative numbers, we clear the upper (unused) bits, so that the resulting (unsigned) value fits into
519 // the specified bit size. For example, -3 with bitSize=3 would be represented as 101. We get the 64 bit
520 // value 1...1101 which (interpreted as unsigned value) doesn't fit into 3 bits. We need 0...0101.
521 v &= relevantBitMask; // set upper bits to zero
522 } else {
523 // Assert that all irrelevant bits are not set
524 STORM_LOG_ASSERT((~relevantBitMask & v) == 0, "Value " << value << " does not fit into the specified bit size of " << bitSize << " bits.");
525 }
526 writeUint64(bytes, bitOffset, bitSize, v);
527 bitSize = 0;
528 break;
529 }
530 }
531 STORM_LOG_ASSERT(bitSize == 0, "Unexpected integer encoding. Not all bits were written.");
532}
533
534template void ValuationsStorage::writeInteger<false>(std::span<char>, uint64_t, uint64_t, Integer const&) const;
535template void ValuationsStorage::writeInteger<true>(std::span<char>, uint64_t, uint64_t, Integer const&) const;
536
537template<typename ValueType>
538void ValuationsStorage::writeValue(std::span<char> bytes, uint64_t bitOffset, uint64_t bitSize, ValueType const& value) {
539 if constexpr (std::is_same_v<ValueType, bool>) {
540 writeUint64(bytes, bitOffset, bitSize, value ? 1ul : 0ul);
541 } else if constexpr (std::is_same_v<ValueType, uint64_t>) {
542 writeUint64(bytes, bitOffset, bitSize, value);
543 } else if constexpr (std::is_same_v<ValueType, int64_t>) {
544 if (value < 0) {
545 // For negative value, take the two's complement (e.g. 1111...1101 is -3)
546 uint64_t v = ~static_cast<uint64_t>(-(value + 1));
547 // Clear upper bits
548 if (bitSize < 64) {
549 v &= (1ull << bitSize) - 1;
550 }
551 writeUint64(bytes, bitOffset, bitSize, v);
552 } else {
553 // For positive value, the binary representation is the same as for unsigned values
554 writeUint64(bytes, bitOffset, bitSize, static_cast<uint64_t>(value));
555 }
556 } else if constexpr (std::is_same_v<ValueType, double>) {
557 writeUint64(bytes, bitOffset, bitSize, std::bit_cast<uint64_t>(value));
558 } else if constexpr (std::is_same_v<ValueType, Integer>) {
559 if (value < 0) {
560 writeInteger<true>(bytes, bitOffset, bitSize, value);
561 } else {
562 writeInteger<false>(bytes, bitOffset, bitSize, value);
563 }
564 } else if constexpr (std::is_same_v<ValueType, storm::RationalNumber>) {
565 STORM_LOG_ASSERT(bitSize % 2 == 0, "Uneven bitsize for rational number not expected.");
566 auto const numDenSize = bitSize / 2;
567 writeInteger<true>(bytes, bitOffset, numDenSize, storm::utility::numerator(value));
568 static_assert(storm::RationalNumberDenominatorAlwaysPositive);
569 writeInteger<false>(bytes, bitOffset + numDenSize, numDenSize, storm::utility::denominator(value));
570 } else {
571 // Note: overwriting the string does not erase the old string from the strings vector as it might still be in use elsewhere
572 static_assert(std::is_same_v<ValueType, std::string_view> || std::is_same_v<ValueType, std::string>);
573 uint64_t const index = storm::umb::StringsBuilder(strings, stringMapping).findOrPushBack(value);
574 writeUint64(bytes, bitOffset, bitSize, index);
575 }
576}
577
578template void ValuationsStorage::writeValue<bool>(std::span<char>, uint64_t, uint64_t, bool const&);
579template void ValuationsStorage::writeValue<uint64_t>(std::span<char>, uint64_t, uint64_t, uint64_t const&);
580template void ValuationsStorage::writeValue<int64_t>(std::span<char>, uint64_t, uint64_t, int64_t const&);
581template void ValuationsStorage::writeValue<double>(std::span<char>, uint64_t, uint64_t, double const&);
582template void ValuationsStorage::writeValue<ValuationsStorage::Integer>(std::span<char>, uint64_t, uint64_t, ValuationsStorage::Integer const&);
583template void ValuationsStorage::writeValue<storm::RationalNumber>(std::span<char>, uint64_t, uint64_t, storm::RationalNumber const&);
584template void ValuationsStorage::writeValue<std::string_view>(std::span<char>, uint64_t, uint64_t, std::string_view const&);
585template void ValuationsStorage::writeValue<std::string>(std::span<char>, uint64_t, uint64_t, std::string const&);
586
587template<typename T>
588ValuationsStorage ValuationsStorage::selectEntities(T const& selectedEntities) const {
589 ValuationsStorage result(variableClasses);
590 result.numEntities = [&selectedEntities]() {
591 if constexpr (std::is_same_v<T, storm::storage::BitVector>) {
592 return selectedEntities.getNumberOfSetBits();
593 } else {
594 return std::ranges::distance(selectedEntities);
595 }
596 }();
597 result.stringMapping = stringMapping;
598 result.strings = strings;
599
600 if (entityClassMappings) {
601 result.entityClassMappings.emplace();
602 result.entityClassMappings->toValuationsMapping.reserve(result.numEntities + 1);
603 result.entityClassMappings->toValuationsMapping.push_back(0); // first entry of toValuationsMapping must be 0
604 result.entityClassMappings->toClassMapping.reserve(result.numEntities);
605 } else {
606 result.valuations.reserve(result.numEntities * result.variableClasses.front().sizeInBytes);
607 }
608 for (auto const oldEntityIndex : selectedEntities) {
609 STORM_LOG_ASSERT(oldEntityIndex < size(), "Selected entity index " << oldEntityIndex << " out of bounds. Only " << size() << " entities known.");
610 auto const bytes = getRawBytes(oldEntityIndex);
611 result.valuations.insert(result.valuations.end(), bytes.begin(), bytes.end());
612 if (entityClassMappings) {
613 result.entityClassMappings->toValuationsMapping.push_back(result.valuations.size());
614 result.entityClassMappings->toClassMapping.push_back(entityClassMappings->toClassMapping[oldEntityIndex]);
615 }
616 }
617 return result;
618}
619
622
623std::size_t ValuationsStorage::hash() const {
624 // As the valuations are stored as a sequence of chars, we can pretend that this is a string and use efficient string_view hashing
625 auto const hashBytes = std::hash<std::string_view>{};
626
627 std::size_t seed = hashBytes(std::string_view(valuations.data(), valuations.size()));
628 boost::hash_combine(seed, hashBytes(std::string_view(strings.data(), strings.size())));
629
630 return seed;
631}
632
633} // namespace storm::storage::sparse
This class is responsible for managing a set of typed variables and all expressions using these varia...
std::string const & getName() const
Retrieves the name of the variable.
Definition Variable.cpp:46
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
Stores valuations of variables for a set of entities (e.g.
ValuationsStorage(ValuationsStorage const &)=default
storm::umb::UmbModel::Valuation getRawUmbData() const
Exports a snapshot of the raw UMB model valuation data (packed bytes, optional class mapping,...
bool entityHasVariable(uint64_t entity, storm::expressions::Variable const &variable) const
Returns true iff the variable is relevant for the given entity's class, i.e.
ValuationsStorage selectEntities(T const &selectedEntities) const
Constructs a new ValuationsStorage containing only the selected entities, in the order they appear in...
void resize(uint64_t newEntityCount, uint64_t classIndex=0)
Resizes the entity count to newEntityCount.
std::set< storm::expressions::Variable > getAllVariables() const
Returns all expression variables that this valuation assigns values to for at least one class.
void readCallback(uint64_t entity, Callback const &callback) const
Reads all variables of the given entity and invokes callback for each one.
void writeValue(uint64_t entity, storm::expressions::Variable const &variable, ValueType const &value)
Directly writes value to the given variable of entity.
std::size_t hash() const
Computes a hash of the entire valuation data.
uint64_t getClassOfEntity(uint64_t entity) const
Returns the class index of the given entity.
storm::expressions::ExpressionManager const & getManager() const
Returns the expression manager shared by all classes.
void setValuesInEvaluator(uint64_t entity, storm::expressions::ExpressionEvaluator< RationalValueType > &evaluator) const
Reads the variable values for the given entity and sets them into the given expression evaluator.
VariableInformation const & getVariableInformation(uint64_t entity, storm::expressions::Variable const &variable) const
Looks up compiled variable information for the given entity and variable.
storm::NumberTraits< storm::RationalNumber >::IntegerType Integer
void emplaceBack(uint64_t classIndex, Callback const &callback)
Appends a new entity of the given class and populates its variables via callback.
ValuationClassDescription getClassDescription(uint64_t classIndex=0) const
Reconstructs the ValuationClassDescription for the given class from the stored compiled variable info...
static void appendEncodedInteger(std::vector< uint64_t > &result, typename storm::NumberTraits< storm::RationalNumber >::IntegerType const &value, uint64_t uint64BucketsPerInteger)
static storm::NumberTraits< storm::RationalNumber >::IntegerType decodeArbitraryPrecisionInteger(InputRange &&input)
static uint64_t getSizeOfIntegerEncoding(typename storm::NumberTraits< storm::RationalNumber >::IntegerType const &value)
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:28
bool fits64Bit(ValuationClassDescription::Variable const &varDesc)
ValuationsStorage::VariablesInformation createVariablesInformation(ManagerType &expressionManager, ValuationClassDescription const &description)
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)
Describes the layout of a class of valuations (e.g.
uint64_t sizeInBits() const
Computes the size in bits of a valuation.
std::vector< std::variant< Padding, Variable > > variables
Compiled information about a single variable within a valuation class.
Compiled information about all variables belonging to one valuation class.
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
TO1< uint32_t > valuationToClass
Definition UmbModel.h:70