You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1219 lines
79 KiB

#pragma once
#include "storm/api/storm.h"
#include "storm-counterexamples/api/counterexamples.h"
#include "storm-parsers/api/storm-parsers.h"
#include "storm/utility/SignalHandler.h"
#include "storm/io/file.h"
#include "storm/utility/macros.h"
#include "storm/utility/NumberTraits.h"
#include "storm/utility/Engine.h"
#include "storm/utility/AutomaticSettings.h"
#include "storm/utility/initialize.h"
#include "storm/utility/Stopwatch.h"
#include <type_traits>
#include "storm/storage/SymbolicModelDescription.h"
#include "storm/storage/jani/Property.h"
#include "storm/builder/BuilderType.h"
#include "storm/models/ModelBase.h"
#include "storm/environment/Environment.h"
#include "storm/exceptions/OptionParserException.h"
#include "storm/modelchecker/results/SymbolicQualitativeCheckResult.h"
#include "storm/models/sparse/StandardRewardModel.h"
#include "storm/models/symbolic/StandardRewardModel.h"
#include "storm/models/symbolic/MarkovAutomaton.h"
#include "storm/settings/SettingsManager.h"
#include "storm/settings/modules/ResourceSettings.h"
#include "storm/settings/modules/JitBuilderSettings.h"
#include "storm/settings/modules/BuildSettings.h"
#include "storm/settings/modules/DebugSettings.h"
#include "storm/settings/modules/IOSettings.h"
#include "storm/settings/modules/CoreSettings.h"
#include "storm/settings/modules/AbstractionSettings.h"
#include "storm/settings/modules/ResourceSettings.h"
#include "storm/settings/modules/ModelCheckerSettings.h"
#include "storm/settings/modules/TransformationSettings.h"
#include "storm/settings/modules/HintSettings.h"
#include "storm/storage/Qvbs.h"
#include "storm/utility/Stopwatch.h"
namespace storm {
namespace cli {
struct SymbolicInput {
// The symbolic model description.
boost::optional<storm::storage::SymbolicModelDescription> model;
// The original properties to check.
std::vector<storm::jani::Property> properties;
// The preprocessed properties to check (in case they needed amendment).
boost::optional<std::vector<storm::jani::Property>> preprocessedProperties;
};
void parseSymbolicModelDescription(storm::settings::modules::IOSettings const& ioSettings, SymbolicInput& input) {
auto buildSettings = storm::settings::getModule<storm::settings::modules::BuildSettings>();
if (ioSettings.isPrismOrJaniInputSet()) {
storm::utility::Stopwatch modelParsingWatch(true);
if (ioSettings.isPrismInputSet()) {
input.model = storm::api::parseProgram(ioSettings.getPrismInputFilename(), buildSettings.isPrismCompatibilityEnabled(), !buildSettings.isNoSimplifySet());
} else {
boost::optional<std::vector<std::string>> propertyFilter;
if (ioSettings.isJaniPropertiesSet()) {
if (ioSettings.areJaniPropertiesSelected()) {
propertyFilter = ioSettings.getSelectedJaniProperties();
} else {
propertyFilter = boost::none;
}
} else {
propertyFilter = std::vector<std::string>();
}
auto janiInput = storm::api::parseJaniModel(ioSettings.getJaniInputFilename(), propertyFilter);
input.model = std::move(janiInput.first);
if (ioSettings.isJaniPropertiesSet()) {
input.properties = std::move(janiInput.second);
}
}
modelParsingWatch.stop();
STORM_PRINT("Time for model input parsing: " << modelParsingWatch << "." << std::endl << std::endl);
}
}
void parseProperties(storm::settings::modules::IOSettings const& ioSettings, SymbolicInput& input, boost::optional<std::set<std::string>> const& propertyFilter) {
if (ioSettings.isPropertySet()) {
std::vector<storm::jani::Property> newProperties;
if (input.model) {
newProperties = storm::api::parsePropertiesForSymbolicModelDescription(ioSettings.getProperty(), input.model.get(), propertyFilter);
} else {
newProperties = storm::api::parseProperties(ioSettings.getProperty(), propertyFilter);
}
input.properties.insert(input.properties.end(), newProperties.begin(), newProperties.end());
}
}
SymbolicInput parseSymbolicInputQvbs(storm::settings::modules::IOSettings const& ioSettings) {
// Parse the model input
SymbolicInput input;
storm::storage::QvbsBenchmark benchmark(ioSettings.getQvbsModelName());
STORM_PRINT_AND_LOG(benchmark.getInfo(ioSettings.getQvbsInstanceIndex(), ioSettings.getQvbsPropertyFilter()));
storm::utility::Stopwatch modelParsingWatch(true);
auto janiInput = storm::api::parseJaniModel(benchmark.getJaniFile(ioSettings.getQvbsInstanceIndex()), ioSettings.getQvbsPropertyFilter());
input.model = std::move(janiInput.first);
input.properties = std::move(janiInput.second);
modelParsingWatch.stop();
STORM_PRINT("Time for model input parsing: " << modelParsingWatch << "." << std::endl << std::endl);
// Parse additional properties
boost::optional<std::set<std::string>> propertyFilter = storm::api::parsePropertyFilter(ioSettings.getPropertyFilter());
parseProperties(ioSettings, input, propertyFilter);
// Substitute constant definitions
auto constantDefinitions = input.model.get().parseConstantDefinitions(benchmark.getConstantDefinition(ioSettings.getQvbsInstanceIndex()));
input.model = input.model.get().preprocess(constantDefinitions);
if (!input.properties.empty()) {
input.properties = storm::api::substituteConstantsInProperties(input.properties, constantDefinitions);
}
return input;
}
SymbolicInput parseSymbolicInput() {
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
if (ioSettings.isQvbsInputSet()) {
return parseSymbolicInputQvbs(ioSettings);
} else {
// Parse the property filter, if any is given.
boost::optional<std::set<std::string>> propertyFilter = storm::api::parsePropertyFilter(ioSettings.getPropertyFilter());
SymbolicInput input;
parseSymbolicModelDescription(ioSettings, input);
parseProperties(ioSettings, input, propertyFilter);
return input;
}
}
struct ModelProcessingInformation {
// The engine to use
storm::utility::Engine engine;
// If set, bisimulation will be applied.
bool applyBisimulation;
// If set, a transformation to Jani will be enforced
bool transformToJani;
// Which data type is to be used for numbers ...
enum class ValueType {
FinitePrecision,
Exact,
Parametric
};
ValueType buildValueType; // ... during model building
ValueType verificationValueType; // ... during model verification
// The Dd library to be used
storm::dd::DdType ddType;
// The environment used during model checking
storm::Environment env;
// A flag which is set to true, if the settings were detected to be compatible.
// If this is false, it could be that the query can not be handled.
bool isCompatible;
};
void getModelProcessingInformationAutomatic(SymbolicInput const& input, ModelProcessingInformation& mpi) {
auto hints = storm::settings::getModule<storm::settings::modules::HintSettings>();
STORM_LOG_THROW(input.model.is_initialized(), storm::exceptions::InvalidArgumentException, "Automatic engine requires a JANI input model.");
STORM_LOG_THROW(input.model->isJaniModel(), storm::exceptions::InvalidArgumentException, "Automatic engine requires a JANI input model.");
std::vector<storm::jani::Property> const& properties = input.preprocessedProperties.is_initialized() ? input.preprocessedProperties.get() : input.properties;
STORM_LOG_THROW(!properties.empty(), storm::exceptions::InvalidArgumentException, "Automatic engine requires a property.");
STORM_LOG_WARN_COND(properties.size() == 1, "Automatic engine does not support decisions based on multiple properties. Only the first property will be considered.");
storm::utility::AutomaticSettings as;
if (hints.isNumberStatesSet()) {
as.predict(input.model->asJaniModel(), properties.front(), hints.getNumberStates());
} else {
as.predict(input.model->asJaniModel(), properties.front());
}
mpi.engine = as.getEngine();
if (as.enableBisimulation()) {
mpi.applyBisimulation = true;
}
if (as.enableExact() && mpi.verificationValueType == ModelProcessingInformation::ValueType::FinitePrecision) {
mpi.verificationValueType = ModelProcessingInformation::ValueType::Exact;
}
STORM_PRINT_AND_LOG( "Automatic engine picked the following settings: " << std::endl
<< "\tengine=" << mpi.engine
<< std::boolalpha
<< "\t bisimulation=" << mpi.applyBisimulation
<< "\t exact=" << (mpi.verificationValueType != ModelProcessingInformation::ValueType::FinitePrecision)
<< std::noboolalpha << std::endl);
}
/*!
* Sets the model processing information based on the given input.
* Finding the right model processing information might require a conversion to jani.
* In this case, the jani conversion is stored in the transformedJaniInput pointer (unless it is null)
*/
ModelProcessingInformation getModelProcessingInformation(SymbolicInput const& input, std::shared_ptr<SymbolicInput> const& transformedJaniInput = nullptr) {
ModelProcessingInformation mpi;
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
auto coreSettings = storm::settings::getModule<storm::settings::modules::CoreSettings>();
auto generalSettings = storm::settings::getModule<storm::settings::modules::GeneralSettings>();
auto bisimulationSettings = storm::settings::getModule<storm::settings::modules::BisimulationSettings>();
// Set the engine.
mpi.engine = coreSettings.getEngine();
// Set whether bisimulation is to be used.
mpi.applyBisimulation = generalSettings.isBisimulationSet();
// Set the value type used for numeric values
if (generalSettings.isParametricSet()) {
mpi.verificationValueType = ModelProcessingInformation::ValueType::Parametric;
} else if (generalSettings.isExactSet()) {
mpi.verificationValueType = ModelProcessingInformation::ValueType::Exact;
} else {
mpi.verificationValueType = ModelProcessingInformation::ValueType::FinitePrecision;
}
auto originalVerificationValueType = mpi.verificationValueType;
// Since the remaining settings could depend on the ones above, we need apply the automatic engine now.
bool useAutomatic = input.model.is_initialized() && mpi.engine == storm::utility::Engine::Automatic;
if (useAutomatic) {
if (input.model->isJaniModel()) {
// This can potentially overwrite the settings above, but will not overwrite settings that were explicitly set by the user (e.g. we will not disable bisimulation or disable exact arithmetic)
getModelProcessingInformationAutomatic(input, mpi);
} else {
// Transform Prism to jani first
STORM_LOG_ASSERT(input.model->isPrismProgram(), "Unexpected type of input.");
SymbolicInput janiInput;
janiInput.properties = input.properties;
storm::prism::Program const& prog = input.model.get().asPrismProgram();
auto modelAndProperties = prog.toJani(input.preprocessedProperties.is_initialized() ? input.preprocessedProperties.get() : input.properties);
janiInput.model = modelAndProperties.first;
if (!modelAndProperties.second.empty()) {
janiInput.preprocessedProperties = std::move(modelAndProperties.second);
}
// This can potentially overwrite the settings above, but will not overwrite settings that were explicitly set by the user (e.g. we will not disable bisimulation or disable exact arithmetic)
getModelProcessingInformationAutomatic(janiInput, mpi);
if (transformedJaniInput) {
// We cache the transformation result.
*transformedJaniInput = std::move(janiInput);
}
}
}
// Check whether these settings are compatible with the provided input.
if (input.model) {
auto checkCompatibleSettings = [&mpi, &input] {
switch (mpi.verificationValueType) {
case ModelProcessingInformation::ValueType::Parametric:
return storm::utility::canHandle<storm::RationalFunction>(mpi.engine, input.preprocessedProperties.is_initialized() ? input.preprocessedProperties.get() : input.properties, input.model.get());
case ModelProcessingInformation::ValueType::Exact:
return storm::utility::canHandle<storm::RationalNumber>(mpi.engine, input.preprocessedProperties.is_initialized() ? input.preprocessedProperties.get() : input.properties, input.model.get());
break;
case ModelProcessingInformation::ValueType::FinitePrecision:
return storm::utility::canHandle<double>(mpi.engine, input.preprocessedProperties.is_initialized() ? input.preprocessedProperties.get() : input.properties, input.model.get());
}
return false;
};
mpi.isCompatible = checkCompatibleSettings();
if (!mpi.isCompatible) {
if (useAutomatic) {
bool useExact = mpi.verificationValueType != ModelProcessingInformation::ValueType::FinitePrecision;
STORM_LOG_WARN("The settings picked by the automatic engine (engine=" << mpi.engine << ", bisim=" << mpi.applyBisimulation << ", exact=" << useExact << ") are incompatible with this model. Falling back to default settings.");
mpi.engine = storm::utility::Engine::Sparse;
mpi.applyBisimulation = false;
mpi.verificationValueType = originalVerificationValueType;
// Retry check with new settings
mpi.isCompatible = checkCompatibleSettings();
}
}
} else {
// If there is no input model, nothing has to be done, actually
mpi.isCompatible = true;
}
// Set whether a transformation to jani is required or necessary
mpi.transformToJani = ioSettings.isPrismToJaniSet();
if (input.model) {
auto builderType = storm::utility::getBuilderType(mpi.engine);
bool transformToJaniForJit = builderType == storm::builder::BuilderType::Jit;
STORM_LOG_WARN_COND(mpi.transformToJani || !transformToJaniForJit, "The JIT-based model builder is only available for JANI models, automatically converting the PRISM input model.");
bool transformToJaniForDdMA = (builderType == storm::builder::BuilderType::Dd) && (input.model->getModelType() == storm::storage::SymbolicModelDescription::ModelType::MA) && (!input.model->isJaniModel());
STORM_LOG_WARN_COND(mpi.transformToJani || !transformToJaniForDdMA, "Dd-based model builder for Markov Automata is only available for JANI models, automatically converting the input model.");
mpi.transformToJani |= (transformToJaniForJit || transformToJaniForDdMA);
}
// Set the Valuetype used during model building
mpi.buildValueType = mpi.verificationValueType;
if (bisimulationSettings.useExactArithmeticInDdBisimulation()) {
if (storm::utility::getBuilderType(mpi.engine) == storm::builder::BuilderType::Dd && mpi.applyBisimulation) {
if (mpi.buildValueType == ModelProcessingInformation::ValueType::FinitePrecision) {
mpi.buildValueType = ModelProcessingInformation::ValueType::Exact;
}
} else {
STORM_LOG_WARN("Requested using exact arithmetic in Dd bisimulation but no dd bisimulation is applied.");
}
}
// Set the Dd library
mpi.ddType = coreSettings.getDdLibraryType();
if (mpi.ddType == storm::dd::DdType::CUDD && coreSettings.isDdLibraryTypeSetFromDefaultValue()) {
if (!(mpi.buildValueType == ModelProcessingInformation::ValueType::FinitePrecision && mpi.verificationValueType == ModelProcessingInformation::ValueType::FinitePrecision)) {
STORM_LOG_INFO("Switching to DD library sylvan to allow for rational arithmetic.");
mpi.ddType = storm::dd::DdType::Sylvan;
}
}
return mpi;
}
void ensureNoUndefinedPropertyConstants(std::vector<storm::jani::Property> const& properties) {
// Make sure there are no undefined constants remaining in any property.
for (auto const& property : properties) {
std::set<storm::expressions::Variable> usedUndefinedConstants = property.getUndefinedConstants();
if (!usedUndefinedConstants.empty()) {
std::vector<std::string> undefinedConstantsNames;
for (auto const& constant : usedUndefinedConstants) {
undefinedConstantsNames.emplace_back(constant.getName());
}
STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "The property '" << property << " still refers to the undefined constants " << boost::algorithm::join(undefinedConstantsNames, ",") << ".");
}
}
}
std::pair<SymbolicInput, ModelProcessingInformation> preprocessSymbolicInput(SymbolicInput const& input) {
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
SymbolicInput output = input;
// Preprocess properties (if requested)
if (ioSettings.isPropertiesAsMultiSet()) {
STORM_LOG_THROW(!input.properties.empty(), storm::exceptions::InvalidArgumentException, "Can not translate properties to multi-objective formula because no properties were specified.");
output.properties = {storm::api::createMultiObjectiveProperty(output.properties)};
}
// Substitute constant definitions in symbolic input.
std::string constantDefinitionString = ioSettings.getConstantDefinitionString();
std::map<storm::expressions::Variable, storm::expressions::Expression> constantDefinitions;
if (output.model) {
constantDefinitions = output.model.get().parseConstantDefinitions(constantDefinitionString);
output.model = output.model.get().preprocess(constantDefinitions);
}
if (!output.properties.empty()) {
output.properties = storm::api::substituteConstantsInProperties(output.properties, constantDefinitions);
}
ensureNoUndefinedPropertyConstants(output.properties);
auto transformedJani = std::make_shared<SymbolicInput>();
ModelProcessingInformation mpi = getModelProcessingInformation(output, transformedJani);
// Check whether conversion for PRISM to JANI is requested or necessary.
if (output.model && output.model.get().isPrismProgram()) {
if (mpi.transformToJani) {
if (transformedJani->model) {
// Use the cached transformation if possible
output = std::move(*transformedJani);
} else {
storm::prism::Program const& model = output.model.get().asPrismProgram();
auto modelAndProperties = model.toJani(output.properties);
output.model = modelAndProperties.first;
if (!modelAndProperties.second.empty()) {
output.preprocessedProperties = std::move(modelAndProperties.second);
}
}
}
}
if (output.model && output.model.get().isJaniModel()) {
storm::jani::ModelFeatures supportedFeatures = storm::api::getSupportedJaniFeatures(storm::utility::getBuilderType(mpi.engine));
storm::api::simplifyJaniModel(output.model.get().asJaniModel(), output.properties, supportedFeatures);
}
return {output, mpi};
}
void exportSymbolicInput(SymbolicInput const& input) {
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
if (input.model && input.model.get().isJaniModel()) {
storm::storage::SymbolicModelDescription const& model = input.model.get();
if (ioSettings.isExportJaniDotSet()) {
storm::api::exportJaniModelAsDot(model.asJaniModel(), ioSettings.getExportJaniDotFilename());
}
}
}
std::vector<std::shared_ptr<storm::logic::Formula const>> createFormulasToRespect(std::vector<storm::jani::Property> const& properties) {
std::vector<std::shared_ptr<storm::logic::Formula const>> result = storm::api::extractFormulasFromProperties(properties);
for (auto const& property : properties) {
if (!property.getFilter().getStatesFormula()->isInitialFormula()) {
result.push_back(property.getFilter().getStatesFormula());
}
}
return result;
}
template <storm::dd::DdType DdType, typename ValueType>
std::shared_ptr<storm::models::ModelBase> buildModelDd(SymbolicInput const& input) {
auto buildSettings = storm::settings::getModule<storm::settings::modules::BuildSettings>();
return storm::api::buildSymbolicModel<DdType, ValueType>(input.model.get(), createFormulasToRespect(input.properties), buildSettings.isBuildFullModelSet(), !buildSettings.isApplyNoMaximumProgressAssumptionSet());
}
template <typename ValueType>
std::shared_ptr<storm::models::ModelBase> buildModelSparse(SymbolicInput const& input, storm::settings::modules::BuildSettings const& buildSettings, bool useJit) {
storm::builder::BuilderOptions options(createFormulasToRespect(input.properties), input.model.get());
options.setBuildChoiceLabels(options.isBuildChoiceLabelsSet() || buildSettings.isBuildChoiceLabelsSet());
options.setBuildStateValuations(options.isBuildStateValuationsSet() || buildSettings.isBuildStateValuationsSet());
options.setBuildAllLabels(options.isBuildAllLabelsSet() || buildSettings.isBuildAllLabelsSet());
bool buildChoiceOrigins = options.isBuildChoiceOriginsSet() || buildSettings.isBuildChoiceOriginsSet();
if (storm::settings::manager().hasModule(storm::settings::modules::CounterexampleGeneratorSettings::moduleName)) {
auto counterexampleGeneratorSettings = storm::settings::getModule<storm::settings::modules::CounterexampleGeneratorSettings>();
if (counterexampleGeneratorSettings.isCounterexampleSet()) {
buildChoiceOrigins |= counterexampleGeneratorSettings.isMinimalCommandSetGenerationSet();
}
}
options.setBuildChoiceOrigins(buildChoiceOrigins);
if (buildSettings.isApplyNoMaximumProgressAssumptionSet()) {
options.setApplyMaximalProgressAssumption(false);
}
if (buildSettings.isExplorationChecksSet()) {
options.setExplorationChecks();
}
options.setReservedBitsForUnboundedVariables(buildSettings.getBitsForUnboundedVariables());
options.setAddOutOfBoundsState(buildSettings.isBuildOutOfBoundsStateSet());
if (buildSettings.isBuildFullModelSet()) {
options.clearTerminalStates();
options.setApplyMaximalProgressAssumption(false);
options.setBuildAllLabels(true);
options.setBuildAllRewardModels(true);
}
if (buildSettings.isAddOverlappingGuardsLabelSet()) {
options.setAddOverlappingGuardsLabel(true);
}
return storm::api::buildSparseModel<ValueType>(input.model.get(), options, useJit, storm::settings::getModule<storm::settings::modules::JitBuilderSettings>().isDoctorSet());
}
template <typename ValueType>
std::shared_ptr<storm::models::ModelBase> buildModelExplicit(storm::settings::modules::IOSettings const& ioSettings, storm::settings::modules::BuildSettings const& buildSettings) {
std::shared_ptr<storm::models::ModelBase> result;
if (ioSettings.isExplicitSet()) {
result = storm::api::buildExplicitModel<ValueType>(ioSettings.getTransitionFilename(), ioSettings.getLabelingFilename(), ioSettings.isStateRewardsSet() ? boost::optional<std::string>(ioSettings.getStateRewardsFilename()) : boost::none, ioSettings.isTransitionRewardsSet() ? boost::optional<std::string>(ioSettings.getTransitionRewardsFilename()) : boost::none, ioSettings.isChoiceLabelingSet() ? boost::optional<std::string>(ioSettings.getChoiceLabelingFilename()) : boost::none);
} else if (ioSettings.isExplicitDRNSet()) {
storm::parser::DirectEncodingParserOptions options;
options.buildChoiceLabeling = buildSettings.isBuildChoiceLabelsSet();
result = storm::api::buildExplicitDRNModel<ValueType>(ioSettings.getExplicitDRNFilename(), options);
} else {
STORM_LOG_THROW(ioSettings.isExplicitIMCASet(), storm::exceptions::InvalidSettingsException, "Unexpected explicit model input type.");
result = storm::api::buildExplicitIMCAModel<ValueType>(ioSettings.getExplicitIMCAFilename());
}
return result;
}
template <storm::dd::DdType DdType, typename ValueType>
std::shared_ptr<storm::models::ModelBase> buildModel(SymbolicInput const& input, storm::settings::modules::IOSettings const& ioSettings, ModelProcessingInformation const& mpi) {
storm::utility::Stopwatch modelBuildingWatch(true);
auto buildSettings = storm::settings::getModule<storm::settings::modules::BuildSettings>();
std::shared_ptr<storm::models::ModelBase> result;
if (input.model) {
auto builderType = storm::utility::getBuilderType(mpi.engine);
if (builderType == storm::builder::BuilderType::Dd) {
result = buildModelDd<DdType, ValueType>(input);
} else if (builderType == storm::builder::BuilderType::Explicit || builderType == storm::builder::BuilderType::Jit) {
result = buildModelSparse<ValueType>(input, buildSettings, builderType == storm::builder::BuilderType::Jit);
}
} else if (ioSettings.isExplicitSet() || ioSettings.isExplicitDRNSet() || ioSettings.isExplicitIMCASet()) {
STORM_LOG_THROW(mpi.engine == storm::utility::Engine::Sparse, storm::exceptions::InvalidSettingsException, "Can only use sparse engine with explicit input.");
result = buildModelExplicit<ValueType>(ioSettings, buildSettings);
}
modelBuildingWatch.stop();
if (result) {
STORM_PRINT("Time for model construction: " << modelBuildingWatch << "." << std::endl << std::endl);
}
return result;
}
template <typename ValueType>
std::shared_ptr<storm::models::sparse::Model<ValueType>> preprocessSparseMarkovAutomaton(std::shared_ptr<storm::models::sparse::MarkovAutomaton<ValueType>> const& model) {
auto transformationSettings = storm::settings::getModule<storm::settings::modules::TransformationSettings>();
auto debugSettings = storm::settings::getModule<storm::settings::modules::DebugSettings>();
std::shared_ptr<storm::models::sparse::Model<ValueType>> result = model;
model->close();
STORM_LOG_WARN_COND(!debugSettings.isAdditionalChecksSet() || !model->containsZenoCycle(), "MA contains a Zeno cycle. Model checking results cannot be trusted.");
if (model->isConvertibleToCtmc()) {
STORM_LOG_WARN_COND(false, "MA is convertible to a CTMC, consider using a CTMC instead.");
result = model->convertToCtmc();
}
if (transformationSettings.isChainEliminationSet() && result->isOfType(storm::models::ModelType::MarkovAutomaton)) {
result = storm::transformer::NonMarkovianChainTransformer<ValueType>::eliminateNonmarkovianStates(
result->template as<storm::models::sparse::MarkovAutomaton<ValueType>>(), transformationSettings.getLabelBehavior());
}
return result;
}
template <typename ValueType>
std::shared_ptr<storm::models::sparse::Model<ValueType>> preprocessSparseModelBisimulation(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, storm::settings::modules::BisimulationSettings const& bisimulationSettings) {
storm::storage::BisimulationType bisimType = storm::storage::BisimulationType::Strong;
if (bisimulationSettings.isWeakBisimulationSet()) {
bisimType = storm::storage::BisimulationType::Weak;
}
STORM_LOG_INFO("Performing bisimulation minimization...");
return storm::api::performBisimulationMinimization<ValueType>(model, createFormulasToRespect(input.properties), bisimType);
}
template <typename ValueType>
std::pair<std::shared_ptr<storm::models::sparse::Model<ValueType>>, bool> preprocessSparseModel(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
auto bisimulationSettings = storm::settings::getModule<storm::settings::modules::BisimulationSettings>();
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
auto transformationSettings = storm::settings::getModule<storm::settings::modules::TransformationSettings>();
std::pair<std::shared_ptr<storm::models::sparse::Model<ValueType>>, bool> result = std::make_pair(model, false);
if (result.first->isOfType(storm::models::ModelType::MarkovAutomaton)) {
result.first = preprocessSparseMarkovAutomaton(result.first->template as<storm::models::sparse::MarkovAutomaton<ValueType>>());
result.second = true;
}
if (mpi.applyBisimulation) {
result.first = preprocessSparseModelBisimulation(result.first, input, bisimulationSettings);
result.second = true;
}
if (transformationSettings.isToDiscreteTimeModelSet()) {
// TODO: we should also transform the properties at this point.
STORM_LOG_WARN_COND(!model->hasRewardModel("_time"), "Scheduled transformation to discrete time model, but a reward model named '_time' is already present in this model. We might take the wrong reward model later.");
result.first = storm::api::transformContinuousToDiscreteTimeSparseModel(std::move(*result.first), storm::api::extractFormulasFromProperties(input.properties)).first;
result.second = true;
}
if (transformationSettings.isToNondeterministicModelSet()) {
result.first = storm::api::transformToNondeterministicModel<ValueType>(std::move(*result.first));
result.second = true;
}
return result;
}
template <typename ValueType>
void exportSparseModel(std::shared_ptr<storm::models::sparse::Model<ValueType>> const& model, SymbolicInput const& input) {
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
if (ioSettings.isExportExplicitSet()) {
storm::api::exportSparseModelAsDrn(model, ioSettings.getExportExplicitFilename(), input.model ? input.model.get().getParameterNames() : std::vector<std::string>(), !ioSettings.isExplicitExportPlaceholdersDisabled());
}
if (ioSettings.isExportDdSet()) {
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Exporting in drdd format is only supported for DDs.");
}
if (ioSettings.isExportDotSet()) {
storm::api::exportSparseModelAsDot(model, ioSettings.getExportDotFilename(), ioSettings.getExportDotMaxWidth());
}
}
template <storm::dd::DdType DdType, typename ValueType>
void exportDdModel(std::shared_ptr<storm::models::symbolic::Model<DdType, ValueType>> const& model, SymbolicInput const& input) {
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
if (ioSettings.isExportExplicitSet()) {
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Exporting in drn format is only supported for sparse models.");
}
if (ioSettings.isExportDdSet()) {
storm::api::exportSparseModelAsDrdd(model, ioSettings.getExportDdFilename());
}
if (ioSettings.isExportDotSet()) {
storm::api::exportSymbolicModelAsDot(model, ioSettings.getExportDotFilename());
}
}
template <storm::dd::DdType DdType, typename ValueType>
void exportModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input) {
if (model->isSparseModel()) {
exportSparseModel<ValueType>(model->as<storm::models::sparse::Model<ValueType>>(), input);
} else {
exportDdModel<DdType, ValueType>(model->as<storm::models::symbolic::Model<DdType, ValueType>>(), input);
}
}
template <storm::dd::DdType DdType, typename ValueType>
typename std::enable_if<DdType != storm::dd::DdType::Sylvan && !std::is_same<ValueType, double>::value, std::shared_ptr<storm::models::Model<ValueType>>>::type
preprocessDdMarkovAutomaton(std::shared_ptr<storm::models::symbolic::Model<DdType, ValueType>> const& model) {
return model;
}
template <storm::dd::DdType DdType, typename ValueType>
typename std::enable_if<DdType == storm::dd::DdType::Sylvan || std::is_same<ValueType, double>::value, std::shared_ptr<storm::models::Model<ValueType>>>::type
preprocessDdMarkovAutomaton(std::shared_ptr<storm::models::symbolic::Model<DdType, ValueType>> const& model) {
auto ma = model->template as<storm::models::symbolic::MarkovAutomaton<DdType, ValueType>>();
if (!ma->isClosed()) {
return std::make_shared<storm::models::symbolic::MarkovAutomaton<DdType, ValueType>>(ma->close());
} else {
return model;
}
}
template <storm::dd::DdType DdType, typename ValueType, typename ExportValueType = ValueType>
std::shared_ptr<storm::models::Model<ExportValueType>> preprocessDdModelBisimulation(std::shared_ptr<storm::models::symbolic::Model<DdType, ValueType>> const& model, SymbolicInput const& input, storm::settings::modules::BisimulationSettings const& bisimulationSettings, ModelProcessingInformation const& mpi) {
STORM_LOG_WARN_COND(!bisimulationSettings.isWeakBisimulationSet(), "Weak bisimulation is currently not supported on DDs. Falling back to strong bisimulation.");
auto quotientFormat = bisimulationSettings.getQuotientFormat();
if (mpi.engine == storm::utility::Engine::DdSparse && quotientFormat != storm::dd::bisimulation::QuotientFormat::Sparse && bisimulationSettings.isQuotientFormatSetFromDefaultValue()) {
STORM_LOG_INFO("Setting bisimulation quotient format to 'sparse'.");
quotientFormat = storm::dd::bisimulation::QuotientFormat::Sparse;
}
STORM_LOG_INFO("Performing bisimulation minimization...");
return storm::api::performBisimulationMinimization<DdType, ValueType, ExportValueType>(model, createFormulasToRespect(input.properties), storm::storage::BisimulationType::Strong, bisimulationSettings.getSignatureMode(), quotientFormat);
}
template <storm::dd::DdType DdType, typename ValueType, typename ExportValueType = ValueType>
std::pair<std::shared_ptr<storm::models::ModelBase>, bool> preprocessDdModel(std::shared_ptr<storm::models::symbolic::Model<DdType, ValueType>> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
auto bisimulationSettings = storm::settings::getModule<storm::settings::modules::BisimulationSettings>();
std::pair<std::shared_ptr<storm::models::Model<ValueType>>, bool> intermediateResult = std::make_pair(model, false);
if (model->isOfType(storm::models::ModelType::MarkovAutomaton)) {
intermediateResult.first = preprocessDdMarkovAutomaton(intermediateResult.first->template as<storm::models::symbolic::Model<DdType, ValueType>>());
intermediateResult.second = true;
}
std::unique_ptr<std::pair<std::shared_ptr<storm::models::Model<ExportValueType>>, bool>> result;
auto symbolicModel = intermediateResult.first->template as<storm::models::symbolic::Model<DdType, ValueType>>();
if (mpi.applyBisimulation) {
std::shared_ptr<storm::models::Model<ExportValueType>> newModel = preprocessDdModelBisimulation<DdType, ValueType, ExportValueType>(symbolicModel, input, bisimulationSettings, mpi);
result = std::make_unique<std::pair<std::shared_ptr<storm::models::Model<ExportValueType>>, bool>>(newModel, true);
} else {
result = std::make_unique<std::pair<std::shared_ptr<storm::models::Model<ExportValueType>>, bool>>(symbolicModel->template toValueType<ExportValueType>(), !std::is_same<ValueType, ExportValueType>::value);
}
if (result && result->first->isSymbolicModel() && mpi.engine == storm::utility::Engine::DdSparse) {
// Mark as changed.
result->second = true;
std::shared_ptr<storm::models::symbolic::Model<DdType, ExportValueType>> symbolicModel = result->first->template as<storm::models::symbolic::Model<DdType, ExportValueType>>();
std::vector<std::shared_ptr<storm::logic::Formula const>> formulas;
for (auto const& property : input.properties) {
formulas.emplace_back(property.getRawFormula());
}
result->first = storm::api::transformSymbolicToSparseModel(symbolicModel, formulas);
STORM_LOG_THROW(result, storm::exceptions::NotSupportedException, "The translation to a sparse model is not supported for the given model type.");
}
return *result;
}
template <storm::dd::DdType DdType, typename BuildValueType, typename ExportValueType = BuildValueType>
std::pair<std::shared_ptr<storm::models::ModelBase>, bool> preprocessModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
storm::utility::Stopwatch preprocessingWatch(true);
std::pair<std::shared_ptr<storm::models::ModelBase>, bool> result = std::make_pair(model, false);
if (model->isSparseModel()) {
result = preprocessSparseModel<BuildValueType>(result.first->as<storm::models::sparse::Model<BuildValueType>>(), input, mpi);
} else {
STORM_LOG_ASSERT(model->isSymbolicModel(), "Unexpected model type.");
result = preprocessDdModel<DdType, BuildValueType, ExportValueType>(result.first->as<storm::models::symbolic::Model<DdType, BuildValueType>>(), input, mpi);
}
preprocessingWatch.stop();
if (result.second) {
STORM_PRINT(std::endl << "Time for model preprocessing: " << preprocessingWatch << "." << std::endl << std::endl);
}
return result;
}
void printComputingCounterexample(storm::jani::Property const& property) {
STORM_PRINT("Computing counterexample for property " << *property.getRawFormula() << " ..." << std::endl);
}
void printCounterexample(std::shared_ptr<storm::counterexamples::Counterexample> const& counterexample, storm::utility::Stopwatch* watch = nullptr) {
if (counterexample) {
STORM_PRINT(*counterexample << std::endl);
if (watch) {
STORM_PRINT("Time for computation: " << *watch << "." << std::endl);
}
} else {
STORM_PRINT(" failed." << std::endl);
}
}
template <typename ValueType>
void generateCounterexamples(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input) {
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Counterexample generation is not supported for this data-type.");
}
template <>
void generateCounterexamples<double>(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input) {
typedef double ValueType;
STORM_LOG_THROW(model->isSparseModel(), storm::exceptions::NotSupportedException, "Counterexample generation is currently only supported for sparse models.");
auto sparseModel = model->as<storm::models::sparse::Model<ValueType>>();
for (auto& rewModel : sparseModel->getRewardModels()) {
rewModel.second.reduceToStateBasedRewards(sparseModel->getTransitionMatrix(), true);
}
STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Dtmc) || sparseModel->isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException, "Counterexample is currently only supported for discrete-time models.");
auto counterexampleSettings = storm::settings::getModule<storm::settings::modules::CounterexampleGeneratorSettings>();
if (counterexampleSettings.isMinimalCommandSetGenerationSet()) {
bool useMilp = counterexampleSettings.isUseMilpBasedMinimalCommandSetGenerationSet();
for (auto const& property : input.properties) {
std::shared_ptr<storm::counterexamples::Counterexample> counterexample;
printComputingCounterexample(property);
storm::utility::Stopwatch watch(true);
if (useMilp) {
STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Mdp), storm::exceptions::NotSupportedException,
"Counterexample generation using MILP is currently only supported for MDPs.");
counterexample = storm::api::computeHighLevelCounterexampleMilp(input.model.get(), sparseModel->template as<storm::models::sparse::Mdp<ValueType>>(),
property.getRawFormula());
} else {
STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Dtmc) || sparseModel->isOfType(storm::models::ModelType::Mdp),
storm::exceptions::NotSupportedException, "Counterexample generation using MaxSAT is currently only supported for discrete-time models.");
if (sparseModel->isOfType(storm::models::ModelType::Dtmc)) {
counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(input.model.get(), sparseModel->template as<storm::models::sparse::Dtmc<ValueType>>(),
property.getRawFormula());
} else {
counterexample = storm::api::computeHighLevelCounterexampleMaxSmt(input.model.get(), sparseModel->template as<storm::models::sparse::Mdp<ValueType>>(),
property.getRawFormula());
}
}
watch.stop();
printCounterexample(counterexample, &watch);
}
} else if (counterexampleSettings.isShortestPathGenerationSet()) {
for (auto const& property : input.properties) {
std::shared_ptr<storm::counterexamples::Counterexample> counterexample;
printComputingCounterexample(property);
storm::utility::Stopwatch watch(true);
STORM_LOG_THROW(sparseModel->isOfType(storm::models::ModelType::Dtmc), storm::exceptions::NotSupportedException, "Counterexample generation using shortest paths is currently only supported for DTMCs.");
counterexample = storm::api::computeKShortestPathCounterexample(sparseModel->template as<storm::models::sparse::Dtmc<ValueType>>(), property.getRawFormula(), counterexampleSettings.getShortestPathMaxK());
watch.stop();
printCounterexample(counterexample, &watch);
}
} else {
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "The selected counterexample formalism is unsupported.");
}
}
template<typename ValueType>
void printFilteredResult(std::unique_ptr<storm::modelchecker::CheckResult> const& result, storm::modelchecker::FilterType ft) {
if (result->isQuantitative()) {
if (ft == storm::modelchecker::FilterType::VALUES) {
STORM_PRINT(*result);
} else {
ValueType resultValue;
switch (ft) {
case storm::modelchecker::FilterType::SUM:
resultValue = result->asQuantitativeCheckResult<ValueType>().sum();
break;
case storm::modelchecker::FilterType::AVG:
resultValue = result->asQuantitativeCheckResult<ValueType>().average();
break;
case storm::modelchecker::FilterType::MIN:
resultValue = result->asQuantitativeCheckResult<ValueType>().getMin();
break;
case storm::modelchecker::FilterType::MAX:
resultValue = result->asQuantitativeCheckResult<ValueType>().getMax();
break;
case storm::modelchecker::FilterType::ARGMIN:
case storm::modelchecker::FilterType::ARGMAX:
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Outputting states is not supported.");
case storm::modelchecker::FilterType::EXISTS:
case storm::modelchecker::FilterType::FORALL:
case storm::modelchecker::FilterType::COUNT:
STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Filter type only defined for qualitative results.");
default:
STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Unhandled filter type.");
}
if (storm::NumberTraits<ValueType>::IsExact && storm::utility::isConstant(resultValue)) {
STORM_PRINT(resultValue << " (approx. " << storm::utility::convertNumber<double>(resultValue) << ")");
} else {
STORM_PRINT(resultValue);
}
}
} else {
switch (ft) {
case storm::modelchecker::FilterType::VALUES:
STORM_PRINT(*result << std::endl);
break;
case storm::modelchecker::FilterType::EXISTS:
STORM_PRINT(result->asQualitativeCheckResult().existsTrue());
break;
case storm::modelchecker::FilterType::FORALL:
STORM_PRINT(result->asQualitativeCheckResult().forallTrue());
break;
case storm::modelchecker::FilterType::COUNT:
STORM_PRINT(result->asQualitativeCheckResult().count());
break;
case storm::modelchecker::FilterType::ARGMIN:
case storm::modelchecker::FilterType::ARGMAX:
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Outputting states is not supported.");
case storm::modelchecker::FilterType::SUM:
case storm::modelchecker::FilterType::AVG:
case storm::modelchecker::FilterType::MIN:
case storm::modelchecker::FilterType::MAX:
STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Filter type only defined for quantitative results.");
}
}
STORM_PRINT(std::endl);
}
void printModelCheckingProperty(storm::jani::Property const& property) {
STORM_PRINT(std::endl << "Model checking property \"" << property.getName() << "\": " << *property.getRawFormula() << " ..." << std::endl);
}
template<typename ValueType>
void printResult(std::unique_ptr<storm::modelchecker::CheckResult> const& result, storm::jani::Property const& property, storm::utility::Stopwatch* watch = nullptr) {
if (result) {
std::stringstream ss;
ss << "'" << *property.getFilter().getStatesFormula() << "'";
STORM_PRINT((storm::utility::resources::isTerminate() ? "Result till abort" : "Result") << " (for " << (property.getFilter().getStatesFormula()->isInitialFormula() ? "initial" : ss.str()) << " states): ");
printFilteredResult<ValueType>(result, property.getFilter().getFilterType());
if (watch) {
STORM_PRINT("Time for model checking: " << *watch << "." << std::endl);
}
} else {
STORM_LOG_ERROR("Property is unsupported by selected engine/settings." << std::endl);
}
}
struct PostprocessingIdentity {
void operator()(std::unique_ptr<storm::modelchecker::CheckResult> const&) {
// Intentionally left empty.
}
};
template<typename ValueType>
void verifyProperties(SymbolicInput const& input, std::function<std::unique_ptr<storm::modelchecker::CheckResult>(std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states)> const& verificationCallback, std::function<void(std::unique_ptr<storm::modelchecker::CheckResult> const&)> const& postprocessingCallback = PostprocessingIdentity()) {
auto transformationSettings = storm::settings::getModule<storm::settings::modules::TransformationSettings>();
auto const& properties = input.preprocessedProperties ? input.preprocessedProperties.get() : input.properties;
for (auto const& property : properties) {
printModelCheckingProperty(property);
bool ignored = false;
storm::utility::Stopwatch watch(true);
std::unique_ptr<storm::modelchecker::CheckResult> result;
try {
auto rawFormula = property.getRawFormula();
if (transformationSettings.isChainEliminationSet() &&
!storm::transformer::NonMarkovianChainTransformer<ValueType>::preservesFormula(*rawFormula)) {
STORM_LOG_WARN("Property is not preserved by elimination of non-markovian states.");
ignored = true;
} else if (transformationSettings.isToDiscreteTimeModelSet()) {
auto propertyFormula = storm::api::checkAndTransformContinuousToDiscreteTimeFormula<ValueType>(*property.getRawFormula());
auto filterFormula = storm::api::checkAndTransformContinuousToDiscreteTimeFormula<ValueType>(*property.getFilter().getStatesFormula());
if (propertyFormula && filterFormula) {
result = verificationCallback(propertyFormula, filterFormula);
} else {
ignored = true;
}
} else {
result = verificationCallback(property.getRawFormula(),
property.getFilter().getStatesFormula());
}
} catch (storm::exceptions::BaseException const& ex) {
STORM_LOG_WARN("Cannot handle property: " << ex.what());
}
watch.stop();
if (!ignored) {
postprocessingCallback(result);
printResult<ValueType>(result, property, &watch);
}
}
}
std::vector<storm::expressions::Expression> parseConstraints(storm::expressions::ExpressionManager const& expressionManager, std::string const& constraintsString) {
std::vector<storm::expressions::Expression> constraints;
std::vector<std::string> constraintsAsStrings;
boost::split(constraintsAsStrings, constraintsString, boost::is_any_of(","));
storm::parser::ExpressionParser expressionParser(expressionManager);
std::unordered_map<std::string, storm::expressions::Expression> variableMapping;
for (auto const& variableTypePair : expressionManager) {
variableMapping[variableTypePair.first.getName()] = variableTypePair.first;
}
expressionParser.setIdentifierMapping(variableMapping);
for (auto const& constraintString : constraintsAsStrings) {
if (constraintString.empty()) {
continue;
}
storm::expressions::Expression constraint = expressionParser.parseFromString(constraintString);
STORM_LOG_TRACE("Adding special (user-provided) constraint " << constraint << ".");
constraints.emplace_back(constraint);
}
return constraints;
}
std::vector<std::vector<storm::expressions::Expression>> parseInjectedRefinementPredicates(storm::expressions::ExpressionManager const& expressionManager, std::string const& refinementPredicatesString) {
std::vector<std::vector<storm::expressions::Expression>> injectedRefinementPredicates;
storm::parser::ExpressionParser expressionParser(expressionManager);
std::unordered_map<std::string, storm::expressions::Expression> variableMapping;
for (auto const& variableTypePair : expressionManager) {
variableMapping[variableTypePair.first.getName()] = variableTypePair.first;
}
expressionParser.setIdentifierMapping(variableMapping);
std::vector<std::string> predicateGroupsAsStrings;
boost::split(predicateGroupsAsStrings, refinementPredicatesString, boost::is_any_of(";"));
if (!predicateGroupsAsStrings.empty()) {
for (auto const& predicateGroupString : predicateGroupsAsStrings) {
if (predicateGroupString.empty()) {
continue;
}
std::vector<std::string> predicatesAsStrings;
boost::split(predicatesAsStrings, predicateGroupString, boost::is_any_of(":"));
if (!predicatesAsStrings.empty()) {
injectedRefinementPredicates.emplace_back();
for (auto const& predicateString : predicatesAsStrings) {
storm::expressions::Expression predicate = expressionParser.parseFromString(predicateString);
STORM_LOG_TRACE("Adding special (user-provided) refinement predicate " << predicateString << ".");
injectedRefinementPredicates.back().emplace_back(predicate);
}
STORM_LOG_THROW(!injectedRefinementPredicates.back().empty(), storm::exceptions::InvalidArgumentException, "Expecting non-empty list of predicates to inject for each (mentioned) refinement step.");
// Finally reverse the list, because we take the predicates from the back.
std::reverse(injectedRefinementPredicates.back().begin(), injectedRefinementPredicates.back().end());
}
}
// Finally reverse the list, because we take the predicates from the back.
std::reverse(injectedRefinementPredicates.begin(), injectedRefinementPredicates.end());
}
return injectedRefinementPredicates;
}
template <storm::dd::DdType DdType, typename ValueType>
void verifyWithAbstractionRefinementEngine(SymbolicInput const& input, ModelProcessingInformation const& mpi) {
STORM_LOG_ASSERT(input.model, "Expected symbolic model description.");
storm::settings::modules::AbstractionSettings const& abstractionSettings = storm::settings::getModule<storm::settings::modules::AbstractionSettings>();
storm::api::AbstractionRefinementOptions options(parseConstraints(input.model->getManager(), abstractionSettings.getConstraintString()), parseInjectedRefinementPredicates(input.model->getManager(), abstractionSettings.getInjectedRefinementPredicates()));
verifyProperties<ValueType>(input, [&input,&options,&mpi] (std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states) {
STORM_LOG_THROW(states->isInitialFormula(), storm::exceptions::NotSupportedException, "Abstraction-refinement can only filter initial states.");
return storm::api::verifyWithAbstractionRefinementEngine<DdType, ValueType>(mpi.env, input.model.get(), storm::api::createTask<ValueType>(formula, true), options);
});
}
template <typename ValueType>
void verifyWithExplorationEngine(SymbolicInput const& input, ModelProcessingInformation const& mpi) {
STORM_LOG_ASSERT(input.model, "Expected symbolic model description.");
STORM_LOG_THROW((std::is_same<ValueType, double>::value), storm::exceptions::NotSupportedException, "Exploration does not support other data-types than floating points.");
verifyProperties<ValueType>(input, [&input,&mpi] (std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states) {
STORM_LOG_THROW(states->isInitialFormula(), storm::exceptions::NotSupportedException, "Exploration can only filter initial states.");
return storm::api::verifyWithExplorationEngine<ValueType>(mpi.env, input.model.get(), storm::api::createTask<ValueType>(formula, true));
});
}
template <typename ValueType>
void verifyWithSparseEngine(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
auto sparseModel = model->as<storm::models::sparse::Model<ValueType>>();
auto const& ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
auto verificationCallback = [&sparseModel,&ioSettings,&mpi] (std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states) {
bool filterForInitialStates = states->isInitialFormula();
auto task = storm::api::createTask<ValueType>(formula, filterForInitialStates);
if (ioSettings.isExportSchedulerSet()) {
task.setProduceSchedulers(true);
}
std::unique_ptr<storm::modelchecker::CheckResult> result = storm::api::verifyWithSparseEngine<ValueType>(mpi.env, sparseModel, task);
std::unique_ptr<storm::modelchecker::CheckResult> filter;
if (filterForInitialStates) {
filter = std::make_unique<storm::modelchecker::ExplicitQualitativeCheckResult>(sparseModel->getInitialStates());
} else {
filter = storm::api::verifyWithSparseEngine<ValueType>(mpi.env, sparseModel, storm::api::createTask<ValueType>(states, false));
}
if (result && filter) {
result->filter(filter->asQualitativeCheckResult());
}
return result;
};
uint64_t exportCount = 0; // this number will be prepended to the export file name of schedulers and/or check results in case of multiple properties.
auto postprocessingCallback = [&sparseModel,&ioSettings,&input,&exportCount] (std::unique_ptr<storm::modelchecker::CheckResult> const& result) {
if (ioSettings.isExportSchedulerSet()) {
if (result->isExplicitQuantitativeCheckResult()) {
if (result->template asExplicitQuantitativeCheckResult<ValueType>().hasScheduler()) {
auto const& scheduler = result->template asExplicitQuantitativeCheckResult<ValueType>().getScheduler();
STORM_PRINT_AND_LOG("Exporting scheduler ... ")
if (input.model) {
STORM_LOG_WARN_COND(sparseModel->hasStateValuations(), "No information of state valuations available. The scheduler output will use internal state ids. You might be interested in building the model with state valuations using --buildstateval.");
STORM_LOG_WARN_COND(sparseModel->hasChoiceLabeling() || sparseModel->hasChoiceOrigins(), "No symbolic choice information is available. The scheduler output will use internal choice ids. You might be interested in building the model with choice labels or choice origins using --buildchoicelab or --buildchoiceorig.");
STORM_LOG_WARN_COND(sparseModel->hasChoiceLabeling() && !sparseModel->hasChoiceOrigins(), "Only partial choice information is available. You might want to build the model with choice origins using --buildchoicelab or --buildchoiceorig.");
}
STORM_LOG_WARN_COND(exportCount == 0, "Prepending " << exportCount << " to file name for this property because there are multiple properties.");
storm::api::exportScheduler(sparseModel, scheduler, (exportCount == 0 ? std::string("") : std::to_string(exportCount)) + ioSettings.getExportSchedulerFilename());
} else {
STORM_LOG_ERROR("Scheduler requested but could not be generated.");
}
} else {
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Scheduler export not supported for this property.");
}
}
if (ioSettings.isExportCheckResultSet()) {
STORM_LOG_WARN_COND(sparseModel->hasStateValuations(), "No information of state valuations available. The result output will use internal state ids. You might be interested in building the model with state valuations using --buildstateval.");
STORM_LOG_WARN_COND(exportCount == 0, "Prepending " << exportCount << " to file name for this property because there are multiple properties.");
storm::api::exportCheckResultToJson(sparseModel, result, (exportCount == 0 ? std::string("") : std::to_string(exportCount)) + ioSettings.getExportCheckResultFilename());
}
++exportCount;
};
verifyProperties<ValueType>(input,verificationCallback, postprocessingCallback);
if (ioSettings.isComputeSteadyStateDistributionSet()) {
storm::utility::Stopwatch watch(true);
std::unique_ptr<storm::modelchecker::CheckResult> result;
try {
result = storm::api::computeSteadyStateDistributionWithSparseEngine<ValueType>(mpi.env, sparseModel);
} catch (storm::exceptions::BaseException const& ex) {
STORM_LOG_WARN("Cannot compute steady-state probabilities: " << ex.what());
}
watch.stop();
postprocessingCallback(result);
STORM_PRINT((storm::utility::resources::isTerminate() ? "Result till abort: " : "Result: ") << *result << std::endl);
STORM_PRINT("Time for model checking: " << watch << "." << std::endl);
}
}
template <storm::dd::DdType DdType, typename ValueType>
void verifyWithHybridEngine(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
verifyProperties<ValueType>(input, [&model,&mpi] (std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states) {
bool filterForInitialStates = states->isInitialFormula();
auto task = storm::api::createTask<ValueType>(formula, filterForInitialStates);
auto symbolicModel = model->as<storm::models::symbolic::Model<DdType, ValueType>>();
std::unique_ptr<storm::modelchecker::CheckResult> result = storm::api::verifyWithHybridEngine<DdType, ValueType>(mpi.env, symbolicModel, task);
std::unique_ptr<storm::modelchecker::CheckResult> filter;
if (filterForInitialStates) {
filter = std::make_unique<storm::modelchecker::SymbolicQualitativeCheckResult<DdType>>(symbolicModel->getReachableStates(), symbolicModel->getInitialStates());
} else {
filter = storm::api::verifyWithHybridEngine<DdType, ValueType>(mpi.env, symbolicModel, storm::api::createTask<ValueType>(states, false));
}
if (result && filter) {
result->filter(filter->asQualitativeCheckResult());
}
return result;
});
}
template <storm::dd::DdType DdType, typename ValueType>
void verifyWithDdEngine(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
verifyProperties<ValueType>(input, [&model,&mpi] (std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states) {
bool filterForInitialStates = states->isInitialFormula();
auto task = storm::api::createTask<ValueType>(formula, filterForInitialStates);
auto symbolicModel = model->as<storm::models::symbolic::Model<DdType, ValueType>>();
std::unique_ptr<storm::modelchecker::CheckResult> result = storm::api::verifyWithDdEngine<DdType, ValueType>(mpi.env, symbolicModel, storm::api::createTask<ValueType>(formula, true));
std::unique_ptr<storm::modelchecker::CheckResult> filter;
if (filterForInitialStates) {
filter = std::make_unique<storm::modelchecker::SymbolicQualitativeCheckResult<DdType>>(symbolicModel->getReachableStates(), symbolicModel->getInitialStates());
} else {
filter = storm::api::verifyWithDdEngine<DdType, ValueType>(mpi.env, symbolicModel, storm::api::createTask<ValueType>(states, false));
}
if (result && filter) {
result->filter(filter->asQualitativeCheckResult());
}
return result;
});
}
template <storm::dd::DdType DdType, typename ValueType>
void verifyWithAbstractionRefinementEngine(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
verifyProperties<ValueType>(input, [&model,&mpi] (std::shared_ptr<storm::logic::Formula const> const& formula, std::shared_ptr<storm::logic::Formula const> const& states) {
STORM_LOG_THROW(states->isInitialFormula(), storm::exceptions::NotSupportedException, "Abstraction-refinement can only filter initial states.");
auto symbolicModel = model->as<storm::models::symbolic::Model<DdType, ValueType>>();
return storm::api::verifyWithAbstractionRefinementEngine<DdType, ValueType>(mpi.env, symbolicModel, storm::api::createTask<ValueType>(formula, true));
});
}
template <storm::dd::DdType DdType, typename ValueType>
typename std::enable_if<DdType != storm::dd::DdType::CUDD || std::is_same<ValueType, double>::value, void>::type verifySymbolicModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
if (mpi.engine == storm::utility::Engine::Hybrid) {
verifyWithHybridEngine<DdType, ValueType>(model, input, mpi);
} else if (mpi.engine == storm::utility::Engine::Dd) {
verifyWithDdEngine<DdType, ValueType>(model, input, mpi);
} else {
verifyWithAbstractionRefinementEngine<DdType, ValueType>(model, input, mpi);
}
}
template <storm::dd::DdType DdType, typename ValueType>
typename std::enable_if<DdType == storm::dd::DdType::CUDD && !std::is_same<ValueType, double>::value, void>::type verifySymbolicModel(std::shared_ptr<storm::models::ModelBase> const&, SymbolicInput const&, ModelProcessingInformation const&) {
STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "CUDD does not support the selected data-type.");
}
template <storm::dd::DdType DdType, typename ValueType>
void verifyModel(std::shared_ptr<storm::models::ModelBase> const& model, SymbolicInput const& input, ModelProcessingInformation const& mpi) {
if (model->isSparseModel()) {
verifyWithSparseEngine<ValueType>(model, input, mpi);
} else {
STORM_LOG_ASSERT(model->isSymbolicModel(), "Unexpected model type.");
verifySymbolicModel<DdType, ValueType>(model, input, mpi);
}
}
template <storm::dd::DdType DdType, typename BuildValueType, typename VerificationValueType = BuildValueType>
std::shared_ptr<storm::models::ModelBase> buildPreprocessModelWithValueTypeAndDdlib(SymbolicInput const& input, ModelProcessingInformation const& mpi) {
auto ioSettings = storm::settings::getModule<storm::settings::modules::IOSettings>();
auto buildSettings = storm::settings::getModule<storm::settings::modules::BuildSettings>();
std::shared_ptr<storm::models::ModelBase> model;
if (!buildSettings.isNoBuildModelSet()) {
model = buildModel<DdType, BuildValueType>(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 = preprocessModel<DdType, BuildValueType, VerificationValueType>(model, input, mpi);
if (preprocessingResult.second) {
model = preprocessingResult.first;
model->printModelInformationToStream(std::cout);
}
}
return model;
}
template <storm::dd::DdType DdType, typename BuildValueType, typename VerificationValueType = BuildValueType>
std::shared_ptr<storm::models::ModelBase> buildPreprocessExportModelWithValueTypeAndDdlib(SymbolicInput const& input, ModelProcessingInformation const& mpi) {
auto model = buildPreprocessModelWithValueTypeAndDdlib<DdType, BuildValueType, VerificationValueType>(input, mpi);
if (model) {
exportModel<DdType, BuildValueType>(model, input);
}
return model;
}
template <storm::dd::DdType DdType, typename BuildValueType, typename VerificationValueType = BuildValueType>
void processInputWithValueTypeAndDdlib(SymbolicInput const& input, ModelProcessingInformation const& mpi) {
auto abstractionSettings = storm::settings::getModule<storm::settings::modules::AbstractionSettings>();
auto counterexampleSettings = storm::settings::getModule<storm::settings::modules::CounterexampleGeneratorSettings>();
// For several engines, no model building step is performed, but the verification is started right away.
if (mpi.engine == storm::utility::Engine::AbstractionRefinement && abstractionSettings.getAbstractionRefinementMethod() == storm::settings::modules::AbstractionSettings::Method::Games) {
verifyWithAbstractionRefinementEngine<DdType, VerificationValueType>(input, mpi);
} else if (mpi.engine == storm::utility::Engine::Exploration) {
verifyWithExplorationEngine<VerificationValueType>(input, mpi);
} else {
std::shared_ptr<storm::models::ModelBase> model = buildPreprocessExportModelWithValueTypeAndDdlib<DdType, BuildValueType, VerificationValueType>(input, mpi);
if (model) {
if (counterexampleSettings.isCounterexampleSet()) {
generateCounterexamples<VerificationValueType>(model, input);
} else {
verifyModel<DdType, VerificationValueType>(model, input, mpi);
}
}
}
}
template <typename ValueType>
void processInputWithValueType(SymbolicInput const& input, ModelProcessingInformation const& mpi) {
if (mpi.ddType == storm::dd::DdType::CUDD) {
STORM_LOG_ASSERT(mpi.verificationValueType == ModelProcessingInformation::ValueType::FinitePrecision && mpi.buildValueType == ModelProcessingInformation::ValueType::FinitePrecision && (std::is_same<ValueType, double>::value), "Unexpected value type for Dd library cudd.");
processInputWithValueTypeAndDdlib<storm::dd::DdType::CUDD, double>(input, mpi);
} else {
STORM_LOG_ASSERT(mpi.ddType == storm::dd::DdType::Sylvan, "Unknown DD library.");
if (mpi.buildValueType == mpi.verificationValueType) {
processInputWithValueTypeAndDdlib<storm::dd::DdType::Sylvan, ValueType>(input, mpi);
} else {
// Right now, we only require (buildType == Exact and verificationType == FinitePrecision).
// We exclude all other combinations to safe a few template instantiations.
STORM_LOG_THROW((std::is_same<ValueType, double>::value) && mpi.buildValueType == ModelProcessingInformation::ValueType::Exact, storm::exceptions::InvalidArgumentException, "Unexpected combination of buildValueType and verificationValueType");
#ifdef STORM_HAVE_CARL
processInputWithValueTypeAndDdlib<storm::dd::DdType::Sylvan, storm::RationalNumber, double>(input, mpi);
#else
STORM_LOG_THROW(false, storm::exceptions::InvalidArgumentException, "Unexpected buildValueType.");
#endif
}
}
}
}
}