#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>; } }