79 bool allowModelSimplifications,
bool graphPreserving) {
80 STORM_LOG_THROW(this->canHandle(parametricModel, checkTask), storm::exceptions::NotSupportedException,
81 "Combination of model " << parametricModel->getType() <<
" and formula '" << checkTask.
getFormula() <<
"' is not supported.");
82 this->specifySplitEstimates(generateRegionSplitEstimates, checkTask);
83 this->specifyMonotonicity(monotonicityBackend, checkTask);
84 this->graphPreserving = graphPreserving;
85 auto dtmc = parametricModel->template as<SparseModelType>();
86 if (isOrderBasedMonotonicityBackend()) {
88 "Allowing model simplification when using order-based monotonicity is not useful, as for order-based monotonicity checking model "
89 "simplification is done as preprocessing");
90 getOrderBasedMonotonicityBackend().initializeMonotonicityChecker(dtmc->getTransitionMatrix());
95 if (allowModelSimplifications && graphPreserving) {
97 simplifier.setPreserveParametricTransitions(
true);
98 if (!simplifier.simplify(checkTask.
getFormula())) {
99 STORM_LOG_THROW(
false, storm::exceptions::UnexpectedException,
"Simplifying the model was not successfull.");
101 this->parametricModel = simplifier.getSimplifiedModel();
102 this->specifyFormula(env, checkTask.
substituteFormula(*simplifier.getSimplifiedFormula()));
104 this->parametricModel = dtmc;
105 this->specifyFormula(env, checkTask);
107 if constexpr (!Robust) {
108 if (isOrderBasedMonotonicityBackend()) {
109 getOrderBasedMonotonicityBackend().registerParameterLifterReference(*parameterLifter);
110 getOrderBasedMonotonicityBackend().registerPLABoundFunction(
112 return this->computeQuantitativeValues(environment, region, dir);
116 std::shared_ptr<storm::logic::Formula> formulaWithoutBounds = this->currentCheckTask->getFormula().clone();
117 formulaWithoutBounds->asOperatorFormula().removeBound();
118 this->currentFormulaNoBound = formulaWithoutBounds->asSharedPointer();
119 this->currentCheckTaskNoBound = std::make_unique<storm::modelchecker::CheckTask<storm::logic::Formula, ParametricType>>(*this->currentFormulaNoBound);
121 this->derivativeChecker =
122 std::make_unique<storm::derivative::SparseDerivativeInstantiationModelChecker<ParametricType, ConstantType>>(*this->parametricModel);
123 this->derivativeChecker->specifyFormula(env, *this->currentCheckTaskNoBound);
132 STORM_LOG_THROW(!checkTask.
getFormula().hasLowerBound(), storm::exceptions::NotSupportedException,
"Lower step bounds are not supported.");
133 STORM_LOG_THROW(checkTask.
getFormula().hasUpperBound(), storm::exceptions::NotSupportedException,
"Expected a bounded until formula with an upper bound.");
135 "Expected a bounded until formula with step bounds.");
136 stepBound = checkTask.
getFormula().getUpperBound().evaluateAsInt();
137 STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException,
138 "Can not apply parameter lifting on step bounded formula: The step bound has to be positive.");
139 if (checkTask.
getFormula().isUpperBoundStrict()) {
140 STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException,
"Expected a strict upper step bound that is greater than zero.");
143 STORM_LOG_THROW(*stepBound > 0, storm::exceptions::NotSupportedException,
144 "Can not apply parameter lifting on step bounded formula: The step bound has to be positive.");
150 storm::exceptions::NotSupportedException,
"Parameter lifting with non-propositional subformulas is not supported");
152 std::move(propositionalChecker.
check(checkTask.
getFormula().getLeftSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector());
154 std::move(propositionalChecker.
check(checkTask.
getFormula().getRightSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector());
158 maybeStates &= ~psiStates;
161 resultsForNonMaybeStates = std::vector<ConstantType>(this->parametricModel->getNumberOfStates(), storm::utility::zero<ConstantType>());
165 if (Robust || !maybeStates.empty()) {
167 std::vector<ParametricType> b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(
169 parameterLifter = std::make_unique<ParameterLifterType<ParametricType, ConstantType, Robust>>(
170 this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates,
false, isOrderBasedMonotonicityBackend());
174 lowerResultBound = storm::utility::zero<ConstantType>();
175 upperResultBound = storm::utility::one<ConstantType>();
177 solverFactory->setRequirementsChecked(
true);
179 if (isOrderBasedMonotonicityBackend()) {
181 getOrderBasedMonotonicityBackend().initializeOrderExtender(prob1, prob0, this->parametricModel->getTransitionMatrix());
192 storm::exceptions::NotSupportedException,
"Parameter lifting with non-propositional subformulas is not supported");
194 std::move(propositionalChecker.
check(checkTask.
getFormula().getLeftSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector());
196 std::move(propositionalChecker.
check(checkTask.
getFormula().getRightSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector());
199 std::pair<storm::storage::BitVector, storm::storage::BitVector> statesWithProbability01 =
201 maybeStates = ~(statesWithProbability01.first | statesWithProbability01.second);
204 resultsForNonMaybeStates = std::vector<ConstantType>(this->parametricModel->getNumberOfStates(), storm::utility::zero<ConstantType>());
208 if (Robust || !maybeStates.empty()) {
209 if constexpr (Robust) {
212 std::vector<ParametricType> target(this->parametricModel->getNumberOfStates(), storm::utility::zero<ParametricType>());
215 if (!graphPreserving) {
217 maybeStates = ~statesWithProbability01.first & ~psiStates;
223 auto rowFilter = this->parametricModel->getTransitionMatrix().getRowFilter(maybeStates);
224 auto filteredMatrix = this->parametricModel->getTransitionMatrix().filterEntries(rowFilter);
226 maybeStates = allTrue;
228 parameterLifter = std::make_unique<ParameterLifterType<ParametricType, ConstantType, Robust>>(
229 filteredMatrix, target, allTrue, allTrue, isValueDeltaRegionSplitEstimates(), isOrderBasedMonotonicityBackend());
232 std::vector<ParametricType> b = this->parametricModel->getTransitionMatrix().getConstrainedRowSumVector(
233 storm::storage::BitVector(this->parametricModel->getTransitionMatrix().getRowCount(),
true), statesWithProbability01.second);
234 parameterLifter = std::make_unique<ParameterLifterType<ParametricType, ConstantType, Robust>>(
235 this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, isValueDeltaRegionSplitEstimates(),
236 isOrderBasedMonotonicityBackend());
241 lowerResultBound = storm::utility::zero<ConstantType>();
242 upperResultBound = storm::utility::one<ConstantType>();
246 auto req = solverFactory->getRequirements(env,
true,
true, boost::none, !Robust);
248 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
249 "Solver requirements " + req.getEnabledRequirementsAsString() +
" not checked.");
250 solverFactory->setRequirementsChecked(
true);
252 if (isOrderBasedMonotonicityBackend()) {
253 getOrderBasedMonotonicityBackend().initializeOrderExtender(statesWithProbability01.second, statesWithProbability01.first,
254 this->parametricModel->getTransitionMatrix());
264 "Parameter lifting with non-propositional subformulas is not supported");
266 std::move(propositionalChecker.
check(checkTask.
getFormula().getSubformula())->asExplicitQualitativeCheckResult().getTruthValuesVector());
269 this->parametricModel->getBackwardTransitions(),
storm::storage::BitVector(this->parametricModel->getNumberOfStates(),
true), targetStates);
271 maybeStates = ~(targetStates | infinityStates);
274 resultsForNonMaybeStates = std::vector<ConstantType>(this->parametricModel->getNumberOfStates(), storm::utility::zero<ConstantType>());
278 if (Robust || !maybeStates.empty()) {
281 (!checkTask.
isRewardModelSet() && this->parametricModel->hasUniqueRewardModel()),
282 storm::exceptions::InvalidPropertyException,
"The reward model specified by the CheckTask is not available in the given model.");
284 typename SparseModelType::RewardModelType
const& rewardModel =
287 std::vector<ParametricType> b = rewardModel.getTotalRewardVector(this->parametricModel->getTransitionMatrix());
289 if constexpr (Robust) {
291 if (!graphPreserving) {
292 maybeStates = ~targetStates;
294 auto rowFilter = this->parametricModel->getTransitionMatrix().getRowFilter(maybeStates);
295 auto filteredMatrix = this->parametricModel->getTransitionMatrix().filterEntries(rowFilter);
296 maybeStates = allTrue;
298 parameterLifter = std::make_unique<ParameterLifterType<ParametricType, ConstantType, Robust>>(
299 filteredMatrix, b, allTrue, allTrue, isValueDeltaRegionSplitEstimates(), isOrderBasedMonotonicityBackend());
301 parameterLifter = std::make_unique<ParameterLifterType<ParametricType, ConstantType, Robust>>(
302 this->parametricModel->getTransitionMatrix(), b, maybeStates, maybeStates, isValueDeltaRegionSplitEstimates(),
303 isOrderBasedMonotonicityBackend());
308 lowerResultBound = storm::utility::zero<ConstantType>();
312 auto req = solverFactory->getRequirements(env,
true,
true, boost::none, !Robust);
313 req.clearLowerBounds();
314 if (req.upperBounds()) {
315 solvingRequiresUpperRewardBounds =
true;
316 req.clearUpperBounds();
318 STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException,
319 "Solver requirements " + req.getEnabledRequirementsAsString() +
" not checked.");
320 solverFactory->setRequirementsChecked(
true);
321 STORM_LOG_WARN_COND(!isOrderBasedMonotonicityBackend(),
"Order-based monotonicity not used for reachability reward formula.");
402 if (maybeStates.empty()) {
403 this->updateKnownValueBoundInRegion(region, dirForParameters, resultsForNonMaybeStates);
404 return resultsForNonMaybeStates;
406 parameterLifter->specifyRegion(region.
region, dirForParameters);
407 auto liftedMatrix = parameterLifter->getMatrix();
408 auto liftedVector = parameterLifter->getVector();
409 bool nonTrivialEndComponents =
false;
410 if constexpr (Robust) {
411 if (parameterLifter->isCurrentRegionAllIllDefined()) {
412 return std::vector<ConstantType>();
414 if (!graphPreserving) {
416 auto const& result = endComponentPreserver.
eliminateMECs(liftedMatrix, liftedVector);
420 liftedMatrix = *result;
421 nonTrivialEndComponents =
true;
425 const uint64_t resultVectorSize = liftedMatrix.getColumnCount();
428 if constexpr (!Robust) {
429 assert(*stepBound > 0);
430 x = std::vector<ConstantType>(resultVectorSize, storm::utility::zero<ConstantType>());
432 multiplier->repeatedMultiplyAndReduce(env, dirForParameters, x, &liftedVector, *stepBound);
437 auto solver = solverFactory->create(env, liftedMatrix);
438 solver->setHasUniqueSolution();
439 solver->setHasNoEndComponents();
441 solver->setUncertaintyResolutionMode(UncertaintyResolutionMode::Cooperative);
442 if (lowerResultBound)
443 solver->setLowerBound(lowerResultBound.value());
444 if (upperResultBound) {
445 solver->setUpperBound(upperResultBound.value());
446 }
else if (solvingRequiresUpperRewardBounds) {
447 if constexpr (!Robust) {
449 std::vector<ConstantType> oneStepProbs;
450 oneStepProbs.reserve(liftedMatrix.getRowCount());
451 for (uint64_t row = 0; row < liftedMatrix.getRowCount(); ++row) {
452 oneStepProbs.push_back(storm::utility::one<ConstantType>() - liftedMatrix.getRowSum(row));
454 if (dirForParameters == storm::OptimizationDirection::Minimize) {
465 solver->setTrackScheduler(
true);
471 if constexpr (!Robust) {
473 if (isOrderBasedMonotonicityBackend()) {
475 if (!choices.has_value()) {
476 choices.emplace(parameterLifter->getRowGroupCount(), 0u);
478 statesWithFixedChoice = getOrderBasedMonotonicityBackend().getChoicesToFixForPLASolver(region, dirForParameters, *choices);
482 if (choices.has_value()) {
483 solver->setInitialScheduler(std::move(choices.value()));
484 if (statesWithFixedChoice.
size() != 0) {
486 solver->setSchedulerFixedForRowGroup(std::move(statesWithFixedChoice));
491 if (!nonTrivialEndComponents && choices.has_value()) {
492 solver->setInitialScheduler(std::move(choices.value()));
496 if (this->currentCheckTask->isBoundSet() && solver->hasInitialScheduler()) {
499 std::unique_ptr<storm::solver::TerminationCondition<ConstantType>> termCond;
501 ? this->parametricModel->getInitialStates() % maybeStates
505 termCond = std::make_unique<storm::solver::TerminateIfFilteredExtremumBelowThreshold<ConstantType>>(
506 relevantStatesInSubsystem,
true, this->currentCheckTask->getBoundThreshold(),
false);
509 termCond = std::make_unique<storm::solver::TerminateIfFilteredExtremumExceedsThreshold<ConstantType>>(
510 relevantStatesInSubsystem,
true, this->currentCheckTask->getBoundThreshold(),
true);
512 solver->setTerminationCondition(std::move(termCond));
516 x.resize(resultVectorSize, storm::utility::zero<ConstantType>());
517 solver->solveEquations(env, dirForParameters, x, liftedVector);
518 if (isValueDeltaRegionSplitEstimates()) {
519 computeStateValueDeltaRegionSplitEstimates(env, x, solver->getSchedulerChoices(), region.
region, dirForParameters);
522 if (!nonTrivialEndComponents) {
523 choices = solver->getSchedulerChoices();
528 std::vector<ConstantType> result = resultsForNonMaybeStates;
529 auto maybeStateResIt = x.begin();
530 for (
auto const& maybeState : maybeStates) {
531 result[maybeState] = *maybeStateResIt;
535 STORM_LOG_INFO(dirForParameters <<
" " << region.
region <<
": " << result[this->getUniqueInitialState()] << std::endl);
537 this->updateKnownValueBoundInRegion(region, dirForParameters, result);
543 Environment const& env, std::vector<ConstantType>
const& quantitativeResult, std::vector<uint64_t>
const& schedulerChoices,
545 auto const& matrix = parameterLifter->getMatrix();
546 auto const& vector = parameterLifter->getVector();
548 std::vector<ConstantType> weighting = std::vector<ConstantType>(vector.size(), utility::one<ConstantType>());
556 uint64_t rowIndex = 0;
557 for (
auto const& state : maybeStates) {
558 weighting[rowIndex++] = visitingTimes[state];
562 switch (*this->specifiedRegionSplitEstimateKind) {
565 std::map<VariableType, ConstantType> deltaLower, deltaUpper;
567 deltaLower.emplace(p, storm::utility::zero<ConstantType>());
568 deltaUpper.emplace(p, storm::utility::zero<ConstantType>());
570 if constexpr (Robust) {
572 static std::map<RationalFunction, RationalFunction> functionDerivatives;
573 static std::vector<std::pair<bool, double>> constantDerivatives;
574 if (constantDerivatives.empty()) {
575 for (uint64_t state : maybeStates) {
576 auto variables = parameterLifter->getOccurringVariablesAtState().at(state);
577 if (variables.size() == 0) {
581 "Cannot compute state-value-delta split estimates in robust mode if there are states with multiple parameters.");
582 auto const p = *variables.begin();
583 for (
auto const& entry : this->parametricModel->getTransitionMatrix().getRow(state)) {
584 auto const& function = entry.getValue();
585 if (functionDerivatives.count(function)) {
586 constantDerivatives.emplace_back(
false, 0);
589 auto const derivative = function.derivative(p);
590 if (derivative.isConstant()) {
591 constantDerivatives.emplace_back(
true, utility::convertNumber<double>(derivative.constantPart()));
593 functionDerivatives.emplace(function, derivative);
594 constantDerivatives.emplace_back(
false, 0);
596 constantDerivatives.emplace_back(
false, 0);
602 cachedRegionSplitEstimates.clear();
604 cachedRegionSplitEstimates.emplace(p, utility::zero<ConstantType>());
607 uint64_t entryCount = 0;
609 for (uint64_t state : maybeStates) {
610 auto variables = parameterLifter->getOccurringVariablesAtState().at(state);
611 if (variables.size() == 0) {
615 "Cannot compute state-value-delta split estimates in robust mode if there are states with multiple parameters.");
617 auto const p = *variables.begin();
619 const uint64_t rowIndex = maybeStates.getNumberOfSetBitsBeforeIndex(state);
621 std::vector<ConstantType> derivatives;
622 for (
auto const& entry : this->parametricModel->getTransitionMatrix().getRow(state)) {
625 ConstantType derivative =
626 annotation.derivative()->template evaluate<ConstantType>(utility::convertNumber<ConstantType>(region.
getCenter(p)));
627 derivatives.push_back(derivative);
629 auto const& cDer = constantDerivatives.at(entryCount);
631 derivatives.push_back(cDer.second);
634 derivatives.push_back(utility::convertNumber<ConstantType>(derivative));
640 std::vector<ConstantType> results(0);
642 ConstantType distrToNegativeDerivative = storm::utility::zero<ConstantType>();
643 ConstantType distrToPositiveDerivative = storm::utility::zero<ConstantType>();
645 for (
auto const& direction : {OptimizationDirection::Maximize, OptimizationDirection::Minimize}) {
650 ConstantType remainingValue = utility::one<ConstantType>();
651 ConstantType result = utility::zero<ConstantType>();
654 "Non-constant vector indices not supported (this includes parametric rewards).");
656 std::vector<std::pair<ConstantType, std::pair<ConstantType, uint64_t>>> robustOrder;
659 for (
auto const& entry : matrix.getRow(rowIndex)) {
660 auto const lower = entry.getValue().lower();
661 result += quantitativeResult[entry.getColumn()] * lower;
662 remainingValue -= lower;
663 auto const diameter = entry.getValue().upper() - lower;
665 robustOrder.emplace_back(quantitativeResult[entry.getColumn()], std::make_pair(diameter, index));
670 std::sort(robustOrder.begin(), robustOrder.end(),
671 [direction](
const std::pair<ConstantType, std::pair<ConstantType, uint64_t>>& a,
672 const std::pair<ConstantType, std::pair<ConstantType, uint64_t>>& b) {
673 if (direction == OptimizationDirection::Maximize) {
674 return a.first > b.first;
676 return a.first < b.first;
680 for (
auto const& pair : robustOrder) {
681 auto availableMass = std::min(pair.second.first, remainingValue);
682 result += availableMass * pair.first;
684 if (direction == this->currentCheckTask->getOptimizationDirection()) {
685 if (derivatives[pair.second.second] > 1e-6) {
686 distrToPositiveDerivative += availableMass;
687 }
else if (derivatives[pair.second.second] < 1e-6) {
688 distrToNegativeDerivative += availableMass;
691 remainingValue -= availableMass;
694 results.push_back(result);
697 ConstantType diff = std::abs(results[0] - results[1]);
698 if (distrToPositiveDerivative > distrToNegativeDerivative) {
699 deltaUpper[p] += diff * weighting[rowIndex];
701 deltaLower[p] += diff * weighting[rowIndex];
705 auto const& choiceValuations = parameterLifter->getRowLabels();
707 std::vector<ConstantType> stateResults;
708 for (uint64_t state = 0; state < schedulerChoices.size(); ++state) {
709 uint64_t rowOffset = matrix.getRowGroupIndices()[state];
710 uint64_t optimalChoice = schedulerChoices[state];
711 auto const& optimalChoiceVal = choiceValuations[rowOffset + optimalChoice];
712 assert(optimalChoiceVal.getUnspecifiedParameters().empty());
713 stateResults.clear();
714 for (uint64_t row = rowOffset; row < matrix.getRowGroupIndices()[state + 1]; ++row) {
715 stateResults.push_back(matrix.multiplyRowWithVector(row, quantitativeResult) + vector[row]);
718 bool checkUpperParameters =
false;
720 auto const& consideredParameters = checkUpperParameters ? optimalChoiceVal.getUpperParameters() : optimalChoiceVal.getLowerParameters();
721 for (
auto const& p : consideredParameters) {
723 ConstantType bestValue = 0;
724 bool foundBestValue =
false;
725 for (uint64_t choice = 0; choice < stateResults.size(); ++choice) {
726 if (choice != optimalChoice) {
727 auto const& otherBoundParsOfChoice = checkUpperParameters ? choiceValuations[rowOffset + choice].getLowerParameters()
728 : choiceValuations[rowOffset + choice].getUpperParameters();
729 if (otherBoundParsOfChoice.find(p) != otherBoundParsOfChoice.end()) {
730 ConstantType
const& choiceValue = stateResults[choice];
731 if (!foundBestValue ||
733 foundBestValue =
true;
734 bestValue = choiceValue;
739 auto const& optimal = stateResults[optimalChoice];
740 auto diff = storm::utility::abs<ConstantType>(optimal - storm::utility::convertNumber<ConstantType>(bestValue));
741 if (foundBestValue) {
742 if (checkUpperParameters) {
743 deltaLower[p] += diff * weighting[state];
745 deltaUpper[p] += diff * weighting[state];
749 checkUpperParameters = !checkUpperParameters;
750 }
while (checkUpperParameters);
754 cachedRegionSplitEstimates.clear();
757 auto minDelta = std::min(deltaLower[p], deltaUpper[p]);
758 cachedRegionSplitEstimates.emplace(p, minDelta);
766 *this->parametricModel);
767 instantiationModelChecker.
specifyFormula(*this->currentCheckTaskNoBound);
771 std::unique_ptr<storm::modelchecker::CheckResult> result = instantiationModelChecker.
check(env, center);
772 auto const reachabilityProbabilities = result->asExplicitQuantitativeCheckResult<ConstantType>().getValueVector();
774 STORM_LOG_ASSERT(this->derivativeChecker,
"Derivative checker not intialized");
777 auto result = this->derivativeChecker->check(env, center, param, reachabilityProbabilities);
778 ConstantType derivative =
779 result->template asExplicitQuantitativeCheckResult<ConstantType>().getValueVector()[this->derivativeChecker->getInitialState()];
780 cachedRegionSplitEstimates[param] =
utility::abs(derivative) * utility::convertNumber<ConstantType>(region.
getDifference(param));
785 STORM_LOG_ERROR(
"Region split estimate kind not handled by SparseDtmcParameterLiftingModelChecker.");