58 changed files with 1745 additions and 335 deletions
-
3CHANGELOG.md
-
25src/storm-pars/parser/ParameterRegionParser.cpp
-
27src/storm-parsers/parser/FormulaParserGrammar.cpp
-
7src/storm-parsers/parser/FormulaParserGrammar.h
-
15src/storm/logic/Bound.h
-
5src/storm/logic/CloneVisitor.cpp
-
1src/storm/logic/CloneVisitor.h
-
12src/storm/logic/Formula.cpp
-
4src/storm/logic/Formula.h
-
4src/storm/logic/FormulaInformationVisitor.cpp
-
1src/storm/logic/FormulaInformationVisitor.h
-
1src/storm/logic/FormulaVisitor.h
-
1src/storm/logic/Formulas.h
-
1src/storm/logic/FormulasForwardDeclarations.h
-
11src/storm/logic/FragmentChecker.cpp
-
1src/storm/logic/FragmentChecker.h
-
43src/storm/logic/FragmentSpecification.cpp
-
11src/storm/logic/FragmentSpecification.h
-
4src/storm/logic/LiftableTransitionRewardsVisitor.cpp
-
1src/storm/logic/LiftableTransitionRewardsVisitor.h
-
96src/storm/logic/QuantileFormula.cpp
-
41src/storm/logic/QuantileFormula.h
-
3src/storm/logic/TimeBound.h
-
2src/storm/logic/TimeBoundType.h
-
4src/storm/logic/ToExpressionVisitor.cpp
-
1src/storm/logic/ToExpressionVisitor.h
-
7src/storm/modelchecker/AbstractModelChecker.cpp
-
5src/storm/modelchecker/AbstractModelChecker.h
-
2src/storm/modelchecker/csl/SparseMarkovAutomatonCslModelChecker.cpp
-
4src/storm/modelchecker/multiobjective/pcaa/RewardBoundedMdpPcaaWeightVectorChecker.cpp
-
2src/storm/modelchecker/multiobjective/pcaa/RewardBoundedMdpPcaaWeightVectorChecker.h
-
26src/storm/modelchecker/prctl/SparseMdpPrctlModelChecker.cpp
-
1src/storm/modelchecker/prctl/SparseMdpPrctlModelChecker.h
-
81src/storm/modelchecker/prctl/helper/SparseDtmcPrctlHelper.cpp
-
107src/storm/modelchecker/prctl/helper/SparseMdpPrctlHelper.cpp
-
27src/storm/modelchecker/prctl/helper/rewardbounded/Dimension.h
-
9src/storm/modelchecker/prctl/helper/rewardbounded/EpochManager.cpp
-
3src/storm/modelchecker/prctl/helper/rewardbounded/EpochManager.h
-
248src/storm/modelchecker/prctl/helper/rewardbounded/EpochModel.cpp
-
48src/storm/modelchecker/prctl/helper/rewardbounded/EpochModel.h
-
218src/storm/modelchecker/prctl/helper/rewardbounded/MultiDimensionalRewardUnfolding.cpp
-
52src/storm/modelchecker/prctl/helper/rewardbounded/MultiDimensionalRewardUnfolding.h
-
92src/storm/modelchecker/prctl/helper/rewardbounded/ProductModel.cpp
-
8src/storm/modelchecker/prctl/helper/rewardbounded/ProductModel.h
-
605src/storm/modelchecker/prctl/helper/rewardbounded/QuantileHelper.cpp
-
70src/storm/modelchecker/prctl/helper/rewardbounded/QuantileHelper.h
-
4src/storm/modelchecker/results/ExplicitParetoCurveCheckResult.h
-
22src/storm/modelchecker/results/ParetoCurveCheckResult.cpp
-
6src/storm/modelchecker/results/ParetoCurveCheckResult.h
-
13src/storm/models/sparse/Model.cpp
-
20src/storm/models/sparse/Model.h
-
8src/storm/storage/MaximalEndComponentDecomposition.cpp
-
2src/storm/storage/bisimulation/NondeterministicModelBisimulationDecomposition.cpp
-
4src/storm/storage/jani/JSONExporter.cpp
-
1src/storm/storage/jani/JSONExporter.h
-
4src/storm/transformer/EndComponentEliminator.h
-
47src/storm/utility/graph.cpp
-
9src/storm/utility/graph.h
@ -0,0 +1,96 @@ |
|||
#include "storm/logic/QuantileFormula.h"
|
|||
|
|||
#include "storm/logic/FormulaVisitor.h"
|
|||
#include "storm/utility/macros.h"
|
|||
#include "storm/exceptions/InvalidArgumentException.h"
|
|||
|
|||
namespace storm { |
|||
namespace logic { |
|||
|
|||
QuantileFormula::QuantileFormula(std::vector<std::pair<storm::solver::OptimizationDirection, storm::expressions::Variable>> const& boundVariables, std::shared_ptr<Formula const> subformula) : boundVariables(boundVariables), subformula(subformula) { |
|||
STORM_LOG_THROW(!boundVariables.empty(), storm::exceptions::InvalidArgumentException, "Quantile formula without bound variables are invalid."); |
|||
} |
|||
|
|||
QuantileFormula::~QuantileFormula() { |
|||
// Intentionally left empty
|
|||
} |
|||
|
|||
bool QuantileFormula::isQuantileFormula() const { |
|||
return true; |
|||
} |
|||
|
|||
bool QuantileFormula::hasQuantitativeResult() const { |
|||
return true; |
|||
} |
|||
|
|||
bool QuantileFormula::hasNumericalResult() const { |
|||
return !isMultiDimensional(); |
|||
} |
|||
|
|||
bool QuantileFormula::hasParetoCurveResult() const { |
|||
return isMultiDimensional(); |
|||
} |
|||
|
|||
Formula const& QuantileFormula::getSubformula() const { |
|||
return *subformula; |
|||
} |
|||
|
|||
uint64_t QuantileFormula::getDimension() const { |
|||
return boundVariables.size(); |
|||
} |
|||
|
|||
bool QuantileFormula::isMultiDimensional() const { |
|||
return getDimension() > 1; |
|||
} |
|||
|
|||
storm::expressions::Variable const& QuantileFormula::getBoundVariable() const { |
|||
STORM_LOG_THROW(boundVariables.size() == 1, storm::exceptions::InvalidArgumentException, "Requested unique bound variables. However, there are multiple bound variables defined."); |
|||
return boundVariables.front().second; |
|||
} |
|||
|
|||
storm::expressions::Variable const& QuantileFormula::getBoundVariable(uint64_t index) const { |
|||
STORM_LOG_THROW(index < boundVariables.size(), storm::exceptions::InvalidArgumentException, "Requested bound variable with index" << index << ". However, there are only " << boundVariables.size() << " bound variables."); |
|||
return boundVariables[index].second; |
|||
} |
|||
|
|||
std::vector<std::pair<storm::solver::OptimizationDirection, storm::expressions::Variable>> const& QuantileFormula::getBoundVariables() const { |
|||
return boundVariables; |
|||
} |
|||
|
|||
storm::solver::OptimizationDirection const& QuantileFormula::getOptimizationDirection() const { |
|||
STORM_LOG_THROW(boundVariables.size() == 1, storm::exceptions::InvalidArgumentException, "Requested unique optimization direction of the bound variables. However, there are multiple bound variables defined."); |
|||
return boundVariables.front().first; |
|||
} |
|||
|
|||
storm::solver::OptimizationDirection const& QuantileFormula::getOptimizationDirection(uint64_t index) const { |
|||
STORM_LOG_THROW(index < boundVariables.size(), storm::exceptions::InvalidArgumentException, "Requested optimization direction with index" << index << ". However, there are only " << boundVariables.size() << " bound variables."); |
|||
return boundVariables[index].first; |
|||
} |
|||
|
|||
boost::any QuantileFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { |
|||
return visitor.visit(*this, data); |
|||
} |
|||
|
|||
void QuantileFormula::gatherAtomicExpressionFormulas(std::vector<std::shared_ptr<AtomicExpressionFormula const>>& atomicExpressionFormulas) const { |
|||
subformula->gatherAtomicExpressionFormulas(atomicExpressionFormulas); |
|||
} |
|||
|
|||
void QuantileFormula::gatherAtomicLabelFormulas(std::vector<std::shared_ptr<AtomicLabelFormula const>>& atomicLabelFormulas) const { |
|||
subformula->gatherAtomicLabelFormulas(atomicLabelFormulas); |
|||
} |
|||
|
|||
void QuantileFormula::gatherReferencedRewardModels(std::set<std::string>& referencedRewardModels) const { |
|||
subformula->gatherReferencedRewardModels(referencedRewardModels); |
|||
} |
|||
|
|||
std::ostream& QuantileFormula::writeToStream(std::ostream& out) const { |
|||
out << "quantile("; |
|||
for (auto const& bv : boundVariables) { |
|||
out << (storm::solver::minimize(bv.first) ? "min" : "max") << " " << bv.second.getName() << ", "; |
|||
} |
|||
subformula->writeToStream(out); |
|||
out << ")"; |
|||
return out; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
#pragma once |
|||
|
|||
#include "storm/logic/Formula.h" |
|||
#include "storm/solver/OptimizationDirection.h" |
|||
|
|||
namespace storm { |
|||
namespace logic { |
|||
class QuantileFormula : public Formula { |
|||
public: |
|||
QuantileFormula(std::vector<std::pair<storm::solver::OptimizationDirection, storm::expressions::Variable>> const& boundVariables, std::shared_ptr<Formula const> subformula); |
|||
|
|||
virtual ~QuantileFormula(); |
|||
|
|||
virtual bool isQuantileFormula() const override; |
|||
|
|||
virtual bool hasQuantitativeResult() const override; // Result is numerical or a pareto curve |
|||
virtual bool hasNumericalResult() const; // Result is numerical |
|||
virtual bool hasParetoCurveResult() const; // Result is a pareto curve |
|||
|
|||
Formula const& getSubformula() const; |
|||
uint64_t getDimension() const; |
|||
bool isMultiDimensional() const; |
|||
|
|||
storm::expressions::Variable const& getBoundVariable() const; |
|||
storm::expressions::Variable const& getBoundVariable(uint64_t index) const; |
|||
std::vector<std::pair<storm::solver::OptimizationDirection, storm::expressions::Variable>> const& getBoundVariables() const; |
|||
storm::solver::OptimizationDirection const& getOptimizationDirection() const; |
|||
storm::solver::OptimizationDirection const& getOptimizationDirection(uint64_t index) const; |
|||
|
|||
virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; |
|||
virtual void gatherAtomicExpressionFormulas(std::vector<std::shared_ptr<AtomicExpressionFormula const>>& atomicExpressionFormulas) const override; |
|||
virtual void gatherAtomicLabelFormulas(std::vector<std::shared_ptr<AtomicLabelFormula const>>& atomicLabelFormulas) const override; |
|||
virtual void gatherReferencedRewardModels(std::set<std::string>& referencedRewardModels) const override; |
|||
|
|||
virtual std::ostream& writeToStream(std::ostream& out) const override; |
|||
private: |
|||
std::vector<std::pair<storm::solver::OptimizationDirection, storm::expressions::Variable>> boundVariables; |
|||
std::shared_ptr<Formula const> subformula; |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,248 @@ |
|||
#include "storm/modelchecker/prctl/helper/rewardbounded/EpochModel.h"
|
|||
#include "storm/modelchecker/prctl/helper/rewardbounded/MultiDimensionalRewardUnfolding.h"
|
|||
|
|||
#include "storm/exceptions/UncheckedRequirementException.h"
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace helper { |
|||
namespace rewardbounded { |
|||
|
|||
|
|||
|
|||
template<typename ValueType> |
|||
std::vector<ValueType> analyzeTrivialDtmcEpochModel(EpochModel<ValueType, true>& epochModel) { |
|||
|
|||
std::vector<ValueType> epochResult; |
|||
epochResult.reserve(epochModel.epochInStates.getNumberOfSetBits()); |
|||
auto stepSolutionIt = epochModel.stepSolutions.begin(); |
|||
auto stepChoiceIt = epochModel.stepChoices.begin(); |
|||
for (auto const& state : epochModel.epochInStates) { |
|||
while (*stepChoiceIt < state) { |
|||
++stepChoiceIt; |
|||
++stepSolutionIt; |
|||
} |
|||
if (epochModel.objectiveRewardFilter.front().get(state)) { |
|||
if (*stepChoiceIt == state) { |
|||
epochResult.push_back(epochModel.objectiveRewards.front()[state] + *stepSolutionIt); |
|||
} else { |
|||
epochResult.push_back(epochModel.objectiveRewards.front()[state]); |
|||
} |
|||
} else { |
|||
if (*stepChoiceIt == state) { |
|||
epochResult.push_back(*stepSolutionIt); |
|||
} else { |
|||
epochResult.push_back(storm::utility::zero<ValueType>()); |
|||
} |
|||
} |
|||
} |
|||
return epochResult; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<ValueType> analyzeNonTrivialDtmcEpochModel(Environment const& env, EpochModel<ValueType, true>& epochModel, std::vector<ValueType>& x, std::vector<ValueType>& b, std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>>& linEqSolver, boost::optional<ValueType> const& lowerBound, boost::optional<ValueType> const& upperBound) { |
|||
|
|||
// Update some data for the case that the Matrix has changed
|
|||
if (epochModel.epochMatrixChanged) { |
|||
x.assign(epochModel.epochMatrix.getRowGroupCount(), storm::utility::zero<ValueType>()); |
|||
storm::solver::GeneralLinearEquationSolverFactory<ValueType> linearEquationSolverFactory; |
|||
linEqSolver = linearEquationSolverFactory.create(env, epochModel.epochMatrix); |
|||
linEqSolver->setCachingEnabled(true); |
|||
auto req = linEqSolver->getRequirements(env); |
|||
if (lowerBound) { |
|||
linEqSolver->setLowerBound(lowerBound.get()); |
|||
req.clearLowerBounds(); |
|||
} |
|||
if (upperBound) { |
|||
linEqSolver->setUpperBound(upperBound.get()); |
|||
req.clearUpperBounds(); |
|||
} |
|||
STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked."); |
|||
} |
|||
|
|||
// Prepare the right hand side of the equation system
|
|||
b.assign(epochModel.epochMatrix.getRowCount(), storm::utility::zero<ValueType>()); |
|||
std::vector<ValueType> const& objectiveValues = epochModel.objectiveRewards.front(); |
|||
for (auto const& choice : epochModel.objectiveRewardFilter.front()) { |
|||
b[choice] = objectiveValues[choice]; |
|||
} |
|||
auto stepSolutionIt = epochModel.stepSolutions.begin(); |
|||
for (auto const& choice : epochModel.stepChoices) { |
|||
b[choice] += *stepSolutionIt; |
|||
++stepSolutionIt; |
|||
} |
|||
assert(stepSolutionIt == epochModel.stepSolutions.end()); |
|||
|
|||
// Solve the minMax equation system
|
|||
linEqSolver->solveEquations(env, x, b); |
|||
|
|||
return storm::utility::vector::filterVector(x, epochModel.epochInStates); |
|||
} |
|||
|
|||
|
|||
|
|||
template<typename ValueType> |
|||
std::vector<ValueType> analyzeTrivialMdpEpochModel(OptimizationDirection dir, EpochModel<ValueType, true>& epochModel) { |
|||
// Assert that the epoch model is indeed trivial
|
|||
assert(epochModel.epochMatrix.getEntryCount() == 0); |
|||
|
|||
std::vector<ValueType> epochResult; |
|||
epochResult.reserve(epochModel.epochInStates.getNumberOfSetBits()); |
|||
|
|||
auto stepSolutionIt = epochModel.stepSolutions.begin(); |
|||
auto stepChoiceIt = epochModel.stepChoices.begin(); |
|||
for (auto const& state : epochModel.epochInStates) { |
|||
// Obtain the best choice for this state
|
|||
ValueType bestValue; |
|||
uint64_t lastChoice = epochModel.epochMatrix.getRowGroupIndices()[state + 1]; |
|||
bool isFirstChoice = true; |
|||
for (uint64_t choice = epochModel.epochMatrix.getRowGroupIndices()[state]; choice < lastChoice; ++choice) { |
|||
while (*stepChoiceIt < choice) { |
|||
++stepChoiceIt; |
|||
++stepSolutionIt; |
|||
} |
|||
|
|||
ValueType choiceValue = storm::utility::zero<ValueType>(); |
|||
if (epochModel.objectiveRewardFilter.front().get(choice)) { |
|||
choiceValue += epochModel.objectiveRewards.front()[choice]; |
|||
} |
|||
if (*stepChoiceIt == choice) { |
|||
choiceValue += *stepSolutionIt; |
|||
} |
|||
|
|||
if (isFirstChoice) { |
|||
bestValue = std::move(choiceValue); |
|||
isFirstChoice = false; |
|||
} else { |
|||
if (storm::solver::minimize(dir)) { |
|||
if (choiceValue < bestValue) { |
|||
bestValue = std::move(choiceValue); |
|||
} |
|||
} else { |
|||
if (choiceValue > bestValue) { |
|||
bestValue = std::move(choiceValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
// Insert the solution w.r.t. this choice
|
|||
epochResult.push_back(std::move(bestValue)); |
|||
} |
|||
return epochResult; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
std::vector<ValueType> analyzeNonTrivialMdpEpochModel(Environment const& env, OptimizationDirection dir, EpochModel<ValueType, true>& epochModel, std::vector<ValueType>& x, std::vector<ValueType>& b, std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>>& minMaxSolver, boost::optional<ValueType> const& lowerBound, boost::optional<ValueType> const& upperBound) { |
|||
|
|||
// Update some data for the case that the Matrix has changed
|
|||
if (epochModel.epochMatrixChanged) { |
|||
x.assign(epochModel.epochMatrix.getRowGroupCount(), storm::utility::zero<ValueType>()); |
|||
storm::solver::GeneralMinMaxLinearEquationSolverFactory<ValueType> minMaxLinearEquationSolverFactory; |
|||
minMaxSolver = minMaxLinearEquationSolverFactory.create(env, epochModel.epochMatrix); |
|||
minMaxSolver->setHasUniqueSolution(); |
|||
minMaxSolver->setOptimizationDirection(dir); |
|||
minMaxSolver->setCachingEnabled(true); |
|||
minMaxSolver->setTrackScheduler(true); |
|||
auto req = minMaxSolver->getRequirements(env, dir, false); |
|||
if (lowerBound) { |
|||
minMaxSolver->setLowerBound(lowerBound.get()); |
|||
req.clearLowerBounds(); |
|||
} |
|||
if (upperBound) { |
|||
minMaxSolver->setUpperBound(upperBound.get()); |
|||
req.clearUpperBounds(); |
|||
} |
|||
STORM_LOG_THROW(!req.hasEnabledCriticalRequirement(), storm::exceptions::UncheckedRequirementException, "Solver requirements " + req.getEnabledRequirementsAsString() + " not checked."); |
|||
minMaxSolver->setRequirementsChecked(); |
|||
} else { |
|||
auto choicesTmp = minMaxSolver->getSchedulerChoices(); |
|||
minMaxSolver->setInitialScheduler(std::move(choicesTmp)); |
|||
} |
|||
|
|||
// Prepare the right hand side of the equation system
|
|||
b.assign(epochModel.epochMatrix.getRowCount(), storm::utility::zero<ValueType>()); |
|||
std::vector<ValueType> const& objectiveValues = epochModel.objectiveRewards.front(); |
|||
for (auto const& choice : epochModel.objectiveRewardFilter.front()) { |
|||
b[choice] = objectiveValues[choice]; |
|||
} |
|||
auto stepSolutionIt = epochModel.stepSolutions.begin(); |
|||
for (auto const& choice : epochModel.stepChoices) { |
|||
b[choice] += *stepSolutionIt; |
|||
++stepSolutionIt; |
|||
} |
|||
assert(stepSolutionIt == epochModel.stepSolutions.end()); |
|||
|
|||
// Solve the minMax equation system
|
|||
minMaxSolver->solveEquations(env, x, b); |
|||
|
|||
return storm::utility::vector::filterVector(x, epochModel.epochInStates); |
|||
} |
|||
|
|||
template<> |
|||
std::vector<double> EpochModel<double, true>::analyzeSingleObjective( |
|||
const storm::Environment &env, std::vector<double> &x, std::vector<double> &b, |
|||
std::unique_ptr<storm::solver::LinearEquationSolver<double>> &linEqSolver, |
|||
const boost::optional<double> &lowerBound, const boost::optional<double> &upperBound) { |
|||
STORM_LOG_ASSERT(epochMatrix.hasTrivialRowGrouping(), "This operation is only allowed if no nondeterminism is present."); |
|||
STORM_LOG_ASSERT(equationSolverProblemFormat.is_initialized(), "Unknown equation problem format."); |
|||
// If the epoch matrix is empty we do not need to solve a linear equation system
|
|||
bool convertToEquationSystem = (equationSolverProblemFormat == storm::solver::LinearEquationSolverProblemFormat::EquationSystem); |
|||
if ((convertToEquationSystem && epochMatrix.isIdentityMatrix()) || (!convertToEquationSystem && epochMatrix.getEntryCount() == 0)) { |
|||
return analyzeTrivialDtmcEpochModel<double>(*this); |
|||
} else { |
|||
return analyzeNonTrivialDtmcEpochModel<double>(env, *this, x, b, linEqSolver, lowerBound, upperBound); |
|||
} |
|||
} |
|||
|
|||
template<> |
|||
std::vector<double> EpochModel<double, true>::analyzeSingleObjective( |
|||
const storm::Environment &env, storm::OptimizationDirection dir, std::vector<double> &x, |
|||
std::vector<double> &b, |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<double>> &minMaxSolver, |
|||
const boost::optional<double> &lowerBound, const boost::optional<double> &upperBound) { |
|||
// If the epoch matrix is empty we do not need to solve a linear equation system
|
|||
if (epochMatrix.getEntryCount() == 0) { |
|||
return analyzeTrivialMdpEpochModel<double>(dir, *this); |
|||
} else { |
|||
return analyzeNonTrivialMdpEpochModel<double>(env, dir, *this, x, b, minMaxSolver, lowerBound, upperBound); |
|||
} |
|||
} |
|||
|
|||
template<> |
|||
std::vector<storm::RationalNumber> EpochModel<storm::RationalNumber, true>::analyzeSingleObjective( |
|||
const storm::Environment &env, std::vector<storm::RationalNumber> &x, std::vector<storm::RationalNumber> &b, |
|||
std::unique_ptr<storm::solver::LinearEquationSolver<storm::RationalNumber>> &linEqSolver, |
|||
const boost::optional<storm::RationalNumber> &lowerBound, const boost::optional<storm::RationalNumber> &upperBound) { |
|||
STORM_LOG_ASSERT(epochMatrix.hasTrivialRowGrouping(), "This operation is only allowed if no nondeterminism is present."); |
|||
STORM_LOG_ASSERT(equationSolverProblemFormat.is_initialized(), "Unknown equation problem format."); |
|||
// If the epoch matrix is empty we do not need to solve a linear equation system
|
|||
bool convertToEquationSystem = (equationSolverProblemFormat == storm::solver::LinearEquationSolverProblemFormat::EquationSystem); |
|||
if ((convertToEquationSystem && epochMatrix.isIdentityMatrix()) || (!convertToEquationSystem && epochMatrix.getEntryCount() == 0)) { |
|||
return analyzeTrivialDtmcEpochModel<storm::RationalNumber>(*this); |
|||
} else { |
|||
return analyzeNonTrivialDtmcEpochModel<storm::RationalNumber>(env, *this, x, b, linEqSolver, lowerBound, upperBound); |
|||
} |
|||
} |
|||
|
|||
template<> |
|||
std::vector<storm::RationalNumber> EpochModel<storm::RationalNumber, true>::analyzeSingleObjective( |
|||
const storm::Environment &env, storm::OptimizationDirection dir, std::vector<storm::RationalNumber> &x, |
|||
std::vector<storm::RationalNumber> &b, |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<storm::RationalNumber>> &minMaxSolver, |
|||
const boost::optional<storm::RationalNumber> &lowerBound, const boost::optional<storm::RationalNumber> &upperBound) { |
|||
// If the epoch matrix is empty we do not need to solve a linear equation system
|
|||
if (epochMatrix.getEntryCount() == 0) { |
|||
return analyzeTrivialMdpEpochModel<storm::RationalNumber>(dir, *this); |
|||
} else { |
|||
return analyzeNonTrivialMdpEpochModel<storm::RationalNumber>(env, dir, *this, x, b, minMaxSolver, lowerBound, upperBound); |
|||
} |
|||
} |
|||
|
|||
template struct EpochModel<double, true>; |
|||
template struct EpochModel<double, false>; |
|||
template struct EpochModel<storm::RationalNumber, true>; |
|||
template struct EpochModel<storm::RationalNumber, false>; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,48 @@ |
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include "storm/storage/SparseMatrix.h" |
|||
#include "storm/storage/BitVector.h" |
|||
#include "storm/solver/LinearEquationSolverProblemFormat.h" |
|||
#include "storm/solver/OptimizationDirection.h" |
|||
#include "storm/solver/MinMaxLinearEquationSolver.h" |
|||
#include "storm/solver/LinearEquationSolver.h" |
|||
|
|||
namespace storm { |
|||
class Environment; |
|||
|
|||
namespace modelchecker { |
|||
namespace helper { |
|||
namespace rewardbounded { |
|||
template<typename ValueType, bool SingleObjectiveMode> |
|||
struct EpochModel { |
|||
typedef typename std::conditional<SingleObjectiveMode, ValueType, std::vector < ValueType>>::type SolutionType; |
|||
|
|||
bool epochMatrixChanged; |
|||
storm::storage::SparseMatrix<ValueType> epochMatrix; |
|||
storm::storage::BitVector stepChoices; |
|||
std::vector<SolutionType> stepSolutions; |
|||
std::vector<std::vector<ValueType>> objectiveRewards; |
|||
std::vector<storm::storage::BitVector> objectiveRewardFilter; |
|||
storm::storage::BitVector epochInStates; |
|||
/// In case of DTMCs we have different options for the equation problem format the epoch model will have. |
|||
boost::optional<storm::solver::LinearEquationSolverProblemFormat> equationSolverProblemFormat; |
|||
|
|||
/*! |
|||
* Analyzes the epoch model, i.e., solves the represented equation system. This method assumes a nondeterministic model. |
|||
*/ |
|||
std::vector<ValueType> analyzeSingleObjective(Environment const& env, OptimizationDirection dir, std::vector<ValueType>& x, std::vector<ValueType>& b, std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>>& minMaxSolver, boost::optional<ValueType> const& lowerBound, boost::optional<ValueType> const& upperBound); |
|||
|
|||
/*! |
|||
* Analyzes the epoch model, i.e., solves the represented equation system. This method assumes a deterministic model. |
|||
*/ |
|||
std::vector<ValueType> analyzeSingleObjective(Environment const& env, std::vector<ValueType>& x, std::vector<ValueType>& b, std::unique_ptr<storm::solver::LinearEquationSolver<ValueType>>& linEqSolver, boost::optional<ValueType> const& lowerBound, boost::optional<ValueType> const& upperBound); |
|||
}; |
|||
|
|||
|
|||
|
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,605 @@ |
|||
#include "storm/modelchecker/prctl/helper/rewardbounded/QuantileHelper.h"
|
|||
|
|||
#include <set>
|
|||
#include <vector>
|
|||
#include <memory>
|
|||
#include <boost/optional.hpp>
|
|||
|
|||
#include "storm/environment/solver/MinMaxSolverEnvironment.h"
|
|||
|
|||
#include "storm/models/sparse/Mdp.h"
|
|||
#include "storm/modelchecker/prctl/helper/rewardbounded/MultiDimensionalRewardUnfolding.h"
|
|||
#include "storm/storage/expressions/ExpressionManager.h"
|
|||
#include "storm/storage/expressions/Expressions.h"
|
|||
#include "storm/storage/BitVector.h"
|
|||
#include "storm/storage/MaximalEndComponentDecomposition.h"
|
|||
#include "storm/utility/vector.h"
|
|||
#include "storm/settings/SettingsManager.h"
|
|||
#include "storm/settings/modules/CoreSettings.h"
|
|||
|
|||
#include "storm/logic/ProbabilityOperatorFormula.h"
|
|||
#include "storm/logic/BoundedUntilFormula.h"
|
|||
|
|||
#include "storm/exceptions/NotSupportedException.h"
|
|||
|
|||
namespace storm { |
|||
namespace modelchecker { |
|||
namespace helper { |
|||
namespace rewardbounded { |
|||
|
|||
template<typename ModelType> |
|||
QuantileHelper<ModelType>::QuantileHelper(ModelType const& model, storm::logic::QuantileFormula const& quantileFormula) : model(model), quantileFormula(quantileFormula) { |
|||
// Intentionally left empty
|
|||
STORM_LOG_THROW(quantileFormula.getSubformula().isProbabilityOperatorFormula(), storm::exceptions::NotSupportedException, "Quantile formula needs probability operator inside. The formula " << quantileFormula << " is not supported."); |
|||
auto const& probOpFormula = quantileFormula.getSubformula().asProbabilityOperatorFormula(); |
|||
STORM_LOG_THROW(probOpFormula.getBound().comparisonType == storm::logic::ComparisonType::Greater || probOpFormula.getBound().comparisonType == storm::logic::ComparisonType::LessEqual, storm::exceptions::NotSupportedException, "Probability operator inside quantile formula needs to have bound > or <=."); |
|||
STORM_LOG_THROW(probOpFormula.getSubformula().isBoundedUntilFormula(), storm::exceptions::NotSupportedException, "Quantile formula needs bounded until probability operator formula as subformula. The formula " << quantileFormula << " is not supported."); |
|||
auto const& boundedUntilFormula = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula(); |
|||
|
|||
// Only > and <= are supported for upper bounds. This is to make sure that Pr>0.7 [F "goal"] holds iff Pr>0.7 [F<=B "goal"] holds for some B.
|
|||
// Only >= and < are supported for lower bounds. (EC construction..)
|
|||
// TODO
|
|||
// Bounds are either constants or variables that are declared in the quantile formula.
|
|||
// Prop op has optimality type
|
|||
// No Prmin with lower cost bounds: Ec construction fails. In single obj we would do 1-Prmax[F "nonRewOrNonGoalEC"] but this invalidates other lower/upper cost bounds.
|
|||
/* Todo: Current assumptions:
|
|||
* Subformula is always prob operator with bounded until |
|||
* Each bound variable occurs at most once (change this?) |
|||
* cost bounds are assumed to be always non-strict (the epochs returned by the reward unfolding assume strict lower and non-strict upper cost bounds, though...) |
|||
* 'reasonable' quantile (e.g. not quantile(max B, Pmax>0.5 [F <=B G]) (we could also filter out these things early on) |
|||
*/ |
|||
} |
|||
|
|||
enum class BoundTransformation { |
|||
None, |
|||
GreaterZero, |
|||
GreaterEqualZero, |
|||
LessEqualZero |
|||
}; |
|||
std::shared_ptr<storm::logic::ProbabilityOperatorFormula> transformBoundedUntilOperator(storm::logic::ProbabilityOperatorFormula const& boundedUntilOperator, std::vector<BoundTransformation> const& transformations) { |
|||
auto const& origBoundedUntil = boundedUntilOperator.getSubformula().asBoundedUntilFormula(); |
|||
STORM_LOG_ASSERT(transformations.size() == origBoundedUntil.getDimension(), "Tried to replace the bound of a dimension that is higher than the number of dimensions of the formula."); |
|||
std::vector<std::shared_ptr<storm::logic::Formula const>> leftSubformulas, rightSubformulas; |
|||
std::vector<boost::optional<storm::logic::TimeBound>> lowerBounds, upperBounds; |
|||
std::vector<storm::logic::TimeBoundReference> timeBoundReferences; |
|||
|
|||
for (uint64_t dim = 0; dim < origBoundedUntil.getDimension(); ++dim) { |
|||
if (origBoundedUntil.hasMultiDimensionalSubformulas()) { |
|||
leftSubformulas.push_back(origBoundedUntil.getLeftSubformula(dim).asSharedPointer()); |
|||
rightSubformulas.push_back(origBoundedUntil.getRightSubformula(dim).asSharedPointer()); |
|||
} |
|||
timeBoundReferences.push_back(origBoundedUntil.getTimeBoundReference(dim)); |
|||
if (transformations[dim] == BoundTransformation::None) { |
|||
if (origBoundedUntil.hasLowerBound(dim)) { |
|||
lowerBounds.push_back(storm::logic::TimeBound(origBoundedUntil.isLowerBoundStrict(dim), origBoundedUntil.getLowerBound(dim))); |
|||
} else { |
|||
lowerBounds.push_back(boost::none); |
|||
} |
|||
if (origBoundedUntil.hasUpperBound(dim)) { |
|||
upperBounds.push_back(storm::logic::TimeBound(origBoundedUntil.isUpperBoundStrict(dim), origBoundedUntil.getUpperBound(dim))); |
|||
} else { |
|||
upperBounds.push_back(boost::none); |
|||
} |
|||
} else { |
|||
// We need a zero expression in all other cases
|
|||
storm::expressions::Expression zero; |
|||
if (origBoundedUntil.hasLowerBound(dim)) { |
|||
zero = origBoundedUntil.getLowerBound(dim).getManager().rational(0.0); |
|||
} else { |
|||
STORM_LOG_THROW(origBoundedUntil.hasUpperBound(dim), storm::exceptions::InvalidOperationException, "The given bounded until formula has no cost-bound for one dimension."); |
|||
zero = origBoundedUntil.getUpperBound(dim).getManager().rational(0.0); |
|||
} |
|||
if (transformations[dim] == BoundTransformation::LessEqualZero) { |
|||
lowerBounds.push_back(boost::none); |
|||
upperBounds.push_back(storm::logic::TimeBound(false, zero)); |
|||
} else { |
|||
STORM_LOG_ASSERT(transformations[dim] == BoundTransformation::GreaterZero || transformations[dim] == BoundTransformation::GreaterEqualZero, "Unhandled bound transformation."); |
|||
lowerBounds.push_back(storm::logic::TimeBound(transformations[dim] == BoundTransformation::GreaterZero, zero)); |
|||
upperBounds.push_back(boost::none); |
|||
} |
|||
} |
|||
} |
|||
std::shared_ptr<storm::logic::Formula> newBoundedUntil; |
|||
if (origBoundedUntil.hasMultiDimensionalSubformulas()) { |
|||
newBoundedUntil = std::make_shared<storm::logic::BoundedUntilFormula>(leftSubformulas, rightSubformulas, lowerBounds, upperBounds, timeBoundReferences); |
|||
} else { |
|||
newBoundedUntil = std::make_shared<storm::logic::BoundedUntilFormula>(origBoundedUntil.getLeftSubformula().asSharedPointer(), origBoundedUntil.getRightSubformula().asSharedPointer(), lowerBounds, upperBounds, timeBoundReferences); |
|||
} |
|||
return std::make_shared<storm::logic::ProbabilityOperatorFormula>(newBoundedUntil, boundedUntilOperator.getOperatorInformation()); |
|||
} |
|||
|
|||
/// Increases the precision of solver results
|
|||
void increasePrecision(storm::Environment& env) { |
|||
STORM_LOG_DEBUG("Increasing precision of underlying solver."); |
|||
auto factor = storm::utility::convertNumber<storm::RationalNumber, std::string>("0.1"); |
|||
env.solver().setLinearEquationSolverPrecision(static_cast<storm::RationalNumber>(env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType()).first.get() * factor)); |
|||
env.solver().minMax().setPrecision(env.solver().minMax().getPrecision() * factor); |
|||
} |
|||
|
|||
/// Computes a lower/ upper bound on the actual result of a minmax or linear equation solver
|
|||
template<typename ValueType> |
|||
std::pair<ValueType, ValueType> getLowerUpperBound(storm::Environment const& env, ValueType const& value, bool minMax = true) { |
|||
ValueType prec; |
|||
bool relative; |
|||
if (minMax) { |
|||
prec = storm::utility::convertNumber<ValueType>(env.solver().minMax().getPrecision()); |
|||
relative = env.solver().minMax().getRelativeTerminationCriterion(); |
|||
} else { |
|||
prec = storm::utility::convertNumber<ValueType>(env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType()).first.get()); |
|||
relative = env.solver().getPrecisionOfLinearEquationSolver(env.solver().getLinearEquationSolverType()).second.get(); |
|||
} |
|||
if (relative) { |
|||
return std::make_pair<ValueType, ValueType>(value * (1/(prec + 1)), value * (1 + prec/(prec +1))); |
|||
} else { |
|||
return std::make_pair<ValueType, ValueType>(value - prec, value + prec); |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
uint64_t QuantileHelper<ModelType>::getDimension() const { |
|||
return quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula().getDimension(); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
storm::storage::BitVector QuantileHelper<ModelType>::getOpenDimensions() const { |
|||
auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula(); |
|||
storm::storage::BitVector res(getDimension(), false); |
|||
for (uint64_t dim = 0; dim < getDimension(); ++dim) { |
|||
auto const& bound = boundedUntil.hasLowerBound(dim) ? boundedUntil.getLowerBound(dim) : boundedUntil.getUpperBound(dim); |
|||
if (bound.containsVariables()) { |
|||
res.set(dim, true); |
|||
} |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
storm::solver::OptimizationDirection const& QuantileHelper<ModelType>::getOptimizationDirForDimension(uint64_t const& dim) const { |
|||
storm::expressions::Variable const& dimVar = getVariableForDimension(dim); |
|||
for (auto const& boundVar : quantileFormula.getBoundVariables()) { |
|||
if (boundVar.second == dimVar) { |
|||
return boundVar.first; |
|||
} |
|||
} |
|||
STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "The bound variable '" << dimVar.getName() << "' is not specified within the quantile formula '" << quantileFormula << "'."); |
|||
return quantileFormula.getOptimizationDirection(); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
storm::expressions::Variable const& QuantileHelper<ModelType>::getVariableForDimension(uint64_t const& dim) const { |
|||
auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula(); |
|||
return (boundedUntil.hasLowerBound(dim) ? boundedUntil.getLowerBound(dim) : boundedUntil.getUpperBound(dim)).getBaseExpression().asVariableExpression().getVariable(); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
storm::storage::BitVector QuantileHelper<ModelType>::getDimensionsForVariable(storm::expressions::Variable const& var) const { |
|||
auto const& boundedUntil = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula(); |
|||
storm::storage::BitVector result(boundedUntil.getDimension(), false); |
|||
for (uint64_t dim = 0; dim < boundedUntil.getDimension(); ++dim) { |
|||
if (boundedUntil.hasLowerBound(dim) && boundedUntil.getLowerBound(dim).isVariable() && boundedUntil.getLowerBound(dim).getBaseExpression().asVariableExpression().getVariable() == var) { |
|||
result.set(dim, true); |
|||
} |
|||
if (boundedUntil.hasUpperBound(dim) && boundedUntil.getUpperBound(dim).isVariable() && boundedUntil.getUpperBound(dim).getBaseExpression().asVariableExpression().getVariable() == var) { |
|||
result.set(dim, true); |
|||
} |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
std::vector<std::vector<typename ModelType::ValueType>> QuantileHelper<ModelType>::computeQuantile(Environment const& env) const { |
|||
std::vector<std::vector<ValueType>> result; |
|||
Environment envCpy = env; // It might be necessary to increase the precision during the computation
|
|||
numCheckedEpochs = 0; numPrecisionRefinements = 0; |
|||
if (getOpenDimensions().getNumberOfSetBits() == 1) { |
|||
uint64_t dimension = *getOpenDimensions().begin(); |
|||
|
|||
bool zeroSatisfiesFormula = checkLimitValue(envCpy, storm::storage::BitVector(getDimension(), false)); |
|||
bool infSatisfiesFormula = checkLimitValue(envCpy, getOpenDimensions()); |
|||
if (zeroSatisfiesFormula != infSatisfiesFormula) { |
|||
while (true) { |
|||
auto quantileRes = computeQuantileForDimension(envCpy, dimension); |
|||
if (quantileRes) { |
|||
result = {{storm::utility::convertNumber<ValueType>(quantileRes->first) * quantileRes->second}}; |
|||
break; |
|||
} |
|||
increasePrecision(envCpy); |
|||
++numPrecisionRefinements; |
|||
} |
|||
} else if (zeroSatisfiesFormula) { |
|||
// thus also infSatisfiesFormula is true, i.e., all bound values satisfy the formula
|
|||
if (storm::solver::minimize(getOptimizationDirForDimension(dimension))) { |
|||
result = {{ storm::utility::zero<ValueType>() }}; |
|||
} else { |
|||
result = {{ storm::utility::infinity<ValueType>()}}; |
|||
} |
|||
} else { |
|||
// i.e., no bound value satisfies the formula
|
|||
result = {{}}; |
|||
} |
|||
} else if (getOpenDimensions().getNumberOfSetBits() == 2) { |
|||
result = computeTwoDimensionalQuantile(envCpy); |
|||
} else { |
|||
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "The quantile formula considers " << getOpenDimensions().getNumberOfSetBits() << " open dimensions. Only one- or two-dimensional quantiles are supported."); |
|||
} |
|||
if (storm::settings::getModule<storm::settings::modules::CoreSettings>().isShowStatisticsSet()) { |
|||
std::cout << "Number of checked epochs: " << numCheckedEpochs << std::endl; |
|||
std::cout << "Number of required precision refinements: " << numPrecisionRefinements << std::endl; |
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template<typename ValueType> |
|||
void filterDominatedPoints(std::vector<std::vector<ValueType>>& points, std::vector<storm::solver::OptimizationDirection> const& dirs) { |
|||
std::vector<std::vector<ValueType>> result; |
|||
// Note: this is slow and not inplace but also most likely not performance critical
|
|||
for (auto const& p1 : points) { |
|||
bool p1Dominated = false; |
|||
for (auto const& p2 : points) { |
|||
assert(p1.size() == p2.size()); |
|||
bool p2DominatesP1 = false; |
|||
for (uint64_t i = 0; i < dirs.size(); ++i) { |
|||
if (storm::solver::minimize(dirs[i]) ? p2[i] <= p1[i] : p2[i] >= p1[i]) { |
|||
if (p2[i] != p1[i]) { |
|||
p2DominatesP1 = true; |
|||
} |
|||
} else { |
|||
p2DominatesP1 = false; |
|||
break; |
|||
} |
|||
} |
|||
if (p2DominatesP1) { |
|||
p1Dominated = true; |
|||
break; |
|||
} |
|||
} |
|||
if (!p1Dominated) { |
|||
result.push_back(p1); |
|||
} |
|||
} |
|||
points = std::move(result); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
std::vector<std::vector<typename ModelType::ValueType>> QuantileHelper<ModelType>::computeTwoDimensionalQuantile(Environment& env) const { |
|||
std::vector<std::vector<ValueType>> result; |
|||
|
|||
auto const& probOpFormula = quantileFormula.getSubformula().asProbabilityOperatorFormula(); |
|||
auto const& boundedUntilFormula = probOpFormula.getSubformula().asBoundedUntilFormula(); |
|||
std::vector<storm::storage::BitVector> dimensionsAsBitVector; |
|||
std::vector<uint64_t> dimensions; |
|||
std::vector<storm::solver::OptimizationDirection> optimizationDirections; |
|||
std::vector<bool> lowerCostBounds; |
|||
for (auto const& dirVar : quantileFormula.getBoundVariables()) { |
|||
dimensionsAsBitVector.push_back(getDimensionsForVariable(dirVar.second)); |
|||
STORM_LOG_THROW(dimensionsAsBitVector.back().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "There is not exactly one reward bound referring to quantile variable '" << dirVar.second.getName() << "'."); |
|||
dimensions.push_back(*dimensionsAsBitVector.back().begin()); |
|||
lowerCostBounds.push_back(boundedUntilFormula.hasLowerBound(dimensions.back())); |
|||
optimizationDirections.push_back(dirVar.first); |
|||
} |
|||
STORM_LOG_ASSERT(dimensions.size() == 2, "Expected to have exactly two open dimensions."); |
|||
if (optimizationDirections[0] == optimizationDirections[1]) { |
|||
if (lowerCostBounds[0] == lowerCostBounds[1]) { |
|||
// TODO: Assert that we have a reasonable probability bound. STORM_LOG_THROW(storm::solver::minimize(optimizationDirections[0])
|
|||
|
|||
bool infInfProbSatisfiesFormula = checkLimitValue(env, storm::storage::BitVector(getDimension(), false)); |
|||
bool zeroZeroProbSatisfiesFormula = checkLimitValue(env, dimensionsAsBitVector[0] | dimensionsAsBitVector[1]); |
|||
if (infInfProbSatisfiesFormula != zeroZeroProbSatisfiesFormula) { |
|||
std::vector<std::pair<int64_t, typename ModelType::ValueType>> singleQuantileValues; |
|||
for (uint64_t i = 0; i < 2; ++i) { |
|||
// find out whether the bounds B_i = 0 and B_1-i = infinity satisfy the formula
|
|||
bool zeroInfSatisfiesFormula = checkLimitValue(env, dimensionsAsBitVector[1-i]); |
|||
if (zeroInfSatisfiesFormula == storm::solver::minimize(optimizationDirections[0])) { |
|||
// There is bound value b such that the point B_i=0 and B_1-i = b is part of the result
|
|||
singleQuantileValues.emplace_back(0, storm::utility::zero<ValueType>()); |
|||
} else { |
|||
// Compute quantile where 1-i is set to infinity
|
|||
singleQuantileValues.push_back(computeQuantileForDimension(env, dimensions[i]).get()); |
|||
if (!storm::solver::minimize(optimizationDirections[i])) { |
|||
// When maximizing bounds, the computed single dimensional quantile value is sat for all values of the other bound.
|
|||
// Increase the computed value so that there is at least one unsat assignment of bounds with B[i] = singleQuantileValues[i]
|
|||
++singleQuantileValues.back().first; |
|||
} |
|||
} |
|||
// Decrease the value for lower cost bounds to convert >= to >
|
|||
if (lowerCostBounds[i]) { |
|||
--singleQuantileValues[i].first; |
|||
} |
|||
} |
|||
std::vector<int64_t> currentEpochValues = {(int64_t) singleQuantileValues[0].first, (int64_t) singleQuantileValues[1].first}; // signed int is needed to allow lower bounds >-1 (aka >=0).
|
|||
while (!exploreTwoDimensionalQuantile(env, singleQuantileValues, currentEpochValues, result)) { |
|||
increasePrecision(env); |
|||
++numPrecisionRefinements; |
|||
} |
|||
} else if (infInfProbSatisfiesFormula) { |
|||
// then also zeroZeroProb satisfies the formula, i.e., all bound values satisfy the formula
|
|||
if (storm::solver::minimize(optimizationDirections[0])) { |
|||
result = {{storm::utility::zero<ValueType>(), storm::utility::zero<ValueType>()}}; |
|||
} else { |
|||
result = {{ storm::utility::infinity<ValueType>(), storm::utility::infinity<ValueType>()}}; |
|||
} |
|||
} else { |
|||
// i.e., no bound value satisfies the formula
|
|||
result = {{}}; |
|||
} |
|||
} else { |
|||
// TODO: this is an "unreasonable" case
|
|||
} |
|||
} else { |
|||
// TODO: find reasonable min/max cases
|
|||
} |
|||
return result; |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
bool QuantileHelper<ModelType>::exploreTwoDimensionalQuantile(Environment const& env, std::vector<std::pair<int64_t, typename ModelType::ValueType>> const& startEpochValues, std::vector<int64_t>& currentEpochValues, std::vector<std::vector<ValueType>>& resultPoints) const { |
|||
// Initialize some data for easy access
|
|||
auto const& probOpFormula = quantileFormula.getSubformula().asProbabilityOperatorFormula(); |
|||
auto const& boundedUntilFormula = probOpFormula.getSubformula().asBoundedUntilFormula(); |
|||
std::vector<storm::storage::BitVector> dimensionsAsBitVector; |
|||
std::vector<uint64_t> dimensions; |
|||
std::vector<storm::solver::OptimizationDirection> optimizationDirections; |
|||
std::vector<bool> lowerCostBounds; |
|||
for (auto const& dirVar : quantileFormula.getBoundVariables()) { |
|||
dimensionsAsBitVector.push_back(getDimensionsForVariable(dirVar.second)); |
|||
STORM_LOG_THROW(dimensionsAsBitVector.back().getNumberOfSetBits() == 1, storm::exceptions::NotSupportedException, "There is not exactly one reward bound referring to quantile variable '" << dirVar.second.getName() << "'."); |
|||
dimensions.push_back(*dimensionsAsBitVector.back().begin()); |
|||
lowerCostBounds.push_back(boundedUntilFormula.hasLowerBound(dimensions.back())); |
|||
optimizationDirections.push_back(dirVar.first); |
|||
} |
|||
STORM_LOG_ASSERT(dimensions.size() == 2, "Expected to have exactly two open dimensions."); |
|||
|
|||
|
|||
MultiDimensionalRewardUnfolding<ValueType, true> rewardUnfolding(model, transformBoundedUntilOperator(quantileFormula.getSubformula().asProbabilityOperatorFormula(), std::vector<BoundTransformation>(getDimension(), BoundTransformation::None))); |
|||
|
|||
// initialize data that will be needed for each epoch
|
|||
auto lowerBound = rewardUnfolding.getLowerObjectiveBound(); |
|||
auto upperBound = rewardUnfolding.getUpperObjectiveBound(); |
|||
std::vector<ValueType> x, b; |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> minMaxSolver; |
|||
|
|||
if (currentEpochValues[0] < 0 && currentEpochValues[1] < 0) { |
|||
// This case can only happen in these cases:
|
|||
assert(lowerCostBounds[0]); |
|||
assert(currentEpochValues[0] == -1); |
|||
assert(currentEpochValues[1] == -1); |
|||
// This case has been checked already, so we can skip it.
|
|||
// Skipping this is actually necessary, since the rewardUnfolding will handle formulas like [F{"a"}>A,{"b"}>B "init"] incorrectly if A=B=-1.
|
|||
++currentEpochValues[1]; |
|||
} |
|||
std::set<typename EpochManager::Epoch> exploredEpochs; |
|||
while (true) { |
|||
auto currEpoch = rewardUnfolding.getStartEpoch(true); |
|||
for (uint64_t i = 0; i < 2; ++i) { |
|||
if (currentEpochValues[i] >= 0) { |
|||
rewardUnfolding.getEpochManager().setDimensionOfEpoch(currEpoch, dimensions[i], (uint64_t) currentEpochValues[i]); |
|||
} else { |
|||
rewardUnfolding.getEpochManager().setBottomDimension(currEpoch, dimensions[i]); |
|||
} |
|||
} |
|||
auto epochOrder = rewardUnfolding.getEpochComputationOrder(currEpoch); |
|||
for (auto const& epoch : epochOrder) { |
|||
if (exploredEpochs.count(epoch) == 0) { |
|||
auto& epochModel = rewardUnfolding.setCurrentEpoch(epoch); |
|||
rewardUnfolding.setSolutionForCurrentEpoch(epochModel.analyzeSingleObjective(env, quantileFormula.getSubformula().asProbabilityOperatorFormula().getOptimalityType(), x, b, minMaxSolver, lowerBound, upperBound)); |
|||
++numCheckedEpochs; |
|||
exploredEpochs.insert(epoch); |
|||
} |
|||
} |
|||
ValueType currValue = rewardUnfolding.getInitialStateResult(currEpoch); |
|||
bool propertySatisfied; |
|||
if (env.solver().isForceSoundness()) { |
|||
auto lowerUpperValue = getLowerUpperBound(env, currValue); |
|||
propertySatisfied = probOpFormula.getBound().isSatisfied(lowerUpperValue.first); |
|||
if (propertySatisfied != probOpFormula.getBound().isSatisfied(lowerUpperValue.second)) { |
|||
// unclear result due to insufficient precision.
|
|||
return false; |
|||
} |
|||
} else { |
|||
propertySatisfied = probOpFormula.getBound().isSatisfied(currValue); |
|||
} |
|||
|
|||
// If the reward bounds should be as small as possible, we should stop as soon as the property is satisfied.
|
|||
// If the reward bounds should be as large as possible, we should stop as soon as the property is violated and then go a step backwards
|
|||
if (storm::solver::minimize(optimizationDirections[0]) == propertySatisfied) { |
|||
// We found another point for the result! Translate it to the original domain
|
|||
auto point = currentEpochValues; |
|||
std::vector<ValueType> convertedPoint; |
|||
for (uint64_t i = 0; i < 2; ++i) { |
|||
if (lowerCostBounds[i]) { |
|||
// Translate > to >=
|
|||
++point[i]; |
|||
} |
|||
if (i == 1 && storm::solver::maximize(optimizationDirections[i])) { |
|||
// When maximizing, we actually searched for each x-value the smallest y-value that leads to a property violation. Hence, decreasing y by one means property satisfaction
|
|||
--point[i]; |
|||
} |
|||
if (point[i] < 0) { |
|||
// Skip this point
|
|||
convertedPoint.clear(); |
|||
continue; |
|||
} |
|||
convertedPoint.push_back(storm::utility::convertNumber<ValueType>(point[i]) * rewardUnfolding.getDimension(dimensions[i]).scalingFactor); |
|||
} |
|||
if (!convertedPoint.empty()) { |
|||
resultPoints.push_back(std::move(convertedPoint)); |
|||
} |
|||
|
|||
if (currentEpochValues[1] == startEpochValues[1].first) { |
|||
break; |
|||
} else { |
|||
++currentEpochValues[0]; |
|||
currentEpochValues[1] = startEpochValues[1].first; |
|||
} |
|||
} else { |
|||
++currentEpochValues[1]; |
|||
} |
|||
} |
|||
|
|||
|
|||
// When maximizing, there are border cases where one dimension can be arbitrarily large
|
|||
for (uint64_t i = 0; i < 2; ++i) { |
|||
if (storm::solver::maximize(optimizationDirections[i]) && startEpochValues[i].first > 0) { |
|||
std::vector<ValueType> newResultPoint(2); |
|||
newResultPoint[i] = storm::utility::convertNumber<ValueType>(startEpochValues[i].first - 1) * startEpochValues[i].second; |
|||
newResultPoint[1-i] = storm::utility::infinity<ValueType>(); |
|||
resultPoints.push_back(newResultPoint); |
|||
} |
|||
} |
|||
|
|||
filterDominatedPoints(resultPoints, optimizationDirections); |
|||
return true; |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
boost::optional<std::pair<uint64_t, typename ModelType::ValueType>> QuantileHelper<ModelType>::computeQuantileForDimension(Environment const& env, uint64_t dimension) const { |
|||
// We assume that there is one bound value that violates the quantile and one bound value that satisfies it.
|
|||
|
|||
// Let all other open bounds approach infinity
|
|||
std::set<storm::expressions::Variable> infinityVariables; |
|||
for (auto const& d : getOpenDimensions()) { |
|||
if (d != dimension) { |
|||
infinityVariables.insert(getVariableForDimension(d)); |
|||
} |
|||
} |
|||
auto transformedFormula = transformBoundedUntilOperator(quantileFormula.getSubformula().asProbabilityOperatorFormula(), std::vector<BoundTransformation>(getDimension(), BoundTransformation::None)); |
|||
MultiDimensionalRewardUnfolding<ValueType, true> rewardUnfolding(model, transformedFormula, infinityVariables); |
|||
|
|||
bool upperCostBound = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula().hasUpperBound(dimension); |
|||
bool minimizingRewardBound = storm::solver::minimize(getOptimizationDirForDimension(dimension)); |
|||
|
|||
// initialize data that will be needed for each epoch
|
|||
auto lowerBound = rewardUnfolding.getLowerObjectiveBound(); |
|||
auto upperBound = rewardUnfolding.getUpperObjectiveBound(); |
|||
std::vector<ValueType> x, b; |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> minMaxSolver; |
|||
|
|||
uint64_t epochValue = 0; |
|||
std::set<typename EpochManager::Epoch> exploredEpochs; |
|||
while (true) { |
|||
auto currEpoch = rewardUnfolding.getStartEpoch(true); |
|||
rewardUnfolding.getEpochManager().setDimensionOfEpoch(currEpoch, dimension, (uint64_t) epochValue); |
|||
auto epochOrder = rewardUnfolding.getEpochComputationOrder(currEpoch); |
|||
for (auto const& epoch : epochOrder) { |
|||
if (exploredEpochs.count(epoch) == 0) { |
|||
auto& epochModel = rewardUnfolding.setCurrentEpoch(epoch); |
|||
rewardUnfolding.setSolutionForCurrentEpoch(epochModel.analyzeSingleObjective(env, quantileFormula.getSubformula().asProbabilityOperatorFormula().getOptimalityType(), x, b, minMaxSolver, lowerBound, upperBound)); |
|||
++numCheckedEpochs; |
|||
exploredEpochs.insert(epoch); |
|||
} |
|||
} |
|||
ValueType currValue = rewardUnfolding.getInitialStateResult(currEpoch); |
|||
|
|||
bool propertySatisfied; |
|||
if (env.solver().isForceSoundness()) { |
|||
auto lowerUpperValue = getLowerUpperBound(env, currValue); |
|||
propertySatisfied = transformedFormula->getBound().isSatisfied(lowerUpperValue.first); |
|||
if (propertySatisfied != transformedFormula->getBound().isSatisfied(lowerUpperValue.second)) { |
|||
// unclear result due to insufficient precision.
|
|||
return boost::none; |
|||
} |
|||
} else { |
|||
propertySatisfied = transformedFormula->getBound().isSatisfied(currValue); |
|||
} |
|||
// If the reward bound should be as small as possible, we should stop as soon as the property is satisfied.
|
|||
// If the reward bound should be as large as possible, we should stop as soon as sthe property is violated and then go a step backwards
|
|||
if (minimizingRewardBound && propertySatisfied) { |
|||
// We found a satisfying value!
|
|||
if (!upperCostBound) { |
|||
// The rewardunfolding assumes strict lower cost bounds while we assume non-strict ones. Hence, >B becomes >=(B+1).
|
|||
++epochValue; |
|||
} |
|||
break; |
|||
} else if (!minimizingRewardBound && !propertySatisfied) { |
|||
// We found a non-satisfying value. Go one step back to get the largest satisfying value.
|
|||
// ... however, lower cost bounds need to be converted from strict bounds >B to non strict bounds >=(B+1).
|
|||
// Hence, no operation is necessary in this case.
|
|||
if (upperCostBound) { |
|||
STORM_LOG_ASSERT(epochValue > 0, "The property does not seem to be satisfiable. This case should have been excluded earlier."); |
|||
--epochValue; |
|||
} |
|||
break; |
|||
} |
|||
++epochValue; |
|||
} |
|||
return std::make_pair(epochValue, rewardUnfolding.getDimension(dimension).scalingFactor); |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
bool QuantileHelper<ModelType>::checkLimitValue(Environment& env, storm::storage::BitVector const& infDimensions) const { |
|||
auto const& probabilityBound = quantileFormula.getSubformula().asProbabilityOperatorFormula().getBound(); |
|||
// Increase the precision until we get a conclusive result
|
|||
while (true) { |
|||
ValueType numericResult = computeLimitValue(env, infDimensions); |
|||
if (env.solver().isForceSoundness()) { |
|||
auto lowerUpper = getLowerUpperBound(env, numericResult); |
|||
bool lowerSat = probabilityBound.isSatisfied(lowerUpper.first); |
|||
bool upperSat = probabilityBound.isSatisfied(lowerUpper.second); |
|||
if (lowerSat == upperSat) { |
|||
return lowerSat; |
|||
} else { |
|||
increasePrecision(env); |
|||
++numPrecisionRefinements; |
|||
} |
|||
} else { |
|||
return probabilityBound.isSatisfied(numericResult); |
|||
} |
|||
} |
|||
} |
|||
|
|||
template<typename ModelType> |
|||
typename ModelType::ValueType QuantileHelper<ModelType>::computeLimitValue(Environment const& env, storm::storage::BitVector const& infDimensions) const { |
|||
// To compute the limit for an upper bounded dimension, we can simply drop the bound
|
|||
// To compute the limit for a lower bounded dimension, we only consider epoch steps that lie in an end component and check for the bound >0 instead.
|
|||
// Notice, however, that this approach becomes problematic if, at the same time, we consider an upper bounded dimension with bound value zero.
|
|||
std::vector<BoundTransformation> bts(getDimension(), BoundTransformation::None); |
|||
std::set<storm::expressions::Variable> infinityVariables; |
|||
for (auto const& d : getOpenDimensions()) { |
|||
bool upperCostBound = quantileFormula.getSubformula().asProbabilityOperatorFormula().getSubformula().asBoundedUntilFormula().hasUpperBound(d); |
|||
if (infDimensions.get(d)) { |
|||
infinityVariables.insert(getVariableForDimension(d)); |
|||
} else { |
|||
if (upperCostBound) { |
|||
bts[d] = BoundTransformation::LessEqualZero; |
|||
} else { |
|||
bts[d] = BoundTransformation::GreaterEqualZero; |
|||
} |
|||
} |
|||
} |
|||
auto transformedFormula = transformBoundedUntilOperator(quantileFormula.getSubformula().asProbabilityOperatorFormula(), bts); |
|||
|
|||
MultiDimensionalRewardUnfolding<ValueType, true> rewardUnfolding(model, transformedFormula, infinityVariables); |
|||
if (!rewardUnfolding.getProb1Objectives().empty()) { |
|||
assert(rewardUnfolding.getProb1Objectives().size() == 1); |
|||
// The probability is one.
|
|||
return storm::utility::one<ValueType>(); |
|||
} |
|||
// Get lower and upper bounds for the solution.
|
|||
auto lowerBound = rewardUnfolding.getLowerObjectiveBound(); |
|||
auto upperBound = rewardUnfolding.getUpperObjectiveBound(); |
|||
|
|||
// Initialize epoch models
|
|||
auto initEpoch = rewardUnfolding.getStartEpoch(); |
|||
auto epochOrder = rewardUnfolding.getEpochComputationOrder(initEpoch); |
|||
// initialize data that will be needed for each epoch
|
|||
std::vector<ValueType> x, b; |
|||
std::unique_ptr<storm::solver::MinMaxLinearEquationSolver<ValueType>> minMaxSolver; |
|||
|
|||
for (auto const& epoch : epochOrder) { |
|||
auto& epochModel = rewardUnfolding.setCurrentEpoch(epoch); |
|||
rewardUnfolding.setSolutionForCurrentEpoch(epochModel.analyzeSingleObjective(env, quantileFormula.getSubformula().asProbabilityOperatorFormula().getOptimalityType(), x, b, minMaxSolver, lowerBound, upperBound)); |
|||
++numCheckedEpochs; |
|||
} |
|||
|
|||
ValueType numericResult = rewardUnfolding.getInitialStateResult(initEpoch); |
|||
STORM_LOG_TRACE("Limit probability for infinity dimensions " << infDimensions << " is " << numericResult << "."); |
|||
return numericResult; |
|||
} |
|||
|
|||
template class QuantileHelper<storm::models::sparse::Mdp<double>>; |
|||
template class QuantileHelper<storm::models::sparse::Mdp<storm::RationalNumber>>; |
|||
|
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,70 @@ |
|||
#pragma once |
|||
|
|||
#include "storm/logic/QuantileFormula.h" |
|||
#include "boost/optional.hpp" |
|||
|
|||
namespace storm { |
|||
class Environment; |
|||
|
|||
namespace storage { |
|||
class BitVector; |
|||
} |
|||
|
|||
namespace modelchecker { |
|||
namespace helper { |
|||
namespace rewardbounded { |
|||
|
|||
template<typename ModelType> |
|||
class QuantileHelper { |
|||
typedef typename ModelType::ValueType ValueType; |
|||
public: |
|||
QuantileHelper(ModelType const& model, storm::logic::QuantileFormula const& quantileFormula); |
|||
|
|||
std::vector<std::vector<ValueType>> computeQuantile(Environment const& env) const; |
|||
|
|||
private: |
|||
std::vector<std::vector<ValueType>> computeTwoDimensionalQuantile(Environment& env) const; |
|||
bool exploreTwoDimensionalQuantile(Environment const& env, std::vector<std::pair<int64_t, typename ModelType::ValueType>> const& startEpochValues, std::vector<int64_t>& currentEpochValues, std::vector<std::vector<ValueType>>& resultPoints) const; |
|||
|
|||
/*! |
|||
* Computes the limit probability, where the given dimensions approach infinity and the remaining dimensions are set to zero. |
|||
*/ |
|||
ValueType computeLimitValue(Environment const& env, storm::storage::BitVector const& infDimensions) const; |
|||
|
|||
/*! |
|||
* Computes the limit probability, where the given dimensions approach infinity and the remaining dimensions are set to zero. |
|||
* The computed value is compared to the probability threshold. |
|||
* In sound mode, precision is iteratively increased in case of 'inconsistent' results. |
|||
*/ |
|||
bool checkLimitValue(Environment& env, storm::storage::BitVector const& infDimensions) const; |
|||
|
|||
/// Computes the quantile with respect to the given dimension. |
|||
/// boost::none is returned in case of insufficient precision. |
|||
boost::optional<std::pair<uint64_t, typename ModelType::ValueType>> computeQuantileForDimension(Environment const& env, uint64_t dim) const; |
|||
|
|||
/*! |
|||
* Gets the number of dimensions of the underlying boudned until formula |
|||
*/ |
|||
uint64_t getDimension() const; |
|||
|
|||
/*! |
|||
* Gets the dimensions that are open, i.e., for which the bound value is not fixed |
|||
* @return |
|||
*/ |
|||
storm::storage::BitVector getOpenDimensions() const; |
|||
|
|||
storm::storage::BitVector getDimensionsForVariable(storm::expressions::Variable const& var) const; |
|||
storm::solver::OptimizationDirection const& getOptimizationDirForDimension(uint64_t const& dim) const; |
|||
storm::expressions::Variable const& getVariableForDimension(uint64_t const& dim) const; |
|||
|
|||
ModelType const& model; |
|||
storm::logic::QuantileFormula const& quantileFormula; |
|||
|
|||
/// Statistics |
|||
mutable uint64_t numCheckedEpochs; |
|||
mutable uint64_t numPrecisionRefinements; |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue