3#include <boost/optional.hpp>
31template<
typename ModelType>
33 : model(model), quantileFormula(quantileFormula) {
35 std::set<storm::expressions::Variable> quantileVariables;
36 for (
auto const& quantileVariable : quantileFormula.getBoundVariables()) {
37 STORM_LOG_THROW(quantileVariables.count(quantileVariable) == 0, storm::exceptions::NotSupportedException,
38 "Quantile formula considers the same bound variable twice.");
39 quantileVariables.insert(quantileVariable);
41 STORM_LOG_THROW(quantileFormula.getSubformula().isProbabilityOperatorFormula(), storm::exceptions::NotSupportedException,
42 "Quantile formula needs probability operator inside. The formula " << quantileFormula <<
" is not supported.");
43 auto const& probOpFormula = quantileFormula.getSubformula().asProbabilityOperatorFormula();
44 STORM_LOG_THROW(probOpFormula.hasBound(), storm::exceptions::InvalidOperationException,
45 "Probability operator inside quantile formula needs to have a bound.");
46 STORM_LOG_THROW(!model.isNondeterministicModel() || probOpFormula.hasOptimalityType(), storm::exceptions::InvalidOperationException,
47 "Probability operator inside quantile formula needs to have an optimality type.");
50 "Probability operator inside quantile formula needs to have bound > or <=. The specified comparison type might lead to "
53 STORM_LOG_THROW(probOpFormula.getSubformula().isBoundedUntilFormula(), storm::exceptions::NotSupportedException,
54 "Quantile formula needs bounded until probability operator formula as subformula. The formula " << quantileFormula <<
" is not supported.");
55 auto const& boundedUntilFormula = probOpFormula.getSubformula().asBoundedUntilFormula();
56 std::set<storm::expressions::Variable> boundVariables;
57 for (uint64_t dim = 0; dim < boundedUntilFormula.getDimension(); ++dim) {
59 if (boundedUntilFormula.hasUpperBound(dim)) {
60 STORM_LOG_THROW(!boundedUntilFormula.hasLowerBound(dim), storm::exceptions::NotSupportedException,
61 "Interval bounds are not supported within quantile formulas.");
62 STORM_LOG_THROW(!boundedUntilFormula.isUpperBoundStrict(dim), storm::exceptions::NotSupportedException,
63 "Only non-strict upper reward bounds are supported for quantiles.");
64 boundExpression = boundedUntilFormula.getUpperBound(dim);
65 }
else if (boundedUntilFormula.hasLowerBound(dim)) {
66 STORM_LOG_THROW(!boundedUntilFormula.isLowerBoundStrict(dim), storm::exceptions::NotSupportedException,
67 "Only non-strict lower reward bounds are supported for quantiles.");
68 boundExpression = boundedUntilFormula.getLowerBound(dim);
72 boundExpression.
isVariable(), storm::exceptions::NotSupportedException,
73 "Non-trivial bound expressions such as '" << boundExpression <<
"' are not supported. Either specify a constant or a quantile variable.");
75 STORM_LOG_THROW(boundVariables.count(boundVariable) == 0, storm::exceptions::NotSupportedException,
76 "Variable " << boundExpression <<
" occurs at multiple reward bounds.");
77 boundVariables.insert(boundVariable);
78 STORM_LOG_THROW(quantileVariables.count(boundVariable) == 1, storm::exceptions::NotSupportedException,
79 "The formula contains undefined constant '" << boundExpression <<
"'.");
86 std::vector<BoundTransformation>
const& transformations,
87 bool complementQuery =
false) {
90 "Tried to replace the bound of a dimension that is higher than the number of dimensions of the formula.");
91 std::vector<std::shared_ptr<storm::logic::Formula const>> leftSubformulas, rightSubformulas;
92 std::vector<std::optional<storm::logic::TimeBound>> lowerBounds, upperBounds;
93 std::vector<storm::logic::TimeBoundReference> timeBoundReferences;
95 for (uint64_t dim = 0; dim < origBoundedUntil.getDimension(); ++dim) {
96 if (origBoundedUntil.hasMultiDimensionalSubformulas()) {
97 leftSubformulas.push_back(origBoundedUntil.getLeftSubformula(dim).asSharedPointer());
98 rightSubformulas.push_back(origBoundedUntil.getRightSubformula(dim).asSharedPointer());
100 timeBoundReferences.push_back(origBoundedUntil.getTimeBoundReference(dim));
102 if (origBoundedUntil.hasLowerBound(dim)) {
103 lowerBounds.push_back(
storm::logic::TimeBound(origBoundedUntil.isLowerBoundStrict(dim), origBoundedUntil.getLowerBound(dim)));
105 lowerBounds.push_back(std::nullopt);
107 if (origBoundedUntil.hasUpperBound(dim)) {
108 upperBounds.push_back(
storm::logic::TimeBound(origBoundedUntil.isUpperBoundStrict(dim), origBoundedUntil.getUpperBound(dim)));
110 upperBounds.push_back(std::nullopt);
115 if (origBoundedUntil.hasLowerBound(dim)) {
116 zero = origBoundedUntil.getLowerBound(dim).getManager().rational(0.0);
118 STORM_LOG_THROW(origBoundedUntil.hasUpperBound(dim), storm::exceptions::InvalidOperationException,
119 "The given bounded until formula has no cost-bound for one dimension.");
120 zero = origBoundedUntil.getUpperBound(dim).getManager().rational(0.0);
123 lowerBounds.push_back(std::nullopt);
127 "Unhandled bound transformation.");
129 upperBounds.push_back(std::nullopt);
133 std::shared_ptr<storm::logic::Formula> newBoundedUntil;
134 if (origBoundedUntil.hasMultiDimensionalSubformulas()) {
135 newBoundedUntil = std::make_shared<storm::logic::BoundedUntilFormula>(leftSubformulas, rightSubformulas, lowerBounds, upperBounds, timeBoundReferences);
137 newBoundedUntil = std::make_shared<storm::logic::BoundedUntilFormula>(origBoundedUntil.getLeftSubformula().asSharedPointer(),
138 origBoundedUntil.getRightSubformula().asSharedPointer(), lowerBounds, upperBounds,
139 timeBoundReferences);
142 if (complementQuery) {
145 return std::make_shared<storm::logic::ProbabilityOperatorFormula>(newBoundedUntil, newOpInfo);
161template<
typename ValueType>
176 ValueType lower = value * (one / (prec + one));
177 ValueType upper = value * (one + prec / (prec + one));
178 return std::make_pair(lower, upper);
180 return std::pair<ValueType, ValueType>(value - prec, value + prec);
184template<
typename ModelType>
185uint64_t QuantileHelper<ModelType>::getDimension()
const {
186 return quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula().getDimension();
189template<
typename ModelType>
191 auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
193 for (uint64_t dim = 0; dim < getDimension(); ++dim) {
194 auto const& bound = boundedUntil.hasLowerBound(dim) ? boundedUntil.getLowerBound(dim) : boundedUntil.getUpperBound(dim);
195 if (bound.containsVariables()) {
202template<
typename ModelType>
203storm::expressions::Variable
const& QuantileHelper<ModelType>::getVariableForDimension(uint64_t
const& dim)
const {
204 auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
205 return (boundedUntil.hasLowerBound(dim) ? boundedUntil.getLowerBound(dim) : boundedUntil.getUpperBound(dim))
207 .asVariableExpression()
211template<
typename ModelType>
213 numCheckedEpochs = 0;
214 numPrecisionRefinements = 0;
215 swEpochAnalysis.reset();
216 swExploration.reset();
217 cachedSubQueryResults.clear();
219 std::vector<std::vector<storm::utility::ExtendedValueType<ValueType>>> result;
222 auto internalResult =
computeQuantile(envCpy, getOpenDimensions(),
false);
225 std::vector<uint64_t> permutation;
226 for (
auto const& v : quantileFormula.getBoundVariables()) {
227 uint64_t openDim = 0;
228 for (
auto dim : getOpenDimensions()) {
229 if (getVariableForDimension(dim) == v) {
230 permutation.push_back(openDim);
236 STORM_LOG_ASSERT(permutation.size() == getOpenDimensions().getNumberOfSetBits(),
"Permutation size mismatch.");
237 for (
auto const& costLimits : internalResult.first.getGenerator()) {
238 std::vector<storm::utility::ExtendedValueType<ValueType>> resultPoint;
239 for (
auto const& dim : permutation) {
243 internalResult.second[dim]));
245 result.push_back(resultPoint);
254template<
typename ModelType>
258 "Considered dimensions for a quantile query should be a subset of the set of dimensions without a fixed bound.");
261 cacheKey.
resize(cacheKey.
size() + 1, complementaryQuery);
262 auto cacheIt = cachedSubQueryResults.find(cacheKey);
263 if (cacheIt != cachedSubQueryResults.end()) {
264 return cacheIt->second;
269 std::set<storm::expressions::Variable> infinityVariables;
273 for (
auto d : getOpenDimensions()) {
274 if (consideredDimensions.
get(d)) {
275 bool hasLowerCostBound = boundedUntilOp->getSubformula().asBoundedUntilFormula().hasLowerBound(d);
276 lowerBoundedDimensions.set(d, hasLowerCostBound);
277 downwardClosedDimensions.set(d, hasLowerCostBound == hasLowerValueBound);
279 infinityVariables.insert(getVariableForDimension(d));
282 downwardClosedDimensions = downwardClosedDimensions % consideredDimensions;
283 CostLimitClosure satCostLimits(downwardClosedDimensions), unsatCostLimits(~downwardClosedDimensions);
286 bool onlyUpperCostBounds = lowerBoundedDimensions.empty();
287 bool onlyLowerCostBounds = lowerBoundedDimensions == consideredDimensions;
288 if (onlyUpperCostBounds || onlyLowerCostBounds) {
289 for (uint64_t k : consideredDimensions) {
291 subQueryDimensions.
set(k,
false);
292 bool subQueryComplement = complementaryQuery != ((onlyUpperCostBounds && hasLowerValueBound) || (onlyLowerCostBounds && !hasLowerValueBound));
293 auto subQueryResult = computeQuantile(env, subQueryDimensions, subQueryComplement);
294 for (
auto const& subQueryCostLimit : subQueryResult.first.getGenerator()) {
297 for (uint64_t dim : consideredDimensions) {
301 initPoint.push_back(subQueryCostLimit[i]);
305 if (subQueryComplement == complementaryQuery) {
306 satCostLimits.insert(initPoint);
308 unsatCostLimits.insert(initPoint);
313 STORM_LOG_WARN(
"Quantile formula considers mixtures of upper and lower reward-bounds. Termination is not guaranteed.");
317 STORM_LOG_DEBUG(
"Computing quantile for dimensions: " << consideredDimensions);
321 if (computeQuantile(env, consideredDimensions, *boundedUntilOp, lowerBoundedDimensions, satCostLimits, unsatCostLimits, rewardUnfolding)) {
322 std::vector<ValueType> scalingFactors;
323 for (uint64_t dim : consideredDimensions) {
324 scalingFactors.push_back(rewardUnfolding.getDimension(dim).scalingFactor);
326 std::pair<CostLimitClosure, std::vector<ValueType>> result(satCostLimits, scalingFactors);
327 cachedSubQueryResults.emplace(cacheKey, result);
330 STORM_LOG_WARN(
"Restarting quantile computation after " << swExploration <<
" seconds due to insufficient precision.");
331 ++numPrecisionRefinements;
337 if (current.size() == 0) {
340 uint64_t iSum = current.front().get();
341 if (iSum == candidateCostLimitSum.
get()) {
344 for (uint64_t i = 1; i < current.size(); ++i) {
345 iSum += current[i].get();
346 if (iSum == candidateCostLimitSum.
get()) {
347 ++current[i - 1].get();
348 uint64_t newVal = current[i].get() - 1;
349 current[i].get() = 0;
350 current.back().get() = newVal;
355 "The entries of the current cost limit candidate do not sum up to the current candidate sum.");
361 for (uint64_t dim = 0; dim < consideredDimensions.
size(); ++dim) {
362 if (consideredDimensions.
get(dim)) {
363 if (lowerBoundedDimensions.
get(dim)) {
365 epochAsCostLimits.push_back(
CostLimit(0));
389template<
typename ModelType>
393 CostLimitClosure& unsatCostLimits, MultiDimensionalRewardUnfolding<ValueType, true>& rewardUnfolding) {
394 auto lowerBound = rewardUnfolding.getLowerObjectiveBound();
395 auto upperBound = rewardUnfolding.getUpperObjectiveBound();
396 std::vector<ValueType> x, b;
397 std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> minMaxSolver;
398 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> linEqSolver;
399 if (!model.isNondeterministicModel()) {
403 swExploration.start();
404 bool progress =
true;
405 for (CostLimit candidateCostLimitSum(0); progress; ++candidateCostLimitSum.get()) {
406 CostLimits currentCandidate(satCostLimits.dimension(), CostLimit(0));
407 if (!currentCandidate.empty()) {
408 currentCandidate.back() = candidateCostLimitSum;
412 progress = (satCostLimits.empty() && !unsatCostLimits.full()) || (unsatCostLimits.empty() && !satCostLimits.full());
414 if (!satCostLimits.contains(currentCandidate) && !unsatCostLimits.contains(currentCandidate)) {
417 auto startEpoch = rewardUnfolding.getStartEpoch(
true);
418 auto costLimitIt = currentCandidate.begin();
419 for (uint64_t dim : consideredDimensions) {
420 if (lowerBoundedDimensions.
get(dim)) {
421 if (costLimitIt->get() > 0) {
422 rewardUnfolding.getEpochManager().setDimensionOfEpoch(startEpoch, dim, costLimitIt->get() - 1);
424 rewardUnfolding.getEpochManager().setBottomDimension(startEpoch, dim);
427 rewardUnfolding.getEpochManager().setDimensionOfEpoch(startEpoch, dim, costLimitIt->get());
431 STORM_LOG_DEBUG(
"Checking start epoch " << rewardUnfolding.getEpochManager().toString(startEpoch) <<
".");
432 auto epochSequence = rewardUnfolding.getEpochComputationOrder(startEpoch,
true);
433 for (
auto const& epoch : epochSequence) {
435 swEpochAnalysis.start();
436 auto& epochModel = rewardUnfolding.setCurrentEpoch(epoch);
437 if (model.isNondeterministicModel()) {
438 rewardUnfolding.setSolutionForCurrentEpoch(
439 epochModel.analyzeSingleObjective(env, boundedUntilOperator.
getOptimalityType(), x, b, minMaxSolver, lowerBound, upperBound));
441 rewardUnfolding.setSolutionForCurrentEpoch(epochModel.analyzeSingleObjective(env, x, b, linEqSolver, lowerBound, upperBound));
443 swEpochAnalysis.stop();
446 if (
translateEpochToCostLimits(epoch, startEpoch, consideredDimensions, lowerBoundedDimensions, rewardUnfolding.getEpochManager(),
447 epochAsCostLimits)) {
448 ValueType currValue = rewardUnfolding.getInitialStateResult(epoch);
449 bool propertySatisfied;
455 if (propertySatisfied != boundedUntilOperator.
getBound().
isSatisfied(lowerUpperValue.second)) {
457 swExploration.stop();
463 if (propertySatisfied) {
464 satCostLimits.insert(epochAsCostLimits);
466 unsatCostLimits.insert(epochAsCostLimits);
476 swExploration.stop();
SolverEnvironment & solver()
storm::RationalNumber const & getPrecision() const
void setPrecision(storm::RationalNumber value)
bool const & getRelativeTerminationCriterion() const
MinMaxSolverEnvironment & minMax()
storm::solver::EquationSolverType const & getLinearEquationSolverType() const
void setLinearEquationSolverPrecision(boost::optional< storm::RationalNumber > const &newPrecision, boost::optional< bool > const &relativePrecision=boost::none)
bool isForceSoundness() const
std::pair< boost::optional< storm::RationalNumber >, boost::optional< bool > > getPrecisionOfLinearEquationSolver(storm::solver::EquationSolverType const &solverType) const
VariableExpression const & asVariableExpression() const
bool isVariable() const
Retrieves whether the expression is a variable.
bool containsVariables() const
Retrieves whether the expression contains a variable.
BaseExpression const & getBaseExpression() const
Retrieves the base expression underlying this expression object.
bool isInitialized() const
Checks whether the object encapsulates a base-expression.
Variable const & getVariable() const
Retrieves the variable associated with this expression.
static bool unionFull(CostLimitClosure const &first, CostLimitClosure const &second)
Returns true if the union of the two closures is full, i.e., contains every point.
static CostLimit infinity()
uint64_t const & get() const
bool isBottomDimension(Epoch const &epoch, uint64_t const &dimension) const
uint64_t getDimensionOfEpoch(Epoch const &epoch, uint64_t const &dimension) const
QuantileHelper(ModelType const &model, storm::logic::QuantileFormula const &quantileFormula)
std::vector< std::vector< storm::utility::ExtendedValueType< ValueType > > > computeQuantile(Environment const &env)
A bit vector that is internally represented as a vector of 64-bit values.
bool isSubsetOf(BitVector const &other) const
Checks whether all bits that are set in the current bit vector are also set in the given bit vector.
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
size_t size() const
Retrieves the number of bits this bit vector can store.
void resize(uint64_t newLength, bool init=false)
Resizes the bit vector to hold the given new number of bits.
bool get(uint64_t index) const
Retrieves the truth value of the bit at the given index and performs a bound check.
#define STORM_LOG_WARN(message)
#define STORM_LOG_DEBUG(message)
#define STORM_LOG_STATISTICS(message)
#define STORM_LOG_ASSERT(cond, message)
#define STORM_LOG_WARN_COND(cond, message)
#define STORM_LOG_THROW(cond, exception, message)
SFTBDDChecker::ValueType ValueType
bool isLowerBound(ComparisonType t)
ComparisonType invert(ComparisonType t)
std::pair< ValueType, ValueType > getLowerUpperBound(storm::Environment const &env, ValueType const &factor, ValueType const &value, bool minMax=true)
Computes a lower / upper bound on the actual result of a sound minmax or linear equation solver.
void increasePrecision(storm::Environment &env)
Increases the precision of solver results.
bool translateEpochToCostLimits(EpochManager::Epoch const &epoch, EpochManager::Epoch const &startEpoch, storm::storage::BitVector const &consideredDimensions, storm::storage::BitVector const &lowerBoundedDimensions, EpochManager const &epochManager, CostLimits &epochAsCostLimits)
std::shared_ptr< storm::logic::ProbabilityOperatorFormula > transformBoundedUntilOperator(storm::logic::ProbabilityOperatorFormula const &boundedUntilOperator, std::vector< BoundTransformation > const &transformations, bool complementQuery=false)
std::vector< CostLimit > CostLimits
bool getNextCandidateCostLimit(CostLimit const &candidateCostLimitSum, CostLimits ¤t)
ExtendedValueType< ValueType > positiveInfinity()
std::conditional_t< storm::NumberTraits< ValueType >::HasInfinity, ValueType, ExtendedNumber< ValueType > > ExtendedValueType
The type to use for values that may be infinite.
TargetType convertNumber(SourceType const &number)
bool isSatisfied(ValueType const &compareValue) const