114 auto const& index = umbModel.
index;
119 auto checkNum = [&err](uint64_t num,
auto&& name, uint64_t lowerBound = 0) {
121 err <<
"Number of " << name <<
" is not set.\n";
123 }
else if (num < lowerBound) {
124 err <<
"Number of " << name <<
" is " << num <<
" which is below lower bound " << lowerBound <<
".\n";
129 isValid &= checkNum(tsIndex.numPlayers,
"players");
130 isValid &= checkNum(tsIndex.numStates,
"states", 1u);
131 isValid &= checkNum(tsIndex.numInitialStates,
"initial-states");
132 isValid &= checkNum(tsIndex.numChoices,
"choices");
133 isValid &= checkNum(tsIndex.numChoiceActions,
"choice-actions");
134 isValid &= checkNum(tsIndex.numBranches,
"branches");
135 isValid &= checkNum(tsIndex.numBranchActions,
"branch-actions");
136 isValid &= checkNum(tsIndex.numObservations,
"observations");
139 if (tsIndex.branchProbabilityType) {
142 err <<
"Branch probability type must be a continuous numeric type.\n";
146 if (tsIndex.exitRateType) {
149 err <<
"Exit rate type must be a continuous numeric type.\n";
153 if (tsIndex.observationProbabilityType) {
156 err <<
"Observation probability type must be a continuous numeric type.\n";
161 if (
bool const hasObservations = tsIndex.numObservations > 0; hasObservations != tsIndex.observationsApplyTo.has_value()) {
162 err <<
"observations-apply-to is " << (tsIndex.observationsApplyTo.has_value() ?
"set" :
"not set") <<
" although the number of observations is "
163 << tsIndex.numObservations <<
".\n";
167 if (index.annotations) {
168 for (
auto const& [annotationType, annotationMap] : index.annotations.value()) {
169 for (
auto const& [name, annotation] : annotationMap) {
171 if (annotation.probabilityType) {
174 err <<
"Probability type for annotation '" << name <<
"' must be a continuous numeric type.\n";
178 if (annotationType ==
"aps") {
180 err <<
"Atomic proposition annotation '" << name <<
"' must be of boolean type.\n";
183 }
else if (annotationType ==
"rewards") {
185 err <<
"Reward annotation '" << name <<
"' must have numeric type.\n";
193 if (index.valuations) {
194 boost::pfr::for_each_field(index.valuations.value(), [&isValid, &err](
auto const& description) {
195 if (!description.has_value()) {
198 if (description->classes.empty()) {
199 err <<
"A valuation description has no classes.\n";
202 for (
auto const& descr : description->classes) {
203 for (auto const& var : descr.variables) {
204 if (std::holds_alternative<ValuationClassDescription::Variable>(var)) {
205 auto const& variable = std::get<ValuationClassDescription::Variable>(var);
206 if (variable.name.empty()) {
207 err <<
"A valuation description has a variable with an empty name.\n";
210 isValid &= validation::validateTypeDeclaration(variable.type, false, err);
213 if (descr.sizeInBits() % 8 != 0) {
214 err <<
"A valuation description has size " << descr.sizeInBits() <<
" bits which is not a multiple of 8.\n";
226 auto isExpectedBoolVectorSize = [](uint64_t
const actual, uint64_t
const expected) {
227 return actual == expected || actual == ((expected + 63) / 64) * 64;
229 auto isExpectedTypedVectorSize = [](uint64_t
const actual,
storm::umb::SizedType const& type, uint64_t
const expected) {
231 if (actual == expected) {
238 return actual == ((expected + 63) / 64) * 64;
243 return actual == 2 * expected;
246 return actual == expected * type.bitSize() / 64;
249 return actual == (expected * type.bitSize() / 64);
258 isValid &= validation::validateCsr(umbModel.stateToChoices,
"state-to-choice", tsIndex.numStates, tsIndex.numChoices, err);
259 if (umbModel.stateToPlayer.has_value()) {
260 if (tsIndex.numPlayers == 0) {
261 err <<
"state-to-player mapping is given but the model has no players.\n";
263 }
else if (umbModel.stateToPlayer->size() != tsIndex.numStates) {
264 err <<
"state-to-player mapping has invalid size: " << umbModel.stateToPlayer->size() <<
" != #states=" << tsIndex.numStates <<
".\n";
268 if (umbModel.stateIsInitial.has_value() && !isExpectedBoolVectorSize(umbModel.stateIsInitial->size(), tsIndex.numStates)) {
269 err <<
"state-is-initial has invalid size: " << umbModel.stateIsInitial->size() <<
" != #states=" << tsIndex.numStates <<
".\n";
272 if (umbModel.stateIsMarkovian.has_value()) {
273 if (tsIndex.time != ModelIndex::TransitionSystem::Time::UrgentStochastic) {
274 err <<
"state-is-markovian is given but the model does not have urgent-stochastic time.\n";
276 }
else if (!isExpectedBoolVectorSize(umbModel.stateIsMarkovian->size(), tsIndex.numStates)) {
277 err <<
"state-is-markovian has invalid size: " << umbModel.stateIsMarkovian->size() <<
" != #states=" << tsIndex.numStates <<
".\n";
281 if (umbModel.stateToExitRate.hasValue()) {
282 if (tsIndex.time == ModelIndex::TransitionSystem::Time::Discrete) {
283 err <<
"state-to-exit-rate mapping is given but the model has discrete time.\n";
286 if (!tsIndex.exitRateType.has_value()) {
287 err <<
"state-to-exit-rate mapping is given but exit rate type is not declared.\n";
289 }
else if (!validation::vectorMatchesType(umbModel.stateToExitRate, tsIndex.exitRateType.value())) {
290 err <<
"state-to-exit-rate mapping has values that do not match the declared exit rate type " << tsIndex.exitRateType->toString() <<
".\n";
292 }
else if (!isExpectedTypedVectorSize(umbModel.stateToExitRate.size(), tsIndex.exitRateType.value(), tsIndex.numStates)) {
293 err <<
"state-to-exit-rate mapping has invalid size: " << umbModel.stateToExitRate.size() <<
" != #states=" << tsIndex.numStates <<
".\n";
299 isValid &= validation::validateCsr(umbModel.choiceToBranches,
"choice-to-branch", tsIndex.numChoices, tsIndex.numBranches, err);
302 if (umbModel.branchToTarget.has_value() && umbModel.branchToTarget->size() != tsIndex.numBranches) {
303 err <<
"branch-to-target mapping has invalid size: " << umbModel.branchToTarget->size() <<
" != #branches=" << tsIndex.numBranches <<
".\n";
306 if (umbModel.branchToProbability.hasValue()) {
307 if (!tsIndex.branchProbabilityType.has_value()) {
308 err <<
"branch-to-probability mapping is given but branch probability type is not declared.\n";
310 }
else if (!validation::vectorMatchesType(umbModel.branchToProbability, tsIndex.branchProbabilityType.value())) {
311 err <<
"branch-to-probability mapping has values that do not match the declared branch probability type "
312 << tsIndex.branchProbabilityType->toString() <<
".\n";
314 }
else if (!isExpectedTypedVectorSize(umbModel.branchToProbability.size(), tsIndex.branchProbabilityType.value(), tsIndex.numBranches)) {
315 err <<
"branch-to-probability mapping has invalid size: " << umbModel.branchToProbability.size() <<
" != #branches=" << tsIndex.numBranches
323 uint64_t
const numEntityActions) {
324 if (al.values.has_value()) {
325 if (numEntityActions == 0) {
326 err <<
"actions/" << entityName <<
"/values given but the number of " << entityName <<
"-actions is zero.\n";
328 }
else if (al.values->size() != numEntity) {
329 err <<
"actions/" << entityName <<
"/values has invalid size: " << al.values->size() <<
" != #" << entityName <<
"=" << numEntity <<
".\n";
333 if (al.stringMapping.has_value() && numEntityActions == 0) {
334 err <<
"actions/" << entityName <<
"/string-mapping given but the number of " << entityName <<
"-actions is zero.\n";
337 if (al.stringMapping.has_value() != al.strings.has_value()) {
338 err <<
"actions/" << entityName <<
"/string-mapping is " << (al.stringMapping.has_value() ?
"set" :
"not set") <<
" although actions/" << entityName
339 <<
"/strings is " << (al.strings.has_value() ?
"set" :
"not set") <<
".\n";
342 if (al.stringMapping.has_value()) {
344 validation::validateCsr(al.stringMapping, std::string(
"actions/") + entityName +
"/string-mapping", numEntityActions, al.strings->size(), err);
347 if (umbModel.choiceActions.has_value()) {
348 validateActionLabels(umbModel.choiceActions.value(),
"choices", tsIndex.numChoices, tsIndex.numChoiceActions);
350 if (umbModel.branchActions.has_value()) {
351 validateActionLabels(umbModel.branchActions.value(),
"branches", tsIndex.numBranches, tsIndex.numBranchActions);
356 uint64_t
const numEntity, uint64_t
const numObservations,
357 std::optional<storm::umb::SizedType>
const& obsProbType) {
358 if (obs.values.has_value()) {
359 if (numObservations == 0) {
360 err <<
"observations/" << entityName <<
"/values given but the number of observations is zero.\n";
362 }
else if (obs.probabilities.hasValue() || obs.values->size() != numEntity) {
363 err <<
"observations/" << entityName <<
"/values has invalid size: " << obs.values->size() <<
" != #" << entityName
364 <<
"-observation-values=" << numEntity <<
".\n";
367 isValid &= validation::validateCsr(obs.distributionMapping, std::string(
"observations/") + entityName +
"/distribution-mapping", numEntity,
368 obs.values->size(), err);
369 if (obs.probabilities.hasValue()) {
370 if (!obsProbType.has_value()) {
371 err <<
"observations/" << entityName <<
"/probabilities given but observation probability type is not declared.\n";
373 }
else if (!validation::vectorMatchesType(obs.probabilities, obsProbType.value())) {
374 err <<
"observations/" << entityName <<
"/probabilities has values that do not match the declared observation probability type "
375 << obsProbType->toString() <<
".\n";
377 }
else if (!isExpectedTypedVectorSize(obs.probabilities.size(), obsProbType.value(), numObservations)) {
378 err <<
"observations/" << entityName <<
"/probabilities has invalid size: " << obs.probabilities.size()
379 <<
" != #observations=" << numObservations <<
".\n";
385 if (umbModel.stateObservations.has_value()) {
386 validateObservations(umbModel.stateObservations.value(),
"states", tsIndex.numStates, tsIndex.numObservations, tsIndex.observationProbabilityType);
388 if (umbModel.branchObservations.has_value()) {
389 validateObservations(umbModel.branchObservations.value(),
"branches", tsIndex.numBranches, tsIndex.numObservations, tsIndex.observationProbabilityType);
394 auto const& id,
auto const& entityName, uint64_t
const numEntity,
396 auto const context = std::string(
"annotations/") + group +
"/" +
id +
"/" + entityName;
397 uint64_t
const numAnnotationValues = ai.numProbabilities.value_or(numEntity);
398 if (av.values.hasValue()) {
399 if (!validation::vectorMatchesType(av.values, ai.type)) {
400 err << context <<
"/values has values that do not match the declared annotation type " << ai.type.toString() <<
".\n";
402 }
else if (!isExpectedTypedVectorSize(av.values.size(), ai.type, numAnnotationValues)) {
403 err << context <<
"/values has invalid size: " << av.values.size() <<
" != #" << entityName <<
"-annotation-values=" << numAnnotationValues;
407 if (
isStringType(ai.type.type) != av.stringMapping.has_value()) {
408 err << context <<
"/string-mapping is " << (av.stringMapping.has_value() ?
"set" :
"not set") <<
" although the annotation type is "
409 << ai.type.toString() <<
".\n";
412 if (av.stringMapping.has_value() && ai.numStrings.value_or(0) == 0) {
413 err << context <<
"/string-mapping given but the number of strings is zero.\n";
416 if (av.stringMapping.has_value() != av.strings.has_value()) {
417 err << context <<
"/string-mapping is " << (av.stringMapping.has_value() ?
"set" :
"not set") <<
" although " << context <<
"/strings is "
418 << (av.strings.has_value() ?
"set" :
"not set") <<
".\n";
421 if (av.stringMapping.has_value()) {
422 isValid &= validation::validateCsr(av.stringMapping, context +
"/string-mapping", ai.numStrings.value(), av.strings->size(), err);
424 isValid &= validation::validateCsr(av.distributionMapping, context +
"/distribution-mapping", numEntity, numAnnotationValues, err);
425 if (av.probabilities.hasValue()) {
426 if (!ai.probabilityType.has_value()) {
427 err << context <<
"/probabilities given but annotation probability type is not declared.\n";
429 }
else if (!validation::vectorMatchesType(av.probabilities, ai.probabilityType.value())) {
430 err << context <<
"/probabilities has values that do not match the declared annotation probability type " << ai.probabilityType->toString()
433 }
else if (!isExpectedTypedVectorSize(av.probabilities.size(), ai.probabilityType.value(), numAnnotationValues)) {
434 err << context <<
"/probabilities has invalid size: " << av.probabilities.size() <<
" != #" << entityName
435 <<
"-annotation-values=" << numAnnotationValues <<
".\n";
440 for (
auto const& [annotationType, annotationMap] : umbModel.annotations) {
441 if (!umbModel.index.annotations.has_value() || !umbModel.index.annotations->contains(annotationType)) {
442 err <<
"Annotation '" << annotationType <<
"' is given but not declared in the index.\n";
446 for (
auto const& [annotationId, annotationValues] : annotationMap) {
447 if (!umbModel.index.annotations->at(annotationType).contains(annotationId)) {
448 err <<
"Annotation '" << annotationId <<
"' of type '" << annotationType <<
"' is given but not declared in the index.\n";
452 auto const& annotationIndex = index.annotations->at(annotationType).at(annotationId);
453 if (annotationValues.states.has_value()) {
454 if (!annotationIndex.appliesToStates()) {
455 err <<
"Annotation '" << annotationId <<
"' of type '" << annotationType
456 <<
"' has states values but does not apply to states according to the index.\n";
459 validateAnnotationValues(annotationValues.states.value(), annotationType, annotationId,
"states", tsIndex.numStates, annotationIndex);
461 if (annotationValues.choices.has_value()) {
462 if (!annotationIndex.appliesToChoices()) {
463 err <<
"Annotation '" << annotationId <<
"' of type '" << annotationType
464 <<
"' has choices values but does not apply to choices according to the index.\n";
467 validateAnnotationValues(annotationValues.choices.value(), annotationType, annotationId,
"choices", tsIndex.numChoices, annotationIndex);
469 if (annotationValues.branches.has_value()) {
470 if (!annotationIndex.appliesToBranches()) {
471 err <<
"Annotation '" << annotationId <<
"' of type '" << annotationType
472 <<
"' has branches values but does not apply to branches according to the index.\n";
475 validateAnnotationValues(annotationValues.branches.value(), annotationType, annotationId,
"branches", tsIndex.numBranches, annotationIndex);
477 if (annotationValues.observations.has_value()) {
478 if (!annotationIndex.appliesToObservations()) {
479 err <<
"Annotation '" << annotationId <<
"' of type '" << annotationType
480 <<
"' has observations values but does not apply to observations according to the index.\n";
483 validateAnnotationValues(annotationValues.observations.value(), annotationType, annotationId,
"observations", tsIndex.numObservations,
486 if (annotationValues.players.has_value()) {
487 if (!annotationIndex.appliesToPlayers()) {
488 err <<
"Annotation '" << annotationId <<
"' of type '" << annotationType
489 <<
"' has players values but does not apply to players according to the index.\n";
492 validateAnnotationValues(annotationValues.players.value(), annotationType, annotationId,
"players", tsIndex.numPlayers, annotationIndex);
500 auto const context = std::string(
"valuations/") + entityName;
501 if (v.valuationToClass.has_value() && v.valuationToClass->size() != numEntity) {
502 err << context <<
"/valuation-to-class has invalid size: " << v.valuationToClass->size() <<
" != #" << entityName <<
"=" << numEntity <<
".\n";
505 if ((!v.valuationToClass.has_value() && !descr.classes.empty()) || descr.classes.size() == 1) {
507 auto const& classDescr = descr.classes.front();
508 if (v.valuations.has_value() && v.valuations->size() * 8 < classDescr.sizeInBits() * numEntity) {
509 err << context <<
"/valuations has invalid size: " << v.valuations->size() <<
" != size of one valuation class (" << classDescr.sizeInBits()
510 <<
" bits) * 8 * #entities=" << (classDescr.sizeInBits() * 8 * numEntity) <<
".\n";
514 if (v.stringMapping.has_value() && descr.numStrings.value_or(0) == 0) {
515 err << context <<
"/string-mapping given but the number of strings is zero.\n";
518 if (v.stringMapping.has_value() != v.strings.has_value()) {
519 err << context <<
"/string-mapping is " << (v.stringMapping.has_value() ?
"set" :
"not set") <<
" although " << context <<
"/strings is "
520 << (v.strings.has_value() ?
"set" :
"not set") <<
".\n";
523 if (v.stringMapping.has_value()) {
524 isValid &= validation::validateCsr(v.stringMapping, context +
"/string-mapping", descr.numStrings.value(), v.strings->size(), err);
527 if (umbModel.valuations.states.has_value()) {
528 if (!umbModel.index.valuations.has_value() || !umbModel.index.valuations->states.has_value()) {
529 err <<
"State valuations are given but no valuation descriptions are declared in the index.\n";
532 validateValuation(umbModel.valuations.states.value(),
"states", tsIndex.numStates, umbModel.index.valuations->states.value());
535 if (umbModel.valuations.choices.has_value()) {
536 if (!umbModel.index.valuations.has_value() || !umbModel.index.valuations->choices.has_value()) {
537 err <<
"Choice valuations are given but no valuation descriptions are declared in the index.\n";
540 validateValuation(umbModel.valuations.choices.value(),
"choices", tsIndex.numChoices, umbModel.index.valuations->choices.value());
543 if (umbModel.valuations.branches.has_value()) {
544 if (!umbModel.index.valuations.has_value() || !umbModel.index.valuations->branches.has_value()) {
545 err <<
"Branch valuations are given but no valuation descriptions are declared in the index.\n";
548 validateValuation(umbModel.valuations.branches.value(),
"branches", tsIndex.numBranches, umbModel.index.valuations->branches.value());
551 if (umbModel.valuations.observations.has_value()) {
552 if (!umbModel.index.valuations.has_value() || !umbModel.index.valuations->observations.has_value()) {
553 err <<
"Observation valuations are given but no valuation descriptions are declared in the index.\n";
556 validateValuation(umbModel.valuations.observations.value(),
"observations", tsIndex.numObservations,
557 umbModel.index.valuations->observations.value());
560 if (umbModel.valuations.players.has_value()) {
561 if (!umbModel.index.valuations.has_value() || !umbModel.index.valuations->players.has_value()) {
562 err <<
"Player valuations are given but no valuation descriptions are declared in the index.\n";
565 validateValuation(umbModel.valuations.players.value(),
"players", tsIndex.numPlayers, umbModel.index.valuations->players.value());