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