diff --git a/CHANGELOG.md b/CHANGELOG.md index 21ad6718a..7dd207331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ The releases of major and minor versions contain an overview of changes since th Version 1.2.x ------------- +### Version 1.2.4 (2018/08) +- New binary `storm-conv` that handles conversions between model files (currently: prism to jani) +- Added support for expected time properties for discrete time models +- Several bug fixes related to jani +- `storm-gspn`: Improved .pnpro parser +- `storm-gspn`: Added support for single/infinite/k-server semantics for GSPNs given in the .pnpro format +- `storm-gspn`: Added option to set a global capacity for all places +- `storm-gspn`: Added option to include a set of standard properties when converting GSPNs to jani + ### Version 1.2.3 (2018/07) - Fix in version parsing diff --git a/src/storm-cli-utilities/model-handling.h b/src/storm-cli-utilities/model-handling.h index f60a3ffb5..524d772be 100644 --- a/src/storm-cli-utilities/model-handling.h +++ b/src/storm-cli-utilities/model-handling.h @@ -18,6 +18,7 @@ #include "storm/storage/SymbolicModelDescription.h" #include "storm/storage/jani/Property.h" +#include "storm/logic/RewardAccumulationEliminationVisitor.h" #include "storm/models/ModelBase.h" @@ -124,6 +125,10 @@ namespace storm { } if (!output.properties.empty()) { output.properties = storm::api::substituteConstantsInProperties(output.properties, constantDefinitions); + if (output.model.is_initialized() && output.model->isJaniModel()) { + storm::logic::RewardAccumulationEliminationVisitor v(output.model->asJaniModel()); + v.eliminateRewardAccumulations(output.properties); + } } // Check whether conversion for PRISM to JANI is requested or necessary. diff --git a/src/storm-conv-cli/storm-conv.cpp b/src/storm-conv-cli/storm-conv.cpp index 294beea6b..a499fccf2 100644 --- a/src/storm-conv-cli/storm-conv.cpp +++ b/src/storm-conv-cli/storm-conv.cpp @@ -86,11 +86,11 @@ namespace storm { auto janiModelProperties = storm::api::convertPrismToJani(prismProg, properties, options); if (outputFilename != "") { - storm::api::exportJaniToFile(janiModelProperties.first, janiModelProperties.second, outputFilename); + storm::api::exportJaniToFile(janiModelProperties.first, janiModelProperties.second, outputFilename, jani.isCompactJsonSet()); } if (output.isStdOutOutputEnabled()) { - storm::api::printJaniToStream(janiModelProperties.first, janiModelProperties.second, std::cout); + storm::api::printJaniToStream(janiModelProperties.first, janiModelProperties.second, std::cout, jani.isCompactJsonSet()); } } diff --git a/src/storm-conv/api/storm-conv.cpp b/src/storm-conv/api/storm-conv.cpp index a8d88a502..aa37dd191 100644 --- a/src/storm-conv/api/storm-conv.cpp +++ b/src/storm-conv/api/storm-conv.cpp @@ -58,12 +58,12 @@ namespace storm { return res; } - void exportJaniToFile(storm::jani::Model const& model, std::vector const& properties, std::string const& filename) { - storm::jani::JsonExporter::toFile(model, properties, filename); + void exportJaniToFile(storm::jani::Model const& model, std::vector const& properties, std::string const& filename, bool compact) { + storm::jani::JsonExporter::toFile(model, properties, filename, true, compact); } - void printJaniToStream(storm::jani::Model const& model, std::vector const& properties, std::ostream& ostream) { - storm::jani::JsonExporter::toStream(model, properties, ostream); + void printJaniToStream(storm::jani::Model const& model, std::vector const& properties, std::ostream& ostream, bool compact) { + storm::jani::JsonExporter::toStream(model, properties, ostream, true, compact); } diff --git a/src/storm-conv/api/storm-conv.h b/src/storm-conv/api/storm-conv.h index ec8a306df..42cbf804d 100644 --- a/src/storm-conv/api/storm-conv.h +++ b/src/storm-conv/api/storm-conv.h @@ -19,9 +19,9 @@ namespace storm { std::pair> convertPrismToJani(storm::prism::Program const& program, std::vector const& properties = std::vector(), storm::converter::PrismToJaniConverterOptions options = storm::converter::PrismToJaniConverterOptions()); - void exportJaniToFile(storm::jani::Model const& model, std::vector const& properties, std::string const& filename); + void exportJaniToFile(storm::jani::Model const& model, std::vector const& properties, std::string const& filename, bool compact = false); - void printJaniToStream(storm::jani::Model const& model, std::vector const& properties, std::ostream& ostream); + void printJaniToStream(storm::jani::Model const& model, std::vector const& properties, std::ostream& ostream, bool compact = false); } diff --git a/src/storm-conv/settings/modules/JaniExportSettings.cpp b/src/storm-conv/settings/modules/JaniExportSettings.cpp index 588bde61a..707e91dac 100644 --- a/src/storm-conv/settings/modules/JaniExportSettings.cpp +++ b/src/storm-conv/settings/modules/JaniExportSettings.cpp @@ -19,13 +19,15 @@ namespace storm { const std::string JaniExportSettings::exportFlattenOptionName = "flatten"; const std::string JaniExportSettings::locationVariablesOptionName = "location-variables"; const std::string JaniExportSettings::globalVariablesOptionName = "globalvars"; + const std::string JaniExportSettings::compactJsonOptionName = "compactjson"; JaniExportSettings::JaniExportSettings() : ModuleSettings(moduleName) { - this->addOption(storm::settings::OptionBuilder(moduleName, locationVariablesOptionName, true, "Variables to export in the location").addArgument(storm::settings::ArgumentBuilder::createStringArgument("variables", "A comma separated list with local variables.").setDefaultValueString("").build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, locationVariablesOptionName, true, "Variables to export in the location").addArgument(storm::settings::ArgumentBuilder::createStringArgument("variables", "A comma separated list of automaton and local variable names seperated by a dot, e.g. A.x,B.y.").setDefaultValueString("").build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, standardCompliantOptionName, false, "Export in standard compliant variant.").setShortName(standardCompliantOptionShortName).build()); - this->addOption(storm::settings::OptionBuilder(moduleName, exportFlattenOptionName, true, "Flattens the composition of Automata to obtain an equivalent model that contains exactly one automaton").build()); + this->addOption(storm::settings::OptionBuilder(moduleName, exportFlattenOptionName, false, "Flattens the composition of Automata to obtain an equivalent model that contains exactly one automaton").build()); this->addOption(storm::settings::OptionBuilder(moduleName, globalVariablesOptionName, false, "If set, variables will preferably be made global, e.g., to guarantee the same variable order as in the input file.").build()); + this->addOption(storm::settings::OptionBuilder(moduleName, compactJsonOptionName, false, "If set, the size of the resulting jani file will be reduced at the cost of (human-)readability.").build()); } bool JaniExportSettings::isExportAsStandardJaniSet() const { @@ -59,6 +61,10 @@ namespace storm { bool JaniExportSettings::isGlobalVarsSet() const { return this->getOption(globalVariablesOptionName).getHasOptionBeenSet(); } + + bool JaniExportSettings::isCompactJsonSet() const { + return this->getOption(compactJsonOptionName).getHasOptionBeenSet(); + } void JaniExportSettings::finalize() { diff --git a/src/storm-conv/settings/modules/JaniExportSettings.h b/src/storm-conv/settings/modules/JaniExportSettings.h index 90dc532f0..99e00ea9b 100644 --- a/src/storm-conv/settings/modules/JaniExportSettings.h +++ b/src/storm-conv/settings/modules/JaniExportSettings.h @@ -14,16 +14,6 @@ namespace storm { */ JaniExportSettings(); - /** - * Retrievew whether the pgcl file option was set - */ - bool isJaniFileSet() const; - - /** - * Retrieves the pgcl file name - */ - std::string getJaniFilename() const; - bool isExportAsStandardJaniSet() const; bool isExportFlattenedSet() const; @@ -31,6 +21,8 @@ namespace storm { bool isLocationVariablesSet() const; bool isGlobalVarsSet() const; + + bool isCompactJsonSet() const; std::vector> getLocationVariables() const; @@ -40,13 +32,12 @@ namespace storm { static const std::string moduleName; private: - static const std::string janiFileOptionName; - static const std::string janiFileOptionShortName; static const std::string standardCompliantOptionName; static const std::string standardCompliantOptionShortName; static const std::string exportFlattenOptionName; static const std::string locationVariablesOptionName; static const std::string globalVariablesOptionName; + static const std::string compactJsonOptionName; }; } diff --git a/src/storm-gspn-cli/storm-gspn.cpp b/src/storm-gspn-cli/storm-gspn.cpp index 6b9c5e2ee..9d6924297 100644 --- a/src/storm-gspn-cli/storm-gspn.cpp +++ b/src/storm-gspn-cli/storm-gspn.cpp @@ -5,9 +5,6 @@ #include "storm-gspn/builder/JaniGSPNBuilder.h" #include "storm-gspn/api/storm-gspn.h" -#include "storm/exceptions/BaseException.h" -#include "storm/exceptions/WrongFormatException.h" - #include "storm/utility/macros.h" #include "storm/utility/initialize.h" @@ -57,25 +54,6 @@ void initializeSettings() { } -std::unordered_map parseCapacitiesList(std::string const& filename) { - std::unordered_map map; - - std::ifstream stream; - storm::utility::openFile(filename, stream); - - std::string line; - while ( std::getline(stream, line) ) { - std::vector strs; - boost::split(strs, line, boost::is_any_of("\t ")); - STORM_LOG_THROW(strs.size() == 2, storm::exceptions::WrongFormatException, "Expect key value pairs"); - std::cout << std::stoll(strs[1]) << std::endl; - map[strs[0]] = std::stoll(strs[1]); - } - storm::utility::closeFile(stream); - return map; -} - - int main(const int argc, const char **argv) { try { storm::utility::setUp(); @@ -91,11 +69,17 @@ int main(const int argc, const char **argv) { // parse gspn from file if (!gspnSettings.isGspnFileSet()) { - return -1; + // If no gspn file is given, nothing needs to be done. + return 0; + } + + std::string constantDefinitionString = ""; + if (gspnSettings.isConstantsSet()) { + constantDefinitionString = gspnSettings.getConstantDefinitionString(); } auto parser = storm::parser::GspnParser(); - auto gspn = parser.parse(gspnSettings.getGspnFilename()); + auto gspn = parser.parse(gspnSettings.getGspnFilename(), constantDefinitionString); std::string formulaString = ""; if (storm::settings::getModule().isPropertySet()) { @@ -104,13 +88,14 @@ int main(const int argc, const char **argv) { boost::optional> propertyFilter; storm::parser::FormulaParser formulaParser(gspn->getExpressionManager()); std::vector properties = storm::api::parseProperties(formulaParser, formulaString, propertyFilter); - + properties = storm::api::substituteConstantsInProperties(properties, gspn->getConstantsSubstitution()); + if (!gspn->isValid()) { STORM_LOG_ERROR("The gspn is not valid."); } if(gspnSettings.isCapacitiesFileSet()) { - auto capacities = parseCapacitiesList(gspnSettings.getCapacitiesFilename()); + auto capacities = storm::api::parseCapacitiesList(gspnSettings.getCapacitiesFilename(), *gspn); gspn->setCapacities(capacities); } else if (gspnSettings.isCapacitySet()) { uint64_t capacity = gspnSettings.getCapacity(); @@ -126,7 +111,6 @@ int main(const int argc, const char **argv) { delete gspn; return 0; -// // // construct ma // auto builder = storm::builder::ExplicitGspnModelBuilder<>(); // auto ma = builder.translateGspn(gspn, formula); diff --git a/src/storm-gspn/api/storm-gspn.cpp b/src/storm-gspn/api/storm-gspn.cpp index b972b474c..63da78250 100644 --- a/src/storm-gspn/api/storm-gspn.cpp +++ b/src/storm-gspn/api/storm-gspn.cpp @@ -5,7 +5,8 @@ #include "storm-gspn/settings/modules/GSPNExportSettings.h" #include "storm-conv/settings/modules/JaniExportSettings.h" #include "storm-conv/api/storm-conv.h" - +#include "storm-parsers/parser/ExpressionParser.h" +#include "storm/exceptions/WrongFormatException.h" namespace storm { namespace api { @@ -71,9 +72,40 @@ namespace storm { if (exportSettings.isAddJaniPropertiesSet()) { properties.insert(properties.end(), builder.getStandardProperties().begin(), builder.getStandardProperties().end()); } - storm::api::exportJaniToFile(*model, properties, exportSettings.getWriteToJaniFilename()); + storm::api::exportJaniToFile(*model, properties, exportSettings.getWriteToJaniFilename(), jani.isCompactJsonSet()); delete model; } } + + std::unordered_map parseCapacitiesList(std::string const& filename, storm::gspn::GSPN const& gspn) { + storm::parser::ExpressionParser expressionParser(*gspn.getExpressionManager()); + std::unordered_map identifierMapping; + for (auto const& var : gspn.getExpressionManager()->getVariables()) { + identifierMapping.emplace(var.getName(), var.getExpression()); + } + expressionParser.setIdentifierMapping(identifierMapping); + expressionParser.setAcceptDoubleLiterals(false); + + std::unordered_map map; + + std::ifstream stream; + storm::utility::openFile(filename, stream); + + std::string line; + while ( std::getline(stream, line) ) { + std::vector strs; + boost::split(strs, line, boost::is_any_of("\t ")); + STORM_LOG_THROW(strs.size() == 2, storm::exceptions::WrongFormatException, "Expect key value pairs"); + storm::expressions::Expression expr = expressionParser.parseFromString(strs[1]); + if (!gspn.getConstantsSubstitution().empty()) { + expr = expr.substitute(gspn.getConstantsSubstitution()); + } + STORM_LOG_THROW(!expr.containsVariables(), storm::exceptions::WrongFormatException, "The capacity expression '" << strs[1] << "' still contains undefined constants."); + map[strs[0]] = expr.evaluateAsInt(); + } + storm::utility::closeFile(stream); + return map; + } } } + diff --git a/src/storm-gspn/api/storm-gspn.h b/src/storm-gspn/api/storm-gspn.h index 7d2360946..a54bd6e28 100644 --- a/src/storm-gspn/api/storm-gspn.h +++ b/src/storm-gspn/api/storm-gspn.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "storm/storage/jani/Model.h" #include "storm-gspn/storage/gspn/GSPN.h" #include "storm-gspn/builder/JaniGSPNBuilder.h" @@ -14,6 +16,8 @@ namespace storm { void handleGSPNExportSettings(storm::gspn::GSPN const& gspn, std::function(storm::builder::JaniGSPNBuilder const&)> const& janiProperyGetter = [](storm::builder::JaniGSPNBuilder const&) { return std::vector(); }); - + + std::unordered_map parseCapacitiesList(std::string const& filename, storm::gspn::GSPN const& gspn); + } } diff --git a/src/storm-gspn/builder/JaniGSPNBuilder.cpp b/src/storm-gspn/builder/JaniGSPNBuilder.cpp index aa8c64a4f..744b76eb4 100644 --- a/src/storm-gspn/builder/JaniGSPNBuilder.cpp +++ b/src/storm-gspn/builder/JaniGSPNBuilder.cpp @@ -3,11 +3,19 @@ #include #include "storm/logic/Formulas.h" + +#include "storm/exceptions/InvalidModelException.h" namespace storm { namespace builder { storm::jani::Model* JaniGSPNBuilder::build(std::string const& automatonName, bool buildStandardProperties) { - storm::jani::Model* model = new storm::jani::Model(gspn.getName(), storm::jani::ModelType::MA, janiVersion, expressionManager); + storm::jani::ModelType modelType = storm::jani::ModelType::MA; + if (gspn.getNumberOfTimedTransitions() == 0) { + storm::jani::ModelType modelType = storm::jani::ModelType::MDP; + } else if (gspn.getNumberOfImmediateTransitions() == 0) { + storm::jani::ModelType modelType = storm::jani::ModelType::CTMC; + } + storm::jani::Model* model = new storm::jani::Model(gspn.getName(), modelType, janiVersion, expressionManager); storm::jani::Automaton mainAutomaton(automatonName, expressionManager->declareIntegerVariable("loc")); addVariables(model); uint64_t locId = addLocation(mainAutomaton); @@ -132,7 +140,7 @@ namespace storm { } for (auto const& trans : gspn.getTimedTransitions()) { storm::expressions::Expression guard = expressionManager->boolean(true); - + std::vector assignments; for (auto const& inPlaceEntry : trans.getInputPlaces()) { guard = guard && (vars[inPlaceEntry.first]->getExpressionVariable() >= inPlaceEntry.second); @@ -153,9 +161,29 @@ namespace storm { std::shared_ptr templateEdge = std::make_shared(guard); automaton.registerTemplateEdge(templateEdge); + + storm::expressions::Expression rate = expressionManager->rational(trans.getRate()); + if (trans.hasInfiniteServerSemantics() || (trans.hasKServerSemantics() && !trans.hasSingleServerSemantics())) { + STORM_LOG_THROW(trans.hasKServerSemantics() || !trans.getInputPlaces().empty(), storm::exceptions::InvalidModelException, "Unclear semantics: Found a transition with infinite-server semantics and without input place."); + storm::expressions::Expression enablingDegree; + bool firstArgumentOfMinExpression = true; + if (trans.hasKServerSemantics()) { + enablingDegree = expressionManager->integer(trans.getNumberOfServers()); + firstArgumentOfMinExpression = false; + } + for (auto const& inPlaceEntry : trans.getInputPlaces()) { + storm::expressions::Expression enablingDegreeInPlace = vars[inPlaceEntry.first]->getExpressionVariable() / expressionManager->integer(inPlaceEntry.second); // Integer division! + if (firstArgumentOfMinExpression == true) { + enablingDegree = enablingDegreeInPlace; + } else { + enablingDegree = storm::expressions::minimum(enablingDegree, enablingDegreeInPlace); + } + } + rate = rate * enablingDegree; + } templateEdge->addDestination(assignments); - storm::jani::Edge e(locId, storm::jani::Model::SILENT_ACTION_INDEX, expressionManager->rational(trans.getRate()), templateEdge, {locId}, {expressionManager->integer(1)}); + storm::jani::Edge e(locId, storm::jani::Model::SILENT_ACTION_INDEX, rate, templateEdge, {locId}, {expressionManager->integer(1)}); automaton.addEdge(e); } diff --git a/src/storm-gspn/parser/GreatSpnEditorProjectParser.cpp b/src/storm-gspn/parser/GreatSpnEditorProjectParser.cpp index f4b4418a2..4c77bc3a2 100644 --- a/src/storm-gspn/parser/GreatSpnEditorProjectParser.cpp +++ b/src/storm-gspn/parser/GreatSpnEditorProjectParser.cpp @@ -23,14 +23,23 @@ namespace { namespace storm { namespace parser { - GreatSpnEditorProjectParser::GreatSpnEditorProjectParser() : manager(std::make_shared()), expressionParser(*manager) { - // Intentionally left empty + GreatSpnEditorProjectParser::GreatSpnEditorProjectParser(std::string const& constantDefinitionString) : manager(std::make_shared()) { + if (constantDefinitionString != "") { + std::vector constDefs; + boost::split( constDefs, constantDefinitionString, boost::is_any_of(",")); + for (auto const& pair : constDefs) { + std::vector keyvaluepair; + boost::split( keyvaluepair, pair, boost::is_any_of("=")); + STORM_LOG_THROW(keyvaluepair.size() == 2, storm::exceptions::WrongFormatException, "Expected a constant definition of the form N=42 but got " << pair); + constantDefinitions.emplace(keyvaluepair.at(0), keyvaluepair.at(1)); + } + } } storm::gspn::GSPN* GreatSpnEditorProjectParser::parse(xercesc::DOMElement const* elementRoot) { if (storm::adapters::XMLtoString(elementRoot->getTagName()) == "project") { traverseProjectElement(elementRoot); - return builder.buildGspn(); + return builder.buildGspn(manager, constantsSubstitution); } else { // If the top-level node is not a "pnml" or "" node, then throw an exception. STORM_LOG_THROW(false, storm::exceptions::UnexpectedException, "Failed to identify the root element.\n"); @@ -121,17 +130,23 @@ namespace storm { // traverse children // First pass: find constant definitions + constantsSubstitution.clear(); + expressionParser = std::make_shared(*manager); std::unordered_map identifierMapping; - expressionParser.setIdentifierMapping(identifierMapping); + expressionParser->setIdentifierMapping(identifierMapping); for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) { auto child = node->getChildNodes()->item(i); auto name = storm::adapters::getName(child); - if (name.compare("constant") == 0) { - traverseConstantElement(child, identifierMapping); + if (name.compare("constant") == 0 || name.compare("template") == 0) { + traverseConstantOrTemplateElement(child); } } - expressionParser.setIdentifierMapping(identifierMapping); - + // Update the expression parser to make the newly created variables known to it + expressionParser = std::make_shared(*manager); + for (auto const& var : manager->getVariables()) { + identifierMapping.emplace(var.getName(), var.getExpression()); + } + expressionParser->setIdentifierMapping(identifierMapping); // Second pass: traverse other children for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) { auto child = node->getChildNodes()->item(i); @@ -141,7 +156,7 @@ namespace storm { traversePlaceElement(child); } else if(name.compare("transition") == 0) { traverseTransitionElement(child); - } else if(name.compare("constant") == 0) { + } else if(name.compare("constant") == 0 || name.compare("template") == 0) { // Ignore constant def in second pass } else if (isOnlyWhitespace(name)) { // ignore node (contains only whitespace) @@ -153,7 +168,7 @@ namespace storm { } } - void GreatSpnEditorProjectParser::traverseConstantElement(xercesc::DOMNode const* const node, std::unordered_map& identifierMapping) { + void GreatSpnEditorProjectParser::traverseConstantOrTemplateElement(xercesc::DOMNode const* const node) { std::string identifier; storm::expressions::Type type; std::string valueStr = ""; @@ -165,9 +180,11 @@ namespace storm { if (name.compare("name") == 0) { identifier = storm::adapters::XMLtoString(attr->getNodeValue()); - } else if (name.compare("consttype") == 0) { + } else if (name.compare("consttype") == 0 || name.compare("type") == 0) { if (storm::adapters::XMLtoString(attr->getNodeValue()).compare("REAL") == 0) { type = manager->getRationalType(); + } else if (storm::adapters::XMLtoString(attr->getNodeValue()).compare("INTEGER") == 0) { + type = manager->getIntegerType(); } else { STORM_PRINT_AND_LOG("Unknown consttype: " << storm::adapters::XMLtoString(attr->getNodeValue()) << std::endl); } @@ -182,15 +199,24 @@ namespace storm { } } - storm::expressions::Expression valueExpression; + STORM_LOG_THROW(!manager->hasVariable(identifier), storm::exceptions::NotSupportedException, "Multiple definitions of constant '" << identifier << "' were found."); + if (valueStr == "") { + auto constDef = constantDefinitions.find(identifier); + STORM_LOG_THROW(constDef != constantDefinitions.end(), storm::exceptions::NotSupportedException, "Constant '" << identifier << "' has no value defined."); + valueStr = constDef->second; + } + storm::expressions::Variable var; if (type.isRationalType()) { - expressionParser.setAcceptDoubleLiterals(true); - valueExpression = manager->rational(expressionParser.parseFromString(valueStr).evaluateAsRational()); + var = manager->declareRationalVariable(identifier); + expressionParser->setAcceptDoubleLiterals(true); + } else if (type.isIntegerType()) { + var = manager->declareIntegerVariable(identifier); + expressionParser->setAcceptDoubleLiterals(false); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Unknown type of constant" << type << "."); } - identifierMapping.emplace(identifier, valueExpression); - + constantsSubstitution.emplace(var, expressionParser->parseFromString(valueStr)); + // traverse children for (uint_fast64_t i = 0; i < node->getChildNodes()->getLength(); ++i) { auto child = node->getChildNodes()->item(i); @@ -257,7 +283,7 @@ namespace storm { if (name.compare("name") == 0) { placeName = storm::adapters::XMLtoString(attr->getNodeValue()); } else if (name.compare("marking") == 0) { - initialTokens = std::stoull(storm::adapters::XMLtoString(attr->getNodeValue())); + initialTokens = parseInt(storm::adapters::XMLtoString(attr->getNodeValue())); } else if (ignorePlaceAttribute(name)) { // ignore node } else { @@ -297,6 +323,9 @@ namespace storm { std::string transitionName; bool immediateTransition; double rate = 1.0; // The default rate in GreatSPN. + double weight = 1.0; // The default weight in GreatSpn + uint64_t priority = 1; // The default priority in GreatSpn + boost::optional numServers; // traverse attributes for (uint_fast64_t i = 0; i < node->getAttributes()->getLength(); ++i) { @@ -308,12 +337,26 @@ namespace storm { } else if (name.compare("type") == 0) { if (storm::adapters::XMLtoString(attr->getNodeValue()).compare("EXP") == 0) { immediateTransition = false; - } else { + } else if (storm::adapters::XMLtoString(attr->getNodeValue()).compare("IMM") == 0) { immediateTransition = true; + } else { + STORM_PRINT_AND_LOG("unknown transition type: " << storm::adapters::XMLtoString(attr->getNodeValue())); } } else if(name.compare("delay") == 0) { - expressionParser.setAcceptDoubleLiterals(true); - rate = expressionParser.parseFromString(storm::adapters::XMLtoString(attr->getNodeValue())).evaluateAsDouble(); + rate = parseReal(storm::adapters::XMLtoString(attr->getNodeValue())); + } else if(name.compare("weight") == 0) { + weight = parseReal(storm::adapters::XMLtoString(attr->getNodeValue())); + } else if(name.compare("priority") == 0) { + priority = parseInt(storm::adapters::XMLtoString(attr->getNodeValue())); + } else if (name.compare("nservers") == 0) { + std::string nservers = storm::adapters::XMLtoString(attr->getNodeValue()); + if (nservers == "Single") { + numServers = 1; + } else if (nservers == "Infinite") { + // Ignore this case as we assume infinite by default (similar to GreatSpn) + } else { + numServers = parseInt(nservers); + } } else if (ignoreTransitionAttribute(name)) { // ignore node } else { @@ -325,9 +368,9 @@ namespace storm { } if (immediateTransition) { - builder.addImmediateTransition(0, 0, transitionName); + builder.addImmediateTransition(priority, weight, transitionName); } else { - builder.addTimedTransition(0, rate, transitionName); + builder.addTimedTransition(0, rate, numServers, transitionName); } // traverse children @@ -374,10 +417,10 @@ namespace storm { head = storm::adapters::XMLtoString(attr->getNodeValue()); } else if (name.compare("tail") == 0) { tail = storm::adapters::XMLtoString(attr->getNodeValue()); - } else if (name.compare("kind") == 0) { + } else if (name.compare("kind") == 0 || name.compare("type") == 0) { kind = storm::adapters::XMLtoString(attr->getNodeValue()); } else if (name.compare("mult") == 0) { - mult = std::stoull(storm::adapters::XMLtoString(attr->getNodeValue())); + mult = parseInt(storm::adapters::XMLtoString(attr->getNodeValue())); } else if (ignoreArcAttribute(name)) { // ignore node } else { @@ -420,6 +463,21 @@ namespace storm { } } + int64_t GreatSpnEditorProjectParser::parseInt(std::string str) { + expressionParser->setAcceptDoubleLiterals(false); + auto expr = expressionParser->parseFromString(str).substitute(constantsSubstitution); + STORM_LOG_ASSERT(!expr.containsVariables(), "Can not evaluate expression " << str << " as it contains undefined variables."); + return expr.evaluateAsInt(); + } + + double GreatSpnEditorProjectParser::parseReal(std::string str) { + expressionParser->setAcceptDoubleLiterals(true); + auto expr = expressionParser->parseFromString(str).substitute(constantsSubstitution); + STORM_LOG_ASSERT(!expr.containsVariables(), "Can not evaluate expression " << str << " as it contains undefined variables."); + return expr.evaluateAsDouble(); + } + + } } #endif diff --git a/src/storm-gspn/parser/GreatSpnEditorProjectParser.h b/src/storm-gspn/parser/GreatSpnEditorProjectParser.h index ba6f67a6a..7663d2a23 100644 --- a/src/storm-gspn/parser/GreatSpnEditorProjectParser.h +++ b/src/storm-gspn/parser/GreatSpnEditorProjectParser.h @@ -20,7 +20,7 @@ namespace storm { public: - GreatSpnEditorProjectParser(); + GreatSpnEditorProjectParser(std::string const& constantDefinitionString); /*! * Parses the given file into the GSPN. @@ -36,17 +36,20 @@ namespace storm { void traverseNodesElement(xercesc::DOMNode const* const node); void traverseEdgesElement(xercesc::DOMNode const* const node); - void traverseConstantElement(xercesc::DOMNode const* const node, std::unordered_map& identifierMapping); + void traverseConstantOrTemplateElement(xercesc::DOMNode const* const node); void traversePlaceElement(xercesc::DOMNode const* const node); void traverseTransitionElement(xercesc::DOMNode const* const node); void traverseArcElement(xercesc::DOMNode const* const node); + int64_t parseInt(std::string str); + double parseReal(std::string str); // the constructed gspn storm::gspn::GspnBuilder builder; std::shared_ptr manager; - storm::parser::ExpressionParser expressionParser; - + std::shared_ptr expressionParser; + std::unordered_map constantDefinitions; + std::map constantsSubstitution; }; } } diff --git a/src/storm-gspn/parser/GspnParser.cpp b/src/storm-gspn/parser/GspnParser.cpp index b92b6f006..89a2f57e9 100644 --- a/src/storm-gspn/parser/GspnParser.cpp +++ b/src/storm-gspn/parser/GspnParser.cpp @@ -12,7 +12,7 @@ namespace storm { namespace parser { - storm::gspn::GSPN* GspnParser::parse(std::string const& filename) { + storm::gspn::GSPN* GspnParser::parse(std::string const& filename, std::string const& constantDefinitions) { #ifdef STORM_HAVE_XERCES // initialize xercesc try { @@ -62,10 +62,11 @@ namespace storm { xercesc::DOMElement* elementRoot = parser->getDocument()->getDocumentElement(); if (storm::adapters::XMLtoString(elementRoot->getTagName()) == "pnml") { + STORM_LOG_WARN_COND(constantDefinitions == "", "Constant definitions for pnml files are currently not supported."); PnmlParser p; return p.parse(elementRoot); } else if (storm::adapters::XMLtoString(elementRoot->getTagName()) == "project") { - GreatSpnEditorProjectParser p; + GreatSpnEditorProjectParser p(constantDefinitions); return p.parse(elementRoot); } else { // If the top-level node is not a "pnml" or "" node, then throw an exception. diff --git a/src/storm-gspn/parser/GspnParser.h b/src/storm-gspn/parser/GspnParser.h index 862dfc757..512f0ddd6 100644 --- a/src/storm-gspn/parser/GspnParser.h +++ b/src/storm-gspn/parser/GspnParser.h @@ -4,7 +4,7 @@ namespace storm { namespace parser { class GspnParser { public: - static storm::gspn::GSPN* parse(std::string const& filename); + static storm::gspn::GSPN* parse(std::string const& filename, std::string const& constantDefinitions = ""); }; } } diff --git a/src/storm-gspn/settings/modules/GSPNSettings.cpp b/src/storm-gspn/settings/modules/GSPNSettings.cpp index fd274726f..b114559a5 100644 --- a/src/storm-gspn/settings/modules/GSPNSettings.cpp +++ b/src/storm-gspn/settings/modules/GSPNSettings.cpp @@ -19,12 +19,16 @@ namespace storm { const std::string GSPNSettings::capacitiesFileOptionName = "capacitiesfile"; const std::string GSPNSettings::capacitiesFileOptionShortName = "capacities"; const std::string GSPNSettings::capacityOptionName = "capacity"; + const std::string GSPNSettings::constantsOptionName = "constants"; + const std::string GSPNSettings::constantsOptionShortName = "const"; + GSPNSettings::GSPNSettings() : ModuleSettings(moduleName) { this->addOption(storm::settings::OptionBuilder(moduleName, gspnFileOptionName, false, "Parses the GSPN.").setShortName(gspnFileOptionShortName).addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "path to file").addValidatorString(ArgumentValidatorFactory::createExistingFileValidator()).build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, capacitiesFileOptionName, false, "Capacaties as invariants for places.").setShortName(capacitiesFileOptionShortName).addArgument(storm::settings::ArgumentBuilder::createStringArgument("filename", "path to file").addValidatorString(ArgumentValidatorFactory::createExistingFileValidator()).build()).build()); this->addOption(storm::settings::OptionBuilder(moduleName, capacityOptionName, false, "Global capacity as invariants for all places.").addArgument(storm::settings::ArgumentBuilder::createUnsignedIntegerArgument("value", "capacity").addValidatorUnsignedInteger(ArgumentValidatorFactory::createUnsignedGreaterValidator(0)).build()).build()); + this->addOption(storm::settings::OptionBuilder(moduleName, constantsOptionName, false, "Specifies the constant replacements to use.").setShortName(constantsOptionShortName).addArgument(storm::settings::ArgumentBuilder::createStringArgument("values", "A comma separated list of constants and their value, e.g. a=1,b=2,c=3.").setDefaultValueString("").build()).build()); } bool GSPNSettings::isGspnFileSet() const { @@ -51,6 +55,14 @@ namespace storm { return this->getOption(capacityOptionName).getArgumentByName("value").getValueAsUnsignedInteger(); } + bool GSPNSettings::isConstantsSet() const { + return this->getOption(constantsOptionName).getHasOptionBeenSet(); + } + + std::string GSPNSettings::getConstantDefinitionString() const { + return this->getOption(constantsOptionName).getArgumentByName("values").getValueAsString(); + } + void GSPNSettings::finalize() { } diff --git a/src/storm-gspn/settings/modules/GSPNSettings.h b/src/storm-gspn/settings/modules/GSPNSettings.h index 665e588fb..78586cf9c 100644 --- a/src/storm-gspn/settings/modules/GSPNSettings.h +++ b/src/storm-gspn/settings/modules/GSPNSettings.h @@ -44,6 +44,16 @@ namespace storm { */ uint64_t getCapacity() const; + /*! + * Retrieves whether the constants ption was set. + */ + bool isConstantsSet() const; + + /*! + * Retrieves the string that defines the constants of a gspn + */ + std::string getConstantDefinitionString() const; + bool check() const override; void finalize() override; @@ -56,7 +66,8 @@ namespace storm { static const std::string capacitiesFileOptionName; static const std::string capacitiesFileOptionShortName; static const std::string capacityOptionName; - + static const std::string constantsOptionName; + static const std::string constantsOptionShortName; }; } } diff --git a/src/storm-gspn/storage/gspn/GSPN.cpp b/src/storm-gspn/storage/gspn/GSPN.cpp index c622cc4fe..903425735 100644 --- a/src/storm-gspn/storage/gspn/GSPN.cpp +++ b/src/storm-gspn/storage/gspn/GSPN.cpp @@ -26,8 +26,8 @@ namespace storm { return tId; } - GSPN::GSPN(std::string const& name, std::vector const& places, std::vector> const& itransitions, std::vector> const& ttransitions, std::vector const& partitions, std::shared_ptr const& exprManager) - : name(name), places(places), immediateTransitions(itransitions), timedTransitions(ttransitions), partitions(partitions), exprManager(exprManager) + GSPN::GSPN(std::string const& name, std::vector const& places, std::vector> const& itransitions, std::vector> const& ttransitions, std::vector const& partitions, std::shared_ptr const& exprManager, std::map const& constantsSubstitution) + : name(name), places(places), immediateTransitions(itransitions), timedTransitions(ttransitions), partitions(partitions), exprManager(exprManager), constantsSubstitution(constantsSubstitution) { } @@ -134,12 +134,16 @@ namespace storm { return getImmediateTransition(id); } + + std::shared_ptr const& GSPN::getExpressionManager() const { + return exprManager; + } + + std::map const& GSPN::getConstantsSubstitution() const { + return constantsSubstitution; + } - std::shared_ptr const& GSPN::getExpressionManager() const { - return exprManager; - } - - void GSPN::setCapacities(std::unordered_map const& mapping) { + void GSPN::setCapacities(std::unordered_map const& mapping) { for(auto const& entry : mapping) { storm::gspn::Place* place = getPlace(entry.first); STORM_LOG_THROW(place != nullptr, storm::exceptions::InvalidArgumentException, "No place with name " << entry.first); @@ -168,6 +172,7 @@ namespace storm { for (auto& trans : this->getTimedTransitions()) { outStream << "\t" << trans.getName() << " [label=\"" << trans.getName(); outStream << "(" << trans.getRate() << ")\"];" << std::endl; + STORM_LOG_WARN_COND(trans.hasSingleServerSemantics(), "Unable to export non-trivial transition semantics"); // TODO } // print arcs @@ -555,6 +560,7 @@ namespace storm { // add timed transitions for (const auto &trans : timedTransitions) { + STORM_LOG_WARN_COND(trans.hasInfiniteServerSemantics(), "Unable to export non-trivial transition semantics"); // TODO stream << space2 << "" << std::endl; stream << space3 << "" << std::endl; stream << space4 << "" << trans.getRate() << "" << std::endl; diff --git a/src/storm-gspn/storage/gspn/GSPN.h b/src/storm-gspn/storage/gspn/GSPN.h index 7b37ed5cb..343dcd887 100644 --- a/src/storm-gspn/storage/gspn/GSPN.h +++ b/src/storm-gspn/storage/gspn/GSPN.h @@ -32,7 +32,7 @@ namespace storm { GSPN(std::string const& name, std::vector const& places, std::vector> const& itransitions, - std::vector> const& ttransitions, std::vector const& partitions, std::shared_ptr const& exprManager); + std::vector> const& ttransitions, std::vector const& partitions, std::shared_ptr const& exprManager, std::map const& constantsSubstitution = std::map()); /*! * Returns the number of places in this gspn. @@ -145,8 +145,13 @@ namespace storm { */ std::shared_ptr const& getExpressionManager() const; + /*! + * Gets an assignment of occurring constants of the GSPN to their value + */ + std::map const& getConstantsSubstitution() const; + /** - * Set Capacities according to name->capacity map. + * Set Capacities of places according to name->capacity map. */ void setCapacities(std::unordered_map const& mapping); @@ -217,6 +222,8 @@ namespace storm { std::vector partitions; std::shared_ptr exprManager; + + std::map constantsSubstitution; // Layout information mutable std::map placeLayout; diff --git a/src/storm-gspn/storage/gspn/GspnBuilder.cpp b/src/storm-gspn/storage/gspn/GspnBuilder.cpp index e273768ea..c39b5c0f7 100644 --- a/src/storm-gspn/storage/gspn/GspnBuilder.cpp +++ b/src/storm-gspn/storage/gspn/GspnBuilder.cpp @@ -65,13 +65,22 @@ namespace storm { return newId; } - + uint_fast64_t GspnBuilder::addTimedTransition(uint_fast64_t const &priority, double const &rate, std::string const& name) { + return addTimedTransition(priority, rate, 1, name); + } + + uint_fast64_t GspnBuilder::addTimedTransition(uint_fast64_t const &priority, double const &rate, boost::optional numServers, std::string const& name) { auto trans = storm::gspn::TimedTransition(); auto newId = GSPN::timedTransitionIdToTransitionId(timedTransitions.size()); trans.setName(name); trans.setPriority(priority); trans.setRate(rate); + if (numServers) { + trans.setKServerSemantics(numServers.get()); + } else { + trans.setInfiniteServerSemantics(); + } trans.setID(newId); timedTransitions.push_back(trans); @@ -162,8 +171,13 @@ namespace storm { - storm::gspn::GSPN* GspnBuilder::buildGspn() const { - std::shared_ptr exprManager(new storm::expressions::ExpressionManager()); + storm::gspn::GSPN* GspnBuilder::buildGspn(std::shared_ptr const& exprManager, std::map const& constantsSubstitution) const { + std::shared_ptr actualExprManager; + if (exprManager) { + actualExprManager = exprManager; + } else { + actualExprManager = std::make_shared(); + } std::vector orderedPartitions; for(auto const& priorityPartitions : partitions) { @@ -179,10 +193,10 @@ namespace storm { } std::reverse(orderedPartitions.begin(), orderedPartitions.end()); for(auto const& placeEntry : placeNames) { - exprManager->declareIntegerVariable(placeEntry.first, false); + actualExprManager->declareIntegerVariable(placeEntry.first, false); } - GSPN* result = new GSPN(gspnName, places, immediateTransitions, timedTransitions, orderedPartitions, exprManager); + GSPN* result = new GSPN(gspnName, places, immediateTransitions, timedTransitions, orderedPartitions, actualExprManager, constantsSubstitution); result->setTransitionLayoutInfo(transitionLayout); result->setPlaceLayoutInfo(placeLayout); return result; diff --git a/src/storm-gspn/storage/gspn/GspnBuilder.h b/src/storm-gspn/storage/gspn/GspnBuilder.h index a9a99699f..5e4db4a34 100644 --- a/src/storm-gspn/storage/gspn/GspnBuilder.h +++ b/src/storm-gspn/storage/gspn/GspnBuilder.h @@ -39,11 +39,20 @@ namespace storm { /** * Adds an timed transition to the gspn. + * The transition is assumed to have Single-Server-Semantics * @param priority The priority for the transtion. - * @param weight The weight for the transition. + * @param rate The rate for the transition. */ uint_fast64_t addTimedTransition(uint_fast64_t const &priority, RateType const& rate, std::string const& name = ""); + /** + * Adds an timed transition to the gspn. + * @param priority The priority for the transtion. + * @param rate The rate for the transition. + * @param numServers The number of servers this transition has (in case of K-Server semantics) or boost::none (in case of Infinite-Server-Semantics). + */ + uint_fast64_t addTimedTransition(uint_fast64_t const &priority, RateType const& rate, boost::optional numServers, std::string const& name = ""); + void setTransitionLayoutInfo(uint64_t transitionId, LayoutInfo const& layoutInfo); @@ -91,10 +100,10 @@ namespace storm { /** - * + * @param exprManager The expression manager that will be associated with the new gspn. If this is nullptr, a new expressionmanager will be created. * @return The gspn which is constructed by the builder. */ - storm::gspn::GSPN* buildGspn() const; + storm::gspn::GSPN* buildGspn(std::shared_ptr const& exprManager = nullptr, std::map const& constantsSubstitution = std::map()) const; private: bool isImmediateTransitionId(uint64_t) const; bool isTimedTransitionId(uint64_t) const; diff --git a/src/storm-gspn/storage/gspn/GspnJsonExporter.cpp b/src/storm-gspn/storage/gspn/GspnJsonExporter.cpp index 4e451d094..f4e910b81 100644 --- a/src/storm-gspn/storage/gspn/GspnJsonExporter.cpp +++ b/src/storm-gspn/storage/gspn/GspnJsonExporter.cpp @@ -160,7 +160,16 @@ namespace storm { data["name"] = transition.getName(); data["rate"] = transition.getRate(); data["priority"] = transition.getPriority(); - + if (!transition.hasSingleServerSemantics()) { + if (transition.hasInfiniteServerSemantics()) { + data["server-semantics"] = "infinite"; + } else if (transition.hasKServerSemantics()) { + data["server-semantics"] = transition.getNumberOfServers(); + } else { + STORM_LOG_WARN("Unable to export transition semantics."); + } + } + modernjson::json position; position["x"] = x * scaleFactor; position["y"] = y * scaleFactor; diff --git a/src/storm-gspn/storage/gspn/TimedTransition.h b/src/storm-gspn/storage/gspn/TimedTransition.h index d8399319d..37b5476f6 100644 --- a/src/storm-gspn/storage/gspn/TimedTransition.h +++ b/src/storm-gspn/storage/gspn/TimedTransition.h @@ -1,12 +1,18 @@ #pragma once #include "storm-gspn/storage/gspn/Transition.h" +#include "storm/exceptions/InvalidArgumentException.h" namespace storm { namespace gspn { template class TimedTransition : public storm::gspn::Transition { public: + + TimedTransition() { + setSingleServerSemantics(); + } + /*! * Sets the rate of this transition to the given value. * @@ -15,7 +21,43 @@ namespace storm { void setRate(RateType const& rate) { this->rate = rate; } - + + /*! + * Sets the semantics of this transition + */ + void setKServerSemantics(uint64_t k) { + STORM_LOG_THROW(k>0, storm::exceptions::InvalidArgumentException, "Invalid Parameter for server semantics: 0"); + nServers = k; + } + + void setSingleServerSemantics() { + nServers = 1; + } + + void setInfiniteServerSemantics() { + nServers = 0; + } + + /*! + * Retrieves the semantics of this transition + */ + bool hasKServerSemantics() const { + return nServers > 0; + } + + bool hasSingleServerSemantics() const { + return nServers == 1; + } + + bool hasInfiniteServerSemantics() const { + return nServers == 0; + } + + uint64_t getNumberOfServers() const { + STORM_LOG_ASSERT(hasKServerSemantics(), "Tried to get the number of servers of a timed transition although it does not have K-Server-Semantics."); + return nServers; + } + /*! * Retrieves the rate of this transition. * @@ -28,6 +70,9 @@ namespace storm { private: // the rate of the transition RateType rate; + + // the number of servers of this transition. 0 means infinite server semantics. + uint64_t nServers; }; } } \ No newline at end of file diff --git a/src/storm-parsers/parser/JaniParser.cpp b/src/storm-parsers/parser/JaniParser.cpp index 6d2df8e36..fc61bed31 100644 --- a/src/storm-parsers/parser/JaniParser.cpp +++ b/src/storm-parsers/parser/JaniParser.cpp @@ -92,6 +92,15 @@ namespace storm { storm::jani::ModelType type = storm::jani::getModelType(modeltypestring); STORM_LOG_THROW(type != storm::jani::ModelType::UNDEFINED, storm::exceptions::InvalidJaniException, "model type " + modeltypestring + " not recognized"); storm::jani::Model model(name, type, version, expressionManager); + size_t featuresCount = parsedStructure.count("features"); + STORM_LOG_THROW(featuresCount < 2, storm::exceptions::InvalidJaniException, "features-declarations can be given at most once."); + if (featuresCount == 1) { + std::unordered_set supportedFeatures = {"derived-operators", "state-exit-rewards"}; + for (auto const& feature : parsedStructure.at("features")) { + std::string featureStr = getString(feature, "Model feature"); + STORM_LOG_WARN_COND(supportedFeatures.find(featureStr) != supportedFeatures.end(), "Storm does not support the model feature " << featureStr << "."); + } + } size_t actionCount = parsedStructure.count("actions"); STORM_LOG_THROW(actionCount < 2, storm::exceptions::InvalidJaniException, "Action-declarations can be given at most once."); if (actionCount > 0) { @@ -190,6 +199,24 @@ namespace storm { } + storm::logic::RewardAccumulation JaniParser::parseRewardAccumulation(json const& accStructure, std::string const& context) { + bool accTime = false; + bool accSteps = false; + bool accExit = false; + STORM_LOG_THROW(accStructure.is_array(), storm::exceptions::InvalidJaniException, "Accumulate should be an array"); + for (auto const& accEntry : accStructure) { + if (accEntry == "steps") { + accSteps = true; + } else if (accEntry == "time") { + accTime = true; + } else if (accEntry == "exit") { + accExit = true; + } else { + STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "One may only accumulate either 'steps' or 'time' or 'exit', got " << accEntry.dump() << " in " << context); + } + } + return storm::logic::RewardAccumulation(accSteps, accTime, accExit); + } std::shared_ptr JaniParser::parseFormula(json const& propertyStructure, storm::logic::FormulaContext formulaContext,std::unordered_map> const& globalVars, std::unordered_map> const& constants, std::string const& context, boost::optional bound) { if (propertyStructure.is_boolean()) { @@ -232,29 +259,17 @@ namespace storm { opInfo.optimalityType = opString == "Emin" ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize; opInfo.bound = bound; - bool accTime = false; - bool accSteps = false; + storm::logic::RewardAccumulation rewardAccumulation(false, false, false); if (propertyStructure.count("accumulate") > 0) { - STORM_LOG_THROW(propertyStructure.at("accumulate").is_array(), storm::exceptions::InvalidJaniException, "Accumulate should be an array"); - for(auto const& accEntry : propertyStructure.at("accumulate")) { - if (accEntry == "steps") { - accSteps = true; - } else if (accEntry == "time") { - accTime = true; - } else { - STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "One may only accumulate either 'steps' or 'time', got " << accEntry.dump() << " in " << context); - } - } + rewardAccumulation = parseRewardAccumulation(propertyStructure.at("accumulate"), context); } - STORM_LOG_THROW(!(accTime && accSteps), storm::exceptions::NotSupportedException, "Storm does not allow to accumulate over both time and steps"); - if (propertyStructure.count("step-instant") > 0) { STORM_LOG_THROW(propertyStructure.count("time-instant") == 0, storm::exceptions::NotSupportedException, "Storm does not support to have a step-instant and a time-instant in " + context); STORM_LOG_THROW(propertyStructure.count("reward-instants") == 0, storm::exceptions::NotSupportedException, "Storm does not support to have a step-instant and a reward-instant in " + context); storm::expressions::Expression stepInstantExpr = parseExpression(propertyStructure.at("step-instant"), "Step instant in " + context, globalVars, constants); - if(!accTime && !accSteps) { + if(rewardAccumulation.isEmpty()) { if (rewExpr.isVariable()) { std::string rewardName = rewExpr.getVariables().begin()->getName(); return std::make_shared(std::make_shared(stepInstantExpr, storm::logic::TimeBoundType::Steps), rewardName, opInfo); @@ -264,7 +279,7 @@ namespace storm { } else { if (rewExpr.isVariable()) { std::string rewardName = rewExpr.getVariables().begin()->getName(); - return std::make_shared(std::make_shared(storm::logic::TimeBound(false, stepInstantExpr), storm::logic::TimeBoundReference(storm::logic::TimeBoundType::Steps)), rewardName, opInfo); + return std::make_shared(std::make_shared(storm::logic::TimeBound(false, stepInstantExpr), storm::logic::TimeBoundReference(storm::logic::TimeBoundType::Steps), rewardAccumulation), rewardName, opInfo); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Only simple reward expressions are currently supported"); } @@ -274,7 +289,7 @@ namespace storm { storm::expressions::Expression timeInstantExpr = parseExpression(propertyStructure.at("time-instant"), "time instant in " + context, globalVars, constants); - if(!accTime && !accSteps) { + if(rewardAccumulation.isEmpty()) { if (rewExpr.isVariable()) { std::string rewardName = rewExpr.getVariables().begin()->getName(); return std::make_shared(std::make_shared(timeInstantExpr, storm::logic::TimeBoundType::Time), rewardName, opInfo); @@ -284,7 +299,7 @@ namespace storm { } else { if (rewExpr.isVariable()) { std::string rewardName = rewExpr.getVariables().begin()->getName(); - return std::make_shared(std::make_shared(storm::logic::TimeBound(false, timeInstantExpr), storm::logic::TimeBoundReference(storm::logic::TimeBoundType::Time)), rewardName, opInfo); + return std::make_shared(std::make_shared(storm::logic::TimeBound(false, timeInstantExpr), storm::logic::TimeBoundReference(storm::logic::TimeBoundType::Time), rewardAccumulation), rewardName, opInfo); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Only simple reward expressions are currently supported"); } @@ -295,24 +310,14 @@ namespace storm { for (auto const& rewInst : propertyStructure.at("reward-instants")) { storm::expressions::Expression rewInstExpression = parseExpression(rewInst.at("exp"), "Reward expression in " + context, globalVars, constants); STORM_LOG_THROW(!rewInstExpression.isVariable(), storm::exceptions::NotSupportedException, "Reward bounded cumulative reward formulas should only argue over reward expressions."); - boundReferences.emplace_back(rewInstExpression.getVariables().begin()->getName()); - bool rewInstAccSteps(false), rewInstAccTime(false); - for (auto const& accEntry : rewInst.at("accumulate")) { - if (accEntry == "steps") { - rewInstAccSteps = true; - } else if (accEntry == "time") { - rewInstAccTime = true; - } else { - STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "One may only accumulate either 'steps' or 'time', got " << accEntry.dump() << " in " << context); - } - } - STORM_LOG_THROW((rewInstAccTime && !rewInstAccSteps) || (!rewInstAccTime && rewInstAccSteps), storm::exceptions::NotSupportedException, "Storm only allows to accumulate either over time or over steps in " + context); + storm::logic::RewardAccumulation boundRewardAccumulation = parseRewardAccumulation(rewInst.at("accumulate"), context); + boundReferences.emplace_back(rewInstExpression.getVariables().begin()->getName(), boundRewardAccumulation); storm::expressions::Expression rewInstantExpr = parseExpression(rewInst.at("instant"), "reward instant in " + context, globalVars, constants); bounds.emplace_back(false, rewInstantExpr); } if (rewExpr.isVariable()) { std::string rewardName = rewExpr.getVariables().begin()->getName(); - return std::make_shared(std::make_shared(bounds, boundReferences), rewardName, opInfo); + return std::make_shared(std::make_shared(bounds, boundReferences, rewardAccumulation), rewardName, opInfo); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Only simple reward expressions are currently supported"); } @@ -320,9 +325,9 @@ namespace storm { std::shared_ptr subformula; if (propertyStructure.count("reach") > 0) { auto context = time ? storm::logic::FormulaContext::Time : storm::logic::FormulaContext::Reward; - subformula = std::make_shared(parseFormula(propertyStructure.at("reach"), context, globalVars, constants, "Reach-expression of operator " + opString), context); + subformula = std::make_shared(parseFormula(propertyStructure.at("reach"), context, globalVars, constants, "Reach-expression of operator " + opString), context, rewardAccumulation); } else { - subformula = std::make_shared(); + subformula = std::make_shared(rewardAccumulation); } if (rewExpr.isVariable()) { assert(!time); @@ -404,6 +409,8 @@ namespace storm { STORM_LOG_THROW(rbStructure.count("exp") == 1, storm::exceptions::InvalidJaniException, "Expecting reward-expression for operator " << opString << " in " << context); storm::expressions::Expression rewExpr = parseExpression(rbStructure.at("exp"), "Reward expression in " + context, globalVars, constants); STORM_LOG_THROW(rewExpr.isVariable(), storm::exceptions::NotSupportedException, "Storm currently does not support complex reward expressions."); + storm::logic::RewardAccumulation boundRewardAccumulation = parseRewardAccumulation(rbStructure.at("accumulate"), context); + tbReferences.emplace_back(rewExpr.getVariables().begin()->getName(), boundRewardAccumulation); std::string rewardName = rewExpr.getVariables().begin()->getName(); STORM_LOG_WARN("Reward-type (steps, time) is deduced from model type."); if (pi.hasLowerBound()) { @@ -481,8 +488,6 @@ namespace storm { } else if(propertyStructure.at("right").count("op") > 0 && (propertyStructure.at("right").at("op") == "Pmin" || propertyStructure.at("right").at("op") == "Pmax" || propertyStructure.at("right").at("op") == "Emin" || propertyStructure.at("right").at("op") == "Emax" || propertyStructure.at("right").at("op") == "Smin" || propertyStructure.at("right").at("op") == "Smax")) { auto expr = parseExpression(propertyStructure.at("left"), "Threshold for operator " + propertyStructure.at("right").at("op").get(),{},{}); - STORM_LOG_THROW(expr.getVariables().empty(), storm::exceptions::NotSupportedException, "Only constant thresholds supported"); - // TODO evaluate this expression directly as rational number return parseFormula(propertyStructure.at("right"),formulaContext, globalVars, constants, "", storm::logic::Bound(ct, expr)); } else { STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "No complex comparisons are allowed."); @@ -999,8 +1004,7 @@ namespace storm { assert(arguments.size() == 2); ensureNumericalType(arguments[0], opstring, 0, scopeDescription); ensureNumericalType(arguments[1], opstring, 1, scopeDescription); - // TODO implement - STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "modulo operation is not yet implemented"); + return arguments[0] % arguments[1]; } else if (opstring == "max") { arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scopeDescription, globalVars, constants, localVars, returnNoneInitializedOnUnknownOperator); assert(arguments.size() == 2); @@ -1037,14 +1041,13 @@ namespace storm { arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scopeDescription, globalVars, constants, localVars, returnNoneInitializedOnUnknownOperator); assert(arguments.size() == 1); ensureNumericalType(arguments[0], opstring, 0, scopeDescription); - return storm::expressions::abs(arguments[0]); + return storm::expressions::truncate(arguments[0]); } else if (opstring == "pow") { arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scopeDescription, globalVars, constants, localVars, returnNoneInitializedOnUnknownOperator); assert(arguments.size() == 2); ensureNumericalType(arguments[0], opstring, 0, scopeDescription); ensureNumericalType(arguments[1], opstring, 1, scopeDescription); - // TODO implement - STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "pow operation is not yet implemented"); + return arguments[0]^arguments[1]; } else if (opstring == "exp") { arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scopeDescription, globalVars, constants, localVars, returnNoneInitializedOnUnknownOperator); assert(arguments.size() == 2); diff --git a/src/storm-parsers/parser/JaniParser.h b/src/storm-parsers/parser/JaniParser.h index a6141c37c..d14b20259 100644 --- a/src/storm-parsers/parser/JaniParser.h +++ b/src/storm-parsers/parser/JaniParser.h @@ -2,6 +2,7 @@ #include "storm/storage/jani/Constant.h" #include "storm/logic/Formula.h" #include "storm/logic/Bound.h" +#include "storm/logic/RewardAccumulation.h" #include "storm/exceptions/FileIoException.h" #include "storm/storage/expressions/ExpressionManager.h" @@ -74,7 +75,8 @@ namespace storm { std::vector> parseUnaryFormulaArgument(json const& propertyStructure, storm::logic::FormulaContext formulaContext, std::string const& opstring, std::unordered_map> const& globalVars, std::unordered_map> const& constants, std::string const& context); std::vector> parseBinaryFormulaArguments(json const& propertyStructure, storm::logic::FormulaContext formulaContext, std::string const& opstring, std::unordered_map> const& globalVars, std::unordered_map> const& constants, std::string const& context); storm::jani::PropertyInterval parsePropertyInterval(json const& piStructure, std::unordered_map> const& constants); - + storm::logic::RewardAccumulation parseRewardAccumulation(json const& accStructure, std::string const& context); + std::shared_ptr parseComposition(json const& compositionStructure); storm::expressions::Variable getVariableOrConstantExpression(std::string const& ident, std::string const& scopeDescription, std::unordered_map> const& globalVars, std::unordered_map> const& constants, std::unordered_map> const& localVars = {}); diff --git a/src/storm-pgcl-cli/storm-pgcl.cpp b/src/storm-pgcl-cli/storm-pgcl.cpp index 5ae5248b3..fe4585023 100644 --- a/src/storm-pgcl-cli/storm-pgcl.cpp +++ b/src/storm-pgcl-cli/storm-pgcl.cpp @@ -43,7 +43,7 @@ void handleJani(storm::jani::Model& model) { storm::converter::JaniConversionOptions options(jani); storm::api::postprocessJani(model, options); if (storm::settings::getModule().isToJaniSet()) { - storm::api::exportJaniToFile(model, {}, storm::settings::getModule().getWriteToJaniFilename()); + storm::api::exportJaniToFile(model, {}, storm::settings::getModule().getWriteToJaniFilename(), jani.isCompactJsonSet()); } else { storm::api::printJaniToStream(model, {}, std::cout); } diff --git a/src/storm/builder/DdPrismModelBuilder.h b/src/storm/builder/DdPrismModelBuilder.h index 57d990b23..fb357a6c8 100644 --- a/src/storm/builder/DdPrismModelBuilder.h +++ b/src/storm/builder/DdPrismModelBuilder.h @@ -3,6 +3,7 @@ #include #include +#include #include "storm/storage/prism/Program.h" diff --git a/src/storm/logic/BoundedUntilFormula.cpp b/src/storm/logic/BoundedUntilFormula.cpp index 97c768944..6054e7cd3 100644 --- a/src/storm/logic/BoundedUntilFormula.cpp +++ b/src/storm/logic/BoundedUntilFormula.cpp @@ -285,7 +285,11 @@ namespace storm { out << ", "; } if (this->getTimeBoundReference(i).isRewardBound()) { - out << "rew{\"" << this->getTimeBoundReference(i).getRewardName() << "\"}"; + out << "rew"; + if (this->getTimeBoundReference(i).hasRewardAccumulation()) { + out << "[" << this->getTimeBoundReference(i).getRewardAccumulation() << "]"; + } + out << "{\"" << this->getTimeBoundReference(i).getRewardName() << "\"}"; } else if (this->getTimeBoundReference(i).isStepBound()) { out << "steps"; //} else if (this->getTimeBoundReference(i).isStepBound()) diff --git a/src/storm/logic/CloneVisitor.cpp b/src/storm/logic/CloneVisitor.cpp index d97dd41d6..4b427b1dc 100644 --- a/src/storm/logic/CloneVisitor.cpp +++ b/src/storm/logic/CloneVisitor.cpp @@ -70,7 +70,11 @@ namespace storm { boost::any CloneVisitor::visit(EventuallyFormula const& f, boost::any const& data) const { std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); - return std::static_pointer_cast(std::make_shared(subformula, f.getContext())); + if (f.hasRewardAccumulation()) { + return std::static_pointer_cast(std::make_shared(subformula, f.getContext(), f.getRewardAccumulation())); + } else { + return std::static_pointer_cast(std::make_shared(subformula, f.getContext())); + } } boost::any CloneVisitor::visit(TimeOperatorFormula const& f, boost::any const& data) const { @@ -119,8 +123,8 @@ namespace storm { return std::static_pointer_cast(std::make_shared(subformula, f.getOptionalRewardModelName(), f.getOperatorInformation())); } - boost::any CloneVisitor::visit(TotalRewardFormula const&, boost::any const&) const { - return std::static_pointer_cast(std::make_shared()); + boost::any CloneVisitor::visit(TotalRewardFormula const& f, boost::any const&) const { + return std::static_pointer_cast(std::make_shared(f)); } boost::any CloneVisitor::visit(UnaryBooleanStateFormula const& f, boost::any const& data) const { diff --git a/src/storm/logic/CumulativeRewardFormula.cpp b/src/storm/logic/CumulativeRewardFormula.cpp index 19a60a623..bbf21cbf0 100644 --- a/src/storm/logic/CumulativeRewardFormula.cpp +++ b/src/storm/logic/CumulativeRewardFormula.cpp @@ -9,11 +9,11 @@ namespace storm { namespace logic { - CumulativeRewardFormula::CumulativeRewardFormula(TimeBound const& bound, TimeBoundReference const& timeBoundReference) : timeBoundReferences({timeBoundReference}), bounds({bound}) { + CumulativeRewardFormula::CumulativeRewardFormula(TimeBound const& bound, TimeBoundReference const& timeBoundReference, boost::optional rewardAccumulation) : timeBoundReferences({timeBoundReference}), bounds({bound}), rewardAccumulation(rewardAccumulation) { // Intentionally left empty. } - CumulativeRewardFormula::CumulativeRewardFormula(std::vector const& bounds, std::vector const& timeBoundReferences) : timeBoundReferences(timeBoundReferences), bounds(bounds) { + CumulativeRewardFormula::CumulativeRewardFormula(std::vector const& bounds, std::vector const& timeBoundReferences, boost::optional rewardAccumulation) : timeBoundReferences(timeBoundReferences), bounds(bounds), rewardAccumulation(rewardAccumulation) { STORM_LOG_ASSERT(this->timeBoundReferences.size() == this->bounds.size(), "Number of time bounds and number of time bound references does not match."); STORM_LOG_ASSERT(!this->timeBoundReferences.empty(), "No time bounds given."); @@ -137,12 +137,24 @@ namespace storm { STORM_LOG_THROW(!bound.containsVariables(), storm::exceptions::InvalidOperationException, "Cannot evaluate time-bound '" << bound << "' as it contains undefined constants."); } + bool CumulativeRewardFormula::hasRewardAccumulation() const { + return rewardAccumulation.is_initialized(); + } + + RewardAccumulation const& CumulativeRewardFormula::getRewardAccumulation() const { + return rewardAccumulation.get(); + } + + std::shared_ptr CumulativeRewardFormula::restrictToDimension(unsigned i) const { return std::make_shared(bounds.at(i), getTimeBoundReference(i)); } std::ostream& CumulativeRewardFormula::writeToStream(std::ostream& out) const { out << "C"; + if (hasRewardAccumulation()) { + out << "[" << getRewardAccumulation() << "]"; + } if (this->isMultiDimensional()) { out << "^{"; } @@ -151,7 +163,11 @@ namespace storm { out << ", "; } if (this->getTimeBoundReference(i).isRewardBound()) { - out << "rew{\"" << this->getTimeBoundReference(i).getRewardName() << "\"}"; + out << "rew"; + if (this->getTimeBoundReference(i).hasRewardAccumulation()) { + out << "[" << this->getTimeBoundReference(i).getRewardAccumulation() << "]"; + } + out << "{\"" << this->getTimeBoundReference(i).getRewardName() << "\"}"; } else if (this->getTimeBoundReference(i).isStepBound()) { out << "steps"; //} else if (this->getTimeBoundReference(i).isStepBound()) diff --git a/src/storm/logic/CumulativeRewardFormula.h b/src/storm/logic/CumulativeRewardFormula.h index e40370b07..4c436037e 100644 --- a/src/storm/logic/CumulativeRewardFormula.h +++ b/src/storm/logic/CumulativeRewardFormula.h @@ -10,8 +10,8 @@ namespace storm { namespace logic { class CumulativeRewardFormula : public PathFormula { public: - CumulativeRewardFormula(TimeBound const& bound, TimeBoundReference const& timeBoundReference = TimeBoundReference(TimeBoundType::Time)); - CumulativeRewardFormula(std::vector const& bounds, std::vector const& timeBoundReferences); + CumulativeRewardFormula(TimeBound const& bound, TimeBoundReference const& timeBoundReference = TimeBoundReference(TimeBoundType::Time), boost::optional rewardAccumulation = boost::none); + CumulativeRewardFormula(std::vector const& bounds, std::vector const& timeBoundReferences, boost::optional rewardAccumulation = boost::none); virtual ~CumulativeRewardFormula() = default; @@ -47,6 +47,9 @@ namespace storm { template ValueType getNonStrictBound() const; + bool hasRewardAccumulation() const; + RewardAccumulation const& getRewardAccumulation() const; + std::shared_ptr restrictToDimension(unsigned i) const; private: @@ -54,6 +57,8 @@ namespace storm { std::vector timeBoundReferences; std::vector bounds; + boost::optional rewardAccumulation; + }; } } diff --git a/src/storm/logic/EventuallyFormula.cpp b/src/storm/logic/EventuallyFormula.cpp index 2f1243f8a..c8ed335e5 100644 --- a/src/storm/logic/EventuallyFormula.cpp +++ b/src/storm/logic/EventuallyFormula.cpp @@ -6,8 +6,9 @@ namespace storm { namespace logic { - EventuallyFormula::EventuallyFormula(std::shared_ptr const& subformula, FormulaContext context) : UnaryPathFormula(subformula), context(context) { + EventuallyFormula::EventuallyFormula(std::shared_ptr const& subformula, FormulaContext context, boost::optional rewardAccumulation) : UnaryPathFormula(subformula), context(context), rewardAccumulation(rewardAccumulation) { STORM_LOG_THROW(context == FormulaContext::Probability || context == FormulaContext::Reward || context == FormulaContext::Time, storm::exceptions::InvalidPropertyException, "Invalid context for formula."); + STORM_LOG_THROW(context != FormulaContext::Probability || !rewardAccumulation.is_initialized(), storm::exceptions::InvalidPropertyException, "Reward accumulations should only be given for time- and reward formulas"); } FormulaContext const& EventuallyFormula::getContext() const { @@ -42,6 +43,14 @@ namespace storm { return this->isReachabilityTimeFormula(); } + bool EventuallyFormula::hasRewardAccumulation() const { + return rewardAccumulation.is_initialized(); + } + + RewardAccumulation const& EventuallyFormula::getRewardAccumulation() const { + return rewardAccumulation.get(); + } + boost::any EventuallyFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } @@ -49,6 +58,9 @@ namespace storm { std::ostream& EventuallyFormula::writeToStream(std::ostream& out) const { out << "F "; this->getSubformula().writeToStream(out); + if (hasRewardAccumulation()) { + out << "[" << getRewardAccumulation() << "]"; + } return out; } } diff --git a/src/storm/logic/EventuallyFormula.h b/src/storm/logic/EventuallyFormula.h index 9cc4d853d..5fa30da6d 100644 --- a/src/storm/logic/EventuallyFormula.h +++ b/src/storm/logic/EventuallyFormula.h @@ -1,14 +1,17 @@ #ifndef STORM_LOGIC_EVENTUALLYFORMULA_H_ #define STORM_LOGIC_EVENTUALLYFORMULA_H_ +#include + #include "storm/logic/UnaryPathFormula.h" #include "storm/logic/FormulaContext.h" +#include "storm/logic/RewardAccumulation.h" namespace storm { namespace logic { class EventuallyFormula : public UnaryPathFormula { public: - EventuallyFormula(std::shared_ptr const& subformula, FormulaContext context = FormulaContext::Probability); + EventuallyFormula(std::shared_ptr const& subformula, FormulaContext context = FormulaContext::Probability, boost::optional rewardAccumulation = boost::none); virtual ~EventuallyFormula() { // Intentionally left empty. @@ -27,9 +30,12 @@ namespace storm { virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; virtual std::ostream& writeToStream(std::ostream& out) const override; + bool hasRewardAccumulation() const; + RewardAccumulation const& getRewardAccumulation() const; private: FormulaContext context; + boost::optional rewardAccumulation; }; } } diff --git a/src/storm/logic/FragmentChecker.cpp b/src/storm/logic/FragmentChecker.cpp index 24ad016c8..adc96728b 100644 --- a/src/storm/logic/FragmentChecker.cpp +++ b/src/storm/logic/FragmentChecker.cpp @@ -70,6 +70,9 @@ namespace storm { } else { assert(tbr.isRewardBound()); result = result && inherited.getSpecification().areRewardBoundedUntilFormulasAllowed(); + if (tbr.hasRewardAccumulation()) { + result = result && inherited.getSpecification().isRewardAccumulationAllowed(); + } } } @@ -118,6 +121,7 @@ namespace storm { bool result = inherited.getSpecification().areCumulativeRewardFormulasAllowed(); result = result && (!f.isMultiDimensional() || inherited.getSpecification().areMultiDimensionalCumulativeRewardFormulasAllowed()); + result = result && (!f.hasRewardAccumulation() || inherited.getSpecification().isRewardAccumulationAllowed()); for (uint64_t i = 0; i < f.getDimension(); ++i) { auto tbr = f.getTimeBoundReference(i); if (tbr.isStepBound()) { @@ -127,6 +131,9 @@ namespace storm { } else { assert(tbr.isRewardBound()); result = result && inherited.getSpecification().areRewardBoundedCumulativeRewardFormulasAllowed(); + if (tbr.hasRewardAccumulation()) { + result = result && inherited.getSpecification().isRewardAccumulationAllowed(); + } } } return result; @@ -140,12 +147,15 @@ namespace storm { if (!inherited.getSpecification().areNestedPathFormulasAllowed()) { result = result && !f.getSubformula().isPathFormula(); } + result = result && !f.hasRewardAccumulation(); } else if (f.isReachabilityRewardFormula()) { result = result && inherited.getSpecification().areReachabilityRewardFormulasAllowed(); result = result && f.getSubformula().isStateFormula(); + result = result && (!f.hasRewardAccumulation() || inherited.getSpecification().isRewardAccumulationAllowed()); } else if (f.isReachabilityTimeFormula()) { result = result && inherited.getSpecification().areReachbilityTimeFormulasAllowed(); result = result && f.getSubformula().isStateFormula(); + result = result && (!f.hasRewardAccumulation() || inherited.getSpecification().isRewardAccumulationAllowed()); } result = result && boost::any_cast(f.getSubformula().accept(*this, data)); return result; @@ -250,6 +260,7 @@ namespace storm { result = result && (!f.hasQuantitativeResult() || inherited.getSpecification().areQuantitativeOperatorResultsAllowed()); result = result && (f.getSubformula().isRewardPathFormula() || f.getSubformula().isConditionalRewardFormula()); result = result && (inherited.getSpecification().isVarianceMeasureTypeAllowed() || f.getMeasureType() == RewardMeasureType::Expectation); + if (!inherited.getSpecification().areNestedOperatorsAllowed()) { result = result && boost::any_cast(f.getSubformula().accept(*this, InheritedInformation(inherited.getSpecification().copy().setOperatorsAllowed(false)))); } else { @@ -258,9 +269,11 @@ namespace storm { return result; } - boost::any FragmentChecker::visit(TotalRewardFormula const&, boost::any const& data) const { + boost::any FragmentChecker::visit(TotalRewardFormula const& f, boost::any const& data) const { InheritedInformation const& inherited = boost::any_cast(data); - return inherited.getSpecification().areTotalRewardFormulasAllowed(); + bool result = (!f.hasRewardAccumulation() || inherited.getSpecification().isRewardAccumulationAllowed()); + result = result && inherited.getSpecification().areTotalRewardFormulasAllowed(); + return result; } boost::any FragmentChecker::visit(UnaryBooleanStateFormula const& f, boost::any const& data) const { diff --git a/src/storm/logic/FragmentSpecification.cpp b/src/storm/logic/FragmentSpecification.cpp index cdcfc6ecd..9b5b8b17e 100644 --- a/src/storm/logic/FragmentSpecification.cpp +++ b/src/storm/logic/FragmentSpecification.cpp @@ -1,6 +1,7 @@ #include "storm/logic/FragmentSpecification.h" #include +#include "storm/logic/RewardAccumulation.h" namespace storm { namespace logic { @@ -161,6 +162,8 @@ namespace storm { operatorAtTopLevelRequired = false; multiObjectiveFormulaAtTopLevelRequired = false; operatorsAtTopLevelOfMultiObjectiveFormulasRequired = false; + + rewardAccumulation = false; } FragmentSpecification FragmentSpecification::copy() const { @@ -564,5 +567,15 @@ namespace storm { return *this; } + bool FragmentSpecification::isRewardAccumulationAllowed() const { + return rewardAccumulation; + } + + FragmentSpecification& FragmentSpecification::setRewardAccumulationAllowed(bool newValue) { + rewardAccumulation = newValue; + return *this; + } + + } } diff --git a/src/storm/logic/FragmentSpecification.h b/src/storm/logic/FragmentSpecification.h index d90ebd5fb..012716c78 100644 --- a/src/storm/logic/FragmentSpecification.h +++ b/src/storm/logic/FragmentSpecification.h @@ -1,8 +1,14 @@ #ifndef STORM_LOGIC_FRAGMENTSPECIFICATION_H_ #define STORM_LOGIC_FRAGMENTSPECIFICATION_H_ +#include +#include + namespace storm { namespace logic { + + class RewardAccumulation; + class FragmentSpecification { public: FragmentSpecification(); @@ -139,6 +145,10 @@ namespace storm { bool areOperatorsAtTopLevelOfMultiObjectiveFormulasRequired() const; FragmentSpecification& setOperatorsAtTopLevelOfMultiObjectiveFormulasRequired(bool newValue); + bool isRewardAccumulationAllowed() const; + FragmentSpecification& setRewardAccumulationAllowed(bool newValue); + + FragmentSpecification& setOperatorsAllowed(bool newValue); FragmentSpecification& setTimeAllowed(bool newValue); FragmentSpecification& setLongRunAverageProbabilitiesAllowed(bool newValue); @@ -195,6 +205,8 @@ namespace storm { bool operatorAtTopLevelRequired; bool multiObjectiveFormulaAtTopLevelRequired; bool operatorsAtTopLevelOfMultiObjectiveFormulasRequired; + + bool rewardAccumulation; }; // Propositional. diff --git a/src/storm/logic/RewardAccumulation.cpp b/src/storm/logic/RewardAccumulation.cpp new file mode 100644 index 000000000..32065268a --- /dev/null +++ b/src/storm/logic/RewardAccumulation.cpp @@ -0,0 +1,51 @@ +#include "storm/logic/RewardAccumulation.h" + +namespace storm { + namespace logic { + + RewardAccumulation::RewardAccumulation(bool steps, bool time, bool exit) : steps(steps), time(time), exit(exit){ + // Intentionally left empty + } + + bool RewardAccumulation::isStepsSet() const { + return steps; + } + + bool RewardAccumulation::isTimeSet() const { + return time; + } + + bool RewardAccumulation::isExitSet() const { + return exit; + } + + bool RewardAccumulation::isEmpty() const { + return !isStepsSet() && !isTimeSet() && !isExitSet(); + } + + std::ostream& operator<<(std::ostream& out, RewardAccumulation const& acc) { + bool hasEntry = false; + if (acc.isStepsSet()) { + out << "steps"; + hasEntry = true; + } + if (acc.isTimeSet()) { + if (hasEntry) { + out << ", "; + } + out << "time"; + hasEntry = true; + } + if (acc.isExitSet()) { + if (hasEntry) { + out << ", "; + } + out << "exit"; + hasEntry = true; + } + + return out; + } + + } +} diff --git a/src/storm/logic/RewardAccumulation.h b/src/storm/logic/RewardAccumulation.h new file mode 100644 index 000000000..4f8a50b29 --- /dev/null +++ b/src/storm/logic/RewardAccumulation.h @@ -0,0 +1,27 @@ +#pragma once +#include + +namespace storm { + namespace logic { + + class RewardAccumulation { + public: + RewardAccumulation(bool steps, bool time, bool exit); + RewardAccumulation(RewardAccumulation const& other) = default; + + bool isStepsSet() const; // If set, choice rewards and transition rewards are accumulated upon taking the transition + bool isTimeSet() const; // If set, state rewards are accumulated over time (assuming 0 time passes in discrete-time model states) + bool isExitSet() const; // If set, state rewards are accumulated upon exiting the state + + // Returns true iff accumulation for all types of reward is disabled. + bool isEmpty() const; + + private: + bool time, steps, exit; + }; + + std::ostream& operator<<(std::ostream& out, RewardAccumulation const& acc); + + } +} + diff --git a/src/storm/logic/RewardAccumulationEliminationVisitor.cpp b/src/storm/logic/RewardAccumulationEliminationVisitor.cpp new file mode 100644 index 000000000..710f959f1 --- /dev/null +++ b/src/storm/logic/RewardAccumulationEliminationVisitor.cpp @@ -0,0 +1,153 @@ +#include "storm/logic/RewardAccumulationEliminationVisitor.h" +#include "storm/logic/Formulas.h" + +#include "storm/storage/jani/Model.h" +#include "storm/storage/jani/traverser/AssignmentsFinder.h" +#include "storm/utility/macros.h" + +#include "storm/exceptions/UnexpectedException.h" +#include "storm/exceptions/InvalidPropertyException.h" + +namespace storm { + namespace logic { + + RewardAccumulationEliminationVisitor::RewardAccumulationEliminationVisitor(storm::jani::Model const& model) : model(model) { + // Intentionally left empty + } + + std::shared_ptr RewardAccumulationEliminationVisitor::eliminateRewardAccumulations(Formula const& f) const { + boost::any result = f.accept(*this, boost::any()); + return boost::any_cast>(result); + } + + void RewardAccumulationEliminationVisitor::eliminateRewardAccumulations(std::vector& properties) const { + for (auto& p : properties) { + auto formula = eliminateRewardAccumulations(*p.getFilter().getFormula()); + auto states = eliminateRewardAccumulations(*p.getFilter().getStatesFormula()); + storm::jani::FilterExpression fe(formula, p.getFilter().getFilterType(), states); + p = storm::jani::Property(p.getName(), storm::jani::FilterExpression(formula, p.getFilter().getFilterType(), states), p.getComment()); + } + } + + boost::any RewardAccumulationEliminationVisitor::visit(BoundedUntilFormula const& f, boost::any const& data) const { + std::vector> lowerBounds, upperBounds; + std::vector timeBoundReferences; + for (uint64_t i = 0; i < f.getDimension(); ++i) { + if (f.hasLowerBound(i)) { + lowerBounds.emplace_back(TimeBound(f.isLowerBoundStrict(i), f.getLowerBound(i))); + } else { + lowerBounds.emplace_back(); + } + if (f.hasUpperBound(i)) { + upperBounds.emplace_back(TimeBound(f.isUpperBoundStrict(i), f.getUpperBound(i))); + } else { + upperBounds.emplace_back(); + } + storm::logic::TimeBoundReference tbr = f.getTimeBoundReference(i); + if (tbr.hasRewardAccumulation() && canEliminate(tbr.getRewardAccumulation(), tbr.getRewardName())) { + // Eliminate accumulation + tbr = storm::logic::TimeBoundReference(tbr.getRewardName(), boost::none); + } + timeBoundReferences.push_back(std::move(tbr)); + } + if (f.hasMultiDimensionalSubformulas()) { + std::vector> leftSubformulas, rightSubformulas; + for (uint64_t i = 0; i < f.getDimension(); ++i) { + leftSubformulas.push_back(boost::any_cast>(f.getLeftSubformula(i).accept(*this, data))); + rightSubformulas.push_back(boost::any_cast>(f.getRightSubformula(i).accept(*this, data))); + } + return std::static_pointer_cast(std::make_shared(leftSubformulas, rightSubformulas, lowerBounds, upperBounds, timeBoundReferences)); + } else { + std::shared_ptr left = boost::any_cast>(f.getLeftSubformula().accept(*this, data)); + std::shared_ptr right = boost::any_cast>(f.getRightSubformula().accept(*this, data)); + return std::static_pointer_cast(std::make_shared(left, right, lowerBounds, upperBounds, timeBoundReferences)); + } + } + + boost::any RewardAccumulationEliminationVisitor::visit(CumulativeRewardFormula const& f, boost::any const& data) const { + boost::optional rewAcc; + STORM_LOG_THROW(!data.empty(), storm::exceptions::UnexpectedException, "Formula " << f << " does not seem to be a subformula of a reward operator."); + auto rewName = boost::any_cast>(data); + if (f.hasRewardAccumulation() && !canEliminate(f.getRewardAccumulation(), rewName)) { + rewAcc = f.getRewardAccumulation(); + } + + std::vector bounds; + std::vector timeBoundReferences; + for (uint64_t i = 0; i < f.getDimension(); ++i) { + bounds.emplace_back(TimeBound(f.isBoundStrict(i), f.getBound(i))); + storm::logic::TimeBoundReference tbr = f.getTimeBoundReference(i); + if (tbr.hasRewardAccumulation() && canEliminate(tbr.getRewardAccumulation(), tbr.getRewardName())) { + // Eliminate accumulation + tbr = storm::logic::TimeBoundReference(tbr.getRewardName(), boost::none); + } + timeBoundReferences.push_back(std::move(tbr)); + } + return std::static_pointer_cast(std::make_shared(bounds, timeBoundReferences, rewAcc)); + } + + boost::any RewardAccumulationEliminationVisitor::visit(EventuallyFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, data)); + if (f.hasRewardAccumulation()) { + if (f.isTimePathFormula()) { + if (model.isDiscreteTimeModel() && ((!f.getRewardAccumulation().isExitSet() && !f.getRewardAccumulation().isStepsSet()) || (f.getRewardAccumulation().isStepsSet() && f.getRewardAccumulation().isExitSet()))) { + return std::static_pointer_cast(std::make_shared(subformula, f.getContext(), f.getRewardAccumulation())); + } else if (!model.isDiscreteTimeModel() && (!f.getRewardAccumulation().isTimeSet() || f.getRewardAccumulation().isExitSet() || f.getRewardAccumulation().isStepsSet())) { + return std::static_pointer_cast(std::make_shared(subformula, f.getContext(), f.getRewardAccumulation())); + } + } else if (f.isRewardPathFormula()) { + STORM_LOG_THROW(!data.empty(), storm::exceptions::UnexpectedException, "Formula " << f << " does not seem to be a subformula of a reward operator."); + auto rewName = boost::any_cast>(data); + if (!canEliminate(f.getRewardAccumulation(), rewName)) { + return std::static_pointer_cast(std::make_shared(subformula, f.getContext(), f.getRewardAccumulation())); + } + } + } + return std::static_pointer_cast(std::make_shared(subformula, f.getContext())); + } + + boost::any RewardAccumulationEliminationVisitor::visit(RewardOperatorFormula const& f, boost::any const& data) const { + std::shared_ptr subformula = boost::any_cast>(f.getSubformula().accept(*this, f.getOptionalRewardModelName())); + return std::static_pointer_cast(std::make_shared(subformula, f.getOptionalRewardModelName(), f.getOperatorInformation())); + } + + boost::any RewardAccumulationEliminationVisitor::visit(TotalRewardFormula const& f, boost::any const& data) const { + STORM_LOG_THROW(!data.empty(), storm::exceptions::UnexpectedException, "Formula " << f << " does not seem to be a subformula of a reward operator."); + auto rewName = boost::any_cast>(data); + if (f.hasRewardAccumulation() || canEliminate(f.getRewardAccumulation(), rewName)) { + return std::static_pointer_cast(std::make_shared()); + } else { + return std::static_pointer_cast(std::make_shared(f.getRewardAccumulation())); + } + } + + bool RewardAccumulationEliminationVisitor::canEliminate(storm::logic::RewardAccumulation const& accumulation, boost::optional rewardModelName) const { + STORM_LOG_THROW(rewardModelName.is_initialized(), storm::exceptions::InvalidPropertyException, "Unable to find transient variable with for unique reward model."); + storm::jani::AssignmentsFinder::ResultType assignmentKinds; + STORM_LOG_THROW(model.hasGlobalVariable(rewardModelName.get()), storm::exceptions::InvalidPropertyException, "Unable to find transient variable with name " << rewardModelName.get() << "."); + storm::jani::Variable const& transientVar = model.getGlobalVariable(rewardModelName.get()); + if (transientVar.getInitExpression().containsVariables() || !storm::utility::isZero(transientVar.getInitExpression().evaluateAsRational())) { + assignmentKinds.hasLocationAssignment = true; + assignmentKinds.hasEdgeAssignment = true; + assignmentKinds.hasEdgeDestinationAssignment = true; + } + assignmentKinds = storm::jani::AssignmentsFinder().find(model, transientVar); + if ((assignmentKinds.hasEdgeAssignment || assignmentKinds.hasEdgeDestinationAssignment) && !accumulation.isStepsSet()) { + return false; + } + if (assignmentKinds.hasLocationAssignment) { + if (model.isDiscreteTimeModel()) { + if (!accumulation.isExitSet()) { + return false; + } + // accumulating over time in discrete time models has no effect, i.e., the value of accumulation.isTimeSet() does not matter here. + } else { + if (accumulation.isExitSet() || !accumulation.isTimeSet()) { + return false; + } + } + } + return true; + } + } +} diff --git a/src/storm/logic/RewardAccumulationEliminationVisitor.h b/src/storm/logic/RewardAccumulationEliminationVisitor.h new file mode 100644 index 000000000..0b77901d9 --- /dev/null +++ b/src/storm/logic/RewardAccumulationEliminationVisitor.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "storm/logic/CloneVisitor.h" +#include "storm/logic/RewardAccumulation.h" +#include "storm/storage/jani/Model.h" +#include "storm/storage/jani/Property.h" + +namespace storm { + namespace logic { + + class RewardAccumulationEliminationVisitor : public CloneVisitor { + public: + RewardAccumulationEliminationVisitor(storm::jani::Model const& model); + + /*! + * Eliminates any reward accumulations of the formula, where the presence of the reward accumulation does not change the result of the formula + */ + std::shared_ptr eliminateRewardAccumulations(Formula const& f) const; + + void eliminateRewardAccumulations(std::vector& properties) const; + + virtual boost::any visit(BoundedUntilFormula const& f, boost::any const& data) const override; + virtual boost::any visit(CumulativeRewardFormula const& f, boost::any const& data) const override; + virtual boost::any visit(EventuallyFormula const& f, boost::any const& data) const override; + virtual boost::any visit(RewardOperatorFormula const& f, boost::any const& data) const override; + virtual boost::any visit(TotalRewardFormula const& f, boost::any const& data) const override; + + + private: + bool canEliminate(storm::logic::RewardAccumulation const& accumulation, boost::optional rewardModelName) const; + + storm::jani::Model const& model; + }; + + } +} diff --git a/src/storm/logic/TimeBoundType.h b/src/storm/logic/TimeBoundType.h index 0bd6aba0c..ec9a753b0 100644 --- a/src/storm/logic/TimeBoundType.h +++ b/src/storm/logic/TimeBoundType.h @@ -1,5 +1,9 @@ #pragma once +#include + +#include "storm/logic/RewardAccumulation.h" + namespace storm { namespace logic { @@ -13,14 +17,15 @@ namespace storm { class TimeBoundReference { TimeBoundType type; std::string rewardName; - + boost::optional rewardAccumulation; + public: explicit TimeBoundReference(TimeBoundType t) : type(t) { // For rewards, use the other constructor. assert(t != TimeBoundType::Reward); } - explicit TimeBoundReference(std::string const& rewardName) : type(TimeBoundType::Reward), rewardName(rewardName) { + explicit TimeBoundReference(std::string const& rewardName, boost::optional rewardAccumulation = boost::none) : type(TimeBoundType::Reward), rewardName(rewardName), rewardAccumulation(rewardAccumulation) { assert(rewardName != ""); // Empty reward name is reserved. } @@ -44,6 +49,16 @@ namespace storm { assert(isRewardBound()); return rewardName; } + + bool hasRewardAccumulation() const { + return rewardAccumulation.is_initialized(); + } + + RewardAccumulation const& getRewardAccumulation() const { + assert(isRewardBound()); + return rewardAccumulation.get(); + } + }; diff --git a/src/storm/logic/TotalRewardFormula.cpp b/src/storm/logic/TotalRewardFormula.cpp index 18c79cd67..6136581a3 100644 --- a/src/storm/logic/TotalRewardFormula.cpp +++ b/src/storm/logic/TotalRewardFormula.cpp @@ -4,7 +4,7 @@ namespace storm { namespace logic { - TotalRewardFormula::TotalRewardFormula() { + TotalRewardFormula::TotalRewardFormula(boost::optional rewardAccumulation) : rewardAccumulation(rewardAccumulation) { // Intentionally left empty. } @@ -16,6 +16,14 @@ namespace storm { return true; } + bool TotalRewardFormula::hasRewardAccumulation() const { + return rewardAccumulation.is_initialized(); + } + + RewardAccumulation const& TotalRewardFormula::getRewardAccumulation() const { + return rewardAccumulation.get(); + } + boost::any TotalRewardFormula::accept(FormulaVisitor const& visitor, boost::any const& data) const { return visitor.visit(*this, data); } diff --git a/src/storm/logic/TotalRewardFormula.h b/src/storm/logic/TotalRewardFormula.h index 90caee9e7..a11fb3b03 100644 --- a/src/storm/logic/TotalRewardFormula.h +++ b/src/storm/logic/TotalRewardFormula.h @@ -1,15 +1,16 @@ #ifndef STORM_LOGIC_TOTALREWARDFORMULA_H_ #define STORM_LOGIC_TOTALREWARDFORMULA_H_ -#include +#include +#include "storm/logic/RewardAccumulation.h" #include "storm/logic/PathFormula.h" namespace storm { namespace logic { class TotalRewardFormula : public PathFormula { public: - TotalRewardFormula(); + TotalRewardFormula(boost::optional rewardAccumulation = boost::none); virtual ~TotalRewardFormula() { // Intentionally left empty. @@ -17,11 +18,15 @@ namespace storm { virtual bool isTotalRewardFormula() const override; virtual bool isRewardPathFormula() const override; - + bool hasRewardAccumulation() const; + RewardAccumulation const& getRewardAccumulation() const; + virtual boost::any accept(FormulaVisitor const& visitor, boost::any const& data) const override; virtual std::ostream& writeToStream(std::ostream& out) const override; - + + private: + boost::optional rewardAccumulation; }; } } diff --git a/src/storm/storage/expressions/SyntacticalEqualityCheckVisitor.cpp b/src/storm/storage/expressions/SyntacticalEqualityCheckVisitor.cpp index 7f45240d7..d49b864dd 100644 --- a/src/storm/storage/expressions/SyntacticalEqualityCheckVisitor.cpp +++ b/src/storm/storage/expressions/SyntacticalEqualityCheckVisitor.cpp @@ -93,7 +93,7 @@ namespace storm { boost::any SyntacticalEqualityCheckVisitor::visit(UnaryBooleanFunctionExpression const& expression, boost::any const& data) { BaseExpression const& otherBaseExpression = boost::any_cast>(data).get(); - if (otherBaseExpression.isBinaryBooleanFunctionExpression()) { + if (otherBaseExpression.isUnaryBooleanFunctionExpression()) { UnaryBooleanFunctionExpression const& otherExpression = otherBaseExpression.asUnaryBooleanFunctionExpression(); bool result = expression.getOperatorType() == otherExpression.getOperatorType(); @@ -108,7 +108,7 @@ namespace storm { boost::any SyntacticalEqualityCheckVisitor::visit(UnaryNumericalFunctionExpression const& expression, boost::any const& data) { BaseExpression const& otherBaseExpression = boost::any_cast>(data).get(); - if (otherBaseExpression.isBinaryBooleanFunctionExpression()) { + if (otherBaseExpression.isUnaryNumericalFunctionExpression()) { UnaryNumericalFunctionExpression const& otherExpression = otherBaseExpression.asUnaryNumericalFunctionExpression(); bool result = expression.getOperatorType() == otherExpression.getOperatorType(); diff --git a/src/storm/storage/jani/Automaton.cpp b/src/storm/storage/jani/Automaton.cpp index cc75d080e..62d2576cf 100644 --- a/src/storm/storage/jani/Automaton.cpp +++ b/src/storm/storage/jani/Automaton.cpp @@ -270,6 +270,10 @@ namespace storm { return ConstEdges(it1, it2); } + EdgeContainer const& Automaton::getEdgeContainer() const { + return edges; + } + void Automaton::addEdge(Edge const& edge) { STORM_LOG_THROW(edge.getSourceLocationIndex() < locations.size(), storm::exceptions::InvalidArgumentException, "Cannot add edge with unknown source location index '" << edge.getSourceLocationIndex() << "'."); assert(validate()); diff --git a/src/storm/storage/jani/Automaton.h b/src/storm/storage/jani/Automaton.h index eb1362741..869f5cd09 100644 --- a/src/storm/storage/jani/Automaton.h +++ b/src/storm/storage/jani/Automaton.h @@ -192,6 +192,8 @@ namespace storm { */ ConstEdges getEdgesFromLocation(uint64_t locationIndex, uint64_t actionIndex) const; + EdgeContainer const& getEdgeContainer() const; + /*! * Adds the template edge to the list of edges */ diff --git a/src/storm/storage/jani/EdgeContainer.cpp b/src/storm/storage/jani/EdgeContainer.cpp index b7a9ca1ec..d735bc80d 100644 --- a/src/storm/storage/jani/EdgeContainer.cpp +++ b/src/storm/storage/jani/EdgeContainer.cpp @@ -121,8 +121,6 @@ namespace storm { } - - std::vector & EdgeContainer::getConcreteEdges() { return edges; } @@ -130,6 +128,10 @@ namespace storm { std::vector const& EdgeContainer::getConcreteEdges() const { return edges; } + + TemplateEdgeContainer const& EdgeContainer::getTemplateEdges() const { + return templates; + } std::set EdgeContainer::getActionIndices() const { std::set result; diff --git a/src/storm/storage/jani/EdgeContainer.h b/src/storm/storage/jani/EdgeContainer.h index f4738e1e0..c2bdfe80c 100644 --- a/src/storm/storage/jani/EdgeContainer.h +++ b/src/storm/storage/jani/EdgeContainer.h @@ -95,6 +95,8 @@ namespace storm { void clearConcreteEdges(); std::vector const& getConcreteEdges() const; std::vector & getConcreteEdges(); + TemplateEdgeContainer const& getTemplateEdges() const; + size_t size() const; iterator begin(); diff --git a/src/storm/storage/jani/JSONExporter.cpp b/src/storm/storage/jani/JSONExporter.cpp index 42bc15d62..cc45468c0 100644 --- a/src/storm/storage/jani/JSONExporter.cpp +++ b/src/storm/storage/jani/JSONExporter.cpp @@ -11,6 +11,7 @@ #include "storm/exceptions/InvalidJaniException.h" #include "storm/exceptions/NotImplementedException.h" +#include "storm/exceptions/InvalidPropertyException.h" #include "storm/storage/expressions/RationalLiteralExpression.h" #include "storm/storage/expressions/IntegerLiteralExpression.h" @@ -29,6 +30,7 @@ #include "storm/storage/jani/AutomatonComposition.h" #include "storm/storage/jani/ParallelComposition.h" #include "storm/storage/jani/Property.h" +#include "storm/storage/jani/traverser/AssignmentsFinder.h" namespace storm { namespace jani { @@ -153,9 +155,58 @@ namespace storm { return iDecl; } - modernjson::json FormulaToJaniJson::translate(storm::logic::Formula const& formula, storm::jani::Model const& model) { + modernjson::json FormulaToJaniJson::constructRewardAccumulation(storm::logic::RewardAccumulation const& rewardAccumulation, std::string const& rewardModelName) const { + + storm::jani::Variable const& transientVar = model.getGlobalVariable(rewardModelName); + storm::jani::AssignmentsFinder::ResultType assignmentKinds; + STORM_LOG_THROW(model.hasGlobalVariable(rewardModelName), storm::exceptions::InvalidPropertyException, "Unable to find transient variable with name " << rewardModelName << "."); + if (transientVar.getInitExpression().containsVariables() || !storm::utility::isZero(transientVar.getInitExpression().evaluateAsRational())) { + assignmentKinds.hasLocationAssignment = true; + assignmentKinds.hasEdgeAssignment = true; + assignmentKinds.hasEdgeDestinationAssignment = true; + } + assignmentKinds = storm::jani::AssignmentsFinder().find(model, transientVar); + + bool steps = rewardAccumulation.isStepsSet() && (assignmentKinds.hasEdgeAssignment || assignmentKinds.hasEdgeDestinationAssignment); + bool time = rewardAccumulation.isTimeSet() && !model.isDiscreteTimeModel() && assignmentKinds.hasLocationAssignment; + bool exit = rewardAccumulation.isExitSet() && assignmentKinds.hasLocationAssignment; + return constructRewardAccumulation(storm::logic::RewardAccumulation(steps, time, exit)); + } + + modernjson::json FormulaToJaniJson::constructRewardAccumulation(storm::logic::RewardAccumulation const& rewardAccumulation) const { + std::vector res; + if (rewardAccumulation.isStepsSet()) { + res.push_back("steps"); + } + if (rewardAccumulation.isTimeSet()) { + res.push_back("time"); + } + if (rewardAccumulation.isExitSet()) { + stateExitRewards = true; + res.push_back("exit"); + } + return res; + } + + modernjson::json FormulaToJaniJson::constructStandardRewardAccumulation(std::string const& rewardModelName) const { + if (model.isDiscreteTimeModel()) { + return constructRewardAccumulation(storm::logic::RewardAccumulation(true, false, true), rewardModelName); + } else { + return constructRewardAccumulation(storm::logic::RewardAccumulation(true, true, false), rewardModelName); + } + } + + modernjson::json FormulaToJaniJson::translate(storm::logic::Formula const& formula, storm::jani::Model const& model, std::set& modelFeatures) { FormulaToJaniJson translator(model); - return boost::any_cast(formula.accept(translator)); + auto result = boost::any_cast(formula.accept(translator)); + if (translator.containsStateExitRewards()) { + modelFeatures.insert("state-exit-rewards"); + } + return result; + } + + bool FormulaToJaniJson::containsStateExitRewards() const { + return stateExitRewards; } boost::any FormulaToJaniJson::visit(storm::logic::AtomicExpressionFormula const& f, boost::any const&) const { @@ -209,13 +260,11 @@ namespace storm { } else if(tbr.isRewardBound()) { modernjson::json rewbound; rewbound["exp"] = tbr.getRewardName(); - std::vector accvec; - if (model.isDiscreteTimeModel()) { - accvec.push_back("steps"); + if (tbr.hasRewardAccumulation()) { + rewbound["accumulate"] = constructRewardAccumulation(tbr.getRewardAccumulation(), tbr.getRewardName()); } else { - accvec.push_back("time"); + rewbound["accumulate"] = constructStandardRewardAccumulation(tbr.getRewardName()); } - rewbound["accumulate"] = modernjson::json(accvec); rewbound["bounds"] = propertyInterval; rewardBounds.push_back(std::move(rewbound)); } else { @@ -249,12 +298,14 @@ namespace storm { boost::any FormulaToJaniJson::visit(storm::logic::TimeOperatorFormula const& f, boost::any const& data) const { modernjson::json opDecl; - std::vector accvec; - if (model.isDiscreteTimeModel()) { - accvec.push_back("steps"); - } else { - accvec.push_back("time"); + + // Create standard reward accumulation for time operator formulas. + storm::logic::RewardAccumulation rewAcc(model.isDiscreteTimeModel(), !model.isDiscreteTimeModel(), false); + if (f.getSubformula().isEventuallyFormula() && f.getSubformula().asEventuallyFormula().hasRewardAccumulation()) { + rewAcc = f.getSubformula().asEventuallyFormula().getRewardAccumulation(); } + auto rewAccJson = constructRewardAccumulation(rewAcc); + if(f.hasBound()) { auto bound = f.getBound(); opDecl["op"] = comparisonTypeToJani(bound.comparisonType); @@ -270,7 +321,7 @@ namespace storm { opDecl["left"]["reach"] = boost::any_cast(f.getSubformula().asEventuallyFormula().getSubformula().accept(*this, data)); } opDecl["left"]["exp"] = modernjson::json(1); - opDecl["left"]["accumulate"] = modernjson::json(accvec); + opDecl["left"]["accumulate"] = rewAccJson; opDecl["right"] = buildExpression(bound.threshold, model.getConstants(), model.getGlobalVariables()); } else { if(f.hasOptimalityType()) { @@ -286,7 +337,7 @@ namespace storm { opDecl["reach"] = boost::any_cast(f.getSubformula().asEventuallyFormula().getSubformula().accept(*this, data)); } opDecl["exp"] = modernjson::json(1); - opDecl["accumulate"] = modernjson::json(accvec); + opDecl["accumulate"] = rewAccJson; } return opDecl; } @@ -403,13 +454,11 @@ namespace storm { boost::any FormulaToJaniJson::visit(storm::logic::RewardOperatorFormula const& f, boost::any const& data) const { modernjson::json opDecl; - std::vector accvec; + std::string instantName; if (model.isDiscreteTimeModel()) { - accvec.push_back("steps"); instantName = "step-instant"; } else { - accvec.push_back("time"); instantName = "time-instant"; } @@ -439,21 +488,29 @@ namespace storm { } if (f.getSubformula().isEventuallyFormula()) { opDecl["left"]["reach"] = boost::any_cast(f.getSubformula().asEventuallyFormula().getSubformula().accept(*this, data)); + if (f.getSubformula().asEventuallyFormula().hasRewardAccumulation()) { + opDecl["left"]["accumulate"] = constructRewardAccumulation(f.getSubformula().asEventuallyFormula().getRewardAccumulation(), rewardModelName); + } else { + opDecl["left"]["accumulate"] = constructStandardRewardAccumulation(rewardModelName); + } } else if (f.getSubformula().isCumulativeRewardFormula()) { + // TODO: support for reward bounded formulas + STORM_LOG_WARN_COND(!f.getSubformula().asCumulativeRewardFormula().getTimeBoundReference().isRewardBound(), "Export for reward bounded cumulative reward formulas currently unsupported."); opDecl["left"][instantName] = buildExpression(f.getSubformula().asCumulativeRewardFormula().getBound(), model.getConstants(), model.getGlobalVariables()); + if (f.getSubformula().asCumulativeRewardFormula().hasRewardAccumulation()) { + opDecl["left"]["accumulate"] = constructRewardAccumulation(f.getSubformula().asCumulativeRewardFormula().getRewardAccumulation(), rewardModelName); + } else { + opDecl["left"]["accumulate"] = constructStandardRewardAccumulation(rewardModelName); + } } else if (f.getSubformula().isInstantaneousRewardFormula()) { opDecl["left"][instantName] = buildExpression(f.getSubformula().asInstantaneousRewardFormula().getBound(), model.getConstants(), model.getGlobalVariables()); } STORM_LOG_THROW(f.hasRewardModelName(), storm::exceptions::NotSupportedException, "Reward name has to be specified for Jani-conversion"); opDecl["left"]["exp"] = rewardModelName; - if (f.getSubformula().isReachabilityRewardFormula() || f.getSubformula().isCumulativeRewardFormula()) { - opDecl["left"]["accumulate"] = modernjson::json(accvec); - } opDecl["right"] = buildExpression(bound.threshold, model.getConstants(), model.getGlobalVariables()); } else { if (f.hasOptimalityType()) { opDecl["op"] = f.getOptimalityType() == storm::solver::OptimizationDirection::Minimize ? "Emin" : "Emax"; - } else { // TODO add checks opDecl["op"] = "Emin"; @@ -461,16 +518,24 @@ namespace storm { if (f.getSubformula().isEventuallyFormula()) { opDecl["reach"] = boost::any_cast(f.getSubformula().asEventuallyFormula().getSubformula().accept(*this, data)); + if (f.getSubformula().asEventuallyFormula().hasRewardAccumulation()) { + opDecl["accumulate"] = constructRewardAccumulation(f.getSubformula().asEventuallyFormula().getRewardAccumulation(), rewardModelName); + } else { + opDecl["accumulate"] = constructStandardRewardAccumulation(rewardModelName); + } } else if (f.getSubformula().isCumulativeRewardFormula()) { + // TODO: support for reward bounded formulas + STORM_LOG_WARN_COND(!f.getSubformula().asCumulativeRewardFormula().getTimeBoundReference().isRewardBound(), "Export for reward bounded cumulative reward formulas currently unsupported."); opDecl[instantName] = buildExpression(f.getSubformula().asCumulativeRewardFormula().getBound(), model.getConstants(), model.getGlobalVariables()); + if (f.getSubformula().asCumulativeRewardFormula().hasRewardAccumulation()) { + opDecl["accumulate"] = constructRewardAccumulation(f.getSubformula().asCumulativeRewardFormula().getRewardAccumulation(), rewardModelName); + } else { + opDecl["accumulate"] = constructStandardRewardAccumulation(rewardModelName); + } } else if (f.getSubformula().isInstantaneousRewardFormula()) { opDecl[instantName] = buildExpression(f.getSubformula().asInstantaneousRewardFormula().getBound(), model.getConstants(), model.getGlobalVariables()); } - opDecl["exp"] = rewardModelName; - if (f.getSubformula().isReachabilityRewardFormula() || f.getSubformula().isCumulativeRewardFormula()) { - opDecl["accumulate"] = modernjson::json(accvec); - } } return opDecl; } @@ -628,21 +693,27 @@ namespace storm { return modernjson::json(expression.getValueAsDouble()); } - void JsonExporter::toFile(storm::jani::Model const& janiModel, std::vector const& formulas, std::string const& filepath, bool checkValid) { + void JsonExporter::toFile(storm::jani::Model const& janiModel, std::vector const& formulas, std::string const& filepath, bool checkValid, bool compact) { std::ofstream stream; storm::utility::openFile(filepath, stream); - toStream(janiModel, formulas, stream, checkValid); + toStream(janiModel, formulas, stream, checkValid, compact); storm::utility::closeFile(stream); } - void JsonExporter::toStream(storm::jani::Model const& janiModel, std::vector const& formulas, std::ostream& os, bool checkValid) { + void JsonExporter::toStream(storm::jani::Model const& janiModel, std::vector const& formulas, std::ostream& os, bool checkValid, bool compact) { if(checkValid) { janiModel.checkValid(); } JsonExporter exporter; - exporter.convertModel(janiModel); + exporter.convertModel(janiModel, !compact); exporter.convertProperties(formulas, janiModel); - os << exporter.finalize().dump(4) << std::endl; + if (compact) { + // Dump without line breaks/indents + os << exporter.finalize().dump() << std::endl; + } else { + // Dump with line breaks and indention with 4 spaces + os << exporter.finalize().dump(4) << std::endl; + } } modernjson::json buildActionArray(std::vector const& actions) { @@ -838,8 +909,8 @@ namespace storm { return modernjson::json(automataDeclarations); } - void JsonExporter::convertModel(storm::jani::Model const& janiModel, bool commentExpressions) { + modelFeatures = {"derived-operators"}; jsonStruct["jani-version"] = janiModel.getJaniVersion(); jsonStruct["name"] = janiModel.getName(); jsonStruct["type"] = to_string(janiModel.getModelType()); @@ -849,14 +920,8 @@ namespace storm { jsonStruct["restrict-initial"]["exp"] = buildExpression(janiModel.getInitialStatesRestriction(), janiModel.getConstants(), janiModel.getGlobalVariables()); jsonStruct["automata"] = buildAutomataArray(janiModel.getAutomata(), janiModel.getActionIndexToNameMap(), janiModel.getConstants(), janiModel.getGlobalVariables(), commentExpressions); jsonStruct["system"] = CompositionJsonExporter::translate(janiModel.getSystemComposition()); - std::vector standardFeatureVector = {"derived-operators"}; - jsonStruct["features"] = standardFeatureVector; - } - - - std::string janiFilterTypeString(storm::modelchecker::FilterType const& ft) { switch(ft) { case storm::modelchecker::FilterType::MIN: @@ -885,12 +950,12 @@ namespace storm { } } - modernjson::json convertFilterExpression(storm::jani::FilterExpression const& fe, storm::jani::Model const& model) { + modernjson::json convertFilterExpression(storm::jani::FilterExpression const& fe, storm::jani::Model const& model, std::set& modelFeatures) { modernjson::json propDecl; propDecl["states"]["op"] = "initial"; propDecl["op"] = "filter"; propDecl["fun"] = janiFilterTypeString(fe.getFilterType()); - propDecl["values"] = FormulaToJaniJson::translate(*fe.getFormula(), model); + propDecl["values"] = FormulaToJaniJson::translate(*fe.getFormula(), model, modelFeatures); return propDecl; } @@ -901,7 +966,7 @@ namespace storm { for(auto const& f : formulas) { modernjson::json propDecl; propDecl["name"] = f.getName(); - propDecl["expression"] = convertFilterExpression(f.getFilter(), model); + propDecl["expression"] = convertFilterExpression(f.getFilter(), model, modelFeatures); ++index; properties.push_back(propDecl); } diff --git a/src/storm/storage/jani/JSONExporter.h b/src/storm/storage/jani/JSONExporter.h index 1bf098bca..1f1a49db8 100644 --- a/src/storm/storage/jani/JSONExporter.h +++ b/src/storm/storage/jani/JSONExporter.h @@ -41,7 +41,8 @@ namespace storm { class FormulaToJaniJson : public storm::logic::FormulaVisitor { public: - static modernjson::json translate(storm::logic::Formula const& formula, storm::jani::Model const& modeln); + static modernjson::json translate(storm::logic::Formula const& formula, storm::jani::Model const& model, std::set& modelFeatures); + bool containsStateExitRewards() const; // Returns true iff the previously translated formula contained state exit rewards virtual boost::any visit(storm::logic::AtomicExpressionFormula const& f, boost::any const& data) const; virtual boost::any visit(storm::logic::AtomicLabelFormula const& f, boost::any const& data) const; virtual boost::any visit(storm::logic::BinaryBooleanStateFormula const& f, boost::any const& data) const; @@ -64,19 +65,24 @@ namespace storm { virtual boost::any visit(storm::logic::UntilFormula const& f, boost::any const& data) const; private: - FormulaToJaniJson(storm::jani::Model const& model) : model(model) { } + FormulaToJaniJson(storm::jani::Model const& model) : model(model), stateExitRewards(false) { } modernjson::json constructPropertyInterval(boost::optional const& lower, boost::optional const& lowerExclusive, boost::optional const& upper, boost::optional const& upperExclusive) const; + + modernjson::json constructRewardAccumulation(storm::logic::RewardAccumulation const& rewardAccumulation) const; + modernjson::json constructRewardAccumulation(storm::logic::RewardAccumulation const& rewardAccumulation, std::string const& rewardModelName) const; + modernjson::json constructStandardRewardAccumulation(std::string const& rewardModelName) const; storm::jani::Model const& model; + mutable bool stateExitRewards; }; class JsonExporter { JsonExporter() = default; public: - static void toFile(storm::jani::Model const& janiModel, std::vector const& formulas, std::string const& filepath, bool checkValid = true); - static void toStream(storm::jani::Model const& janiModel, std::vector const& formulas, std::ostream& ostream, bool checkValid = false); + static void toFile(storm::jani::Model const& janiModel, std::vector const& formulas, std::string const& filepath, bool checkValid = true, bool compact = false); + static void toStream(storm::jani::Model const& janiModel, std::vector const& formulas, std::ostream& ostream, bool checkValid = false, bool compact = false); private: @@ -85,11 +91,13 @@ namespace storm { void appendVariableDeclaration(storm::jani::Variable const& variable); modernjson::json finalize() { + std::vector featureVector(modelFeatures.begin(), modelFeatures.end()); + jsonStruct["features"] = featureVector; return jsonStruct; } modernjson::json jsonStruct; - + std::set modelFeatures; }; } diff --git a/src/storm/storage/jani/OrderedAssignments.cpp b/src/storm/storage/jani/OrderedAssignments.cpp index 7d1967ff7..9c622fe2b 100644 --- a/src/storm/storage/jani/OrderedAssignments.cpp +++ b/src/storm/storage/jani/OrderedAssignments.cpp @@ -27,19 +27,17 @@ namespace storm { bool OrderedAssignments::add(Assignment const& assignment, bool addToExisting) { // If the element is contained in this set of assignment, nothing needs to be added. - if (this->contains(assignment)) { + if (!addToExisting && this->contains(assignment)) { return false; } // Otherwise, we find the spot to insert it. auto it = lowerBound(assignment, allAssignments); - - if (it != allAssignments.end()) { - if ((!addToExisting || !assignment.getExpressionVariable().hasNumericalType())) { - STORM_LOG_THROW(assignment.getExpressionVariable() != (*it)->getExpressionVariable(), storm::exceptions::InvalidArgumentException, "Cannot add assignment ('" << assignment.getAssignedExpression() << "') as an assignment ('" << (*it)->getAssignedExpression() << "') to variable '" << (*it)->getVariable().getName() << "' already exists."); - } else if (addToExisting && assignment.getExpressionVariable().hasNumericalType()) { - (*it)->setAssignedExpression((*it)->getAssignedExpression() + assignment.getAssignedExpression()); - } + + // Check if an assignment to this variable is already present + if (it != allAssignments.end() && assignment.getExpressionVariable() == (*it)->getExpressionVariable()) { + STORM_LOG_THROW(addToExisting && assignment.getExpressionVariable().hasNumericalType(), storm::exceptions::InvalidArgumentException, "Cannot add assignment ('" << assignment.getAssignedExpression() << "') as an assignment ('" << (*it)->getAssignedExpression() << "') to variable '" << (*it)->getVariable().getName() << "' already exists."); + (*it)->setAssignedExpression((*it)->getAssignedExpression() + assignment.getAssignedExpression()); } else { // Finally, insert the new element in the correct vectors. auto elementToInsert = std::make_shared(assignment); diff --git a/src/storm/storage/jani/traverser/AssignmentsFinder.cpp b/src/storm/storage/jani/traverser/AssignmentsFinder.cpp new file mode 100644 index 000000000..f1450d004 --- /dev/null +++ b/src/storm/storage/jani/traverser/AssignmentsFinder.cpp @@ -0,0 +1,47 @@ +#include "storm/storage/jani/traverser/AssignmentsFinder.h" + + +namespace storm { + namespace jani { + + AssignmentsFinder::ResultType AssignmentsFinder::find(Model const& model, Variable const& variable) { + ResultType res; + res.hasLocationAssignment = false; + res.hasEdgeAssignment = false; + res.hasEdgeDestinationAssignment = false; + JaniTraverser::traverse(model, std::make_pair(&variable, &res)); + return res; + } + + void AssignmentsFinder::traverse(Location const& location, boost::any const& data) const { + auto resVar = boost::any_cast>(data); + for (auto const& assignment : location.getAssignments()) { + if (assignment.getVariable() == *resVar.first) { + resVar.second->hasLocationAssignment = true; + } + } + JaniTraverser::traverse(location, data); + } + + void AssignmentsFinder::traverse(TemplateEdge const& templateEdge, boost::any const& data) const { + auto resVar = boost::any_cast>(data); + for (auto const& assignment : templateEdge.getAssignments()) { + if (assignment.getVariable() == *resVar.first) { + resVar.second->hasEdgeAssignment = true; + } + } + JaniTraverser::traverse(templateEdge, data); + } + + void AssignmentsFinder::traverse(TemplateEdgeDestination const& templateEdgeDestination, boost::any const& data) const { + auto resVar = boost::any_cast>(data); + for (auto const& assignment : templateEdgeDestination.getOrderedAssignments()) { + if (assignment.getVariable() == *resVar.first) { + resVar.second->hasEdgeDestinationAssignment = true; + } + } + JaniTraverser::traverse(templateEdgeDestination, data); + } + } +} + diff --git a/src/storm/storage/jani/traverser/AssignmentsFinder.h b/src/storm/storage/jani/traverser/AssignmentsFinder.h new file mode 100644 index 000000000..2a2d2cdd0 --- /dev/null +++ b/src/storm/storage/jani/traverser/AssignmentsFinder.h @@ -0,0 +1,29 @@ +#pragma once + + +#include + +#include "storm/storage/jani/traverser/JaniTraverser.h" + +namespace storm { + namespace jani { + class AssignmentsFinder : public JaniTraverser { + public: + + struct ResultType { + bool hasLocationAssignment, hasEdgeAssignment, hasEdgeDestinationAssignment; + }; + + AssignmentsFinder() = default; + + ResultType find(Model const& model, Variable const& variable); + + virtual ~AssignmentsFinder() = default; + + virtual void traverse(Location const& location, boost::any const& data) const override; + virtual void traverse(TemplateEdge const& templateEdge, boost::any const& data) const override; + virtual void traverse(TemplateEdgeDestination const& templateEdgeDestination, boost::any const& data) const override; + }; + } +} + diff --git a/src/storm/storage/jani/traverser/JaniTraverser.cpp b/src/storm/storage/jani/traverser/JaniTraverser.cpp new file mode 100644 index 000000000..2461792b5 --- /dev/null +++ b/src/storm/storage/jani/traverser/JaniTraverser.cpp @@ -0,0 +1,138 @@ +#include "storm/storage/jani/traverser/JaniTraverser.h" + + +namespace storm { + namespace jani { + void JaniTraverser::traverse(Model const& model, boost::any const& data) const { + for (auto const& act : model.getActions()) { + traverse(act, data); + } + for (auto const& c : model.getConstants()) { + traverse(c, data); + } + traverse(model.getGlobalVariables(), data); + for (auto const& aut : model.getAutomata()) { + traverse(aut, data); + } + if (model.hasInitialStatesRestriction()) { + traverse(model.getInitialStatesRestriction(), data); + } + } + + void JaniTraverser::traverse(Action const& action, boost::any const& data) const { + // Intentionally left empty. + } + + void JaniTraverser::traverse(Automaton const& automaton, boost::any const& data) const { + traverse(automaton.getVariables(), data); + for (auto const& loc : automaton.getLocations()) { + traverse(loc, data); + } + traverse(automaton.getEdgeContainer(), data); + if (automaton.hasInitialStatesRestriction()) { + traverse(automaton.getInitialStatesRestriction(), data); + } + } + + void JaniTraverser::traverse(Constant const& constant, boost::any const& data) const { + if (constant.isDefined()) { + traverse(constant.getExpression(), data); + } + } + + void JaniTraverser::traverse(VariableSet const& variableSet, boost::any const& data) const { + for (auto const& v : variableSet.getBooleanVariables()) { + traverse(v, data); + } + for (auto const& v : variableSet.getBoundedIntegerVariables()) { + traverse(v, data); + } + for (auto const& v : variableSet.getUnboundedIntegerVariables()) { + traverse(v, data); + } + for (auto const& v : variableSet.getRealVariables()) { + traverse(v, data); + } + } + + void JaniTraverser::traverse(Location const& location, boost::any const& data) const { + traverse(location.getAssignments(), data); + } + + void JaniTraverser::traverse(BooleanVariable const& variable, boost::any const& data) const { + if (variable.hasInitExpression()) { + traverse(variable.getInitExpression(), data); + } + } + + void JaniTraverser::traverse(BoundedIntegerVariable const& variable, boost::any const& data) const { + if (variable.hasInitExpression()) { + traverse(variable.getInitExpression(), data); + } + traverse(variable.getLowerBound(), data); + traverse(variable.getUpperBound(), data); + } + + void JaniTraverser::traverse(UnboundedIntegerVariable const& variable, boost::any const& data) const { + if (variable.hasInitExpression()) { + traverse(variable.getInitExpression(), data); + } + } + + void JaniTraverser::traverse(RealVariable const& variable, boost::any const& data) const { + if (variable.hasInitExpression()) { + traverse(variable.getInitExpression(), data); + } + } + + void JaniTraverser::traverse(EdgeContainer const& edgeContainer, boost::any const& data) const { + for (auto const& templateEdge : edgeContainer.getTemplateEdges()) { + traverse(*templateEdge, data); + } + for (auto const& concreteEdge : edgeContainer.getConcreteEdges()) { + traverse(concreteEdge, data); + } + } + + void JaniTraverser::traverse(TemplateEdge const& templateEdge, boost::any const& data) const { + traverse(templateEdge.getGuard(), data); + for (auto const& dest : templateEdge.getDestinations()) { + traverse(dest, data); + } + traverse(templateEdge.getAssignments(), data); + } + + void JaniTraverser::traverse(TemplateEdgeDestination const& templateEdgeDestination, boost::any const& data) const { + traverse(templateEdgeDestination.getOrderedAssignments(), data); + } + + void JaniTraverser::traverse(Edge const& edge, boost::any const& data) const { + if (edge.hasRate()) { + traverse(edge.getRate(), data); + } + for (auto const& dest : edge.getDestinations()) { + traverse(dest, data); + } + } + + void JaniTraverser::traverse(EdgeDestination const& edgeDestination, boost::any const& data) const { + traverse(edgeDestination.getProbability(), data); + } + + void JaniTraverser::traverse(OrderedAssignments const& orderedAssignments, boost::any const& data) const { + for (auto const& assignment : orderedAssignments) { + traverse(assignment, data); + } + } + + void JaniTraverser::traverse(Assignment const& assignment, boost::any const& data) const { + traverse(assignment.getAssignedExpression(), data); + } + + void JaniTraverser::traverse(storm::expressions::Expression const& expression, boost::any const& data) const { + // intentionally left empty. + } + + } +} + diff --git a/src/storm/storage/jani/traverser/JaniTraverser.h b/src/storm/storage/jani/traverser/JaniTraverser.h new file mode 100644 index 000000000..905ad0208 --- /dev/null +++ b/src/storm/storage/jani/traverser/JaniTraverser.h @@ -0,0 +1,36 @@ +#pragma once + + +#include + +#include "storm/storage/jani/Model.h" + +namespace storm { + namespace jani { + class JaniTraverser { + public: + virtual ~JaniTraverser() = default; + + virtual void traverse(Model const& model, boost::any const& data) const; + + virtual void traverse(Action const& action, boost::any const& data) const; + virtual void traverse(Automaton const& automaton, boost::any const& data) const; + virtual void traverse(Constant const& constant, boost::any const& data) const; + virtual void traverse(VariableSet const& variableSet, boost::any const& data) const; + virtual void traverse(Location const& location, boost::any const& data) const; + virtual void traverse(BooleanVariable const& variable, boost::any const& data) const; + virtual void traverse(BoundedIntegerVariable const& variable, boost::any const& data) const; + virtual void traverse(UnboundedIntegerVariable const& variable, boost::any const& data) const; + virtual void traverse(RealVariable const& variable, boost::any const& data) const; + virtual void traverse(EdgeContainer const& edgeContainer, boost::any const& data) const; + virtual void traverse(TemplateEdge const& templateEdge, boost::any const& data) const; + virtual void traverse(TemplateEdgeDestination const& templateEdgeDestination, boost::any const& data) const; + virtual void traverse(Edge const& edge, boost::any const& data) const; + virtual void traverse(EdgeDestination const& edgeDestination, boost::any const& data) const; + virtual void traverse(OrderedAssignments const& orderedAssignments, boost::any const& data) const; + virtual void traverse(Assignment const& assignment, boost::any const& data) const; + virtual void traverse(storm::expressions::Expression const& expression, boost::any const& data) const; + }; + } +} +