// // Created by Jip Spel on 05.09.18. // #include "MonotonicityChecker.h" #include "storm-pars/analysis/AssumptionMaker.h" #include "storm-pars/analysis/AssumptionChecker.h" #include "storm-pars/analysis/Lattice.h" #include "storm-pars/analysis/LatticeExtender.h" #include "storm/exceptions/NotSupportedException.h" #include "storm/exceptions/UnexpectedException.h" #include "storm/exceptions/InvalidOperationException.h" #include "storm/utility/Stopwatch.h" #include "storm/models/ModelType.h" #include "storm/api/verification.h" #include "storm-pars/api/storm-pars.h" #include "storm/modelchecker/results/CheckResult.h" #include "storm/modelchecker/results/ExplicitQuantitativeCheckResult.h" #include "storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h" #include "storm/solver/Z3SmtSolver.h" #include "storm/storage/expressions/ExpressionManager.h" #include "storm/storage/expressions/RationalFunctionToExpression.h" namespace storm { namespace analysis { template <typename ValueType> MonotonicityChecker<ValueType>::MonotonicityChecker(std::shared_ptr<storm::models::ModelBase> model, std::vector<std::shared_ptr<storm::logic::Formula const>> formulas, bool validate, bool sccElimination) { outfile.open(filename, std::ios_base::app); this->model = model; this->formulas = formulas; this->validate = validate; this->sccElimination = sccElimination; this->resultCheckOnSamples = std::map<carl::Variable, std::pair<bool, bool>>(); if (model != nullptr) { std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); this->extender = new storm::analysis::LatticeExtender<ValueType>(sparseModel); outfile << model->getNumberOfStates() << ", " << model->getNumberOfTransitions() << ", "; } outfile.close(); totalWatch = storm::utility::Stopwatch(true); } template <typename ValueType> std::map<storm::analysis::Lattice*, std::map<carl::Variable, std::pair<bool, bool>>> MonotonicityChecker<ValueType>::checkMonotonicity() { // TODO: check on samples or not? totalWatch = storm::utility::Stopwatch(true); auto latticeWatch = storm::utility::Stopwatch(true); auto map = createLattice(); // STORM_PRINT(std::endl << "Time for creating lattice: " << latticeWatch << "." << std::endl << std::endl); std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); auto matrix = sparseModel->getTransitionMatrix(); return checkMonotonicity(map, matrix); } template <typename ValueType> std::vector<storm::storage::ParameterRegion<ValueType>> MonotonicityChecker<ValueType>::checkAssumptionsOnRegion(std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>> assumptions) { assert (formulas[0]->isProbabilityOperatorFormula()); assert (formulas[0]->asProbabilityOperatorFormula().getSubformula().isUntilFormula() || formulas[0]->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()); Environment env = Environment(); std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); bool generateSplitEstimates = false; bool allowModelSimplification = false; auto task = storm::api::createTask<ValueType>(formulas[0], true); // TODO: storm::RationalNumber or double? // TODO: Also allow different models STORM_LOG_THROW (sparseModel->isOfType(storm::models::ModelType::Dtmc), storm::exceptions::NotImplementedException, "Checking assumptions on a region not implemented for this type of model"); auto modelChecker = storm::api::initializeParameterLiftingDtmcModelChecker<ValueType, storm::RationalNumber>(env, sparseModel, task, generateSplitEstimates, allowModelSimplification); std::stack<std::pair<storm::storage::ParameterRegion<ValueType>, int>> regions; std::vector<storm::storage::ParameterRegion<ValueType>> satRegions; std::string regionText = ""; auto parameters = storm::models::sparse::getProbabilityParameters(*sparseModel); for (auto itr = parameters.begin(); itr != parameters.end(); ++itr) { if (regionText != "") { regionText += ","; } // TODO: region bounds regionText += "0.1 <= " + itr->name() + " <= 0.9"; } auto initialRegion = storm::api::parseRegion<ValueType>(regionText, parameters); regions.push(std::pair<storm::storage::ParameterRegion<ValueType>, int>(initialRegion,0)); while (!regions.empty()) { auto lastElement = regions.top(); regions.pop(); storm::storage::ParameterRegion<ValueType> currentRegion = lastElement.first; // TODO: depth if (lastElement.second < 5) { auto upperBound = modelChecker->getBound(env, currentRegion, storm::solver::OptimizationDirection::Maximize); auto lowerBound = modelChecker->getBound(env, currentRegion, storm::solver::OptimizationDirection::Minimize); std::vector<storm::RationalNumber> valuesUpper = upperBound->template asExplicitQuantitativeCheckResult<storm::RationalNumber>().getValueVector(); std::vector<storm::RationalNumber> valuesLower = lowerBound->template asExplicitQuantitativeCheckResult<storm::RationalNumber>().getValueVector(); bool assumptionsHold = true; for (auto itr = assumptions.begin(); assumptionsHold && itr != assumptions.end(); ++itr) { auto assumption = *itr; if (assumption->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Greater) { auto state1 = std::stoi( assumption->getFirstOperand()->asVariableExpression().getVariableName()); auto state2 = std::stoi( assumption->getSecondOperand()->asVariableExpression().getVariableName()); assumptionsHold &= valuesLower[state1] >= valuesUpper[state2]; } else if (assumption->getRelationType() == storm::expressions::BinaryRelationExpression::RelationType::Equal) { auto state1 = std::stoi( assumption->getFirstOperand()->asVariableExpression().getVariableName()); auto state2 = std::stoi( assumption->getSecondOperand()->asVariableExpression().getVariableName()); assumptionsHold &= valuesLower[state1] == valuesUpper[state2]; } else { assert(false); } } if (!assumptionsHold) { std::vector<storm::storage::ParameterRegion<ValueType>> newRegions; currentRegion.split(currentRegion.getCenterPoint(), newRegions); for (auto itr = newRegions.begin(); itr != newRegions.end(); ++itr) { regions.push(std::pair<storm::storage::ParameterRegion<ValueType>, int>(*itr, lastElement.second + 1)); } } else { satRegions.push_back(currentRegion); } } } return satRegions; } template <typename ValueType> std::map<storm::analysis::Lattice*, std::map<carl::Variable, std::pair<bool, bool>>> MonotonicityChecker<ValueType>::checkMonotonicity(std::map<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> map, storm::storage::SparseMatrix<ValueType> matrix) { storm::utility::Stopwatch monotonicityCheckWatch(true); std::map<storm::analysis::Lattice *, std::map<carl::Variable, std::pair<bool, bool>>> result; outfile.open(filename, std::ios_base::app); if (map.size() == 0) { // Nothing is known outfile << " No assumptions; ?"; // STORM_PRINT(std::endl << "Do not know about monotonicity" << std::endl); } else { auto i = 0; for (auto itr = map.begin(); i < map.size() && itr != map.end(); ++itr) { auto lattice = itr->first; auto addedStates = lattice->getAddedStates().getNumberOfSetBits(); assert (addedStates == lattice->getAddedStates().size()); std::map<carl::Variable, std::pair<bool, bool>> varsMonotone = analyseMonotonicity(i, lattice, matrix); auto assumptions = itr->second; bool validSomewhere = false; for (auto itr2 = varsMonotone.begin(); !validSomewhere && itr2 != varsMonotone.end(); ++itr2) { validSomewhere = itr2->second.first || itr2->second.second; } if (assumptions.size() > 0) { // auto regions = checkAssumptionsOnRegion(assumptions); // if (regions.size() > 0) { // // STORM_PRINT("For regions: " << std::endl); // bool first = true; // for (auto itr2 = regions.begin(); itr2 != regions.end(); ++itr2) { // if (first) { // // STORM_PRINT(" "); // first = false; // } // // STORM_PRINT(*itr2); // outfile << (*itr2); // } // // STORM_PRINT(std::endl); // outfile << ", "; // } else { // STORM_PRINT("Assumption(s): "); bool first = true; for (auto itr2 = assumptions.begin(); itr2 != assumptions.end(); ++itr2) { if (!first) { // STORM_PRINT(" ^ "); outfile << (" ^ "); } else { first = false; } // STORM_PRINT(*(*itr2)); outfile << (*(*itr2)); } // STORM_PRINT(std::endl); outfile << " - "; // } } else if (assumptions.size() == 0) { outfile << "No assumptions - "; } if (validSomewhere && varsMonotone.size() == 0) { // STORM_PRINT("Result is constant" << std::endl); outfile << "No params"; } else if (validSomewhere) { auto itr2 = varsMonotone.begin(); while (itr2 != varsMonotone.end()) { // if (resultCheckOnSamples.find(itr2->first) != resultCheckOnSamples.end() && // (!resultCheckOnSamples[itr2->first].first && // !resultCheckOnSamples[itr2->first].second)) { // // STORM_PRINT(" - Not monotone in: " << itr2->first << std::endl); // outfile << "X " << itr2->first; // } else { if (itr2->second.first && itr2->second.second) { // STORM_PRINT(" - Constant in" << itr2->first << std::endl); outfile << "C " << itr2->first; } else if (itr2->second.first) { // STORM_PRINT(" - Monotone increasing in: " << itr2->first << std::endl); outfile << "I " << itr2->first; } else if (itr2->second.second) { // STORM_PRINT(" - Monotone decreasing in: " << itr2->first << std::endl); outfile << "D " << itr2->first; } else { // STORM_PRINT(" - Do not know if monotone incr/decreasing in: " << itr2->first << std::endl); outfile << "? " << itr2->first; } // } ++itr2; if (itr2 != varsMonotone.end()) { outfile << " "; } } result.insert( std::pair<storm::analysis::Lattice *, std::map<carl::Variable, std::pair<bool, bool>>>( lattice, varsMonotone)); } else { result.insert( std::pair<storm::analysis::Lattice *, std::map<carl::Variable, std::pair<bool, bool>>>( lattice, varsMonotone)); outfile << "no monotonicity found"; } ++i; outfile << ";"; } } outfile << ", "; monotonicityCheckWatch.stop(); outfile << monotonicityCheckWatch << ", "; // STORM_PRINT(std::endl << "Time for monotonicity check on lattice: " << monotonicityCheckWatch << "." << std::endl << std::endl); outfile.close(); return result; } template <typename ValueType> std::map<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> MonotonicityChecker<ValueType>::createLattice() { // Transform to Lattices storm::utility::Stopwatch latticeWatch(true); std::tuple<storm::analysis::Lattice*, uint_fast64_t, uint_fast64_t> criticalTuple = extender->toLattice(formulas); std::map<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> result; auto val1 = std::get<1>(criticalTuple); auto val2 = std::get<2>(criticalTuple); auto numberOfStates = model->getNumberOfStates(); std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>> assumptions; if (val1 == numberOfStates && val2 == numberOfStates) { result.insert(std::pair<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>>(std::get<0>(criticalTuple), assumptions)); } else if (val1 != numberOfStates && val2 != numberOfStates) { storm::analysis::AssumptionChecker<ValueType> *assumptionChecker; if (model->isOfType(storm::models::ModelType::Dtmc)) { auto dtmc = model->as<storm::models::sparse::Dtmc<ValueType>>(); assumptionChecker = new storm::analysis::AssumptionChecker<ValueType>(formulas[0], dtmc, 3); } else if (model->isOfType(storm::models::ModelType::Mdp)) { auto mdp = model->as<storm::models::sparse::Mdp<ValueType>>(); assumptionChecker = new storm::analysis::AssumptionChecker<ValueType>(formulas[0], mdp, 3); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); } auto assumptionMaker = new storm::analysis::AssumptionMaker<ValueType>(assumptionChecker, numberOfStates, validate); result = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, val1, val2, assumptions); } else { assert(false); } latticeWatch.stop(); // STORM_PRINT(std::endl << "Total time for lattice creation: " << latticeWatch << "." << std::endl << std::endl); outfile.open(filename, std::ios_base::app); outfile << latticeWatch << ", "; outfile.close(); return result; } template <typename ValueType> std::map<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> MonotonicityChecker<ValueType>::extendLatticeWithAssumptions(storm::analysis::Lattice* lattice, storm::analysis::AssumptionMaker<ValueType>* assumptionMaker, uint_fast64_t val1, uint_fast64_t val2, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>> assumptions) { std::map<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>> result; auto numberOfStates = model->getNumberOfStates(); if (val1 == numberOfStates || val2 == numberOfStates) { assert (val1 == val2); assert (lattice->getAddedStates().size() == lattice->getAddedStates().getNumberOfSetBits()); result.insert(std::pair<storm::analysis::Lattice*, std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>>(lattice, assumptions)); } else { auto assumptionTriple = assumptionMaker->createAndCheckAssumption(val1, val2, lattice); assert (assumptionTriple.size() == 3); auto itr = assumptionTriple.begin(); auto assumption1 = *itr; ++itr; auto assumption2 = *itr; ++itr; auto assumption3 = *itr; if (!assumption1.second && !assumption2.second && !assumption3.second) { // Both assumption cannot be validated, so we need to keep them both // TODO: hier niet verder gaan als je iets gevonden hebt? auto assumptionsCopy = std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>( assumptions); auto assumptionsCopy2 = std::vector<std::shared_ptr<storm::expressions::BinaryRelationExpression>>( assumptions); auto latticeCopy = new storm::analysis::Lattice(lattice); auto latticeCopy2 = new storm::analysis::Lattice(lattice); assumptions.push_back(assumption1.first); assumptionsCopy.push_back(assumption2.first); assumptionsCopy2.push_back(assumption2.first); auto criticalTuple = extender->extendLattice(lattice, assumption1.first); if (somewhereMonotonicity(std::get<0>(criticalTuple))) { auto map = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptions); result.insert(map.begin(), map.end()); } criticalTuple = extender->extendLattice(latticeCopy, assumption2.first); if (somewhereMonotonicity(std::get<0>(criticalTuple))) { auto map = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptionsCopy); result.insert(map.begin(), map.end()); } criticalTuple = extender->extendLattice(latticeCopy2, assumption3.first); if (somewhereMonotonicity(std::get<0>(criticalTuple))) { auto map = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptionsCopy2); result.insert(map.begin(), map.end()); } } // } else if (assumption1.second && assumption2.second) { // assert (false); // //TODO Both assumptions hold --> should not happen if we change it to < instead of <= // auto assumption = assumptionMaker->createEqualAssumption(val1, val2); // if (!validate) { // assumptions.push_back(assumption); // } // // if validate is true and both hold, then they must be valid, so no need to add to assumptions // auto criticalTuple = extender->extendLattice(lattice, assumption); // if (somewhereMonotonicity(std::get<0>(criticalTuple))) { // result = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptions); // } // } else if (assumption1.second) { // if (!validate) { // assert(false); // assumptions.push_back(assumption1.first); // } // // if validate is true and both hold, then they must be valid, so no need to add to assumptions // // auto criticalTuple = extender->extendLattice(lattice, assumption1.first); // // if (somewhereMonotonicity(std::get<0>(criticalTuple))) { // result = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptions); // } // // } else { // assert (assumption2.second); // if (!validate) { // assumptions.push_back(assumption2.first); // } // // if validate is true and both hold, then they must be valid, so no need to add to assumptions // auto criticalTuple = extender->extendLattice(lattice, assumption2.first); // if (somewhereMonotonicity(std::get<0>(criticalTuple))) { // result = extendLatticeWithAssumptions(std::get<0>(criticalTuple), assumptionMaker, std::get<1>(criticalTuple), std::get<2>(criticalTuple), assumptions); // } // } } return result; } template <typename ValueType> std::map<carl::Variable, std::pair<bool, bool>> MonotonicityChecker<ValueType>::analyseMonotonicity(uint_fast64_t j, storm::analysis::Lattice* lattice, storm::storage::SparseMatrix<ValueType> matrix) { // storm::utility::Stopwatch analyseWatch(true); std::map<carl::Variable, std::pair<bool, bool>> varsMonotone; for (uint_fast64_t i = 0; i < matrix.getColumnCount(); ++i) { // go over all rows auto row = matrix.getRow(i); auto first = (*row.begin()); if (first.getValue() != ValueType(1)) { std::map<uint_fast64_t, ValueType> transitions; for (auto itr = row.begin(); itr != row.end(); ++itr) { transitions.insert(std::pair<uint_fast64_t, ValueType>((*itr).getColumn(), (*itr).getValue())); } auto val = first.getValue(); auto vars = val.gatherVariables(); for (auto itr = vars.begin(); itr != vars.end(); ++itr) { // if (resultCheckOnSamples.find(*itr) != resultCheckOnSamples.end() && // (!resultCheckOnSamples[*itr].first && !resultCheckOnSamples[*itr].second)) { // if (varsMonotone.find(*itr) == varsMonotone.end()) { // varsMonotone[*itr].first = false; // varsMonotone[*itr].second = false; // } // } else { if (varsMonotone.find(*itr) == varsMonotone.end()) { varsMonotone[*itr].first = true; varsMonotone[*itr].second = true; } std::pair<bool, bool> *value = &varsMonotone.find(*itr)->second; std::pair<bool, bool> old = *value; for (auto itr2 = transitions.begin(); itr2 != transitions.end(); ++itr2) { for (auto itr3 = transitions.begin(); itr3 != transitions.end(); ++itr3) { auto derivative2 = itr2->second.derivative(*itr); auto derivative3 = itr3->second.derivative(*itr); auto compare = lattice->compare(itr2->first, itr3->first); if (compare == storm::analysis::Lattice::ABOVE) { // As the first state (itr2) is above the second state (itr3) it is sufficient to look at the derivative of itr2. std::pair<bool,bool> mon2; if (derivative2.isConstant()) { mon2 = std::pair<bool,bool>(derivative2.constantPart() >= 0, derivative2.constantPart() <=0); } else { mon2 = checkDerivative(derivative2); } value->first &= mon2.first; value->second &= mon2.second; } else if (compare == storm::analysis::Lattice::BELOW) { // As the second state (itr3) is above the first state (itr2) it is sufficient to look at the derivative of itr3. std::pair<bool,bool> mon3; if (derivative2.isConstant()) { mon3 = std::pair<bool,bool>(derivative3.constantPart() >= 0, derivative3.constantPart() <=0); } else { mon3 = checkDerivative(derivative3); } value->first &= mon3.first; value->second &= mon3.second; } else if (compare == storm::analysis::Lattice::SAME) { // TODO: klopt dit // Behaviour doesn't matter, as the states are at the same level. } else { // As the relation between the states is unknown, we can't claim anything about the monotonicity. value->first = false; value->second = false; } // } } } } } } // analyseWatch.stop(); // STORM_PRINT(std::endl << "Time to check monotonicity based on the lattice: " << analyseWatch << "." << std::endl << std::endl); // outfile << analyseWatch << "; "; return varsMonotone; } template <typename ValueType> std::pair<bool, bool> MonotonicityChecker<ValueType>::checkDerivative(ValueType derivative) { bool monIncr = false; bool monDecr = false; if (derivative.isZero()) { monIncr = true; monDecr = true; } else { std::shared_ptr<storm::utility::solver::SmtSolverFactory> smtSolverFactory = std::make_shared<storm::utility::solver::MathsatSmtSolverFactory>(); std::shared_ptr<storm::expressions::ExpressionManager> manager( new storm::expressions::ExpressionManager()); storm::solver::Z3SmtSolver s(*manager); storm::solver::SmtSolver::CheckResult smtResult = storm::solver::SmtSolver::CheckResult::Unknown; std::set<carl::Variable> variables = derivative.gatherVariables(); for (auto variable : variables) { manager->declareRationalVariable(variable.name()); } storm::expressions::Expression exprBounds = manager->boolean(true); auto managervars = manager->getVariables(); for (auto var : managervars) { exprBounds = exprBounds && manager->rational(0) <= var && manager->rational(1) >= var; } auto converter = storm::expressions::RationalFunctionToExpression<ValueType>(manager); storm::expressions::Expression exprToCheck1 = converter.toExpression(derivative) >= manager->rational(0); s.add(exprBounds); s.add(exprToCheck1); smtResult = s.check(); monIncr = smtResult == storm::solver::SmtSolver::CheckResult::Sat; storm::expressions::Expression exprToCheck2 = converter.toExpression(derivative) <= manager->rational(0); s.reset(); smtResult = storm::solver::SmtSolver::CheckResult::Unknown; s.add(exprBounds); s.add(exprToCheck2); smtResult = s.check(); monDecr = smtResult == storm::solver::SmtSolver::CheckResult::Sat; } assert (!(monIncr && monDecr) || derivative.isZero()); return std::pair<bool, bool>(monIncr, monDecr); } template <typename ValueType> bool MonotonicityChecker<ValueType>::somewhereMonotonicity(storm::analysis::Lattice* lattice) { std::shared_ptr<storm::models::sparse::Model<ValueType>> sparseModel = model->as<storm::models::sparse::Model<ValueType>>(); auto matrix = sparseModel->getTransitionMatrix(); // TODO: tussenresultaten hergebruiken std::map<carl::Variable, std::pair<bool, bool>> varsMonotone; for (uint_fast64_t i = 0; i < matrix.getColumnCount(); ++i) { // go over all rows auto row = matrix.getRow(i); auto first = (*row.begin()); if (first.getValue() != ValueType(1)) { std::map<uint_fast64_t, ValueType> transitions; for (auto itr = row.begin(); itr != row.end(); ++itr) { transitions.insert(std::pair<uint_fast64_t, ValueType>((*itr).getColumn(), (*itr).getValue())); } auto val = first.getValue(); auto vars = val.gatherVariables(); for (auto itr = vars.begin(); itr != vars.end(); ++itr) { if (varsMonotone.find(*itr) == varsMonotone.end()) { varsMonotone[*itr].first = true; varsMonotone[*itr].second = true; } std::pair<bool, bool> *value = &varsMonotone.find(*itr)->second; std::pair<bool, bool> old = *value; // TODO deze ook aanpassen aan deel met smt solver for (auto itr2 = transitions.begin(); itr2 != transitions.end(); ++itr2) { for (auto itr3 = transitions.begin(); itr3 != transitions.end(); ++itr3) { auto derivative2 = itr2->second.derivative(*itr); auto derivative3 = itr3->second.derivative(*itr); auto compare = lattice->compare(itr2->first, itr3->first); if (compare == storm::analysis::Lattice::ABOVE) { // As the first state (itr2) is above the second state (itr3) it is sufficient to look at the derivative of itr2. std::pair<bool,bool> mon2; if (derivative2.isConstant()) { mon2 = std::pair<bool,bool>(derivative2.constantPart() >= 0, derivative2.constantPart() <=0); } else { mon2 = checkDerivative(derivative2); } value->first &= mon2.first; value->second &= mon2.second; } else if (compare == storm::analysis::Lattice::BELOW) { // As the second state (itr3) is above the first state (itr2) it is sufficient to look at the derivative of itr3. std::pair<bool,bool> mon3; if (derivative2.isConstant()) { mon3 = std::pair<bool,bool>(derivative3.constantPart() >= 0, derivative3.constantPart() <=0); } else { mon3 = checkDerivative(derivative3); } value->first &= mon3.first; value->second &= mon3.second; } else if (compare == storm::analysis::Lattice::SAME) { // TODO: klopt dit // Behaviour doesn't matter, as the states are at the same level. } else { // As the relation between the states is unknown, we don't do anything } } } } } } bool result = false; for (auto itr = varsMonotone.begin(); !result && itr != varsMonotone.end(); ++itr) { result = itr->second.first || itr->second.second; } return result; } template <typename ValueType> std::map<carl::Variable, std::pair<bool, bool>> MonotonicityChecker<ValueType>::checkOnSamples(std::shared_ptr<storm::models::sparse::Dtmc<ValueType>> model, uint_fast64_t numberOfSamples) { storm::utility::Stopwatch samplesWatch(true); std::map<carl::Variable, std::pair<bool, bool>> result; auto instantiator = storm::utility::ModelInstantiator<storm::models::sparse::Dtmc<ValueType>, storm::models::sparse::Dtmc<double>>(*model); auto matrix = model->getTransitionMatrix(); std::set<carl::Variable> variables = storm::models::sparse::getProbabilityParameters(*model); for (auto itr = variables.begin(); itr != variables.end(); ++itr) { double previous = -1; bool monDecr = true; bool monIncr = true; for (auto i = 0; i < numberOfSamples; ++i) { auto valuation = storm::utility::parametric::Valuation<ValueType>(); for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) { // Only change value for current variable if ((*itr) == (*itr2)) { auto val = std::pair<carl::Variable, storm::RationalFunctionCoefficient>( (*itr2), storm::utility::convertNumber<storm::RationalFunctionCoefficient>( boost::lexical_cast<std::string>((i + 1) / (double(numberOfSamples + 1))))); valuation.insert(val); } else { auto val = std::pair<carl::Variable, storm::RationalFunctionCoefficient>( (*itr2), storm::utility::convertNumber<storm::RationalFunctionCoefficient>( boost::lexical_cast<std::string>((1) / (double(numberOfSamples + 1))))); valuation.insert(val); } } storm::models::sparse::Dtmc<double> sampleModel = instantiator.instantiate(valuation); auto checker = storm::modelchecker::SparseDtmcPrctlModelChecker<storm::models::sparse::Dtmc<double>>(sampleModel); std::unique_ptr<storm::modelchecker::CheckResult> checkResult; auto formula = formulas[0]; if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { const storm::modelchecker::CheckTask<storm::logic::UntilFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::UntilFormula, double>( (*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); checkResult = checker.computeUntilProbabilities(Environment(), checkTask); } else if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { const storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double>( (*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Expecting until or eventually formula"); } auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<double>(); std::vector<double> values = quantitativeResult.getValueVector(); auto initialStates = model->getInitialStates(); double initial = 0; for (auto i = initialStates.getNextSetIndex(0); i < model->getNumberOfStates(); i = initialStates.getNextSetIndex(i+1)) { initial += values[i]; } float diff = previous - initial; // TODO: define precission if (previous != -1 && diff > 0.000005 && diff < -0.000005) { monDecr &= previous >= initial; monIncr &= previous <= initial; } previous = initial; } result.insert(std::pair<carl::Variable, std::pair<bool, bool>>(*itr, std::pair<bool,bool>(monIncr, monDecr))); } samplesWatch.stop(); // STORM_PRINT(std::endl << "Time to check monotonicity on samples: " << samplesWatch << "." << std::endl << std::endl); resultCheckOnSamples = result; return result; } template <typename ValueType> std::map<carl::Variable, std::pair<bool, bool>> MonotonicityChecker<ValueType>::checkOnSamples(std::shared_ptr<storm::models::sparse::Mdp<ValueType>> model, uint_fast64_t numberOfSamples) { storm::utility::Stopwatch samplesWatch(true); std::map<carl::Variable, std::pair<bool, bool>> result; auto instantiator = storm::utility::ModelInstantiator<storm::models::sparse::Mdp<ValueType>, storm::models::sparse::Mdp<double>>(*model); auto matrix = model->getTransitionMatrix(); std::set<carl::Variable> variables = storm::models::sparse::getProbabilityParameters(*model); for (auto itr = variables.begin(); itr != variables.end(); ++itr) { double previous = -1; bool monDecr = true; bool monIncr = true; for (auto i = 0; i < numberOfSamples; ++i) { auto valuation = storm::utility::parametric::Valuation<ValueType>(); for (auto itr2 = variables.begin(); itr2 != variables.end(); ++itr2) { // Only change value for current variable if ((*itr) == (*itr2)) { auto val = std::pair<carl::Variable, storm::RationalFunctionCoefficient>( (*itr2), storm::utility::convertNumber<storm::RationalFunctionCoefficient>( boost::lexical_cast<std::string>((i + 1) / (double(numberOfSamples + 1))))); valuation.insert(val); } else { auto val = std::pair<carl::Variable, storm::RationalFunctionCoefficient>( (*itr2), storm::utility::convertNumber<storm::RationalFunctionCoefficient>( boost::lexical_cast<std::string>((1) / (double(numberOfSamples + 1))))); valuation.insert(val); } } storm::models::sparse::Mdp<double> sampleModel = instantiator.instantiate(valuation); auto checker = storm::modelchecker::SparseMdpPrctlModelChecker<storm::models::sparse::Mdp<double>>(sampleModel); std::unique_ptr<storm::modelchecker::CheckResult> checkResult; auto formula = formulas[0]; if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isUntilFormula()) { const storm::modelchecker::CheckTask<storm::logic::UntilFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::UntilFormula, double>( (*formula).asProbabilityOperatorFormula().getSubformula().asUntilFormula()); checkResult = checker.computeUntilProbabilities(Environment(), checkTask); } else if (formula->isProbabilityOperatorFormula() && formula->asProbabilityOperatorFormula().getSubformula().isEventuallyFormula()) { const storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double> checkTask = storm::modelchecker::CheckTask<storm::logic::EventuallyFormula, double>( (*formula).asProbabilityOperatorFormula().getSubformula().asEventuallyFormula()); checkResult = checker.computeReachabilityProbabilities(Environment(), checkTask); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Expecting until or eventually formula"); } auto quantitativeResult = checkResult->asExplicitQuantitativeCheckResult<double>(); std::vector<double> values = quantitativeResult.getValueVector(); auto initialStates = model->getInitialStates(); double initial = 0; for (auto i = initialStates.getNextSetIndex(0); i < model->getNumberOfStates(); i = initialStates.getNextSetIndex(i+1)) { initial += values[i]; } if (previous != -1) { monDecr &= previous >= initial; monIncr &= previous <= initial; } previous = initial; } result.insert(std::pair<carl::Variable, std::pair<bool, bool>>(*itr, std::pair<bool,bool>(monIncr, monDecr))); } samplesWatch.stop(); // STORM_PRINT(std::endl << "Time to check monotonicity on samples: " << samplesWatch << "." << std::endl << std::endl); resultCheckOnSamples = result; return result; } template class MonotonicityChecker<storm::RationalFunction>; } }