#include "storm-cli-utilities/cli.h" #include "storm-cli-utilities/model-handling.h" #include "storm-pars/api/storm-pars.h" #include "storm-pars/api/region.h" #include "storm-pars/analysis/MonotonicityHelper.h" #include "storm-pars/modelchecker/instantiation/SparseCtmcInstantiationModelChecker.h" #include "storm-pars/modelchecker/region/SparseParameterLiftingModelChecker.h" #include "storm-pars/modelchecker/region/SparseDtmcParameterLiftingModelChecker.h" #include "storm-pars/settings/ParsSettings.h" #include "storm-pars/settings/modules/ParametricSettings.h" #include "storm-pars/settings/modules/MonotonicitySettings.h" #include "storm-pars/settings/modules/RegionSettings.h" #include "storm-pars/transformer/SparseParametricMdpSimplifier.h" #include "storm-pars/transformer/SparseParametricDtmcSimplifier.h" #include "storm/api/storm.h" #include "storm/exceptions/BaseException.h" #include "storm/exceptions/InvalidSettingsException.h" #include "storm/exceptions/NotSupportedException.h" #include "storm/models/ModelBase.h" #include "storm/settings/SettingsManager.h" #include "storm/solver/stateelimination/NondeterministicModelStateEliminator.h" #include "storm/storage/StronglyConnectedComponentDecomposition.h" #include "storm/storage/SymbolicModelDescription.h" #include "storm/io/file.h" #include "storm/utility/initialize.h" #include "storm/utility/Stopwatch.h" #include "storm/utility/macros.h" #include "storm/utility/Engine.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/settings/modules/CoreSettings.h" #include "storm/settings/modules/IOSettings.h" #include "storm/settings/modules/BisimulationSettings.h" #include "storm/settings/modules/TransformationSettings.h" namespace storm { namespace pars { typedef typename storm::cli::SymbolicInput SymbolicInput; template struct SampleInformation { SampleInformation(bool graphPreserving = false, bool exact = false) : graphPreserving(graphPreserving), exact(exact) { // Intentionally left empty. } bool empty() const { return cartesianProducts.empty(); } std::vector::type, std::vector::type>>> cartesianProducts; bool graphPreserving; bool exact; }; struct PreprocessResult { PreprocessResult(std::shared_ptr const& model, bool changed) : changed(changed), model(model) { // Intentionally left empty. } bool changed; std::shared_ptr model; boost::optional>> formulas; }; template std::vector> parseRegions(std::shared_ptr const& model) { std::vector> result; auto regionSettings = storm::settings::getModule(); boost::optional splittingThreshold; if (regionSettings.isSplittingThresholdSet()) { splittingThreshold = regionSettings.getSplittingThreshold(); } if (regionSettings.isRegionSet()) { result = storm::api::parseRegions(regionSettings.getRegionString(), *model, splittingThreshold); } else if (regionSettings.isRegionBoundSet()) { result = storm::api::createRegion(regionSettings.getRegionBoundString(), *model, splittingThreshold); } return result; } template SampleInformation parseSamples(std::shared_ptr const& model, std::string const& sampleString, bool graphPreserving) { STORM_LOG_THROW(!model || model->isSparseModel(), storm::exceptions::NotSupportedException, "Sampling is only supported for sparse models."); SampleInformation sampleInfo(graphPreserving); if (sampleString.empty()) { return sampleInfo; } // Get all parameters from the model. std::set::type> modelParameters; auto const& sparseModel = *model->as>(); modelParameters = storm::models::sparse::getProbabilityParameters(sparseModel); auto rewParameters = storm::models::sparse::getRewardParameters(sparseModel); modelParameters.insert(rewParameters.begin(), rewParameters.end()); std::vector cartesianProducts; boost::split(cartesianProducts, sampleString, boost::is_any_of(";")); for (auto& product : cartesianProducts) { boost::trim(product); // Get the values string for each variable. std::vector valuesForVariables; boost::split(valuesForVariables, product, boost::is_any_of(",")); for (auto& values : valuesForVariables) { boost::trim(values); } std::set::type> encounteredParameters; sampleInfo.cartesianProducts.emplace_back(); auto& newCartesianProduct = sampleInfo.cartesianProducts.back(); for (auto const& varValues : valuesForVariables) { auto equalsPosition = varValues.find("="); STORM_LOG_THROW(equalsPosition != varValues.npos, storm::exceptions::WrongFormatException, "Incorrect format of samples."); std::string variableName = varValues.substr(0, equalsPosition); boost::trim(variableName); std::string values = varValues.substr(equalsPosition + 1); boost::trim(values); bool foundParameter = false; typename utility::parametric::VariableType::type theParameter; for (auto const& parameter : modelParameters) { std::stringstream parameterStream; parameterStream << parameter; if (parameterStream.str() == variableName) { foundParameter = true; theParameter = parameter; encounteredParameters.insert(parameter); } } STORM_LOG_THROW(foundParameter, storm::exceptions::WrongFormatException, "Unknown parameter '" << variableName << "'."); std::vector splitValues; boost::split(splitValues, values, boost::is_any_of(":")); STORM_LOG_THROW(!splitValues.empty(), storm::exceptions::WrongFormatException, "Expecting at least one value per parameter."); auto& list = newCartesianProduct[theParameter]; for (auto& value : splitValues) { boost::trim(value); list.push_back(storm::utility::convertNumber::type>(value)); } } STORM_LOG_THROW(encounteredParameters == modelParameters, storm::exceptions::WrongFormatException, "Variables for all parameters are required when providing samples."); } return sampleInfo; } template std::shared_ptr eliminateScc(std::shared_ptr const& model) { storm::utility::Stopwatch eliminationWatch(true); std::shared_ptr result; if (model->isOfType(storm::models::ModelType::Dtmc)) { STORM_PRINT("Applying scc elimination" << std::endl); auto sparseModel = model->as>(); auto matrix = sparseModel->getTransitionMatrix(); auto backwardsTransitionMatrix = matrix.transpose(); storm::storage::StronglyConnectedComponentDecompositionOptions const options; auto decomposition = storm::storage::StronglyConnectedComponentDecomposition(matrix, options); storm::storage::BitVector selectedStates(matrix.getRowCount()); storm::storage::BitVector selfLoopStates(matrix.getRowCount()); for (size_t i = 0; i < decomposition.size(); ++i) { auto scc = decomposition.getBlock(i); if (scc.size() > 1) { auto statesScc = scc.getStates(); std::vector entryStates; for (auto state : statesScc) { auto row = backwardsTransitionMatrix.getRow(state); bool found = false; for (auto backState : row) { if (!scc.containsState(backState.getColumn())) { found = true; } } if (found) { entryStates.push_back(state); selfLoopStates.set(state); } else { selectedStates.set(state); } } if (entryStates.size() != 1) { STORM_LOG_THROW(entryStates.size() > 1, storm::exceptions::NotImplementedException, "state elimination not implemented for scc with more than 1 entry points"); } } } storm::storage::FlexibleSparseMatrix flexibleMatrix(matrix); storm::storage::FlexibleSparseMatrix flexibleBackwardTransitions(backwardsTransitionMatrix, true); auto actionRewards = std::vector(matrix.getRowCount(), storm::utility::zero()); storm::solver::stateelimination::NondeterministicModelStateEliminator stateEliminator(flexibleMatrix, flexibleBackwardTransitions, actionRewards); for(auto state : selectedStates) { stateEliminator.eliminateState(state, true); } for (auto state : selfLoopStates) { auto row = flexibleMatrix.getRow(state); stateEliminator.eliminateLoop(state); } selectedStates.complement(); auto keptRows = matrix.getRowFilter(selectedStates); storm::storage::SparseMatrix newTransitionMatrix = flexibleMatrix.createSparseMatrix(keptRows, selectedStates); // TODO @Jip: note that rewards get lost result = std::make_shared>(std::move(newTransitionMatrix), sparseModel->getStateLabeling().getSubLabeling(selectedStates)); eliminationWatch.stop(); STORM_PRINT(std::endl << "Time for scc elimination: " << eliminationWatch << "." << std::endl << std::endl); result->printModelInformationToStream(std::cout); } else if (model->isOfType(storm::models::ModelType::Mdp)) { STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Unable to perform SCC elimination for monotonicity analysis on MDP: Not mplemented"); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); } return result; } template std::shared_ptr simplifyModel(std::shared_ptr const& model, SymbolicInput const& input) { storm::utility::Stopwatch simplifyingWatch(true); std::shared_ptr result; if (model->isOfType(storm::models::ModelType::Dtmc)) { storm::transformer::SparseParametricDtmcSimplifier> simplifier(*(model->template as>())); std::vector> formulas = storm::api::extractFormulasFromProperties(input.properties); STORM_LOG_THROW(formulas.begin()!=formulas.end(), storm::exceptions::NotSupportedException, "Only one formula at the time supported"); if (!simplifier.simplify(*(formulas[0]))) { STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); } result = simplifier.getSimplifiedModel(); } else if (model->isOfType(storm::models::ModelType::Mdp)) { storm::transformer::SparseParametricMdpSimplifier> simplifier(*(model->template as>())); std::vector> formulas = storm::api::extractFormulasFromProperties(input.properties); STORM_LOG_THROW(formulas.begin()!=formulas.end(), storm::exceptions::NotSupportedException, "Only one formula at the time supported"); if (!simplifier.simplify(*(formulas[0]))) { STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Simplifying the model was not successfull."); } result = simplifier.getSimplifiedModel(); } else { STORM_LOG_THROW(false, storm::exceptions::InvalidOperationException, "Unable to perform monotonicity analysis on the provided model type."); } simplifyingWatch.stop(); STORM_PRINT(std::endl << "Time for model simplification: " << simplifyingWatch << "." << std::endl << std::endl); result->printModelInformationToStream(std::cout); return result; } template PreprocessResult preprocessSparseModel(std::shared_ptr> const& model, SymbolicInput const& input, storm::cli::ModelProcessingInformation const& mpi) { auto bisimulationSettings = storm::settings::getModule(); auto parametricSettings = storm::settings::getModule(); auto transformationSettings = storm::settings::getModule(); auto monSettings = storm::settings::getModule(); PreprocessResult result(model, false); if (monSettings.isMonotonicityAnalysisSet() || parametricSettings.isUseMonotonicitySet()) { result.model = storm::pars::simplifyModel(result.model, input); result.changed = true; } if (result.model->isOfType(storm::models::ModelType::MarkovAutomaton)) { result.model = storm::cli::preprocessSparseMarkovAutomaton(result.model->template as>()); result.changed = true; } if (mpi.applyBisimulation) { result.model = storm::cli::preprocessSparseModelBisimulation(result.model->template as>(), input, bisimulationSettings); result.changed = true; } if (transformationSettings.isChainEliminationSet() && model->isOfType(storm::models::ModelType::MarkovAutomaton)) { auto eliminationResult = storm::api::eliminateNonMarkovianChains( result.model->template as>(), storm::api::extractFormulasFromProperties(input.properties), transformationSettings.getLabelBehavior()); result.model = eliminationResult.first; // Set transformed properties as new properties in input result.formulas = eliminationResult.second; result.changed = true; } if (parametricSettings.transformContinuousModel() && (model->isOfType(storm::models::ModelType::Ctmc) || model->isOfType(storm::models::ModelType::MarkovAutomaton))) { auto transformResult = storm::api::transformContinuousToDiscreteTimeSparseModel(std::move(*model->template as>()), storm::api::extractFormulasFromProperties(input.properties)); result.model = transformResult.first; // Set transformed properties as new properties in input result.formulas = transformResult.second; result.changed = true; } if (monSettings.isSccEliminationSet()) { result.model = storm::pars::eliminateScc(result.model); result.changed = true; } return result; } template PreprocessResult preprocessDdModel(std::shared_ptr> const& model, SymbolicInput const& input, storm::cli::ModelProcessingInformation const& mpi) { auto bisimulationSettings = storm::settings::getModule(); PreprocessResult result(model, false); if (mpi.engine == storm::utility::Engine::Hybrid) { // Currently, hybrid engine for parametric models just refers to building the model symbolically. STORM_LOG_INFO("Translating symbolic model to sparse model..."); result.model = storm::api::transformSymbolicToSparseModel(model); result.changed = true; // Invoke preprocessing on the sparse model PreprocessResult sparsePreprocessingResult = storm::pars::preprocessSparseModel(result.model->as>(), input, mpi); if (sparsePreprocessingResult.changed) { result.model = sparsePreprocessingResult.model; result.formulas = sparsePreprocessingResult.formulas; } } else { STORM_LOG_ASSERT(mpi.engine == storm::utility::Engine::Dd, "Expected Dd engine."); if (mpi.applyBisimulation) { result.model = storm::cli::preprocessDdModelBisimulation(result.model->template as>(), input, bisimulationSettings, mpi); result.changed = true; } } return result; } template PreprocessResult preprocessModel(std::shared_ptr const& model, SymbolicInput const& input, storm::cli::ModelProcessingInformation const& mpi) { storm::utility::Stopwatch preprocessingWatch(true); PreprocessResult result(model, false); if (model->isSparseModel()) { result = storm::pars::preprocessSparseModel(result.model->as>(), input, mpi); } else { STORM_LOG_ASSERT(model->isSymbolicModel(), "Unexpected model type."); result = storm::pars::preprocessDdModel(result.model->as>(), input, mpi); } if (result.changed) { STORM_PRINT_AND_LOG(std::endl << "Time for model preprocessing: " << preprocessingWatch << "." << std::endl << std::endl); } return result; } template void printInitialStatesResult(std::unique_ptr const& result, storm::jani::Property const& property, storm::utility::Stopwatch* watch = nullptr, storm::utility::parametric::Valuation const* valuation = nullptr) { if (result) { STORM_PRINT_AND_LOG("Result (initial states)"); if (valuation) { bool first = true; std::stringstream ss; for (auto const& entry : *valuation) { if (!first) { ss << ", "; } else { first = false; } ss << entry.first << "=" << entry.second; } STORM_PRINT_AND_LOG(" for instance [" << ss.str() << "]"); } STORM_PRINT_AND_LOG(": ") auto const* regionCheckResult = dynamic_cast const*>(result.get()); if (regionCheckResult != nullptr) { auto regionSettings = storm::settings::getModule(); std::stringstream outStream; if (regionSettings.isPrintFullResultSet()) { regionCheckResult->writeToStream(outStream); } else { regionCheckResult->writeCondensedToStream(outStream); } outStream << std::endl; if (!regionSettings.isPrintNoIllustrationSet()) { auto const* regionRefinementCheckResult = dynamic_cast const*>(regionCheckResult); if (regionRefinementCheckResult != nullptr) { regionRefinementCheckResult->writeIllustrationToStream(outStream); } } outStream << std::endl; STORM_PRINT_AND_LOG(outStream.str()); } else { STORM_PRINT_AND_LOG(*result << std::endl); } if (watch) { STORM_PRINT_AND_LOG("Time for model checking: " << *watch << "." << std::endl << std::endl); } } else { STORM_LOG_ERROR("Property is unsupported by selected engine/settings." << std::endl); } } template void verifyProperties(std::vector const& properties, std::function(std::shared_ptr const& formula)> const& verificationCallback, std::function const&)> const& postprocessingCallback) { for (auto const& property : properties) { storm::cli::printModelCheckingProperty(property); storm::utility::Stopwatch watch(true); std::unique_ptr result = verificationCallback(property.getRawFormula()); watch.stop(); printInitialStatesResult(result, property, &watch); postprocessingCallback(result); } } template class ModelCheckerType, typename ModelType, typename ValueType, typename SolveValueType = double> void verifyPropertiesAtSamplePoints(ModelType const& model, SymbolicInput const& input, SampleInformation const& samples) { // When samples are provided, we create an instantiation model checker. ModelCheckerType modelchecker(model); for (auto const& property : input.properties) { storm::cli::printModelCheckingProperty(property); modelchecker.specifyFormula(storm::api::createTask(property.getRawFormula(), true)); modelchecker.setInstantiationsAreGraphPreserving(samples.graphPreserving); storm::utility::parametric::Valuation valuation; std::vector::type> parameters; std::vector::type>::const_iterator> iterators; std::vector::type>::const_iterator> iteratorEnds; storm::utility::Stopwatch watch(true); for (auto const& product : samples.cartesianProducts) { parameters.clear(); iterators.clear(); iteratorEnds.clear(); for (auto const& entry : product) { parameters.push_back(entry.first); iterators.push_back(entry.second.cbegin()); iteratorEnds.push_back(entry.second.cend()); } bool done = false; while (!done) { // Read off valuation. for (uint64_t i = 0; i < parameters.size(); ++i) { valuation[parameters[i]] = *iterators[i]; } storm::utility::Stopwatch valuationWatch(true); std::unique_ptr result = modelchecker.check(Environment(), valuation); valuationWatch.stop(); if (result) { result->filter(storm::modelchecker::ExplicitQualitativeCheckResult(model.getInitialStates())); } printInitialStatesResult(result, property, &valuationWatch, &valuation); for (uint64_t i = 0; i < parameters.size(); ++i) { ++iterators[i]; if (iterators[i] == iteratorEnds[i]) { // Reset iterator and proceed to move next iterator. iterators[i] = product.at(parameters[i]).cbegin(); // If the last iterator was removed, we are done. if (i == parameters.size() - 1) { done = true; } } else { // If an iterator was moved but not reset, we have another valuation to check. break; } } } } watch.stop(); STORM_PRINT_AND_LOG("Overall time for sampling all instances: " << watch << std::endl << std::endl); } } template void verifyPropertiesAtSamplePoints(std::shared_ptr> const& model, SymbolicInput const& input, SampleInformation const& samples) { if (model->isOfType(storm::models::ModelType::Dtmc)) { verifyPropertiesAtSamplePoints, ValueType, SolveValueType>(*model->template as>(), input, samples); } else if (model->isOfType(storm::models::ModelType::Ctmc)) { verifyPropertiesAtSamplePoints, ValueType, SolveValueType>(*model->template as>(), input, samples); } else if (model->isOfType(storm::models::ModelType::Mdp)) { verifyPropertiesAtSamplePoints, ValueType, SolveValueType>(*model->template as>(), input, samples); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Sampling is currently only supported for DTMCs, CTMCs and MDPs."); } } template void verifyPropertiesWithSparseEngine(std::shared_ptr> const& model, SymbolicInput const& input, SampleInformation const& samples) { if (samples.empty()) { verifyProperties(input.properties, [&model] (std::shared_ptr const& formula) { std::unique_ptr result = storm::api::verifyWithSparseEngine(model, storm::api::createTask(formula, true)); if (result) { result->filter(storm::modelchecker::ExplicitQualitativeCheckResult(model->getInitialStates())); } return result; }, [&model] (std::unique_ptr const& result) { auto parametricSettings = storm::settings::getModule(); if (parametricSettings.exportResultToFile() && model->isOfType(storm::models::ModelType::Dtmc)) { auto dtmc = model->template as>(); boost::optional rationalFunction = result->asExplicitQuantitativeCheckResult()[*model->getInitialStates().begin()]; storm::api::exportParametricResultToFile(rationalFunction, storm::analysis::ConstraintCollector(*dtmc), parametricSettings.exportResultPath()); } else if (parametricSettings.exportResultToFile() && model->isOfType(storm::models::ModelType::Ctmc)) { auto ctmc = model->template as>(); boost::optional rationalFunction = result->asExplicitQuantitativeCheckResult()[*model->getInitialStates().begin()]; storm::api::exportParametricResultToFile(rationalFunction, storm::analysis::ConstraintCollector(*ctmc), parametricSettings.exportResultPath()); } }); } else { STORM_LOG_TRACE("Sampling the model at given points."); if (samples.exact) { verifyPropertiesAtSamplePoints(model, input, samples); } else { verifyPropertiesAtSamplePoints(model, input, samples); } } } template void analyzeMonotonicity(std::shared_ptr> const& model, SymbolicInput const& input, std::vector> const& regions) { std::ofstream outfile; auto monSettings = storm::settings::getModule(); if (monSettings.isExportMonotonicitySet()) { utility::openFile(monSettings.getExportMonotonicityFilename(), outfile); } std::vector> formulas = storm::api::extractFormulasFromProperties(input.properties); storm::utility::Stopwatch monotonicityWatch(true); STORM_LOG_THROW(regions.size() <= 1, storm::exceptions::InvalidArgumentException, "Monotonicity analysis only allowed on single region"); if (!monSettings.isMonSolutionSet()) { auto monotonicityHelper = storm::analysis::MonotonicityHelper(model, formulas, regions, monSettings.getNumberOfSamples(), storm::settings::getModule().getPrecision(), monSettings.isDotOutputSet()); if (monSettings.isExportMonotonicitySet()) { monotonicityHelper.checkMonotonicityInBuild(outfile, monSettings.isUsePLABoundsSet(), monSettings.getDotOutputFilename()); } else { monotonicityHelper.checkMonotonicityInBuild(std::cout, monSettings.isUsePLABoundsSet(), monSettings.getDotOutputFilename()); } } else { // Checking monotonicity based on solution function auto parametricSettings = storm::settings::getModule(); auto regionSettings = storm::settings::getModule(); std::function(std::shared_ptr const& formula)> verificationCallback; std::function const&)> postprocessingCallback; // Check the given set of regions with or without refinement verificationCallback = [&] (std::shared_ptr const& formula) { std::unique_ptr result = storm::api::verifyWithSparseEngine(model, storm::api::createTask(formula, true)); return result; }; for (auto & property : input.properties) { auto result = verificationCallback(property.getRawFormula())->asExplicitQuantitativeCheckResult().getValueVector(); ValueType valuation; auto states= model->getInitialStates(); for (auto state : states) { valuation += result[state]; } storm::analysis::MonotonicityResult monRes; for (auto & var : storm::models::sparse::getProbabilityParameters(*model)) { auto res = storm::analysis::MonotonicityChecker::checkDerivative(valuation.derivative(var), regions[0]); if (res.first && res.second) { monRes.addMonotonicityResult(var, analysis::MonotonicityResult::Monotonicity::Constant); } else if (res.first) { monRes.addMonotonicityResult(var, analysis::MonotonicityResult::Monotonicity::Incr); } else if (res.second) { monRes.addMonotonicityResult(var, analysis::MonotonicityResult::Monotonicity::Decr); } else { monRes.addMonotonicityResult(var, analysis::MonotonicityResult::Monotonicity::Not); } } if (monSettings.isExportMonotonicitySet()) { outfile << monRes.toString(); } else { STORM_PRINT(monRes.toString()); } } } if (monSettings.isExportMonotonicitySet()) { utility::closeFile(outfile); } monotonicityWatch.stop(); STORM_PRINT(std::endl << "Total time for monotonicity checking: " << monotonicityWatch << "." << std::endl << std::endl); return; } template void computeRegionExtremumWithSparseEngine(std::shared_ptr> const& model, SymbolicInput const& input, std::vector> const& regions, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), boost::optional::VariableType>, std::set::VariableType>>>& monotoneParameters = boost::none) { STORM_LOG_ASSERT(!regions.empty(), "Can not analyze an empty set of regions."); auto regionSettings = storm::settings::getModule(); auto monSettings = storm::settings::getModule(); auto engine = regionSettings.getRegionCheckEngine(); storm::solver::OptimizationDirection direction = regionSettings.getExtremumDirection(); ValueType precision = storm::utility::convertNumber(regionSettings.getExtremumValuePrecision()); bool generateSplitEstimates = regionSettings.isSplittingThresholdSet(); for (auto const& property : input.properties) { for (auto const& region : regions) { if (monotonicitySettings.useMonotonicity) { STORM_PRINT_AND_LOG("Computing extremal value for property " << property.getName() << ": " << *property.getRawFormula() << " within region " << region << " and using monotonicity ..." << std::endl); } else { STORM_PRINT_AND_LOG("Computing extremal value for property " << property.getName() << ": " << *property.getRawFormula() << " within region " << region << "..." << std::endl); } storm::utility::Stopwatch watch(true); // TODO: hier eventueel checkExtremalValue van maken if (regionSettings.isExtremumSuggestionSet()) { ValueType suggestion = storm::utility::convertNumber(regionSettings.getExtremumSuggestion()); if (storm::api::checkExtremalValue(model, storm::api::createTask(property.getRawFormula(), true), region, engine, direction, precision, suggestion, monotonicitySettings, generateSplitEstimates, monotoneParameters)) { STORM_PRINT_AND_LOG(suggestion << " is the extremum "); } else { STORM_PRINT_AND_LOG(suggestion << " is NOT the extremum "); } } else { auto valueValuation = storm::api::computeExtremalValue(model, storm::api::createTask(property.getRawFormula(), true), region, engine, direction, precision, monotonicitySettings, generateSplitEstimates, monotoneParameters); watch.stop(); std::stringstream valuationStr; bool first = true; for (auto const& v : valueValuation.second) { if (first) { first = false; } else { valuationStr << ", "; } valuationStr << v.first << "=" << v.second; } STORM_PRINT_AND_LOG("Result at initial state: " << valueValuation.first << " ( approx. " << storm::utility::convertNumber(valueValuation.first) << ") at [" << valuationStr.str() << "]." << std::endl) STORM_PRINT_AND_LOG("Time for model checking: " << watch << "." << std::endl); } } } } template void verifyRegionsWithSparseEngine(std::shared_ptr> const& model, SymbolicInput const& input, std::vector> const& regions, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), uint64_t monThresh = 0) { STORM_LOG_ASSERT(!regions.empty(), "Can not analyze an empty set of regions."); auto parametricSettings = storm::settings::getModule(); auto regionSettings = storm::settings::getModule(); std::function(std::shared_ptr const& formula)> verificationCallback; std::function const&)> postprocessingCallback; STORM_PRINT_AND_LOG(std::endl); if (regionSettings.isHypothesisSet()) { STORM_PRINT_AND_LOG("Checking hypothesis " << regionSettings.getHypothesis() << " on "); } else { STORM_PRINT_AND_LOG("Analyzing "); } if (regions.size() == 1) { STORM_PRINT_AND_LOG("parameter region " << regions.front()); } else { STORM_PRINT_AND_LOG(regions.size() << " parameter regions"); } auto engine = regionSettings.getRegionCheckEngine(); STORM_PRINT_AND_LOG(" using " << engine); if (monotonicitySettings.useMonotonicity) { STORM_PRINT_AND_LOG(" with local monotonicity and"); } // Check the given set of regions with or without refinement if (regionSettings.isRefineSet()) { STORM_LOG_THROW(regions.size() == 1, storm::exceptions::NotSupportedException, "Region refinement is not supported for multiple initial regions."); STORM_PRINT_AND_LOG(" with iterative refinement until " << (1.0 - regionSettings.getCoverageThreshold()) * 100.0 << "% is covered." << (regionSettings.isDepthLimitSet() ? " Depth limit is " + std::to_string(regionSettings.getDepthLimit()) + "." : "") << std::endl); verificationCallback = [&] (std::shared_ptr const& formula) { ValueType refinementThreshold = storm::utility::convertNumber(regionSettings.getCoverageThreshold()); boost::optional optionalDepthLimit; if (regionSettings.isDepthLimitSet()) { optionalDepthLimit = regionSettings.getDepthLimit(); } // TODO @Jip: change allow model simplification when not using monotonicity, for benchmarking purposes simplification is moved forward. std::unique_ptr> result = storm::api::checkAndRefineRegionWithSparseEngine(model, storm::api::createTask(formula, true), regions.front(), engine, refinementThreshold, optionalDepthLimit, regionSettings.getHypothesis(), false, monotonicitySettings, monThresh); return result; }; } else { STORM_PRINT_AND_LOG("." << std::endl); verificationCallback = [&] (std::shared_ptr const& formula) { std::unique_ptr result = storm::api::checkRegionsWithSparseEngine(model, storm::api::createTask(formula, true), regions, engine, regionSettings.getHypothesis()); return result; }; } postprocessingCallback = [&] (std::unique_ptr const& result) { if (parametricSettings.exportResultToFile()) { storm::api::exportRegionCheckResultToFile(result, parametricSettings.exportResultPath()); } }; verifyProperties(input.properties, verificationCallback, postprocessingCallback); } template void verifyWithSparseEngine(std::shared_ptr> const& model, SymbolicInput const& input, std::vector> const& regions, SampleInformation const& samples, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), boost::optional::VariableType>, std::set::VariableType>>>& monotoneParameters = boost::none, uint64_t monThresh = 0) { if (regions.empty()) { storm::pars::verifyPropertiesWithSparseEngine(model, input, samples); } else { auto regionSettings = storm::settings::getModule(); auto monSettings = storm::settings::getModule(); if (monSettings.isMonotonicityAnalysisSet()) { storm::pars::analyzeMonotonicity(model, input, regions); } else if (regionSettings.isExtremumSet()) { storm::pars::computeRegionExtremumWithSparseEngine(model, input, regions, monotonicitySettings, monotoneParameters); } else { assert (monotoneParameters == boost::none); assert (!monotonicitySettings.useOnlyGlobalMonotonicity); assert (!monotonicitySettings.useBoundsFromPLA); storm::pars::verifyRegionsWithSparseEngine(model, input, regions, monotonicitySettings, monThresh); } } } template void verifyPropertiesWithSymbolicEngine(std::shared_ptr> const& model, SymbolicInput const& input, SampleInformation const& samples) { if (samples.empty()) { verifyProperties(input.properties, [&model] (std::shared_ptr const& formula) { std::unique_ptr result = storm::api::verifyWithDdEngine(model, storm::api::createTask(formula, true)); if (result) { result->filter(storm::modelchecker::SymbolicQualitativeCheckResult(model->getReachableStates(), model->getInitialStates())); } return result; }, [&model] (std::unique_ptr const& result) { auto parametricSettings = storm::settings::getModule(); if (parametricSettings.exportResultToFile() && model->isOfType(storm::models::ModelType::Dtmc)) { //auto dtmc = model->template as>(); //boost::optional rationalFunction = result->asSymbolicQuantitativeCheckResult().sum(); //storm::api::exportParametricResultToFile(rationalFunction, storm::analysis::ConstraintCollector(*dtmc), parametricSettings.exportResultPath()); } }); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Sampling is not supported in the symbolic engine."); } } template void verifyWithDdEngine(std::shared_ptr> const& model, SymbolicInput const& input, std::vector> const& regions, SampleInformation const& samples) { if (regions.empty()) { storm::pars::verifyPropertiesWithSymbolicEngine(model, input, samples); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Region verification is not supported in the symbolic engine."); } } template void verifyParametricModel(std::shared_ptr const& model, SymbolicInput const& input, std::vector> const& regions, SampleInformation const& samples, storm::api::MonotonicitySetting monotonicitySettings = storm::api::MonotonicitySetting(), boost::optional::VariableType>, std::set::VariableType>>>& monotoneParameters = boost::none, uint64_t monThresh = 0) { if (model->isSparseModel()) { storm::pars::verifyWithSparseEngine(model->as>(), input, regions, samples, monotonicitySettings, monotoneParameters, monThresh); } else { assert (!monotonicitySettings.useMonotonicity); assert (monotoneParameters == boost::none); storm::pars::verifyWithDdEngine(model->as>(), input, regions, samples); } } template void processInputWithValueTypeAndDdlib(SymbolicInput& input, storm::cli::ModelProcessingInformation const& mpi) { auto ioSettings = storm::settings::getModule(); auto buildSettings = storm::settings::getModule(); auto parSettings = storm::settings::getModule(); auto monSettings = storm::settings::getModule(); STORM_LOG_THROW(mpi.engine == storm::utility::Engine::Sparse || mpi.engine == storm::utility::Engine::Hybrid || mpi.engine == storm::utility::Engine::Dd, storm::exceptions::InvalidSettingsException, "The selected engine is not supported for parametric models."); std::shared_ptr model; if (!buildSettings.isNoBuildModelSet()) { model = storm::cli::buildModel(input, ioSettings, mpi); } if (model) { model->printModelInformationToStream(std::cout); } STORM_LOG_THROW(model || input.properties.empty(), storm::exceptions::InvalidSettingsException, "No input model."); if (model) { auto preprocessingResult = storm::pars::preprocessModel(model, input, mpi); if (preprocessingResult.changed) { model = preprocessingResult.model; if (preprocessingResult.formulas) { std::vector newProperties; for (size_t i = 0; i < preprocessingResult.formulas.get().size(); ++i) { auto formula = preprocessingResult.formulas.get().at(i); STORM_LOG_ASSERT(i < input.properties.size(), "Index " << i << " greater than number of properties."); storm::jani::Property property = input.properties.at(i); newProperties.push_back(storm::jani::Property(property.getName(), formula, property.getUndefinedConstants(), nullptr, property.getComment())); } input.properties = newProperties; } model->printModelInformationToStream(std::cout); } } std::vector> regions = parseRegions(model); std::string samplesAsString = parSettings.getSamples(); SampleInformation samples; if (!samplesAsString.empty()) { samples = parseSamples(model, samplesAsString, parSettings.isSamplesAreGraphPreservingSet()); samples.exact = parSettings.isSampleExactSet(); } if (model) { storm::cli::exportModel(model, input); } if (parSettings.onlyObtainConstraints()) { STORM_LOG_THROW(parSettings.exportResultToFile(), storm::exceptions::InvalidSettingsException, "When computing constraints, export path has to be specified."); storm::api::exportParametricResultToFile(boost::none, storm::analysis::ConstraintCollector( *(model->as>())), parSettings.exportResultPath()); return; } if (model) { boost::optional, std::set>> monotoneParameters; if (monSettings.isMonotoneParametersSet()) { monotoneParameters = std::move( storm::api::parseMonotoneParameters(monSettings.getMonotoneParameterFilename(), model->as>())); } // TODO: is onlyGlobalSet was used here verifyParametricModel(model, input, regions, samples, storm::api::MonotonicitySetting(parSettings.isUseMonotonicitySet(), false, monSettings.isUsePLABoundsSet()), monotoneParameters, monSettings.getMonotonicityThreshold()); } } void processOptions() { // Start by setting some urgent options (log levels, resources, etc.) storm::cli::setUrgentOptions(); auto coreSettings = storm::settings::getModule(); auto engine = coreSettings.getEngine(); STORM_LOG_WARN_COND(engine != storm::utility::Engine::Dd || engine != storm::utility::Engine::Hybrid || coreSettings.getDdLibraryType() == storm::dd::DdType::Sylvan, "The selected DD library does not support parametric models. Switching to Sylvan..."); // Parse and preprocess symbolic input (PRISM, JANI, properties, etc.) auto symbolicInput = storm::cli::parseSymbolicInput(); storm::cli::ModelProcessingInformation mpi; std::tie(symbolicInput, mpi) = storm::cli::preprocessSymbolicInput(symbolicInput); processInputWithValueTypeAndDdlib(symbolicInput, mpi); } } } /*! * Main entry point of the executable storm-pars. */ int main(const int argc, const char** argv) { try { storm::utility::setUp(); storm::cli::printHeader("Storm-pars", argc, argv); storm::settings::initializeParsSettings("Storm-pars", "storm-pars"); storm::utility::Stopwatch totalTimer(true); if (!storm::cli::parseOptions(argc, argv)) { return -1; } storm::pars::processOptions(); totalTimer.stop(); if (storm::settings::getModule().isPrintTimeAndMemorySet()) { storm::cli::printTimeAndMemoryStatistics(totalTimer.getTimeInMilliseconds()); } storm::utility::cleanUp(); return 0; } catch (storm::exceptions::BaseException const& exception) { STORM_LOG_ERROR("An exception caused Storm-pars to terminate. The message of the exception is: " << exception.what()); return 1; } catch (std::exception const& exception) { STORM_LOG_ERROR("An unexpected exception occurred and caused Storm-pars to terminate. The message of this exception is: " << exception.what()); return 2; } }