3#include <boost/optional.hpp>
28namespace modelchecker {
30namespace rewardbounded {
32template<
typename ModelType>
34 : model(model), quantileFormula(quantileFormula) {
36 std::set<storm::expressions::Variable> quantileVariables;
38 STORM_LOG_THROW(quantileVariables.count(quantileVariable) == 0, storm::exceptions::NotSupportedException,
39 "Quantile formula considers the same bound variable twice.");
40 quantileVariables.insert(quantileVariable);
43 "Quantile formula needs probability operator inside. The formula " << quantileFormula <<
" is not supported.");
45 STORM_LOG_THROW(probOpFormula.hasBound(), storm::exceptions::InvalidOperationException,
46 "Probability operator inside quantile formula needs to have a bound.");
47 STORM_LOG_THROW(!model.isNondeterministicModel() || probOpFormula.hasOptimalityType(), storm::exceptions::InvalidOperationException,
48 "Probability operator inside quantile formula needs to have an optimality type.");
51 "Probability operator inside quantile formula needs to have bound > or <=. The specified comparison type might lead to "
54 STORM_LOG_THROW(probOpFormula.getSubformula().isBoundedUntilFormula(), storm::exceptions::NotSupportedException,
55 "Quantile formula needs bounded until probability operator formula as subformula. The formula " << quantileFormula <<
" is not supported.");
56 auto const& boundedUntilFormula = probOpFormula.getSubformula().asBoundedUntilFormula();
57 std::set<storm::expressions::Variable> boundVariables;
58 for (uint64_t dim = 0; dim < boundedUntilFormula.getDimension(); ++dim) {
60 if (boundedUntilFormula.hasUpperBound(dim)) {
61 STORM_LOG_THROW(!boundedUntilFormula.hasLowerBound(dim), storm::exceptions::NotSupportedException,
62 "Interval bounds are not supported within quantile formulas.");
63 STORM_LOG_THROW(!boundedUntilFormula.isUpperBoundStrict(dim), storm::exceptions::NotSupportedException,
64 "Only non-strict upper reward bounds are supported for quantiles.");
65 boundExpression = boundedUntilFormula.getUpperBound(dim);
66 }
else if (boundedUntilFormula.hasLowerBound(dim)) {
67 STORM_LOG_THROW(!boundedUntilFormula.isLowerBoundStrict(dim), storm::exceptions::NotSupportedException,
68 "Only non-strict lower reward bounds are supported for quantiles.");
69 boundExpression = boundedUntilFormula.getLowerBound(dim);
73 boundExpression.
isVariable(), storm::exceptions::NotSupportedException,
74 "Non-trivial bound expressions such as '" << boundExpression <<
"' are not supported. Either specify a constant or a quantile variable.");
76 STORM_LOG_THROW(boundVariables.count(boundVariable) == 0, storm::exceptions::NotSupportedException,
77 "Variable " << boundExpression <<
" occurs at multiple reward bounds.");
78 boundVariables.insert(boundVariable);
79 STORM_LOG_THROW(quantileVariables.count(boundVariable) == 1, storm::exceptions::NotSupportedException,
80 "The formula contains undefined constant '" << boundExpression <<
"'.");
87 std::vector<BoundTransformation>
const& transformations,
88 bool complementQuery =
false) {
91 "Tried to replace the bound of a dimension that is higher than the number of dimensions of the formula.");
92 std::vector<std::shared_ptr<storm::logic::Formula const>> leftSubformulas, rightSubformulas;
93 std::vector<boost::optional<storm::logic::TimeBound>> lowerBounds, upperBounds;
94 std::vector<storm::logic::TimeBoundReference> timeBoundReferences;
96 for (uint64_t dim = 0; dim < origBoundedUntil.getDimension(); ++dim) {
97 if (origBoundedUntil.hasMultiDimensionalSubformulas()) {
98 leftSubformulas.push_back(origBoundedUntil.getLeftSubformula(dim).asSharedPointer());
99 rightSubformulas.push_back(origBoundedUntil.getRightSubformula(dim).asSharedPointer());
101 timeBoundReferences.push_back(origBoundedUntil.getTimeBoundReference(dim));
103 if (origBoundedUntil.hasLowerBound(dim)) {
104 lowerBounds.push_back(
storm::logic::TimeBound(origBoundedUntil.isLowerBoundStrict(dim), origBoundedUntil.getLowerBound(dim)));
106 lowerBounds.push_back(boost::none);
108 if (origBoundedUntil.hasUpperBound(dim)) {
109 upperBounds.push_back(
storm::logic::TimeBound(origBoundedUntil.isUpperBoundStrict(dim), origBoundedUntil.getUpperBound(dim)));
111 upperBounds.push_back(boost::none);
116 if (origBoundedUntil.hasLowerBound(dim)) {
117 zero = origBoundedUntil.getLowerBound(dim).getManager().rational(0.0);
119 STORM_LOG_THROW(origBoundedUntil.hasUpperBound(dim), storm::exceptions::InvalidOperationException,
120 "The given bounded until formula has no cost-bound for one dimension.");
121 zero = origBoundedUntil.getUpperBound(dim).getManager().rational(0.0);
124 lowerBounds.push_back(boost::none);
128 "Unhandled bound transformation.");
130 upperBounds.push_back(boost::none);
134 std::shared_ptr<storm::logic::Formula> newBoundedUntil;
135 if (origBoundedUntil.hasMultiDimensionalSubformulas()) {
136 newBoundedUntil = std::make_shared<storm::logic::BoundedUntilFormula>(leftSubformulas, rightSubformulas, lowerBounds, upperBounds, timeBoundReferences);
138 newBoundedUntil = std::make_shared<storm::logic::BoundedUntilFormula>(origBoundedUntil.getLeftSubformula().asSharedPointer(),
139 origBoundedUntil.getRightSubformula().asSharedPointer(), lowerBounds, upperBounds,
140 timeBoundReferences);
143 if (complementQuery) {
146 return std::make_shared<storm::logic::ProbabilityOperatorFormula>(newBoundedUntil, newOpInfo);
152 auto factor = storm::utility::convertNumber<storm::RationalNumber, std::string>(
"0.1");
162template<
typename ValueType>
176 ValueType one = storm::utility::one<ValueType>();
177 ValueType lower = value * (one / (prec + one));
178 ValueType upper = value * (one + prec / (prec + one));
179 return std::make_pair(lower, upper);
181 return std::pair<ValueType, ValueType>(value - prec, value + prec);
185template<
typename ModelType>
186uint64_t QuantileHelper<ModelType>::getDimension()
const {
187 return quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula().getDimension();
190template<
typename ModelType>
192 auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
194 for (uint64_t dim = 0; dim < getDimension(); ++dim) {
195 auto const& bound = boundedUntil.hasLowerBound(dim) ? boundedUntil.getLowerBound(dim) : boundedUntil.getUpperBound(dim);
196 if (bound.containsVariables()) {
203template<
typename ModelType>
205 auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula();
206 return (boundedUntil.hasLowerBound(dim) ? boundedUntil.getLowerBound(dim) : boundedUntil.getUpperBound(dim))
208 .asVariableExpression()
212template<
typename ModelType>
214 numCheckedEpochs = 0;
215 numPrecisionRefinements = 0;
216 swEpochAnalysis.reset();
217 swExploration.reset();
218 cachedSubQueryResults.clear();
220 std::vector<std::vector<ValueType>> result;
223 auto internalResult = computeQuantile(envCpy, getOpenDimensions(),
false);
226 std::vector<uint64_t> permutation;
227 for (
auto const& v : quantileFormula.getBoundVariables()) {
228 uint64_t openDim = 0;
229 for (
auto dim : getOpenDimensions()) {
230 if (getVariableForDimension(dim) == v) {
231 permutation.push_back(openDim);
237 assert(permutation.size() == getOpenDimensions().getNumberOfSetBits());
238 for (
auto const& costLimits : internalResult.first.getGenerator()) {
239 std::vector<ValueType> resultPoint;
240 for (
auto const& dim : permutation) {
242 resultPoint.push_back(cl.
isInfinity() ? storm::utility::infinity<ValueType>()
243 : storm::utility::convertNumber<ValueType>(cl.
get()) * internalResult.second[dim]);
245 result.push_back(resultPoint);
248 std::cout <<
"Number of checked epochs: " << numCheckedEpochs <<
'\n';
249 std::cout <<
"Number of required precision refinements: " << numPrecisionRefinements <<
'\n';
250 std::cout <<
"Time for epoch exploration: " << swExploration <<
" seconds.\n";
251 std::cout <<
"\tTime for epoch model analysis: " << swEpochAnalysis <<
" seconds.\n";
256template<
typename ModelType>
260 "Considered dimensions for a quantile query should be a subset of the set of dimensions without a fixed bound.");
263 cacheKey.
resize(cacheKey.
size() + 1, complementaryQuery);
264 auto cacheIt = cachedSubQueryResults.find(cacheKey);
265 if (cacheIt != cachedSubQueryResults.end()) {
266 return cacheIt->second;
271 std::set<storm::expressions::Variable> infinityVariables;
275 for (
auto d : getOpenDimensions()) {
276 if (consideredDimensions.
get(d)) {
277 bool hasLowerCostBound = boundedUntilOp->getSubformula().asBoundedUntilFormula().hasLowerBound(d);
278 lowerBoundedDimensions.set(d, hasLowerCostBound);
279 downwardClosedDimensions.set(d, hasLowerCostBound == hasLowerValueBound);
281 infinityVariables.insert(getVariableForDimension(d));
284 downwardClosedDimensions = downwardClosedDimensions % consideredDimensions;
285 CostLimitClosure satCostLimits(downwardClosedDimensions), unsatCostLimits(~downwardClosedDimensions);
288 bool onlyUpperCostBounds = lowerBoundedDimensions.empty();
289 bool onlyLowerCostBounds = lowerBoundedDimensions == consideredDimensions;
290 if (onlyUpperCostBounds || onlyLowerCostBounds) {
291 for (
auto k : consideredDimensions) {
293 subQueryDimensions.
set(k,
false);
294 bool subQueryComplement = complementaryQuery != ((onlyUpperCostBounds && hasLowerValueBound) || (onlyLowerCostBounds && !hasLowerValueBound));
295 auto subQueryResult = computeQuantile(env, subQueryDimensions, subQueryComplement);
296 for (
auto const& subQueryCostLimit : subQueryResult.first.getGenerator()) {
299 for (
auto dim : consideredDimensions) {
303 initPoint.push_back(subQueryCostLimit[i]);
307 if (subQueryComplement == complementaryQuery) {
308 satCostLimits.insert(initPoint);
310 unsatCostLimits.insert(initPoint);
315 STORM_LOG_WARN(
"Quantile formula considers mixtures of upper and lower reward-bounds. Termination is not guaranteed.");
319 STORM_LOG_DEBUG(
"Computing quantile for dimensions: " << consideredDimensions);
322 MultiDimensionalRewardUnfolding<ValueType, true> rewardUnfolding(model, boundedUntilOp, infinityVariables);
323 if (computeQuantile(env, consideredDimensions, *boundedUntilOp, lowerBoundedDimensions, satCostLimits, unsatCostLimits, rewardUnfolding)) {
324 std::vector<ValueType> scalingFactors;
325 for (
auto dim : consideredDimensions) {
326 scalingFactors.push_back(rewardUnfolding.getDimension(dim).scalingFactor);
328 std::pair<CostLimitClosure, std::vector<ValueType>> result(satCostLimits, scalingFactors);
329 cachedSubQueryResults.emplace(cacheKey, result);
332 STORM_LOG_WARN(
"Restarting quantile computation after " << swExploration <<
" seconds due to insufficient precision.");
333 ++numPrecisionRefinements;
339 if (current.size() == 0) {
342 uint64_t iSum = current.front().get();
343 if (iSum == candidateCostLimitSum.
get()) {
346 for (uint64_t i = 1; i < current.size(); ++i) {
347 iSum += current[i].get();
348 if (iSum == candidateCostLimitSum.
get()) {
349 ++current[i - 1].get();
350 uint64_t newVal = current[i].get() - 1;
351 current[i].get() = 0;
352 current.back().get() = newVal;
357 "The entries of the current cost limit candidate do not sum up to the current candidate sum.");
363 for (uint64_t dim = 0; dim < consideredDimensions.
size(); ++dim) {
364 if (consideredDimensions.
get(dim)) {
365 if (lowerBoundedDimensions.
get(dim)) {
367 epochAsCostLimits.push_back(
CostLimit(0));
391template<
typename ModelType>
395 CostLimitClosure& unsatCostLimits, MultiDimensionalRewardUnfolding<ValueType, true>& rewardUnfolding) {
396 auto lowerBound = rewardUnfolding.getLowerObjectiveBound();
397 auto upperBound = rewardUnfolding.getUpperObjectiveBound();
398 std::vector<ValueType> x, b;
399 std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> minMaxSolver;
400 std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>> linEqSolver;
401 if (!model.isNondeterministicModel()) {
405 swExploration.start();
406 bool progress =
true;
407 for (CostLimit candidateCostLimitSum(0); progress; ++candidateCostLimitSum.get()) {
408 CostLimits currentCandidate(satCostLimits.dimension(), CostLimit(0));
409 if (!currentCandidate.empty()) {
410 currentCandidate.back() = candidateCostLimitSum;
414 progress = (satCostLimits.empty() && !unsatCostLimits.full()) || (unsatCostLimits.empty() && !satCostLimits.full());
416 if (!satCostLimits.contains(currentCandidate) && !unsatCostLimits.contains(currentCandidate)) {
419 auto startEpoch = rewardUnfolding.getStartEpoch(
true);
420 auto costLimitIt = currentCandidate.begin();
421 for (
auto dim : consideredDimensions) {
422 if (lowerBoundedDimensions.
get(dim)) {
423 if (costLimitIt->get() > 0) {
424 rewardUnfolding.getEpochManager().setDimensionOfEpoch(startEpoch, dim, costLimitIt->get() - 1);
426 rewardUnfolding.getEpochManager().setBottomDimension(startEpoch, dim);
429 rewardUnfolding.getEpochManager().setDimensionOfEpoch(startEpoch, dim, costLimitIt->get());
433 STORM_LOG_DEBUG(
"Checking start epoch " << rewardUnfolding.getEpochManager().toString(startEpoch) <<
".");
434 auto epochSequence = rewardUnfolding.getEpochComputationOrder(startEpoch,
true);
435 for (
auto const& epoch : epochSequence) {
437 swEpochAnalysis.start();
438 auto& epochModel = rewardUnfolding.setCurrentEpoch(epoch);
439 if (model.isNondeterministicModel()) {
440 rewardUnfolding.setSolutionForCurrentEpoch(
441 epochModel.analyzeSingleObjective(env, boundedUntilOperator.
getOptimalityType(), x, b, minMaxSolver, lowerBound, upperBound));
443 rewardUnfolding.setSolutionForCurrentEpoch(epochModel.analyzeSingleObjective(env, x, b, linEqSolver, lowerBound, upperBound));
445 swEpochAnalysis.stop();
448 if (
translateEpochToCostLimits(epoch, startEpoch, consideredDimensions, lowerBoundedDimensions, rewardUnfolding.getEpochManager(),
449 epochAsCostLimits)) {
450 ValueType currValue = rewardUnfolding.getInitialStateResult(epoch);
451 bool propertySatisfied;
454 storm::utility::convertNumber<ValueType>(rewardUnfolding.getEpochManager().getSumOfDimensions(epoch) + 1);
457 if (propertySatisfied != boundedUntilOperator.
getBound().
isSatisfied(lowerUpperValue.second)) {
459 swExploration.stop();
465 if (propertySatisfied) {
466 satCostLimits.insert(epochAsCostLimits);
468 unsatCostLimits.insert(epochAsCostLimits);
478 swExploration.stop();
482template class QuantileHelper<storm::models::sparse::Mdp<double>>;
483template class QuantileHelper<storm::models::sparse::Mdp<storm::RationalNumber>>;
484template class QuantileHelper<storm::models::sparse::Dtmc<double>>;
485template class QuantileHelper<storm::models::sparse::Dtmc<storm::RationalNumber>>;
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
std::vector< std::vector< ValueType > > computeQuantile(Environment const &env)
QuantileHelper(ModelType const &model, storm::logic::QuantileFormula const &quantileFormula)
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 resize(uint_fast64_t newLength, bool init=false)
Resizes the bit vector to hold the given new number of bits.
void set(uint_fast64_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.
bool get(uint_fast64_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_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)
SettingsType const & getModule()
Get module.
bool isSatisfied(ValueType const &compareValue) const