325 lines
20 KiB
325 lines
20 KiB
#include "MonotonicityHelper.h"
|
|
|
|
#include "storm/exceptions/NotSupportedException.h"
|
|
#include "storm/exceptions/InvalidOperationException.h"
|
|
|
|
#include "storm/models/ModelType.h"
|
|
|
|
#include "storm/modelchecker/results/CheckResult.h"
|
|
|
|
#include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h"
|
|
|
|
#include "storm-pars/analysis/AssumptionChecker.h"
|
|
|
|
|
|
namespace storm {
|
|
namespace analysis {
|
|
/*** Constructor ***/
|
|
template <typename ValueType, typename ConstantType>
|
|
MonotonicityHelper<ValueType, ConstantType>::MonotonicityHelper(std::shared_ptr<models::sparse::Model<ValueType>> model, std::vector<std::shared_ptr<logic::Formula const>> formulas, std::vector<storage::ParameterRegion<ValueType>> regions, uint_fast64_t numberOfSamples, double const& precision, bool dotOutput) : assumptionMaker(model->getTransitionMatrix()){
|
|
assert (model != nullptr);
|
|
STORM_LOG_THROW(regions.size() <= 1, exceptions::NotSupportedException, "Monotonicity checking is not (yet) supported for multiple regions");
|
|
STORM_LOG_THROW(formulas.size() <= 1, exceptions::NotSupportedException, "Monotonicity checking is not (yet) supported for multiple formulas");
|
|
|
|
this->model = model;
|
|
this->formulas = formulas;
|
|
this->precision = utility::convertNumber<ConstantType>(precision);
|
|
this->matrix = model->getTransitionMatrix();
|
|
this->dotOutput = dotOutput;
|
|
|
|
if (regions.size() == 1) {
|
|
this->region = *(regions.begin());
|
|
} else {
|
|
typename storage::ParameterRegion<ValueType>::Valuation lowerBoundaries;
|
|
typename storage::ParameterRegion<ValueType>::Valuation upperBoundaries;
|
|
std::set<VariableType> vars;
|
|
vars = models::sparse::getProbabilityParameters(*model);
|
|
for (auto var : vars) {
|
|
typename storage::ParameterRegion<ValueType>::CoefficientType lb = utility::convertNumber<CoefficientType>(0 + precision) ;
|
|
typename storage::ParameterRegion<ValueType>::CoefficientType ub = utility::convertNumber<CoefficientType>(1 - precision) ;
|
|
lowerBoundaries.insert(std::make_pair(var, lb));
|
|
upperBoundaries.insert(std::make_pair(var, ub));
|
|
}
|
|
this->region = storage::ParameterRegion<ValueType>(std::move(lowerBoundaries), std::move(upperBoundaries));
|
|
}
|
|
|
|
if (numberOfSamples > 2) {
|
|
// sampling
|
|
if (model->isOfType(models::ModelType::Dtmc)) {
|
|
checkMonotonicityOnSamples(model->template as<models::sparse::Dtmc<ValueType>>(), numberOfSamples);
|
|
} else if (model->isOfType(models::ModelType::Mdp)) {
|
|
checkMonotonicityOnSamples(model->template as<models::sparse::Mdp<ValueType>>(), numberOfSamples);
|
|
}
|
|
checkSamples = true;
|
|
} else {
|
|
if (numberOfSamples > 0) {
|
|
STORM_LOG_WARN("At least 3 sample points are needed to check for monotonicity on samples, not using samples for now");
|
|
}
|
|
checkSamples = false;
|
|
}
|
|
|
|
this->extender = new analysis::OrderExtender<ValueType, ConstantType>(model, formulas[0]);
|
|
|
|
for (size_t i = 0; i < matrix.getRowCount(); ++i) {
|
|
std::set<VariableType> occurringVariables;
|
|
|
|
for (auto &entry : matrix.getRow(i)) {
|
|
storm::utility::parametric::gatherOccurringVariables(entry.getValue(), occurringVariables);
|
|
}
|
|
for (auto& var : occurringVariables) {
|
|
occuringStatesAtVariable[var].push_back(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*** Public methods ***/
|
|
template <typename ValueType, typename ConstantType>
|
|
std::map<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<typename MonotonicityHelper<ValueType, ConstantType>::VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>> MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityInBuild(std::ostream& outfile, bool usePLA, std::string dotOutfileName) {
|
|
if (usePLA) {
|
|
storm::utility::Stopwatch plaWatch(true);
|
|
this->extender->initializeMinMaxValues(region);
|
|
plaWatch.stop();
|
|
STORM_PRINT(std::endl << "Total time for pla checking: " << plaWatch << "." << std::endl << std::endl);
|
|
}
|
|
createOrder();
|
|
|
|
//output of results
|
|
for (auto itr : monResults) {
|
|
if (itr.first != nullptr) {
|
|
std::cout << "Number of done states: " << itr.first->getNumberOfDoneStates() << std::endl;
|
|
}
|
|
if (checkSamples) {
|
|
for (auto & entry : resultCheckOnSamples.getMonotonicityResult()) {
|
|
if (entry.second == Monotonicity::Not) {
|
|
itr.second.first->updateMonotonicityResult(entry.first, entry.second, true);
|
|
}
|
|
}
|
|
}
|
|
std::string temp = itr.second.first->toString();
|
|
bool first = true;
|
|
for (auto& assumption : itr.second.second) {
|
|
if (!first) {
|
|
outfile << " & ";
|
|
} else {
|
|
outfile << "Assumptions: " << std::endl << " ";
|
|
first = false;
|
|
}
|
|
outfile << *assumption;
|
|
}
|
|
if (!first) {
|
|
outfile << std::endl;
|
|
} else {
|
|
outfile << "No Assumptions" << std::endl;
|
|
}
|
|
outfile << "Monotonicity Result: " << std::endl << " " << temp << std::endl << std::endl;
|
|
}
|
|
|
|
if (monResults.size() == 0) {
|
|
outfile << "No monotonicity found, as the order is insufficient" << std::endl;
|
|
if (checkSamples) {
|
|
outfile << "Monotonicity Result on samples: " << resultCheckOnSamples.toString() << std::endl;
|
|
}
|
|
}
|
|
|
|
//dotoutput
|
|
if (dotOutput) {
|
|
STORM_LOG_WARN_COND(monResults.size() <= 10, "Too many Reachability Orders. Dot Output will only be created for 10.");
|
|
int i = 0;
|
|
auto orderItr = monResults.begin();
|
|
while (i < 10 && orderItr != monResults.end()) {
|
|
std::ofstream dotOutfile;
|
|
std::string name = dotOutfileName + std::to_string(i);
|
|
utility::openFile(name, dotOutfile);
|
|
dotOutfile << "Assumptions:" << std::endl;
|
|
auto assumptionItr = orderItr->second.second.begin();
|
|
while (assumptionItr != orderItr->second.second.end()) {
|
|
dotOutfile << *assumptionItr << std::endl;
|
|
dotOutfile << std::endl;
|
|
assumptionItr++;
|
|
}
|
|
dotOutfile << std::endl;
|
|
orderItr->first->dotOutputToFile(dotOutfile);
|
|
utility::closeFile(dotOutfile);
|
|
i++;
|
|
orderItr++;
|
|
}
|
|
}
|
|
return monResults;
|
|
}
|
|
|
|
/*** Private methods ***/
|
|
template <typename ValueType, typename ConstantType>
|
|
void MonotonicityHelper<ValueType, ConstantType>::createOrder() {
|
|
// Transform to Orders
|
|
std::tuple<std::shared_ptr<Order>, uint_fast64_t, uint_fast64_t> criticalTuple;
|
|
|
|
// Create initial order
|
|
auto monRes = std::make_shared<MonotonicityResult<VariableType>>(MonotonicityResult<VariableType>());
|
|
criticalTuple = extender->toOrder(region, monRes);
|
|
// Continue based on not (yet) sorted states
|
|
std::map<std::shared_ptr<Order>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>> result;
|
|
|
|
auto val1 = std::get<1>(criticalTuple);
|
|
auto val2 = std::get<2>(criticalTuple);
|
|
auto numberOfStates = model->getNumberOfStates();
|
|
std::vector<std::shared_ptr<expressions::BinaryRelationExpression>> assumptions;
|
|
|
|
if (val1 == numberOfStates && val2 == numberOfStates) {
|
|
auto resAssumptionPair = std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>(monRes, assumptions);
|
|
monResults.insert(std::pair<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>>(std::get<0>(criticalTuple), resAssumptionPair));
|
|
} else if (val1 != numberOfStates && val2 != numberOfStates) {
|
|
extendOrderWithAssumptions(std::get<0>(criticalTuple), val1, val2, assumptions, monRes);
|
|
} else {
|
|
assert (false);
|
|
}
|
|
}
|
|
|
|
template <typename ValueType, typename ConstantType>
|
|
void MonotonicityHelper<ValueType, ConstantType>::extendOrderWithAssumptions(std::shared_ptr<Order> order, uint_fast64_t val1, uint_fast64_t val2, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>> assumptions, std::shared_ptr<MonotonicityResult<VariableType>> monRes) {
|
|
std::map<std::shared_ptr<Order>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>> result;
|
|
if (order->isInvalid()) {
|
|
// We don't add anything as the order we created with assumptions turns out to be invalid
|
|
STORM_LOG_INFO(" The order was invalid, so we stop here");
|
|
return;
|
|
}
|
|
auto numberOfStates = model->getNumberOfStates();
|
|
if (val1 == numberOfStates || val2 == numberOfStates) {
|
|
assert (val1 == val2);
|
|
assert (order->getNumberOfAddedStates() == order->getNumberOfStates());
|
|
auto resAssumptionPair = std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>(monRes, assumptions);
|
|
monResults.insert(std::pair<std::shared_ptr<Order>, std::pair<std::shared_ptr<MonotonicityResult<VariableType>>, std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>>>(std::move(order), std::move(resAssumptionPair)));
|
|
} else {
|
|
// Make the three assumptions
|
|
STORM_LOG_INFO("Creating assumptions for " << val1 << " and " << val2 << ". ");
|
|
auto newAssumptions = assumptionMaker.createAndCheckAssumptions(val1, val2, order, region);
|
|
assert (newAssumptions.size() <= 3);
|
|
auto itr = newAssumptions.begin();
|
|
if (newAssumptions.size() == 0) {
|
|
monRes = std::make_shared<MonotonicityResult<VariableType>>(MonotonicityResult<VariableType>());
|
|
for (auto& entry : occuringStatesAtVariable) {
|
|
for (auto & state : entry.second) {
|
|
extender->checkParOnStateMonRes(state, order, entry.first, monRes);
|
|
if (monRes->getMonotonicity(entry.first) == Monotonicity::Unknown) {
|
|
break;
|
|
}
|
|
}
|
|
monRes->setDoneForVar(entry.first);
|
|
}
|
|
monResults.insert({order, {monRes, assumptions}});
|
|
STORM_LOG_INFO(" None of the assumptions were valid, we stop exploring the current order");
|
|
} else {
|
|
STORM_LOG_INFO(" Created " << newAssumptions.size() << " assumptions, we continue extending the current order");
|
|
}
|
|
|
|
while (itr != newAssumptions.end()) {
|
|
auto assumption = *itr;
|
|
++itr;
|
|
if (assumption.second != AssumptionStatus::INVALID) {
|
|
if (itr != newAssumptions.end()) {
|
|
// We make a copy of the order and the assumptions
|
|
auto orderCopy = order->copy();
|
|
auto assumptionsCopy = std::vector<std::shared_ptr<expressions::BinaryRelationExpression>>(assumptions);
|
|
auto monResCopy = monRes->copy();
|
|
|
|
if (assumption.second == AssumptionStatus::UNKNOWN) {
|
|
// only add assumption to the set of assumptions if it is unknown whether it holds or not
|
|
assumptionsCopy.push_back(std::move(assumption.first));
|
|
}
|
|
auto criticalTuple = extender->extendOrder(orderCopy, region, monResCopy, assumption.first);
|
|
extendOrderWithAssumptions(std::get<0>(criticalTuple), std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptionsCopy, monResCopy);
|
|
} else {
|
|
// It is the last one, so we don't need to create a copy.
|
|
if (assumption.second == AssumptionStatus::UNKNOWN) {
|
|
// only add assumption to the set of assumptions if it is unknown whether it holds or not
|
|
assumptions.push_back(std::move(assumption.first));
|
|
}
|
|
auto criticalTuple = extender->extendOrder(order, region, monRes, assumption.first);
|
|
extendOrderWithAssumptions(std::get<0>(criticalTuple), std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptions, monRes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename ValueType, typename ConstantType>
|
|
void MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityOnSamples(std::shared_ptr<models::sparse::Dtmc<ValueType>> model, uint_fast64_t numberOfSamples) {
|
|
assert (numberOfSamples > 2);
|
|
|
|
auto instantiator = utility::ModelInstantiator<models::sparse::Dtmc<ValueType>, models::sparse::Dtmc<ConstantType>>(*model);
|
|
std::set<VariableType> variables = models::sparse::getProbabilityParameters(*model);
|
|
std::vector<std::vector<ConstantType>> samples;
|
|
// For each of the variables create a model in which we only change the value for this specific variable
|
|
for (auto itr = variables.begin(); itr != variables.end(); ++itr) {
|
|
ConstantType previous = -1;
|
|
bool monDecr = true;
|
|
bool monIncr = true;
|
|
|
|
// Check monotonicity in variable (*itr) by instantiating the model
|
|
// all other variables fixed on lb, only increasing (*itr)
|
|
for (uint_fast64_t i = 0; (monDecr || monIncr) && i < numberOfSamples; ++i) {
|
|
// Create valuation
|
|
auto valuation = utility::parametric::Valuation<ValueType>();
|
|
for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) {
|
|
// Only change value for current variable
|
|
if ((*itr) == (*itr2)) {
|
|
auto lb = region.getLowerBoundary(itr->name());
|
|
auto ub = region.getUpperBoundary(itr->name());
|
|
// Creates samples between lb and ub, that is: lb, lb + (ub-lb)/(#samples -1), lb + 2* (ub-lb)/(#samples -1), ..., ub
|
|
valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb + i*(ub-lb)/(numberOfSamples-1));
|
|
} else {
|
|
auto lb = region.getLowerBoundary(itr2->name());
|
|
valuation[*itr2] = utility::convertNumber<typename utility::parametric::CoefficientType<ValueType>::type>(lb);
|
|
}
|
|
}
|
|
|
|
// Instantiate model and get result
|
|
models::sparse::Dtmc<ConstantType> sampleModel = instantiator.instantiate(valuation);
|
|
auto checker = modelchecker::SparseDtmcPrctlModelChecker<models::sparse::Dtmc<ConstantType>>(sampleModel);
|
|
std::unique_ptr<modelchecker::CheckResult> checkResult;
|
|
auto formula = formulas[0];
|
|
if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) {
|
|
const modelchecker::CheckTask<logic::UntilFormula, ConstantType> checkTask = modelchecker::CheckTask<logic::UntilFormula, ConstantType>((*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula());
|
|
checkResult = checker.computeUntilProbabilities(Environment(), checkTask);
|
|
} else if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) {
|
|
const modelchecker::CheckTask<logic::EventuallyFormula, ConstantType> checkTask = modelchecker::CheckTask<logic::EventuallyFormula, ConstantType>((*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula());
|
|
checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask);
|
|
} else {
|
|
STORM_LOG_THROW(false, exceptions::NotSupportedException, "Expecting until or eventually formula");
|
|
}
|
|
|
|
auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<ConstantType>();
|
|
std::vector<ConstantType> values = quantitativeResult.getValueVector();
|
|
auto initialStates = model->getInitialStates();
|
|
ConstantType initial = 0;
|
|
// Get total probability from initial states
|
|
for (auto j = initialStates.getNextSetIndex(0); j < model->getNumberOfStates(); j = initialStates.getNextSetIndex(j + 1)) {
|
|
initial += values[j];
|
|
}
|
|
// Calculate difference with result for previous valuation
|
|
assert (initial >= 0 - precision && initial <= 1 + precision);
|
|
ConstantType diff = previous - initial;
|
|
assert (previous == -1 || diff >= -1 - precision && diff <= 1 + precision);
|
|
|
|
if (previous != -1 && (diff > precision || diff < -precision)) {
|
|
monDecr &= diff > precision; // then previous value is larger than the current value from the initial states
|
|
monIncr &= diff < -precision;
|
|
}
|
|
previous = initial;
|
|
samples.push_back(std::move(values));
|
|
}
|
|
auto res = (!monIncr && !monDecr) ? MonotonicityResult<VariableType>::Monotonicity::Not : MonotonicityResult<VariableType>::Monotonicity::Unknown;
|
|
resultCheckOnSamples.addMonotonicityResult(*itr, res);
|
|
}
|
|
assumptionMaker.setSampleValues(std::move(samples));
|
|
}
|
|
|
|
template <typename ValueType, typename ConstantType>
|
|
void MonotonicityHelper<ValueType, ConstantType>::checkMonotonicityOnSamples(std::shared_ptr<models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples) {
|
|
STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Checking monotonicity on samples not implemented for mdps");
|
|
}
|
|
|
|
template class MonotonicityHelper<RationalFunction, double>;
|
|
template class MonotonicityHelper<RationalFunction, RationalNumber>;
|
|
}
|
|
}
|