Storm 1.11.1.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
JaniParser.cpp
Go to the documentation of this file.
1#include "JaniParser.h"
2
16
22
24
29
31
32#include <algorithm> // std::iter_swap
33#include <boost/lexical_cast.hpp>
34#include <fstream>
35#include <iostream>
36#include <sstream>
37
38#include "storm/io/file.h"
40
41namespace storm {
42namespace parser {
43
45// Defaults
47template<typename ValueType>
48const bool JaniParser<ValueType>::defaultVariableTransient = false;
49const std::string VARIABLE_AUTOMATON_DELIMITER = "_";
50template<typename ValueType>
51const std::set<std::string> JaniParser<ValueType>::unsupportedOpstrings({"tan", "cot", "sec", "csc", "asin", "acos", "atan",
52 "acot", "asec", "acsc", "sinh", "cosh", "tanh", "coth",
53 "sech", "csch", "asinh", "acosh", "atanh", "asinh", "acosh"});
54
55template<typename ValueType>
56std::string getString(typename JaniParser<ValueType>::Json const& structure, std::string const& errorInfo) {
57 STORM_LOG_THROW(structure.is_string(), storm::exceptions::InvalidJaniException,
58 "Expected a string in " << errorInfo << ", got '" << structure.dump() << "'");
59 return structure.front();
60}
61
62template<typename ValueType>
63bool getBoolean(typename JaniParser<ValueType>::Json const& structure, std::string const& errorInfo) {
64 STORM_LOG_THROW(structure.is_boolean(), storm::exceptions::InvalidJaniException,
65 "Expected a Boolean in " << errorInfo << ", got " << structure.dump() << "'");
66 return structure.front();
67}
68
69template<typename ValueType>
70uint64_t getUnsignedInt(typename JaniParser<ValueType>::Json const& structure, std::string const& errorInfo) {
71 STORM_LOG_THROW(structure.is_number(), storm::exceptions::InvalidJaniException,
72 "Expected a number in " << errorInfo << ", got '" << structure.dump() << "'");
73 int64_t num = structure.front();
74 STORM_LOG_THROW(num >= 0, storm::exceptions::InvalidJaniException, "Expected a positive number in " << errorInfo << ", got '" << num << "'");
75 return static_cast<uint64_t>(num);
76}
77
78template<typename ValueType>
79int64_t getSignedInt(typename JaniParser<ValueType>::Json const& structure, std::string const& errorInfo) {
80 STORM_LOG_THROW(structure.is_number(), storm::exceptions::InvalidJaniException,
81 "Expected a number in " << errorInfo << ", got '" << structure.dump() << "'");
82 return structure.front();
83}
84
85template<typename ValueType>
86std::pair<storm::jani::Model, std::vector<storm::jani::Property>> JaniParser<ValueType>::parse(std::string const& path, bool parseProperties) {
87 JaniParser parser;
88 parser.readFile(path);
89 return parser.parseModel(parseProperties);
90}
91
92template<typename ValueType>
93std::pair<storm::jani::Model, std::vector<storm::jani::Property>> JaniParser<ValueType>::parseFromString(std::string const& jsonstring, bool parseProperties) {
94 JaniParser parser(jsonstring);
95 return parser.parseModel(parseProperties);
96}
97
98template<typename ValueType>
99JaniParser<ValueType>::JaniParser(std::string const& jsonstring) : expressionManager(new storm::expressions::ExpressionManager()) {
100 parsedStructure = Json::parse(jsonstring);
101}
102
103template<typename ValueType>
104void JaniParser<ValueType>::readFile(std::string const& path) {
105 std::ifstream file;
106 storm::io::openFile(path, file);
107 file >> parsedStructure;
109}
110
111template<typename ValueType>
112std::pair<storm::jani::Model, std::vector<storm::jani::Property>> JaniParser<ValueType>::parseModel(bool parseProperties) {
113 // jani-version
114 STORM_LOG_THROW(parsedStructure.count("jani-version") == 1, storm::exceptions::InvalidJaniException, "Jani-version must be given exactly once.");
115 uint64_t version = getUnsignedInt<ValueType>(parsedStructure.at("jani-version"), "jani version");
116 STORM_LOG_WARN_COND(version >= 1 && version <= 1, "JANI Version " << version << " is not supported. Results may be wrong.");
117 // name
118 STORM_LOG_THROW(parsedStructure.count("name") == 1, storm::exceptions::InvalidJaniException, "A model must have a (single) name");
119 std::string name = getString<ValueType>(parsedStructure.at("name"), "model name");
120 // model type
121 STORM_LOG_THROW(parsedStructure.count("type") == 1, storm::exceptions::InvalidJaniException, "A type must be given exactly once");
122 std::string modeltypestring = getString<ValueType>(parsedStructure.at("type"), "type of the model");
124 STORM_LOG_THROW(type != storm::jani::ModelType::UNDEFINED, storm::exceptions::InvalidJaniException, "model type " + modeltypestring + " not recognized");
125 storm::jani::Model model(name, type, version, expressionManager);
126 uint_fast64_t featuresCount = parsedStructure.count("features");
127 STORM_LOG_THROW(featuresCount < 2, storm::exceptions::InvalidJaniException, "features-declarations can be given at most once.");
128 if (featuresCount == 1) {
129 const auto allKnownModelFeatures = storm::jani::getAllKnownModelFeatures();
130 for (auto const& feature : parsedStructure.at("features")) {
131 std::string featureStr = getString<ValueType>(feature, "Model feature");
132 bool found = false;
133 for (auto const& knownFeature : allKnownModelFeatures.asSet()) {
134 if (featureStr == storm::jani::toString(knownFeature)) {
135 model.getModelFeatures().add(knownFeature);
136 found = true;
137 break;
138 }
139 }
140 STORM_LOG_THROW(found, storm::exceptions::NotSupportedException, "Storm does not support the model feature " << featureStr);
141 }
142 }
143 uint_fast64_t actionCount = parsedStructure.count("actions");
144 STORM_LOG_THROW(actionCount < 2, storm::exceptions::InvalidJaniException, "Action-declarations can be given at most once.");
145 if (actionCount > 0) {
146 parseActions(parsedStructure.at("actions"), model);
147 }
148
149 Scope scope(name);
150
151 // Parse constants
152 ConstantsMap constants;
153 scope.constants = &constants;
154 uint_fast64_t constantsCount = parsedStructure.count("constants");
155 STORM_LOG_THROW(constantsCount < 2, storm::exceptions::InvalidJaniException, "Constant-declarations can be given at most once.");
156 if (constantsCount == 1) {
157 // Reserve enough space to make sure that pointers to constants remain valid after adding new ones.
158 model.getConstants().reserve(parsedStructure.at("constants").size());
159 for (auto const& constStructure : parsedStructure.at("constants")) {
160 std::shared_ptr<storm::jani::Constant> constant =
161 parseConstant(constStructure, scope.refine("constants[" + std::to_string(constants.size()) + "]"));
162 model.addConstant(*constant);
163 assert(model.getConstants().back().getName() == constant->getName());
164 constants.emplace(constant->getName(), &model.getConstants().back());
165 }
166 }
167
168 // Parse variables
169 uint_fast64_t variablesCount = parsedStructure.count("variables");
170 STORM_LOG_THROW(variablesCount < 2, storm::exceptions::InvalidJaniException, "Variable-declarations can be given at most once for global variables.");
171 VariablesMap globalVars;
172 scope.globalVars = &globalVars;
173 if (variablesCount == 1) {
174 for (auto const& varStructure : parsedStructure.at("variables")) {
175 std::shared_ptr<storm::jani::Variable> variable = parseVariable(varStructure, scope.refine("variables[" + std::to_string(globalVars.size())));
176 globalVars.emplace(variable->getName(), &model.addVariable(*variable));
177 }
178 }
179
180 uint64_t funDeclCount = parsedStructure.count("functions");
181 STORM_LOG_THROW(funDeclCount < 2, storm::exceptions::InvalidJaniException, "Model '" << name << "' has more than one list of functions");
182 FunctionsMap globalFuns;
183 scope.globalFunctions = &globalFuns;
184 if (funDeclCount > 0) {
185 // We require two passes through the function definitions array to allow referring to functions before they were defined.
186 std::vector<storm::jani::FunctionDefinition> dummyFunctionDefinitions;
187 for (auto const& funStructure : parsedStructure.at("functions")) {
188 // Skip parsing of function body
189 dummyFunctionDefinitions.push_back(
190 parseFunctionDefinition(funStructure, scope.refine("functions[" + std::to_string(globalFuns.size()) + "] of model " + name), true));
191 }
192 // Store references to the dummy function definitions. This needs to happen in a separate loop since otherwise, references to FunDefs can be invalidated
193 // after calling dummyFunctionDefinitions.push_back
194 for (auto const& funDef : dummyFunctionDefinitions) {
195 bool unused = globalFuns.emplace(funDef.getName(), &funDef).second;
196 STORM_LOG_THROW(unused, storm::exceptions::InvalidJaniException,
197 "Multiple definitions of functions with the name " << funDef.getName() << " in " << scope.description);
198 }
199 for (auto const& funStructure : parsedStructure.at("functions")) {
200 // Actually parse the function body
202 parseFunctionDefinition(funStructure, scope.refine("functions[" + std::to_string(globalFuns.size()) + "] of model " + name), false);
203 assert(globalFuns.count(funDef.getName()) == 1);
204 globalFuns[funDef.getName()] = &model.addFunctionDefinition(funDef);
205 }
206 }
207
208 // Parse Automata
209 STORM_LOG_THROW(parsedStructure.count("automata") == 1, storm::exceptions::InvalidJaniException, "Exactly one list of automata must be given");
210 STORM_LOG_THROW(parsedStructure.at("automata").is_array(), storm::exceptions::InvalidJaniException, "Automata must be an array");
211 // Automatons can only be parsed after constants and variables.
212 for (auto const& automataEntry : parsedStructure.at("automata")) {
213 model.addAutomaton(parseAutomaton(automataEntry, model, scope.refine("automata[" + std::to_string(model.getNumberOfAutomata()) + "]")));
214 }
215 STORM_LOG_THROW(parsedStructure.count("restrict-initial") < 2, storm::exceptions::InvalidJaniException, "Model has multiple initial value restrictions");
216 storm::expressions::Expression initialValueRestriction = expressionManager->boolean(true);
217 if (parsedStructure.count("restrict-initial") > 0) {
218 STORM_LOG_THROW(parsedStructure.at("restrict-initial").count("exp") == 1, storm::exceptions::InvalidJaniException,
219 "Model needs an expression inside the initial restricion");
220 initialValueRestriction = parseExpression(parsedStructure.at("restrict-initial").at("exp"), scope.refine("Initial value restriction"));
221 }
222 model.setInitialStatesRestriction(initialValueRestriction);
223 STORM_LOG_THROW(parsedStructure.count("system") == 1, storm::exceptions::InvalidJaniException, "Exactly one system description must be given");
224 std::shared_ptr<storm::jani::Composition> composition = parseComposition(parsedStructure.at("system"));
225 model.setSystemComposition(composition);
226 model.finalize();
227
228 // Parse properties
230 STORM_LOG_THROW(parsedStructure.count("properties") <= 1, storm::exceptions::InvalidJaniException, "At most one list of properties can be given");
231 std::vector<storm::jani::Property> properties;
232 if (parseProperties && parsedStructure.count("properties") == 1) {
233 STORM_LOG_THROW(parsedStructure.at("properties").is_array(), storm::exceptions::InvalidJaniException, "Properties should be an array");
234 for (auto const& propertyEntry : parsedStructure.at("properties")) {
235 try {
236 auto prop = this->parseProperty(model, propertyEntry, scope.refine("property[" + std::to_string(properties.size()) + "]"));
237 // Eliminate reward accumulations as much as possible
238 rewAccEliminator.eliminateRewardAccumulations(prop);
239 properties.push_back(prop);
240 } catch (storm::exceptions::NotSupportedException const& ex) {
241 STORM_LOG_WARN("Cannot handle property: " << ex.what());
242 } catch (storm::exceptions::NotImplementedException const& ex) {
243 STORM_LOG_WARN("Cannot handle property: " << ex.what());
244 }
245 }
246 }
247 return {model, properties};
248}
249
250template<typename ValueType>
251std::vector<std::shared_ptr<storm::logic::Formula const>> JaniParser<ValueType>::parseUnaryFormulaArgument(storm::jani::Model& model,
252 Json const& propertyStructure,
253 storm::logic::FormulaContext formulaContext,
254 std::string const& opstring, Scope const& scope) {
255 STORM_LOG_THROW(propertyStructure.count("exp") == 1, storm::exceptions::InvalidJaniException,
256 "Expecting operand for operator " << opstring << " in " << scope.description);
257 return {parseFormula(model, propertyStructure.at("exp"), formulaContext, scope.refine("Operand of operator " + opstring))};
258}
259
260template<typename ValueType>
261std::vector<std::shared_ptr<storm::logic::Formula const>> JaniParser<ValueType>::parseBinaryFormulaArguments(storm::jani::Model& model,
262 Json const& propertyStructure,
263 storm::logic::FormulaContext formulaContext,
264 std::string const& opstring, Scope const& scope) {
265 STORM_LOG_THROW(propertyStructure.count("left") == 1, storm::exceptions::InvalidJaniException,
266 "Expecting left operand for operator " << opstring << " in " << scope.description);
267 STORM_LOG_THROW(propertyStructure.count("right") == 1, storm::exceptions::InvalidJaniException,
268 "Expecting right operand for operator " << opstring << " in " << scope.description);
269 return {parseFormula(model, propertyStructure.at("left"), formulaContext, scope.refine("Operand of operator " + opstring)),
270 parseFormula(model, propertyStructure.at("right"), formulaContext, scope.refine("Operand of operator " + opstring))};
271}
272
273template<typename ValueType>
274storm::jani::PropertyInterval JaniParser<ValueType>::parsePropertyInterval(Json const& piStructure, Scope const& scope) {
276 if (piStructure.count("lower") > 0) {
277 pi.lowerBound = parseExpression(piStructure.at("lower"), scope.refine("Lower bound for property interval"));
278 }
279 if (piStructure.count("lower-exclusive") > 0) {
280 STORM_LOG_THROW(pi.lowerBound.isInitialized(), storm::exceptions::InvalidJaniException, "Lower-exclusive can only be set if a lower bound is present");
281 pi.lowerBoundStrict = piStructure.at("lower-exclusive");
282 }
283 if (piStructure.count("upper") > 0) {
284 pi.upperBound = parseExpression(piStructure.at("upper"), scope.refine("Upper bound for property interval"));
285 }
286 if (piStructure.count("upper-exclusive") > 0) {
287 STORM_LOG_THROW(pi.upperBound.isInitialized(), storm::exceptions::InvalidJaniException, "Lower-exclusive can only be set if a lower bound is present");
288 pi.upperBoundStrict = piStructure.at("upper-exclusive");
289 }
290 STORM_LOG_THROW(pi.lowerBound.isInitialized() || pi.upperBound.isInitialized(), storm::exceptions::InvalidJaniException,
291 "Bounded operator must have a bounded interval, but no bounds found in '" << piStructure << "'");
292 return pi;
293}
294
295template<typename ValueType>
296storm::logic::RewardAccumulation JaniParser<ValueType>::parseRewardAccumulation(Json const& accStructure, std::string const& context) {
297 bool accTime = false;
298 bool accSteps = false;
299 bool accExit = false;
300 STORM_LOG_THROW(accStructure.is_array(), storm::exceptions::InvalidJaniException, "Accumulate should be an array");
301 for (auto const& accEntry : accStructure) {
302 if (accEntry == "steps") {
303 accSteps = true;
304 } else if (accEntry == "time") {
305 accTime = true;
306 } else if (accEntry == "exit") {
307 accExit = true;
308 } else {
309 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
310 "One may only accumulate either 'steps' or 'time' or 'exit', got " << accEntry.dump() << " in " << context);
311 }
312 }
313 return storm::logic::RewardAccumulation(accSteps, accTime, accExit);
314}
315
316void insertLowerUpperTimeBounds(std::vector<boost::optional<storm::logic::TimeBound>>& lowerBounds,
317 std::vector<boost::optional<storm::logic::TimeBound>>& upperBounds, storm::jani::PropertyInterval const& pi) {
318 if (pi.hasLowerBound()) {
319 lowerBounds.push_back(storm::logic::TimeBound(pi.lowerBoundStrict, pi.lowerBound));
320 } else {
321 lowerBounds.push_back(boost::none);
322 }
323 if (pi.hasUpperBound()) {
324 upperBounds.push_back(storm::logic::TimeBound(pi.upperBoundStrict, pi.upperBound));
325 } else {
326 upperBounds.push_back(boost::none);
327 }
328}
329
330template<typename ValueType>
331std::shared_ptr<storm::logic::Formula const> JaniParser<ValueType>::parseFormula(storm::jani::Model& model, Json const& propertyStructure,
332 storm::logic::FormulaContext formulaContext, Scope const& scope,
333 boost::optional<storm::logic::Bound> bound) {
334 if (propertyStructure.is_boolean()) {
335 return std::make_shared<storm::logic::BooleanLiteralFormula>(propertyStructure.template get<bool>());
336 }
337 if (propertyStructure.is_string()) {
338 if (labels.count(propertyStructure.template get<std::string>()) > 0) {
339 return std::make_shared<storm::logic::AtomicLabelFormula>(propertyStructure.template get<std::string>());
340 }
341 }
342 storm::expressions::Expression expr = parseExpression(propertyStructure, scope.refine("expression in property"), true);
343 if (expr.isInitialized()) {
344 bool exprContainsLabel = false;
345 auto varsInExpr = expr.getVariables();
346 for (auto const& varInExpr : varsInExpr) {
347 if (labels.count(varInExpr.getName()) > 0) {
348 exprContainsLabel = true;
349 break;
350 }
351 }
352 if (!exprContainsLabel) {
353 assert(bound == boost::none);
354 return std::make_shared<storm::logic::AtomicExpressionFormula>(expr);
355 }
356 }
357 if (propertyStructure.count("op") == 1) {
358 std::string opString = getString<ValueType>(propertyStructure.at("op"), "Operation description");
359
360 if (opString == "Pmin" || opString == "Pmax") {
361 std::vector<std::shared_ptr<storm::logic::Formula const>> args =
362 parseUnaryFormulaArgument(model, propertyStructure, storm::logic::FormulaContext::Probability, opString, scope);
363 assert(args.size() == 1);
365 opInfo.optimalityType = opString == "Pmin" ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize;
366 opInfo.bound = bound;
367 return std::make_shared<storm::logic::ProbabilityOperatorFormula>(args[0], opInfo);
368
369 } else if (opString == "∀" || opString == "∃") {
370 assert(bound == boost::none);
371 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Forall and Exists are currently not supported in " << scope.description);
372 } else if (opString == "Emin" || opString == "Emax") {
373 STORM_LOG_WARN_COND(model.getJaniVersion() == 1, "Model not compliant: Contains Emin/Emax property in " << scope.description << ".");
374 STORM_LOG_THROW(propertyStructure.count("exp") == 1, storm::exceptions::InvalidJaniException,
375 "Expecting reward-expression for operator " << opString << " in " << scope.description);
376 storm::expressions::Expression rewExpr = parseExpression(propertyStructure.at("exp"), scope.refine("Reward expression"));
377 STORM_LOG_THROW(rewExpr.hasNumericalType(), storm::exceptions::InvalidJaniException,
378 "Reward expression '" << rewExpr << "' does not have numerical type in " << scope.description);
379 std::string rewardName = rewExpr.toString();
380
382 opInfo.optimalityType = opString == "Emin" ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize;
383 opInfo.bound = bound;
384
385 storm::logic::RewardAccumulation rewardAccumulation(false, false, false);
386 if (propertyStructure.count("accumulate") > 0) {
387 rewardAccumulation = parseRewardAccumulation(propertyStructure.at("accumulate"), scope.description);
388 }
389
390 bool time = false;
391 if (propertyStructure.count("step-instant") > 0) {
392 STORM_LOG_THROW(propertyStructure.count("time-instant") == 0, storm::exceptions::NotSupportedException,
393 "Storm does not support to have a step-instant and a time-instant in " + scope.description);
394 STORM_LOG_THROW(propertyStructure.count("reward-instants") == 0, storm::exceptions::NotSupportedException,
395 "Storm does not support to have a step-instant and a reward-instant in " + scope.description);
396
397 storm::expressions::Expression stepInstantExpr = parseExpression(propertyStructure.at("step-instant"), scope.refine("Step instant"));
398 if (!rewExpr.isVariable()) {
399 model.addNonTrivialRewardExpression(rewardName, rewExpr);
400 }
401 if (rewardAccumulation.isEmpty()) {
402 return std::make_shared<storm::logic::RewardOperatorFormula>(
403 std::make_shared<storm::logic::InstantaneousRewardFormula>(stepInstantExpr, storm::logic::TimeBoundType::Steps), rewardName, opInfo);
404 } else {
405 return std::make_shared<storm::logic::RewardOperatorFormula>(
406 std::make_shared<storm::logic::CumulativeRewardFormula>(storm::logic::TimeBound(false, stepInstantExpr),
408 rewardAccumulation),
409 rewardName, opInfo);
410 }
411 } else if (propertyStructure.count("time-instant") > 0) {
412 STORM_LOG_THROW(propertyStructure.count("reward-instants") == 0, storm::exceptions::NotSupportedException,
413 "Storm does not support to have a time-instant and a reward-instant in " + scope.description);
414 storm::expressions::Expression timeInstantExpr = parseExpression(propertyStructure.at("time-instant"), scope.refine("time instant"));
415 if (!rewExpr.isVariable()) {
416 model.addNonTrivialRewardExpression(rewardName, rewExpr);
417 }
418 if (rewardAccumulation.isEmpty()) {
419 return std::make_shared<storm::logic::RewardOperatorFormula>(
420 std::make_shared<storm::logic::InstantaneousRewardFormula>(timeInstantExpr, storm::logic::TimeBoundType::Time), rewardName, opInfo);
421 } else {
422 return std::make_shared<storm::logic::RewardOperatorFormula>(
423 std::make_shared<storm::logic::CumulativeRewardFormula>(storm::logic::TimeBound(false, timeInstantExpr),
425 rewardAccumulation),
426 rewardName, opInfo);
427 }
428 } else if (propertyStructure.count("reward-instants") > 0) {
429 std::vector<storm::logic::TimeBound> bounds;
430 std::vector<storm::logic::TimeBoundReference> boundReferences;
431 for (auto const& rewInst : propertyStructure.at("reward-instants")) {
432 storm::expressions::Expression rewInstRewardModelExpression =
433 parseExpression(rewInst.at("exp"), scope.refine("Reward expression at reward instant"));
434 STORM_LOG_THROW(rewInstRewardModelExpression.hasNumericalType(), storm::exceptions::InvalidJaniException,
435 "Reward expression '" << rewInstRewardModelExpression << "' does not have numerical type in " << scope.description);
436 storm::logic::RewardAccumulation boundRewardAccumulation = parseRewardAccumulation(rewInst.at("accumulate"), scope.description);
437 bool steps = (boundRewardAccumulation.isStepsSet() || boundRewardAccumulation.isExitSet()) && boundRewardAccumulation.size() == 1;
438 bool time = boundRewardAccumulation.isTimeSet() && boundRewardAccumulation.size() == 1 && !model.isDiscreteTimeModel();
439 if ((steps || time) && !rewInstRewardModelExpression.containsVariables() &&
440 storm::utility::isOne(rewInstRewardModelExpression.evaluateAsRational())) {
441 boundReferences.emplace_back(steps ? storm::logic::TimeBoundType::Steps : storm::logic::TimeBoundType::Time);
442 } else {
443 std::string rewInstRewardModelName = rewInstRewardModelExpression.toString();
444 if (!rewInstRewardModelExpression.isVariable()) {
445 model.addNonTrivialRewardExpression(rewInstRewardModelName, rewInstRewardModelExpression);
446 }
447 boundReferences.emplace_back(rewInstRewardModelName, boundRewardAccumulation);
448 }
449 storm::expressions::Expression rewInstantExpr = parseExpression(rewInst.at("instant"), scope.refine("reward instant"));
450 bounds.emplace_back(false, rewInstantExpr);
451 }
452 if (!rewExpr.isVariable()) {
453 model.addNonTrivialRewardExpression(rewardName, rewExpr);
454 }
455 return std::make_shared<storm::logic::RewardOperatorFormula>(
456 std::make_shared<storm::logic::CumulativeRewardFormula>(bounds, boundReferences, rewardAccumulation), rewardName, opInfo);
457 } else {
458 time = !rewExpr.containsVariables() && storm::utility::isOne(rewExpr.evaluateAsRational());
459 std::shared_ptr<storm::logic::Formula const> subformula;
460 if (propertyStructure.count("reach") > 0) {
462 subformula = std::make_shared<storm::logic::EventuallyFormula>(
463 parseFormula(model, propertyStructure.at("reach"), formulaContext, scope.refine("Reach-expression of operator " + opString)),
464 formulaContext, rewardAccumulation);
465 } else {
466 subformula = std::make_shared<storm::logic::TotalRewardFormula>(rewardAccumulation);
467 }
468 if (time) {
469 assert(subformula->isTotalRewardFormula() || subformula->isTimePathFormula());
470 return std::make_shared<storm::logic::TimeOperatorFormula>(subformula, opInfo);
471 } else {
472 if (!rewExpr.isVariable()) {
473 model.addNonTrivialRewardExpression(rewardName, rewExpr);
474 }
475 return std::make_shared<storm::logic::RewardOperatorFormula>(subformula, rewardName, opInfo);
476 }
477 }
478 } else if (opString == "Smin" || opString == "Smax") {
480 opInfo.optimalityType = opString == "Smin" ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize;
481 opInfo.bound = bound;
482 // Reward accumulation is optional as it was not available in the early days...
483 boost::optional<storm::logic::RewardAccumulation> rewardAccumulation;
484 if (propertyStructure.count("accumulate") > 0) {
485 STORM_LOG_WARN_COND(model.getJaniVersion() == 1, "Unexpected accumulate field in " << scope.description << ".");
486 rewardAccumulation = parseRewardAccumulation(propertyStructure.at("accumulate"), scope.description);
487 }
488 STORM_LOG_THROW(propertyStructure.count("exp") > 0, storm::exceptions::InvalidJaniException,
489 "Expected an expression at steady state property at " << scope.description);
490 auto exp = parseExpression(propertyStructure["exp"], scope.refine("steady-state operator"), true);
491 if (!exp.isInitialized() || exp.hasBooleanType()) {
492 STORM_LOG_THROW(!rewardAccumulation.is_initialized(), storm::exceptions::InvalidJaniException,
493 "Long-run average probabilities are not allowed to have a reward accumulation at" << scope.description);
494 std::shared_ptr<storm::logic::Formula const> subformula =
495 parseUnaryFormulaArgument(model, propertyStructure, formulaContext, opString, scope.refine("Steady-state operator"))[0];
496 return std::make_shared<storm::logic::LongRunAverageOperatorFormula>(subformula, opInfo);
497 }
498 STORM_LOG_THROW(exp.hasNumericalType(), storm::exceptions::InvalidJaniException,
499 "Reward expression '" << exp << "' does not have numerical type in " << scope.description);
500 std::string rewardName = exp.toString();
501 if (!exp.isVariable()) {
502 model.addNonTrivialRewardExpression(rewardName, exp);
503 }
504 auto subformula = std::make_shared<storm::logic::LongRunAverageRewardFormula>(rewardAccumulation);
505 return std::make_shared<storm::logic::RewardOperatorFormula>(subformula, rewardName, opInfo);
506
507 } else if (opString == "U" || opString == "F") {
508 assert(bound == boost::none);
509 std::vector<std::shared_ptr<storm::logic::Formula const>> args;
510 if (opString == "U") {
511 args = parseBinaryFormulaArguments(model, propertyStructure, formulaContext, opString, scope);
512 } else {
513 assert(opString == "F");
514 args = parseUnaryFormulaArgument(model, propertyStructure, formulaContext, opString, scope);
515 args.push_back(args[0]);
517 }
518
519 std::vector<boost::optional<storm::logic::TimeBound>> lowerBounds, upperBounds;
520 std::vector<storm::logic::TimeBoundReference> tbReferences;
521 if (propertyStructure.count("step-bounds") > 0) {
522 STORM_LOG_WARN_COND(model.getJaniVersion() == 1, "Jani model not compliant: Contains step-bounds in " << scope.description << ".");
524 parsePropertyInterval(propertyStructure.at("step-bounds"), scope.refine("step-bounded until").clearVariables());
525 insertLowerUpperTimeBounds(lowerBounds, upperBounds, pi);
526 tbReferences.emplace_back(storm::logic::TimeBoundType::Steps);
527 }
528 if (propertyStructure.count("time-bounds") > 0) {
529 STORM_LOG_WARN_COND(model.getJaniVersion() == 1, "Jani model not compliant: Contains time-bounds in " << scope.description << ".");
531 parsePropertyInterval(propertyStructure.at("time-bounds"), scope.refine("time-bounded until").clearVariables());
532 insertLowerUpperTimeBounds(lowerBounds, upperBounds, pi);
533 tbReferences.emplace_back(storm::logic::TimeBoundType::Time);
534 }
535 if (propertyStructure.count("reward-bounds") > 0) {
536 for (auto const& rbStructure : propertyStructure.at("reward-bounds")) {
537 storm::jani::PropertyInterval pi = parsePropertyInterval(rbStructure.at("bounds"), scope.refine("reward-bounded until").clearVariables());
538 insertLowerUpperTimeBounds(lowerBounds, upperBounds, pi);
539 STORM_LOG_THROW(rbStructure.count("exp") == 1, storm::exceptions::InvalidJaniException,
540 "Expecting reward-expression for operator " << opString << " in " << scope.description);
541 storm::expressions::Expression rewInstRewardModelExpression =
542 parseExpression(rbStructure.at("exp"), scope.refine("Reward expression at reward-bounds"));
543 STORM_LOG_THROW(rewInstRewardModelExpression.hasNumericalType(), storm::exceptions::InvalidJaniException,
544 "Reward expression '" << rewInstRewardModelExpression << "' does not have numerical type in " << scope.description);
545 storm::logic::RewardAccumulation boundRewardAccumulation = parseRewardAccumulation(rbStructure.at("accumulate"), scope.description);
546 bool steps = (boundRewardAccumulation.isStepsSet() || boundRewardAccumulation.isExitSet()) && boundRewardAccumulation.size() == 1;
547 bool time = boundRewardAccumulation.isTimeSet() && boundRewardAccumulation.size() == 1 && !model.isDiscreteTimeModel();
548 if ((steps || time) && !rewInstRewardModelExpression.containsVariables() &&
549 storm::utility::isOne(rewInstRewardModelExpression.evaluateAsRational())) {
550 tbReferences.emplace_back(steps ? storm::logic::TimeBoundType::Steps : storm::logic::TimeBoundType::Time);
551 } else {
552 std::string rewInstRewardModelName = rewInstRewardModelExpression.toString();
553 if (!rewInstRewardModelExpression.isVariable()) {
554 model.addNonTrivialRewardExpression(rewInstRewardModelName, rewInstRewardModelExpression);
555 }
556 tbReferences.emplace_back(rewInstRewardModelName, boundRewardAccumulation);
557 }
558 }
559 }
560 if (!tbReferences.empty()) {
561 return std::make_shared<storm::logic::BoundedUntilFormula const>(args[0], args[1], lowerBounds, upperBounds, tbReferences);
562 } else if (args[0]->isTrueFormula()) {
563 return std::make_shared<storm::logic::EventuallyFormula const>(args[1], formulaContext);
564 } else {
565 return std::make_shared<storm::logic::UntilFormula const>(args[0], args[1]);
566 }
567 } else if (opString == "G") {
568 assert(bound == boost::none);
569 std::vector<std::shared_ptr<storm::logic::Formula const>> args =
570 parseUnaryFormulaArgument(model, propertyStructure, formulaContext, opString, scope.refine("Subformula of globally operator "));
571 if (propertyStructure.count("step-bounds") > 0) {
572 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Globally and step-bounds are not supported.");
573 } else if (propertyStructure.count("time-bounds") > 0) {
574 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Globally and time bounds are not supported.");
575 } else if (propertyStructure.count("reward-bounds") > 0) {
576 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "Globally and reward bounded properties are not supported.");
577 }
578 return std::make_shared<storm::logic::GloballyFormula const>(args[0]);
579
580 } else if (opString == "W") {
581 assert(bound == boost::none);
582 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Weak until is not supported");
583 } else if (opString == "R") {
584 assert(bound == boost::none);
585 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "Release is not supported");
586 } else if (opString == "∧" || opString == "∨") {
587 assert(bound == boost::none);
588 std::vector<std::shared_ptr<storm::logic::Formula const>> args =
589 parseBinaryFormulaArguments(model, propertyStructure, formulaContext, opString, scope);
590 assert(args.size() == 2);
592 opString == "∧" ? storm::logic::BinaryBooleanStateFormula::OperatorType::And : storm::logic::BinaryBooleanStateFormula::OperatorType::Or;
593 return std::make_shared<storm::logic::BinaryBooleanStateFormula const>(oper, args[0], args[1]);
594 } else if (opString == "⇒") {
595 assert(bound == boost::none);
596 std::vector<std::shared_ptr<storm::logic::Formula const>> args =
597 parseBinaryFormulaArguments(model, propertyStructure, formulaContext, opString, scope);
598 assert(args.size() == 2);
599 std::shared_ptr<storm::logic::UnaryBooleanStateFormula const> tmp =
600 std::make_shared<storm::logic::UnaryBooleanStateFormula const>(storm::logic::UnaryBooleanStateFormula::OperatorType::Not, args[0]);
601 return std::make_shared<storm::logic::BinaryBooleanStateFormula const>(storm::logic::BinaryBooleanStateFormula::OperatorType::Or, tmp, args[1]);
602 } else if (opString == "¬") {
603 assert(bound == boost::none);
604 std::vector<std::shared_ptr<storm::logic::Formula const>> args =
605 parseUnaryFormulaArgument(model, propertyStructure, formulaContext, opString, scope);
606 assert(args.size() == 1);
607 return std::make_shared<storm::logic::UnaryBooleanStateFormula const>(storm::logic::UnaryBooleanStateFormula::OperatorType::Not, args[0]);
608 } else if (!expr.isInitialized() && (opString == "≥" || opString == "≤" || opString == "<" || opString == ">" || opString == "=" || opString == "≠")) {
609 assert(bound == boost::none);
611 if (opString == "≥") {
613 } else if (opString == "≤") {
615 } else if (opString == "<") {
617 } else if (opString == ">") {
619 }
620
621 std::vector<std::string> const leftRight = {"left", "right"};
622 for (uint64_t i = 0; i < 2; ++i) {
623 if (propertyStructure.at(leftRight[i]).count("op") > 0) {
624 std::string propertyOperatorString = getString<ValueType>(propertyStructure.at(leftRight[i]).at("op"), "property-operator");
625 std::set<std::string> const propertyOperatorStrings = {"Pmin", "Pmax", "Emin", "Emax", "Smin", "Smax"};
626 if (propertyOperatorStrings.count(propertyOperatorString) > 0) {
627 auto boundExpr =
628 parseExpression(propertyStructure.at(leftRight[1 - i]),
629 scope.refine("Threshold for operator " + propertyStructure.at(leftRight[i]).at("op").template get<std::string>()));
630 if ((opString == "=" || opString == "≠")) {
631 STORM_LOG_THROW(!boundExpr.containsVariables(), storm::exceptions::NotSupportedException,
632 "Comparison operators '=' or '≠' in property specifications are currently not supported.");
633 auto boundValue = boundExpr.evaluateAsRational();
634 if (storm::utility::isZero(boundValue)) {
635 if (opString == "=") {
637 } else {
639 }
640 } else if (storm::utility::isOne(boundValue) && (propertyOperatorString == "Pmin" || propertyOperatorString == "Pmax")) {
641 if (opString == "=") {
643 } else {
645 }
646 } else {
648 false, storm::exceptions::NotSupportedException,
649 "Comparison operators '=' or '≠' in property specifications are currently not supported in " << scope.description << ".");
650 }
651 }
652 return parseFormula(model, propertyStructure.at(leftRight[i]), formulaContext, scope, storm::logic::Bound(ct, boundExpr));
653 }
654 }
655 }
656 STORM_LOG_THROW(false, storm::exceptions::NotSupportedException, "No complex comparisons for properties are supported.");
657 } else if (opString == "Multi") {
660 << " not enabled but model contains multi-objective property in " << scope.description
661 << ". Continuing with that property anyways");
662 assert(bound == boost::none);
663 STORM_LOG_THROW(propertyStructure.count("properties") == 1, storm::exceptions::InvalidJaniException,
664 "Expecting properties for multi-objective operator in " << scope.description);
665 std::vector<std::shared_ptr<storm::logic::Formula const>> subformulas;
666 uint64_t i = 0;
667 for (auto const& subPropStructure : propertyStructure.at("properties")) {
668 STORM_LOG_THROW(subPropStructure.count("exp") == 1, storm::exceptions::InvalidJaniException,
669 "Expecting property expression in subproperty #" << i << " in " << scope.description);
670 subformulas.push_back(parseFormula(model, subPropStructure["exp"], formulaContext,
671 scope.refine("Subproperty #" + std::to_string(i) + " of multi-objective operator")));
672 if (subPropStructure.count("opt") == 1) {
673 STORM_LOG_THROW(subformulas.back()->hasQuantitativeResult(), storm::exceptions::InvalidJaniException,
674 "Subformula #" << i << " has an optimization direction but is not numeric in " << scope.description);
675 STORM_LOG_THROW(subformulas.back()->isOperatorFormula(), storm::exceptions::NotSupportedException,
676 "Subformula #" << i << " is not an operator formula in " << scope.description);
677 std::string const optString =
678 getString<ValueType>(subPropStructure.at("opt"), "optimization direction for subproperty #" + std::to_string(i));
679 STORM_LOG_THROW(optString == "min" || optString == "max", storm::exceptions::InvalidJaniException,
680 "Unknown optimization direction " << optString << " for subproperty #" << i << " in " << scope.description);
681 auto const opt = optString == "min" ? storm::solver::OptimizationDirection::Minimize : storm::solver::OptimizationDirection::Maximize;
682 auto newFormula = subformulas.back()->clone();
683 newFormula->asOperatorFormula().setOptimalityType(opt);
684 subformulas.back() = newFormula;
685 } else {
686 STORM_LOG_THROW(subformulas.back()->hasQualitativeResult(), storm::exceptions::InvalidJaniException,
687 "Subformula #" << i << " has non-Boolean result but no optimization direction in " << scope.description);
688 }
689 ++i;
690 }
691 STORM_LOG_THROW(propertyStructure.count("type") == 1, storm::exceptions::InvalidJaniException,
692 "Expecting type for multi-objective operator in " << scope.description);
693 std::string const typeString = getString<ValueType>(propertyStructure.at("type"), "type of multi-objective operator");
694 STORM_LOG_THROW(typeString == "tradeoff" || typeString == "lexicographic", storm::exceptions::InvalidJaniException,
695 "Unknown type " << typeString << " for multi-objective operator in " << scope.description);
696 auto const type =
698 return std::make_shared<storm::logic::MultiObjectiveFormula const>(subformulas, type);
699 } else if (expr.isInitialized()) {
700 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
701 "Non-trivial Expression '" << expr << "' contains a boolean transient variable. Can not translate to PRCTL-like formula at "
702 << scope.description << ".");
703 } else {
704 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Unknown operator " << opString);
705 }
706 } else {
707 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
708 "Looking for operator for formula " << propertyStructure.dump() << ", but did not find one");
709 }
710}
711
712template<typename ValueType>
714 STORM_LOG_THROW(propertyStructure.count("name") == 1, storm::exceptions::InvalidJaniException, "Property must have a name");
715 // TODO check unique name
716 std::string name = getString<ValueType>(propertyStructure.at("name"), "property-name");
717 STORM_LOG_TRACE("Parsing property named: " << name);
718 std::string comment = "";
719 if (propertyStructure.count("comment") > 0) {
720 comment = getString<ValueType>(propertyStructure.at("comment"), "comment for property named '" + name + "'.");
721 }
722 STORM_LOG_THROW(propertyStructure.count("expression") == 1, storm::exceptions::InvalidJaniException, "Property must have an expression");
723 // Parse filter expression.
724 Json const& expressionStructure = propertyStructure.at("expression");
725
726 STORM_LOG_THROW(expressionStructure.count("op") == 1, storm::exceptions::InvalidJaniException, "Expression in property must have an operation description");
727 STORM_LOG_THROW(expressionStructure.at("op") == "filter", storm::exceptions::InvalidJaniException, "Top level operation of a property must be a filter");
728 STORM_LOG_THROW(expressionStructure.count("fun") == 1, storm::exceptions::InvalidJaniException, "Filter must have a function descritpion");
729 std::string funDescr = getString<ValueType>(expressionStructure.at("fun"), "Filter function in property named " + name);
731 if (funDescr == "min") {
733 } else if (funDescr == "max") {
735 } else if (funDescr == "sum") {
737 } else if (funDescr == "avg") {
739 } else if (funDescr == "count") {
741 } else if (funDescr == "∀") {
743 } else if (funDescr == "∃") {
745 } else if (funDescr == "argmin") {
747 } else if (funDescr == "argmax") {
749 } else if (funDescr == "values") {
751 } else {
752 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Unknown filter description " << funDescr << " in property named " << name);
753 }
754
755 STORM_LOG_THROW(expressionStructure.count("states") == 1, storm::exceptions::InvalidJaniException, "Filter must have a states description");
756 std::shared_ptr<storm::logic::Formula const> statesFormula;
757 if (expressionStructure.at("states").count("op") > 0) {
758 std::string statesDescr = getString<ValueType>(expressionStructure.at("states").at("op"), "Filtered states in property named " + name);
759 if (statesDescr == "initial") {
760 statesFormula = std::make_shared<storm::logic::AtomicLabelFormula>("init");
761 }
762 }
763 if (!statesFormula) {
764 try {
765 // Try to parse the states as formula.
766 statesFormula =
767 parseFormula(model, expressionStructure.at("states"), storm::logic::FormulaContext::Undefined, scope.refine("Values of property " + name));
768 } catch (storm::exceptions::NotSupportedException const& ex) {
769 throw ex;
770 } catch (storm::exceptions::NotImplementedException const& ex) {
771 throw ex;
772 }
773 }
774 STORM_LOG_THROW(statesFormula, storm::exceptions::NotImplementedException, "Could not derive states formula.");
775 STORM_LOG_THROW(expressionStructure.count("values") == 1, storm::exceptions::InvalidJaniException, "Values as input for a filter must be given");
776 auto formula = parseFormula(model, expressionStructure.at("values"), storm::logic::FormulaContext::Undefined, scope.refine("Values of property " + name));
777 return storm::jani::Property(name, storm::jani::FilterExpression(formula, ft, statesFormula), {}, comment);
778}
779
780template<typename ValueType>
781std::shared_ptr<storm::jani::Constant> JaniParser<ValueType>::parseConstant(Json const& constantStructure, Scope const& scope) {
782 STORM_LOG_THROW(constantStructure.count("name") == 1, storm::exceptions::InvalidJaniException,
783 "Variable (scope: " + scope.description + ") must have a name");
784 std::string name = getString<ValueType>(constantStructure.at("name"), "variable-name in " + scope.description + "-scope");
785 // TODO check existance of name.
786 // TODO store prefix in variable.
787 std::string exprManagerName = name;
788
789 STORM_LOG_THROW(constantStructure.count("type") == 1, storm::exceptions::InvalidJaniException,
790 "Constant '" + name + "' (scope: " + scope.description + ") must have a (single) type-declaration.");
791 auto type = parseType(constantStructure.at("type"), name, scope);
792 STORM_LOG_THROW((type.first->isBasicType() || type.first->isBoundedType()), storm::exceptions::InvalidJaniException,
793 "Constant '" + name + "' (scope: " + scope.description + ") has unexpected type");
794
795 uint_fast64_t valueCount = constantStructure.count("value");
796 storm::expressions::Expression definingExpression;
797 STORM_LOG_THROW(valueCount < 2, storm::exceptions::InvalidJaniException,
798 "Value for constant '" + name + "' (scope: " + scope.description + ") must be given at most once.");
799 if (valueCount == 1) {
800 // Read initial value before; that makes creation later on a bit easier, and has as an additional benefit that we do not need to check whether the
801 // variable occurs also on the assignment.
802 definingExpression = parseExpression(constantStructure.at("value"), scope.refine("Value of constant " + name));
803 assert(definingExpression.isInitialized());
804 // Check that the defined and actual expression value match OR the defined value is a rational and the actual value is a numerical type.
805 STORM_LOG_THROW((type.second == definingExpression.getType() || (type.second.isRationalType() && definingExpression.getType().isNumericalType())),
806 storm::exceptions::InvalidJaniException,
807 "Type of value for constant '" + name + "' (scope: " + scope.description + ") does not match the given type '" +
808 type.first->getStringRepresentation() + ".");
809 }
810
811 storm::expressions::Variable var = expressionManager->declareVariable(exprManagerName, type.second);
812
813 storm::expressions::Expression constraintExpression;
814 if (type.first->isBoundedType()) {
815 auto const& bndType = type.first->asBoundedType();
816 if (bndType.hasLowerBound()) {
817 constraintExpression = var.getExpression() >= bndType.getLowerBound();
818 if (bndType.hasUpperBound()) {
819 constraintExpression = constraintExpression && var.getExpression() <= bndType.getUpperBound();
820 }
821 } else if (bndType.hasUpperBound()) {
822 constraintExpression = var.getExpression() <= bndType.getUpperBound();
823 }
824 }
825 return std::make_shared<storm::jani::Constant>(name, std::move(var), definingExpression, constraintExpression);
826}
827
828template<typename ValueType>
829std::pair<std::unique_ptr<storm::jani::JaniType>, storm::expressions::Type> JaniParser<ValueType>::parseType(Json const& typeStructure,
830 std::string variableName, Scope const& scope) {
831 std::pair<std::unique_ptr<storm::jani::JaniType>, storm::expressions::Type> result;
832 if (typeStructure.is_string()) {
833 if (typeStructure == "real") {
834 result.first = std::make_unique<storm::jani::BasicType>(storm::jani::BasicType::Type::Real);
835 result.second = expressionManager->getRationalType();
836 } else if (typeStructure == "bool") {
837 result.first = std::make_unique<storm::jani::BasicType>(storm::jani::BasicType::Type::Bool);
838 result.second = expressionManager->getBooleanType();
839 } else if (typeStructure == "int") {
840 result.first = std::make_unique<storm::jani::BasicType>(storm::jani::BasicType::Type::Int);
841 result.second = expressionManager->getIntegerType();
842 } else if (typeStructure == "clock") {
843 result.first = std::make_unique<storm::jani::ClockType>();
844 result.second = expressionManager->getRationalType();
845 } else if (typeStructure == "continuous") {
846 result.first = std::make_unique<storm::jani::ContinuousType>();
847 result.second = expressionManager->getRationalType();
848 } else {
849 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
850 "Unsupported type " << typeStructure.dump() << " for variable '" << variableName << "' (scope: " << scope.description << ").");
851 }
852 } else if (typeStructure.is_object()) {
853 STORM_LOG_THROW(typeStructure.count("kind") == 1, storm::exceptions::InvalidJaniException,
854 "For complex type as in variable " << variableName << "(scope: " << scope.description << ") kind must be given");
855 std::string kind =
856 getString<ValueType>(typeStructure.at("kind"), "kind for complex type as in variable " + variableName + "(scope: " + scope.description + ") ");
857 if (kind == "bounded") {
859 typeStructure.count("lower-bound") + typeStructure.count("upper-bound") > 0, storm::exceptions::InvalidJaniException,
860 "For bounded type as in variable " << variableName << "(scope: " << scope.description << ") lower-bound or upper-bound must be given");
861 storm::expressions::Expression lowerboundExpr;
862 if (typeStructure.count("lower-bound") > 0) {
863 lowerboundExpr = parseExpression(typeStructure.at("lower-bound"), scope.refine("Lower bound for variable " + variableName));
864 }
865 storm::expressions::Expression upperboundExpr;
866 if (typeStructure.count("upper-bound") > 0) {
867 upperboundExpr = parseExpression(typeStructure.at("upper-bound"), scope.refine("Upper bound for variable " + variableName));
868 }
869 STORM_LOG_THROW(typeStructure.count("base") == 1, storm::exceptions::InvalidJaniException,
870 "For bounded type as in variable " << variableName << "(scope: " << scope.description << ") base must be given");
871 std::string basictype =
872 getString<ValueType>(typeStructure.at("base"), "base for bounded type as in variable " + variableName + "(scope: " + scope.description + ") ");
873 if (basictype == "int") {
874 STORM_LOG_THROW(!lowerboundExpr.isInitialized() || lowerboundExpr.hasIntegerType(), storm::exceptions::InvalidJaniException,
875 "Lower bound for bounded integer variable " << variableName << "(scope: " << scope.description << ") must be integer-typed");
876 STORM_LOG_THROW(!upperboundExpr.isInitialized() || upperboundExpr.hasIntegerType(), storm::exceptions::InvalidJaniException,
877 "Upper bound for bounded integer variable " << variableName << "(scope: " << scope.description << ") must be integer-typed");
878 if (lowerboundExpr.isInitialized() && upperboundExpr.isInitialized() && !lowerboundExpr.containsVariables() &&
879 !upperboundExpr.containsVariables()) {
880 STORM_LOG_THROW(lowerboundExpr.evaluateAsInt() <= upperboundExpr.evaluateAsInt(), storm::exceptions::InvalidJaniException,
881 "Lower bound must not be larger than upper bound for bounded integer variable " << variableName
882 << "(scope: " << scope.description << ").");
883 }
884 result.first = std::make_unique<storm::jani::BoundedType>(storm::jani::BoundedType::BaseType::Int, lowerboundExpr, upperboundExpr);
885 result.second = expressionManager->getIntegerType();
886 } else if (basictype == "real") {
887 STORM_LOG_THROW(!lowerboundExpr.isInitialized() || lowerboundExpr.hasNumericalType(), storm::exceptions::InvalidJaniException,
888 "Lower bound for bounded real variable " << variableName << "(scope: " << scope.description << ") must be numeric");
889 STORM_LOG_THROW(!upperboundExpr.isInitialized() || upperboundExpr.hasNumericalType(), storm::exceptions::InvalidJaniException,
890 "Upper bound for bounded real variable " << variableName << "(scope: " << scope.description << ") must be numeric");
891 if (lowerboundExpr.isInitialized() && upperboundExpr.isInitialized() && !lowerboundExpr.containsVariables() &&
892 !upperboundExpr.containsVariables()) {
893 using SubMap = std::map<storm::expressions::Variable, storm::expressions::Expression>;
894 storm::expressions::JaniExpressionSubstitutionVisitor<SubMap> transcendentalVisitor(SubMap(), true);
895 const storm::RationalNumber lowerboundValue = transcendentalVisitor.substitute(lowerboundExpr).evaluateAsRational();
896 const storm::RationalNumber upperboundValue = transcendentalVisitor.substitute(upperboundExpr).evaluateAsRational();
897 STORM_LOG_THROW(lowerboundValue <= upperboundValue, storm::exceptions::InvalidJaniException,
898 "Lower bound must not be larger than upper bound for bounded real variable " << variableName
899 << "(scope: " << scope.description << ").");
900 }
901 result.first = std::make_unique<storm::jani::BoundedType>(storm::jani::BoundedType::BaseType::Real, lowerboundExpr, upperboundExpr);
902 result.second = expressionManager->getRationalType();
903 } else {
904 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
905 "Unsupported base " << basictype << " for bounded variable " << variableName << "(scope: " << scope.description << ").");
906 }
907 } else if (kind == "array") {
908 STORM_LOG_THROW(typeStructure.count("base") == 1, storm::exceptions::InvalidJaniException,
909 "For array type as in variable " << variableName << "(scope: " << scope.description << ") base must be given");
910 auto base = parseType(typeStructure.at("base"), variableName, scope);
911 result.first = std::make_unique<storm::jani::ArrayType>(std::move(base.first));
912 result.second = expressionManager->getArrayType(base.second);
913 } else {
914 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
915 "Unsupported kind " << kind << " for complex type of variable " << variableName << "(scope: " << scope.description << ").");
916 }
917 }
918 return result;
919}
920
921template<typename ValueType>
922storm::jani::FunctionDefinition JaniParser<ValueType>::parseFunctionDefinition(Json const& functionDefinitionStructure, Scope const& scope, bool firstPass,
923 std::string const& parameterNamePrefix) {
924 STORM_LOG_THROW(functionDefinitionStructure.count("name") == 1, storm::exceptions::InvalidJaniException,
925 "Function definition (scope: " + scope.description + ") must have a name");
926 std::string functionName = getString<ValueType>(functionDefinitionStructure.at("name"), "function-name in " + scope.description);
927 STORM_LOG_THROW(functionDefinitionStructure.count("type") == 1, storm::exceptions::InvalidJaniException,
928 "Function definition '" + functionName + "' (scope: " + scope.description + ") must have a (single) type-declaration.");
929 auto type = parseType(functionDefinitionStructure.at("type"), functionName, scope);
931 !(type.first->isClockType() || type.first->isContinuousType()), storm::exceptions::InvalidJaniException,
932 "Function definition '" + functionName + "' (scope: " + scope.description + ") uses illegal type '" + type.first->getStringRepresentation() + "'.");
933
934 std::unordered_map<std::string, storm::expressions::Variable> parameterNameToVariableMap;
935 std::vector<storm::expressions::Variable> parameters;
936 if (!firstPass && functionDefinitionStructure.count("parameters") > 0) {
937 STORM_LOG_THROW(functionDefinitionStructure.count("parameters") == 1, storm::exceptions::InvalidJaniException,
938 "Function definition '" + functionName + "' (scope: " + scope.description + ") must have exactly one list of parameters.");
939 for (auto const& parameterStructure : functionDefinitionStructure.at("parameters")) {
940 STORM_LOG_THROW(parameterStructure.count("name") == 1, storm::exceptions::InvalidJaniException,
941 "Parameter declaration of parameter " + std::to_string(parameters.size()) + " of Function definition '" + functionName +
942 "' (scope: " + scope.description + ") must have a name");
943 std::string parameterName =
944 getString<ValueType>(parameterStructure.at("name"), "parameter-name of parameter " + std::to_string(parameters.size()) +
945 " of Function definition '" + functionName + "' (scope: " + scope.description + ").");
946 STORM_LOG_THROW(parameterStructure.count("type") == 1, storm::exceptions::InvalidJaniException,
947 "Parameter declaration of parameter " + std::to_string(parameters.size()) + " of Function definition '" + functionName +
948 "' (scope: " + scope.description + ") must have exactly one type.");
949 auto parameterType =
950 parseType(parameterStructure.at("type"), parameterName,
951 scope.refine("parameter declaration of parameter " + std::to_string(parameters.size()) + " of Function definition " + functionName));
952 STORM_LOG_THROW(!(parameterType.first->isClockType() || parameterType.first->isContinuousType()), storm::exceptions::InvalidJaniException,
953 "Type of parameter " + std::to_string(parameters.size()) + " of function definition '" + functionName +
954 "' (scope: " + scope.description + ") uses illegal type '" + parameterType.first->getStringRepresentation() + "'.");
955 STORM_LOG_WARN_COND(!parameterType.first->isBoundedType(),
956 "Bounds on parameter" + parameterName + " of function definition " + functionName + " will be ignored.");
957
958 std::string exprParameterName = parameterNamePrefix + functionName + VARIABLE_AUTOMATON_DELIMITER + parameterName;
959 parameters.push_back(expressionManager->declareVariable(exprParameterName, parameterType.second));
960 parameterNameToVariableMap.emplace(parameterName, parameters.back());
961 }
962 }
963
964 STORM_LOG_THROW(functionDefinitionStructure.count("body") == 1, storm::exceptions::InvalidJaniException,
965 "Function definition '" + functionName + "' (scope: " + scope.description + ") must have a (single) body.");
967 if (!firstPass) {
968 functionBody = parseExpression(functionDefinitionStructure.at("body"), scope.refine("body of function definition " + functionName), false,
969 parameterNameToVariableMap);
970 STORM_LOG_WARN_COND(functionBody.getType() == type.second || (functionBody.getType().isIntegerType() && type.second.isRationalType()),
971 "Type of body of function " + functionName + "' (scope: " + scope.description + ") has type "
972 << functionBody.getType() << " although the function type is given as " << type.second);
973 }
974 return storm::jani::FunctionDefinition(functionName, type.second, parameters, functionBody);
975}
976
977template<typename ValueType>
978std::shared_ptr<storm::jani::Variable> JaniParser<ValueType>::parseVariable(Json const& variableStructure, Scope const& scope, std::string const& namePrefix) {
979 STORM_LOG_THROW(variableStructure.count("name") == 1, storm::exceptions::InvalidJaniException,
980 "Variable (scope: " + scope.description + ") must have a name");
981 std::string name = getString<ValueType>(variableStructure.at("name"), "variable-name in " + scope.description + "-scope");
982 // TODO check existance of name.
983 // TODO store prefix in variable.
984 std::string exprManagerName = namePrefix + name;
985 bool transientVar = defaultVariableTransient; // Default value for variables.
986 uint_fast64_t tvarcount = variableStructure.count("transient");
987 STORM_LOG_THROW(tvarcount <= 1, storm::exceptions::InvalidJaniException,
988 "Multiple definitions of transient not allowed in variable '" + name + "' (scope: " + scope.description + ").");
989 if (tvarcount == 1) {
990 transientVar =
991 getBoolean<ValueType>(variableStructure.at("transient"), "transient-attribute in variable '" + name + "' (scope: " + scope.description + ").");
992 }
993 STORM_LOG_THROW(variableStructure.count("type") == 1, storm::exceptions::InvalidJaniException,
994 "Variable '" + name + "' (scope: " + scope.description + ") must have a (single) type-declaration.");
995 auto type = parseType(variableStructure.at("type"), name, scope);
996
997 uint_fast64_t initvalcount = variableStructure.count("initial-value");
998 if (transientVar) {
999 STORM_LOG_THROW(initvalcount == 1, storm::exceptions::InvalidJaniException,
1000 "Initial value must be given once for transient variable '" + name + "' (scope: " + scope.description + ") " + name +
1001 "' (scope: " + scope.description + ").");
1002 } else {
1003 STORM_LOG_THROW(initvalcount <= 1, storm::exceptions::InvalidJaniException,
1004 "Initial value can be given at most one for variable " + name + "' (scope: " + scope.description + ").");
1005 }
1006 boost::optional<storm::expressions::Expression> initVal;
1007 if (initvalcount == 1 && !variableStructure.at("initial-value").is_null()) {
1008 initVal = parseExpression(variableStructure.at("initial-value"), scope.refine("Initial value for variable " + name));
1009 // STORM_LOG_THROW((type.second == initVal->getType() || type.second.isRationalType() && initVal->getType().isIntegerType()),
1010 // storm::exceptions::InvalidJaniException,"Type of initial value for variable " + name + "' (scope: " + scope.description + ") does not match the
1011 // variable type '" + type.first->getStringRepresentation() + "'.");
1012 } else {
1013 assert(!transientVar);
1014 }
1015
1016 if (transientVar && type.first->isBasicType() && type.first->asBasicType().isBooleanType()) {
1017 labels.insert(name);
1018 }
1019
1020 auto expressionVariable = expressionManager->declareVariable(exprManagerName, type.second);
1021 return storm::jani::Variable::makeVariable(name, *type.first, expressionVariable, initVal, transientVar);
1022}
1023
1027void ensureNumberOfArguments(uint64_t expected, uint64_t actual, std::string const& opstring, std::string const& errorInfo) {
1028 STORM_LOG_THROW(expected == actual, storm::exceptions::InvalidJaniException,
1029 "Operator " << opstring << " expects " << expected << " arguments, but got " << actual << " in " << errorInfo << ".");
1030}
1031
1032template<typename ValueType>
1033std::vector<storm::expressions::Expression> JaniParser<ValueType>::parseUnaryExpressionArguments(
1034 Json const& expressionDecl, std::string const& opstring, Scope const& scope, bool returnNoneInitializedOnUnknownOperator,
1035 std::unordered_map<std::string, storm::expressions::Variable> const& auxiliaryVariables) {
1037 parseExpression(expressionDecl.at("exp"), scope.refine("Argument of operator " + opstring), returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1038 return {left};
1039}
1040
1041template<typename ValueType>
1042std::vector<storm::expressions::Expression> JaniParser<ValueType>::parseBinaryExpressionArguments(
1043 Json const& expressionDecl, std::string const& opstring, Scope const& scope, bool returnNoneInitializedOnUnknownOperator,
1044 std::unordered_map<std::string, storm::expressions::Variable> const& auxiliaryVariables) {
1045 storm::expressions::Expression left = parseExpression(expressionDecl.at("left"), scope.refine("Left argument of operator " + opstring),
1046 returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1047 storm::expressions::Expression right = parseExpression(expressionDecl.at("right"), scope.refine("Right argument of operator " + opstring),
1048 returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1049 return {left, right};
1050}
1054void ensureBooleanType(storm::expressions::Expression const& expr, std::string const& opstring, unsigned argNr, std::string const& errorInfo) {
1055 STORM_LOG_THROW(expr.hasBooleanType(), storm::exceptions::InvalidJaniException,
1056 "Operator " << opstring << " expects argument[" << argNr << "]: '" << expr << "' to be Boolean in " << errorInfo << ".");
1057}
1058
1062void ensureNumericalType(storm::expressions::Expression const& expr, std::string const& opstring, unsigned argNr, std::string const& errorInfo) {
1063 STORM_LOG_THROW(expr.hasNumericalType(), storm::exceptions::InvalidJaniException,
1064 "Operator " << opstring << " expects argument " + std::to_string(argNr) + " to be numerical in " << errorInfo << ".");
1065}
1066
1070void ensureIntegerType(storm::expressions::Expression const& expr, std::string const& opstring, unsigned argNr, std::string const& errorInfo) {
1071 STORM_LOG_THROW(expr.hasIntegerType(), storm::exceptions::InvalidJaniException,
1072 "Operator " << opstring << " expects argument " + std::to_string(argNr) + " to be numerical in " << errorInfo << ".");
1073}
1074
1078void ensureArrayType(storm::expressions::Expression const& expr, std::string const& opstring, unsigned argNr, std::string const& errorInfo) {
1079 STORM_LOG_THROW(expr.getType().isArrayType(), storm::exceptions::InvalidJaniException,
1080 "Operator " << opstring << " expects argument " + std::to_string(argNr) + " to be of type 'array' in " << errorInfo << ".");
1081}
1082
1083template<typename ValueType>
1085 if (lValueStructure.is_string()) {
1086 std::string ident = getString<ValueType>(lValueStructure, scope.description);
1087 storm::jani::Variable const* var = nullptr;
1088 if (scope.localVars != nullptr) {
1089 auto localVar = scope.localVars->find(ident);
1090 if (localVar != scope.localVars->end()) {
1091 var = localVar->second;
1092 }
1093 }
1094 if (var == nullptr) {
1095 STORM_LOG_THROW(scope.globalVars != nullptr, storm::exceptions::InvalidJaniException,
1096 "Unknown identifier '" << ident << "' occurs in " << scope.description);
1097 auto globalVar = scope.globalVars->find(ident);
1098 STORM_LOG_THROW(globalVar != scope.globalVars->end(), storm::exceptions::InvalidJaniException,
1099 "Unknown identifier '" << ident << "' occurs in " << scope.description);
1100 var = globalVar->second;
1101 }
1102
1103 return storm::jani::LValue(*var);
1104 } else if (lValueStructure.count("op") == 1) {
1105 // structure will be something like "op": "aa", "exp": {}, "index": {}
1106 // in exp we have something that is either a variable, or some other array access.
1107 // e.g. a[1][4] will look like: "op": "aa", "exp": {"op": "aa", "exp": "a", "index": {1}}, "index": {4}
1108 std::string opstring = getString<ValueType>(lValueStructure.at("op"), scope.description);
1109 STORM_LOG_THROW(opstring == "aa", storm::exceptions::InvalidJaniException, "Unknown operation '" << opstring << "' occurs in " << scope.description);
1110 STORM_LOG_THROW(lValueStructure.count("exp") == 1, storm::exceptions::InvalidJaniException, "Missing 'exp' in array access at " << scope.description);
1111 auto expLValue = parseLValue(lValueStructure.at("exp"), scope.refine("Expression of array access"));
1112 STORM_LOG_THROW(expLValue.isArray(), storm::exceptions::InvalidJaniException, "Array access considers non-array expression at " << scope.description);
1113 STORM_LOG_THROW(lValueStructure.count("index"), storm::exceptions::InvalidJaniException, "Missing 'index' in array access at " << scope.description);
1114 auto indexExpression = parseExpression(lValueStructure.at("index"), scope.refine("Index of array access"));
1115 expLValue.addArrayAccessIndex(indexExpression);
1116 return expLValue;
1117 } else {
1118 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Unknown LValue '" << lValueStructure.dump() << "' occurs in " << scope.description);
1119 // Silly warning suppression.
1120 return storm::jani::LValue(*scope.globalVars->end()->second);
1121 }
1122}
1123
1124template<typename ValueType>
1126 std::string const& ident, Scope const& scope, std::unordered_map<std::string, storm::expressions::Variable> const& auxiliaryVariables) {
1127 {
1128 auto it = auxiliaryVariables.find(ident);
1129 if (it != auxiliaryVariables.end()) {
1130 return it->second;
1131 }
1132 }
1133 if (scope.localVars != nullptr) {
1134 auto it = scope.localVars->find(ident);
1135 if (it != scope.localVars->end()) {
1136 return it->second->getExpressionVariable();
1137 }
1138 }
1139 if (scope.globalVars != nullptr) {
1140 auto it = scope.globalVars->find(ident);
1141 if (it != scope.globalVars->end()) {
1142 return it->second->getExpressionVariable();
1143 }
1144 }
1145 if (scope.constants != nullptr) {
1146 auto it = scope.constants->find(ident);
1147 if (it != scope.constants->end()) {
1148 return it->second->getExpressionVariable();
1149 }
1150 }
1151 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Unknown identifier '" << ident << "' occurs in " << scope.description);
1152 // Silly warning suppression.
1154}
1155
1156template<typename ValueType>
1158 bool returnNoneInitializedOnUnknownOperator,
1159 std::unordered_map<std::string, storm::expressions::Variable> const& auxiliaryVariables) {
1160 if (expressionStructure.is_boolean()) {
1161 if (expressionStructure.template get<bool>()) {
1162 return expressionManager->boolean(true);
1163 } else {
1164 return expressionManager->boolean(false);
1165 }
1166 } else if (expressionStructure.is_number_integer()) {
1167 return expressionManager->integer(expressionStructure.template get<int64_t>());
1168 } else if (expressionStructure.is_number_float()) {
1169 return expressionManager->rational(storm::utility::convertNumber<storm::RationalNumber>(expressionStructure.template get<ValueType>()));
1170 } else if (expressionStructure.is_string()) {
1171 std::string ident = expressionStructure.template get<std::string>();
1172 return storm::expressions::Expression(getVariableOrConstantExpression(ident, scope, auxiliaryVariables));
1173 } else if (expressionStructure.is_object()) {
1174 if (expressionStructure.count("distribution") == 1) {
1176 false, storm::exceptions::InvalidJaniException,
1177 "Distributions are not supported by storm expressions, cannot import " << expressionStructure.dump() << " in " << scope.description << ".");
1178 }
1179 if (expressionStructure.count("op") == 1) {
1180 std::string opstring = getString<ValueType>(expressionStructure.at("op"), scope.description);
1181 std::vector<storm::expressions::Expression> arguments = {};
1182 if (opstring == "ite") {
1183 STORM_LOG_THROW(expressionStructure.count("if") == 1, storm::exceptions::InvalidJaniException, "If operator required");
1184 STORM_LOG_THROW(expressionStructure.count("else") == 1, storm::exceptions::InvalidJaniException, "Else operator required");
1185 STORM_LOG_THROW(expressionStructure.count("then") == 1, storm::exceptions::InvalidJaniException, "Then operator required");
1186 arguments.push_back(
1187 parseExpression(expressionStructure.at("if"), scope.refine("if-formula"), returnNoneInitializedOnUnknownOperator, auxiliaryVariables));
1188 arguments.push_back(
1189 parseExpression(expressionStructure.at("then"), scope.refine("then-formula"), returnNoneInitializedOnUnknownOperator, auxiliaryVariables));
1190 arguments.push_back(
1191 parseExpression(expressionStructure.at("else"), scope.refine("else-formula"), returnNoneInitializedOnUnknownOperator, auxiliaryVariables));
1192 ensureNumberOfArguments(3, arguments.size(), opstring, scope.description);
1193 assert(arguments.size() == 3);
1194 ensureBooleanType(arguments[0], opstring, 0, scope.description);
1195 return storm::expressions::ite(arguments[0], arguments[1], arguments[2]);
1196 } else if (opstring == "∨") {
1197 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1198 assert(arguments.size() == 2);
1199 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1201 }
1202 ensureBooleanType(arguments[0], opstring, 0, scope.description);
1203 ensureBooleanType(arguments[1], opstring, 1, scope.description);
1204 return arguments[0] || arguments[1];
1205 } else if (opstring == "∧") {
1206 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1207 assert(arguments.size() == 2);
1208 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1210 }
1211 ensureBooleanType(arguments[0], opstring, 0, scope.description);
1212 ensureBooleanType(arguments[1], opstring, 1, scope.description);
1213 return arguments[0] && arguments[1];
1214 } else if (opstring == "⇒") {
1215 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1216 assert(arguments.size() == 2);
1217 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1219 }
1220 ensureBooleanType(arguments[0], opstring, 0, scope.description);
1221 ensureBooleanType(arguments[1], opstring, 1, scope.description);
1222 return (!arguments[0]) || arguments[1];
1223 } else if (opstring == "¬") {
1224 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1225 assert(arguments.size() == 1);
1226 if (!arguments[0].isInitialized()) {
1228 }
1229 ensureBooleanType(arguments[0], opstring, 0, scope.description);
1230 return !arguments[0];
1231 } else if (opstring == "=") {
1232 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1233 assert(arguments.size() == 2);
1234 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1236 }
1237 if (arguments[0].hasBooleanType()) {
1238 ensureBooleanType(arguments[1], opstring, 1, scope.description);
1239 return storm::expressions::iff(arguments[0], arguments[1]);
1240 } else {
1241 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1242 return arguments[0] == arguments[1];
1243 }
1244 } else if (opstring == "≠") {
1245 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1246 assert(arguments.size() == 2);
1247 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1249 }
1250 if (arguments[0].hasBooleanType()) {
1251 ensureBooleanType(arguments[1], opstring, 1, scope.description);
1252 return storm::expressions::xclusiveor(arguments[0], arguments[1]);
1253 } else {
1254 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1255 return arguments[0] != arguments[1];
1256 }
1257 } else if (opstring == "<") {
1258 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1259 assert(arguments.size() == 2);
1260 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1262 }
1263 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1264 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1265 return arguments[0] < arguments[1];
1266 } else if (opstring == "≤") {
1267 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1268 assert(arguments.size() == 2);
1269 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1271 }
1272 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1273 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1274 return arguments[0] <= arguments[1];
1275 } else if (opstring == ">") {
1276 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1277 assert(arguments.size() == 2);
1278 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1280 }
1281 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1282 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1283 return arguments[0] > arguments[1];
1284 } else if (opstring == "≥") {
1285 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1286 assert(arguments.size() == 2);
1287 if (!arguments[0].isInitialized() || !arguments[1].isInitialized()) {
1289 }
1290 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1291 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1292 return arguments[0] >= arguments[1];
1293 } else if (opstring == "+") {
1294 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1295 assert(arguments.size() == 2);
1296 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1297 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1298 return arguments[0] + arguments[1];
1299 } else if (opstring == "-" && expressionStructure.count("left") > 0) {
1300 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1301 assert(arguments.size() == 2);
1302 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1303 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1304 return arguments[0] - arguments[1];
1305 } else if (opstring == "-") {
1306 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1307 assert(arguments.size() == 1);
1308 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1309 return -arguments[0];
1310 } else if (opstring == "*") {
1311 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1312 assert(arguments.size() == 2);
1313 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1314 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1315 return arguments[0] * arguments[1];
1316 } else if (opstring == "/") {
1317 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1318 assert(arguments.size() == 2);
1319 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1320 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1321 return arguments[0] / arguments[1];
1322 } else if (opstring == "%") {
1323 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1324 assert(arguments.size() == 2);
1325 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1326 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1327 return arguments[0] % arguments[1];
1328 } else if (opstring == "max") {
1329 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1330 assert(arguments.size() == 2);
1331 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1332 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1333 return storm::expressions::maximum(arguments[0], arguments[1]);
1334 } else if (opstring == "min") {
1335 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1336 assert(arguments.size() == 2);
1337 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1338 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1339 return storm::expressions::minimum(arguments[0], arguments[1]);
1340 } else if (opstring == "floor") {
1341 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1342 assert(arguments.size() == 1);
1343 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1344 return storm::expressions::floor(arguments[0]);
1345 } else if (opstring == "ceil") {
1346 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1347 assert(arguments.size() == 1);
1348 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1349 return storm::expressions::ceil(arguments[0]);
1350 } else if (opstring == "abs") {
1351 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1352 assert(arguments.size() == 1);
1353 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1354 return storm::expressions::abs(arguments[0]);
1355 } else if (opstring == "sgn") {
1356 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1357 assert(arguments.size() == 1);
1358 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1359 return storm::expressions::sign(arguments[0]);
1360 } else if (opstring == "trc") {
1361 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1362 assert(arguments.size() == 1);
1363 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1364 return storm::expressions::truncate(arguments[0]);
1365 } else if (opstring == "pow") {
1366 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1367 assert(arguments.size() == 2);
1368 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1369 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1370 return storm::expressions::pow(arguments[0], arguments[1]);
1371 } else if (opstring == "exp") {
1372 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1373 assert(arguments.size() == 2);
1374 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1375 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1376 // TODO implement
1377 STORM_LOG_THROW(false, storm::exceptions::NotImplementedException, "exp operation is not yet implemented");
1378 } else if (opstring == "log") {
1379 arguments = parseBinaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1380 assert(arguments.size() == 2);
1381 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1382 ensureNumericalType(arguments[1], opstring, 1, scope.description);
1383 return storm::expressions::logarithm(arguments[0], arguments[1]);
1384 } else if (opstring == "cos") {
1385 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1386 assert(arguments.size() == 1);
1387 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1388 return storm::expressions::cos(arguments[0]);
1389 } else if (opstring == "sin") {
1390 arguments = parseUnaryExpressionArguments(expressionStructure, opstring, scope, returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1391 assert(arguments.size() == 1);
1392 ensureNumericalType(arguments[0], opstring, 0, scope.description);
1393 return storm::expressions::sin(arguments[0]);
1394 } else if (opstring == "aa") {
1395 STORM_LOG_THROW(expressionStructure.count("exp") == 1, storm::exceptions::InvalidJaniException,
1396 "Array access operator requires exactly one exp (at " + scope.description + ").");
1397 storm::expressions::Expression exp = parseExpression(expressionStructure.at("exp"), scope.refine("'exp' of array access operator"),
1398 returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1399 STORM_LOG_THROW(expressionStructure.count("index") == 1, storm::exceptions::InvalidJaniException,
1400 "Array access operator requires exactly one index (at " + scope.description + ").");
1401 storm::expressions::Expression index = parseExpression(expressionStructure.at("index"), scope.refine("index of array access operator"),
1402 returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1403 ensureArrayType(exp, opstring, 0, scope.description);
1404 ensureIntegerType(index, opstring, 1, scope.description);
1405 return std::make_shared<storm::expressions::ArrayAccessExpression>(exp.getManager(), exp.getType().getElementType(),
1407 ->toExpression();
1408 } else if (opstring == "av") {
1409 STORM_LOG_THROW(expressionStructure.count("elements") == 1, storm::exceptions::InvalidJaniException,
1410 "Array value operator requires exactly one 'elements' (at " + scope.description + ").");
1411 std::vector<std::shared_ptr<storm::expressions::BaseExpression const>> elements;
1412 storm::expressions::Type commonType;
1413 bool first = true;
1414 for (auto const& element : expressionStructure.at("elements")) {
1415 elements.push_back(parseExpression(element, scope.refine("element " + std::to_string(elements.size()) + " of array value expression"),
1416 returnNoneInitializedOnUnknownOperator, auxiliaryVariables)
1417 .getBaseExpressionPointer());
1418 if (first) {
1419 commonType = elements.back()->getType();
1420 first = false;
1421 } else if (!(commonType == elements.back()->getType())) {
1422 if (commonType.isIntegerType() && elements.back()->getType().isRationalType()) {
1423 commonType = elements.back()->getType();
1424 } else {
1425 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
1426 "Incompatible element types " << commonType << " and " << elements.back()->getType()
1427 << " of array value expression at " << scope.description);
1428 }
1429 }
1430 }
1431 return std::make_shared<storm::expressions::ValueArrayExpression>(*expressionManager, expressionManager->getArrayType(commonType), elements)
1432 ->toExpression();
1433 } else if (opstring == "ac") {
1434 STORM_LOG_THROW(expressionStructure.count("length") == 1, storm::exceptions::InvalidJaniException,
1435 "Array access operator requires exactly one length (at " + scope.description + ").");
1436 storm::expressions::Expression length = parseExpression(expressionStructure.at("length"), scope.refine("index of array constructor expression"),
1437 returnNoneInitializedOnUnknownOperator, auxiliaryVariables);
1438 ensureIntegerType(length, opstring, 1, scope.description);
1439 STORM_LOG_THROW(expressionStructure.count("var") == 1, storm::exceptions::InvalidJaniException,
1440 "Array access operator requires exactly one var (at " + scope.description + ").");
1441 std::string indexVarName =
1442 getString<ValueType>(expressionStructure.at("var"), "Field 'var' of Array access operator (at " + scope.description + ").");
1443 STORM_LOG_THROW(auxiliaryVariables.find(indexVarName) == auxiliaryVariables.end(), storm::exceptions::InvalidJaniException,
1444 "Index variable " << indexVarName << " is already defined as an auxiliary variable (at " + scope.description + ").");
1445 auto newAuxVars = auxiliaryVariables;
1446 storm::expressions::Variable indexVar = expressionManager->declareFreshIntegerVariable(false, "ac_" + indexVarName);
1447 newAuxVars.emplace(indexVarName, indexVar);
1448 STORM_LOG_THROW(expressionStructure.count("exp") == 1, storm::exceptions::InvalidJaniException,
1449 "Array constructor operator requires exactly one exp (at " + scope.description + ").");
1450 storm::expressions::Expression exp = parseExpression(expressionStructure.at("exp"), scope.refine("exp of array constructor"),
1451 returnNoneInitializedOnUnknownOperator, newAuxVars);
1452 return std::make_shared<storm::expressions::ConstructorArrayExpression>(*expressionManager, expressionManager->getArrayType(exp.getType()),
1453 length.getBaseExpressionPointer(), indexVar,
1455 ->toExpression();
1456 } else if (opstring == "call") {
1457 STORM_LOG_THROW(expressionStructure.count("function") == 1, storm::exceptions::InvalidJaniException,
1458 "Function call operator requires exactly one function (at " + scope.description + ").");
1459 std::string functionName =
1460 getString<ValueType>(expressionStructure.at("function"), "in function call operator (at " + scope.description + ").");
1461 storm::jani::FunctionDefinition const* functionDefinition;
1462 if (scope.localFunctions != nullptr && scope.localFunctions->count(functionName) > 0) {
1463 functionDefinition = scope.localFunctions->at(functionName);
1464 } else if (scope.globalFunctions != nullptr && scope.globalFunctions->count(functionName) > 0) {
1465 functionDefinition = scope.globalFunctions->at(functionName);
1466 } else {
1467 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
1468 "Function call operator calls unknown function '" + functionName + "' (at " + scope.description + ").");
1469 }
1470 STORM_LOG_THROW(expressionStructure.count("args") == 1, storm::exceptions::InvalidJaniException,
1471 "Function call operator requires exactly one args (at " + scope.description + ").");
1472 std::vector<std::shared_ptr<storm::expressions::BaseExpression const>> args;
1473 if (expressionStructure.count("args") > 0) {
1474 STORM_LOG_THROW(expressionStructure.count("args") == 1, storm::exceptions::InvalidJaniException,
1475 "Function call operator requires exactly one args (at " + scope.description + ").");
1476 for (auto const& arg : expressionStructure.at("args")) {
1477 args.push_back(parseExpression(arg, scope.refine("argument " + std::to_string(args.size()) + " of function call expression"),
1478 returnNoneInitializedOnUnknownOperator, auxiliaryVariables)
1479 .getBaseExpressionPointer());
1480 }
1481 }
1482 return std::make_shared<storm::expressions::FunctionCallExpression>(*expressionManager, functionDefinition->getType(), functionName, args)
1483 ->toExpression();
1484 } else if (unsupportedOpstrings.count(opstring) > 0) {
1485 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Opstring " + opstring + " is not supported by storm");
1486 } else {
1487 if (returnNoneInitializedOnUnknownOperator) {
1489 }
1490 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Unknown operator " << opstring << " in " << scope.description << ".");
1491 }
1492 }
1493 if (expressionStructure.count("constant") == 1) {
1494 // Convert constants to a numeric value (only PI and Euler number, as in Jani specification)
1495 const std::string constantStr = getString<ValueType>(expressionStructure.at("constant"), scope.description);
1496 if (constantStr == "Ï€") {
1497 return std::make_shared<storm::expressions::TranscendentalNumberLiteralExpression>(
1499 ->toExpression();
1500 }
1501 if (constantStr == "e") {
1502 return std::make_shared<storm::expressions::TranscendentalNumberLiteralExpression>(
1504 ->toExpression();
1505 }
1506 }
1508 false, storm::exceptions::InvalidJaniException,
1509 "No supported operator declaration found for complex expressions as " << expressionStructure.dump() << " in " << scope.description << ".");
1510 }
1511 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException,
1512 "No supported expression found at " << expressionStructure.dump() << " in " << scope.description << ".");
1513 // Silly warning suppression.
1515}
1516
1517template<typename ValueType>
1518void JaniParser<ValueType>::parseActions(Json const& actionStructure, storm::jani::Model& parentModel) {
1519 std::set<std::string> actionNames;
1520 for (auto const& actionEntry : actionStructure) {
1521 STORM_LOG_THROW(actionEntry.count("name") == 1, storm::exceptions::InvalidJaniException, "Actions must have exactly one name.");
1522 std::string actionName = getString<ValueType>(actionEntry.at("name"), "name of action");
1523 STORM_LOG_THROW(actionNames.count(actionName) == 0, storm::exceptions::InvalidJaniException, "Action with name " + actionName + " already exists.");
1524 parentModel.addAction(storm::jani::Action(actionName));
1525 actionNames.emplace(actionName);
1526 }
1527}
1528
1529template<typename ValueType>
1530storm::jani::Automaton JaniParser<ValueType>::parseAutomaton(Json const& automatonStructure, storm::jani::Model const& parentModel, Scope const& globalScope) {
1531 STORM_LOG_THROW(automatonStructure.count("name") == 1, storm::exceptions::InvalidJaniException, "Each automaton must have a name");
1532 std::string name = getString<ValueType>(automatonStructure.at("name"), " the name field for automaton");
1533 Scope scope = globalScope.refine(name);
1534 storm::jani::Automaton automaton(name, expressionManager->declareIntegerVariable("_loc_" + name));
1535
1536 uint64_t varDeclCount = automatonStructure.count("variables");
1537 STORM_LOG_THROW(varDeclCount < 2, storm::exceptions::InvalidJaniException, "Automaton '" << name << "' has more than one list of variables");
1538 VariablesMap localVars;
1539 scope.localVars = &localVars;
1540 if (varDeclCount > 0) {
1541 for (auto const& varStructure : automatonStructure.at("variables")) {
1542 std::shared_ptr<storm::jani::Variable> var = parseVariable(
1543 varStructure, scope.refine("variables[" + std::to_string(localVars.size()) + "] of automaton " + name), name + VARIABLE_AUTOMATON_DELIMITER);
1544 assert(localVars.count(var->getName()) == 0);
1545 localVars.emplace(var->getName(), &automaton.addVariable(*var));
1546 }
1547 }
1548
1549 uint64_t funDeclCount = automatonStructure.count("functions");
1550 STORM_LOG_THROW(funDeclCount < 2, storm::exceptions::InvalidJaniException, "Automaton '" << name << "' has more than one list of functions");
1551 FunctionsMap localFuns;
1552 scope.localFunctions = &localFuns;
1553 if (funDeclCount > 0) {
1554 // We require two passes through the function definitions array to allow referring to functions before they were defined.
1555 std::vector<storm::jani::FunctionDefinition> dummyFunctionDefinitions;
1556 for (auto const& funStructure : automatonStructure.at("functions")) {
1557 // Skip parsing of function body
1558 dummyFunctionDefinitions.push_back(
1559 parseFunctionDefinition(funStructure, scope.refine("functions[" + std::to_string(localFuns.size()) + "] of automaton " + name), true));
1560 }
1561 // Store references to the dummy function definitions. This needs to happen in a separate loop since otherwise, references to FunDefs can be invalidated
1562 // after calling dummyFunctionDefinitions.push_back
1563 for (auto const& funDef : dummyFunctionDefinitions) {
1564 bool unused = localFuns.emplace(funDef.getName(), &funDef).second;
1565 STORM_LOG_THROW(unused, storm::exceptions::InvalidJaniException,
1566 "Multiple definitions of functions with the name " << funDef.getName() << " in " << scope.description);
1567 }
1568 for (auto const& funStructure : automatonStructure.at("functions")) {
1569 // Actually parse the function body
1571 parseFunctionDefinition(funStructure, scope.refine("functions[" + std::to_string(localFuns.size()) + "] of automaton " + name), false,
1573 assert(localFuns.count(funDef.getName()) == 1);
1574 localFuns[funDef.getName()] = &automaton.addFunctionDefinition(funDef);
1575 }
1576 }
1577
1578 STORM_LOG_THROW(automatonStructure.count("locations") > 0, storm::exceptions::InvalidJaniException, "Automaton '" << name << "' does not have locations.");
1579 std::unordered_map<std::string, uint64_t> locIds;
1580 for (auto const& locEntry : automatonStructure.at("locations")) {
1581 STORM_LOG_THROW(locEntry.count("name") == 1, storm::exceptions::InvalidJaniException,
1582 "Locations for automaton '" << name << "' must have exactly one name");
1583 std::string locName = getString<ValueType>(locEntry.at("name"), "location of automaton " + name);
1584 STORM_LOG_THROW(locIds.count(locName) == 0, storm::exceptions::InvalidJaniException,
1585 "Location with name '" + locName + "' already exists in automaton '" + name + "'");
1586 STORM_LOG_THROW(locEntry.count("invariant") == 0, storm::exceptions::InvalidJaniException,
1587 "Invariants in locations as in '" + locName + "' in automaton '" + name + "' are not supported");
1588 // STORM_LOG_THROW(locEntry.count("invariant") > 0 && !supportsInvariants(parentModel.getModelType()), storm::exceptions::InvalidJaniException,
1589 // "Invariants are not supported in the model type " + to_string(parentModel.getModelType()));
1590 std::vector<storm::jani::Assignment> transientAssignments;
1591 if (locEntry.count("transient-values") > 0) {
1592 for (auto const& transientValueEntry : locEntry.at("transient-values")) {
1593 STORM_LOG_THROW(transientValueEntry.count("ref") == 1, storm::exceptions::InvalidJaniException,
1594 "Transient values in location " << locName << " need exactly one ref that is assigned to");
1595 STORM_LOG_THROW(transientValueEntry.count("value") == 1, storm::exceptions::InvalidJaniException,
1596 "Transient values in location " << locName << " need exactly one assigned value");
1597 storm::jani::LValue lValue = parseLValue(transientValueEntry.at("ref"), scope.refine("LHS of assignment in location " + locName));
1598 STORM_LOG_THROW(lValue.isTransient(), storm::exceptions::InvalidJaniException,
1599 "Assigned non-transient variable " << lValue << " in location " + locName + " (automaton: '" + name + "').");
1601 parseExpression(transientValueEntry.at("value"), scope.refine("Assignment of lValue in location " + locName));
1602 transientAssignments.emplace_back(lValue, rhs);
1603 }
1604 }
1605 uint64_t id = automaton.addLocation(storm::jani::Location(locName, transientAssignments));
1606 locIds.emplace(locName, id);
1607 }
1608 STORM_LOG_THROW(automatonStructure.count("initial-locations") == 1, storm::exceptions::InvalidJaniException,
1609 "Automaton '" << name << "' does not have initial locations.");
1610 for (Json const& initLocStruct : automatonStructure.at("initial-locations")) {
1611 automaton.addInitialLocation(getString<ValueType>(initLocStruct, "Initial locations for automaton '" + name + "'."));
1612 }
1613 STORM_LOG_THROW(automatonStructure.count("restrict-initial") < 2, storm::exceptions::InvalidJaniException,
1614 "Automaton '" << name << "' has multiple initial value restrictions");
1615 storm::expressions::Expression initialValueRestriction = expressionManager->boolean(true);
1616 if (automatonStructure.count("restrict-initial") > 0) {
1617 STORM_LOG_THROW(automatonStructure.at("restrict-initial").count("exp") == 1, storm::exceptions::InvalidJaniException,
1618 "Automaton '" << name << "' needs an expression inside the initial restricion");
1619 initialValueRestriction = parseExpression(automatonStructure.at("restrict-initial").at("exp"), scope.refine("Initial value restriction"));
1620 }
1621 automaton.setInitialStatesRestriction(initialValueRestriction);
1622
1623 STORM_LOG_THROW(automatonStructure.count("edges") > 0, storm::exceptions::InvalidJaniException, "Automaton '" << name << "' must have a list of edges");
1624 for (auto const& edgeEntry : automatonStructure.at("edges")) {
1625 // source location
1626 STORM_LOG_THROW(edgeEntry.count("location") == 1, storm::exceptions::InvalidJaniException,
1627 "Each edge in automaton '" << name << "' must have a source");
1628 std::string sourceLoc = getString<ValueType>(edgeEntry.at("location"), "source location for edge in automaton '" + name + "'");
1629 STORM_LOG_THROW(locIds.count(sourceLoc) == 1, storm::exceptions::InvalidJaniException,
1630 "Source of edge has unknown location '" << sourceLoc << "' in automaton '" << name << "'.");
1631 // action
1632 STORM_LOG_THROW(edgeEntry.count("action") < 2, storm::exceptions::InvalidJaniException,
1633 "Edge from " << sourceLoc << " in automaton " << name << " has multiple actions");
1634 std::string action = storm::jani::Model::SILENT_ACTION_NAME; // def is tau
1635 if (edgeEntry.count("action") > 0) {
1636 action = getString<ValueType>(edgeEntry.at("action"), "action name in edge from '" + sourceLoc + "' in automaton '" + name + "'");
1637 // TODO check if action is known
1638 assert(action != "");
1639 }
1640 // rate
1641 STORM_LOG_THROW(edgeEntry.count("rate") < 2, storm::exceptions::InvalidJaniException,
1642 "Edge from '" << sourceLoc << "' in automaton '" << name << "' has multiple rates");
1644 if (edgeEntry.count("rate") > 0) {
1645 STORM_LOG_THROW(edgeEntry.at("rate").count("exp") == 1, storm::exceptions::InvalidJaniException,
1646 "Rate in edge from '" << sourceLoc << "' in automaton '" << name << "' must have a defing expression.");
1647 rateExpr = parseExpression(edgeEntry.at("rate").at("exp"), scope.refine("rate expression in edge from '" + sourceLoc));
1648 STORM_LOG_THROW(rateExpr.hasNumericalType(), storm::exceptions::InvalidJaniException, "Rate '" << rateExpr << "' has not a numerical type");
1649 STORM_LOG_THROW(rateExpr.containsVariables() || rateExpr.evaluateAsRational() > storm::utility::zero<storm::RationalNumber>(),
1650 storm::exceptions::InvalidJaniException, "Only positive rates are allowed but rate '" << rateExpr << " was found.");
1651 }
1652 // guard
1653 STORM_LOG_THROW(edgeEntry.count("guard") <= 1, storm::exceptions::InvalidJaniException,
1654 "Guard can be given at most once in edge from '" << sourceLoc << "' in automaton '" << name << "'");
1655 storm::expressions::Expression guardExpr = expressionManager->boolean(true);
1656 if (edgeEntry.count("guard") == 1) {
1657 STORM_LOG_THROW(edgeEntry.at("guard").count("exp") == 1, storm::exceptions::InvalidJaniException,
1658 "Guard in edge from '" + sourceLoc + "' in automaton '" + name + "' must have one expression");
1659 guardExpr = parseExpression(edgeEntry.at("guard").at("exp"), scope.refine("guard expression in edge from '" + sourceLoc));
1660 STORM_LOG_THROW(guardExpr.hasBooleanType(), storm::exceptions::InvalidJaniException, "Guard " << guardExpr << " does not have Boolean type.");
1661 }
1662 assert(guardExpr.isInitialized());
1663 std::shared_ptr<storm::jani::TemplateEdge> templateEdge = std::make_shared<storm::jani::TemplateEdge>(guardExpr);
1664
1665 // edge assignments
1666 if (edgeEntry.count("assignments") > 0) {
1667 STORM_LOG_THROW(edgeEntry.count("assignments") == 1, storm::exceptions::InvalidJaniException,
1668 "Multiple edge assignments in edge from '" + sourceLoc + "' in automaton '" + name + "'.");
1669 for (auto const& assignmentEntry : edgeEntry.at("assignments")) {
1670 // ref
1671 STORM_LOG_THROW(assignmentEntry.count("ref") == 1, storm::exceptions::InvalidJaniException,
1672 "Assignment in edge from '" << sourceLoc << "' in automaton '" << name << "'must have one ref field");
1673 storm::jani::LValue lValue =
1674 parseLValue(assignmentEntry.at("ref"), scope.refine("Assignment variable in edge from '" + sourceLoc + "' in automaton '" + name + "'"));
1675 // value
1676 STORM_LOG_THROW(assignmentEntry.count("value") == 1, storm::exceptions::InvalidJaniException,
1677 "Assignment in edge from '" << sourceLoc << "' in automaton '" << name << "' must have one value field");
1678 storm::expressions::Expression assignmentExpr =
1679 parseExpression(assignmentEntry.at("value"), scope.refine("assignment in edge from '" + sourceLoc + "' in automaton '" + name + "'"));
1680 // TODO check types
1681 // index
1682 int64_t assignmentIndex = 0; // default.
1683 if (assignmentEntry.count("index") > 0) {
1684 assignmentIndex =
1685 getSignedInt<ValueType>(assignmentEntry.at("index"), "assignment index in edge from '" + sourceLoc + "' in automaton '" + name + "'");
1686 }
1687 templateEdge->getAssignments().add(storm::jani::Assignment(lValue, assignmentExpr, assignmentIndex));
1688 }
1689 }
1690
1691 // destinations
1692 STORM_LOG_THROW(edgeEntry.count("destinations") == 1, storm::exceptions::InvalidJaniException,
1693 "A single list of destinations must be given in edge from '" << sourceLoc << "' in automaton '" << name << "'");
1694 std::vector<std::pair<uint64_t, storm::expressions::Expression>> destinationLocationsAndProbabilities;
1695 for (auto const& destEntry : edgeEntry.at("destinations")) {
1696 // target location
1697 STORM_LOG_THROW(destEntry.count("location") == 1, storm::exceptions::InvalidJaniException,
1698 "Each destination in edge from '" << sourceLoc << "' in automaton '" << name << "' must have a target location");
1699 std::string targetLoc =
1700 getString<ValueType>(destEntry.at("location"), "target location for edge from '" + sourceLoc + "' in automaton '" + name + "'");
1701 STORM_LOG_THROW(locIds.count(targetLoc) == 1, storm::exceptions::InvalidJaniException,
1702 "Target of edge has unknown location '" << targetLoc << "' in automaton '" << name << "'.");
1703 // probability
1705 unsigned probDeclCount = destEntry.count("probability");
1706 STORM_LOG_THROW(probDeclCount < 2, storm::exceptions::InvalidJaniException,
1707 "Destination in edge from '" << sourceLoc << "' to '" << targetLoc << "' in automaton '" << name << "' has multiple probabilites");
1708 if (probDeclCount == 0) {
1709 probExpr = expressionManager->rational(1.0);
1710 } else {
1711 STORM_LOG_THROW(destEntry.at("probability").count("exp") == 1, storm::exceptions::InvalidJaniException,
1712 "Destination in edge from '" << sourceLoc << "' to '" << targetLoc << "' in automaton '" << name
1713 << "' must have a probability expression.");
1714 probExpr = parseExpression(destEntry.at("probability").at("exp"), scope.refine("probability expression in edge from '" + sourceLoc + "' to '" +
1715 targetLoc + "' in automaton '" + name + "'"));
1716 }
1717 assert(probExpr.isInitialized());
1718 STORM_LOG_THROW(probExpr.hasNumericalType(), storm::exceptions::InvalidJaniException,
1719 "Probability expression " << probExpr << " does not have a numerical type.");
1720 // assignments
1721 std::vector<storm::jani::Assignment> assignments;
1722 unsigned assignmentDeclCount = destEntry.count("assignments");
1724 assignmentDeclCount < 2, storm::exceptions::InvalidJaniException,
1725 "Destination in edge from '" << sourceLoc << "' to '" << targetLoc << "' in automaton '" << name << "' has multiple assignment lists");
1726 if (assignmentDeclCount > 0) {
1727 for (auto const& assignmentEntry : destEntry.at("assignments")) {
1728 // ref
1730 assignmentEntry.count("ref") == 1, storm::exceptions::InvalidJaniException,
1731 "Assignment in edge from '" << sourceLoc << "' to '" << targetLoc << "' in automaton '" << name << "' must have one ref field");
1732 storm::jani::LValue lValue = parseLValue(assignmentEntry.at("ref"), scope.refine("Assignment variable in edge from '" + sourceLoc +
1733 "' to '" + targetLoc + "' in automaton '" + name + "'"));
1734 // value
1736 assignmentEntry.count("value") == 1, storm::exceptions::InvalidJaniException,
1737 "Assignment in edge from '" << sourceLoc << "' to '" << targetLoc << "' in automaton '" << name << "' must have one value field");
1738 storm::expressions::Expression assignmentExpr =
1739 parseExpression(assignmentEntry.at("value"),
1740 scope.refine("assignment in edge from '" + sourceLoc + "' to '" + targetLoc + "' in automaton '" + name + "'"));
1741 // TODO check types
1742 // index
1743 int64_t assignmentIndex = 0; // default.
1744 if (assignmentEntry.count("index") > 0) {
1745 assignmentIndex = getSignedInt<ValueType>(assignmentEntry.at("index"), "assignment index in edge from '" + sourceLoc + "' to '" +
1746 targetLoc + "' in automaton '" + name + "'");
1747 }
1748 assignments.emplace_back(lValue, assignmentExpr, assignmentIndex);
1749 }
1750 }
1751 destinationLocationsAndProbabilities.emplace_back(locIds.at(targetLoc), probExpr);
1752 templateEdge->addDestination(storm::jani::TemplateEdgeDestination(assignments));
1753 }
1754 automaton.addEdge(storm::jani::Edge(locIds.at(sourceLoc), parentModel.getActionIndex(action),
1755 rateExpr.isInitialized() ? boost::optional<storm::expressions::Expression>(rateExpr) : boost::none, templateEdge,
1756 destinationLocationsAndProbabilities));
1757 }
1758
1759 return automaton;
1760}
1761
1762template<typename ValueType>
1763std::vector<storm::jani::SynchronizationVector> parseSyncVectors(typename JaniParser<ValueType>::Json const& syncVectorStructure) {
1764 std::vector<storm::jani::SynchronizationVector> syncVectors;
1765 // TODO add error checks
1766 for (auto const& syncEntry : syncVectorStructure) {
1767 std::vector<std::string> inputs;
1768 for (auto const& syncInput : syncEntry.at("synchronise")) {
1769 if (syncInput.is_null()) {
1771 } else {
1772 inputs.push_back(syncInput);
1773 }
1774 }
1775 std::string syncResult;
1776 if (syncEntry.count("result")) {
1777 syncResult = syncEntry.at("result");
1778 } else {
1780 }
1781 syncVectors.emplace_back(inputs, syncResult);
1782 }
1783 return syncVectors;
1784}
1785
1786template<typename ValueType>
1787std::shared_ptr<storm::jani::Composition> JaniParser<ValueType>::parseComposition(Json const& compositionStructure) {
1788 if (compositionStructure.count("automaton")) {
1789 std::set<std::string> inputEnabledActions;
1790 if (compositionStructure.count("input-enable")) {
1791 for (auto const& actionDecl : compositionStructure.at("input-enable")) {
1792 inputEnabledActions.insert(actionDecl.template get<std::string>());
1793 }
1794 }
1795 return std::shared_ptr<storm::jani::AutomatonComposition>(
1796 new storm::jani::AutomatonComposition(compositionStructure.at("automaton").template get<std::string>(), inputEnabledActions));
1797 }
1798
1799 STORM_LOG_THROW(compositionStructure.count("elements") == 1, storm::exceptions::InvalidJaniException,
1800 "Elements of a composition must be given, got " << compositionStructure.dump());
1801
1802 if (compositionStructure.at("elements").size() == 1 && compositionStructure.count("syncs") == 0) {
1803 // We might have an automaton.
1804 STORM_LOG_THROW(compositionStructure.at("elements").back().count("automaton") == 1, storm::exceptions::InvalidJaniException,
1805 "Automaton must be given in composition");
1806 if (compositionStructure.at("elements").back().at("automaton").is_string()) {
1807 std::string name = compositionStructure.at("elements").back().at("automaton");
1808 // TODO check whether name exist?
1809 return std::shared_ptr<storm::jani::AutomatonComposition>(new storm::jani::AutomatonComposition(name));
1810 }
1811 STORM_LOG_THROW(false, storm::exceptions::InvalidJaniException, "Trivial nesting parallel composition is not yet supported");
1812 }
1813
1814 std::vector<std::shared_ptr<storm::jani::Composition>> compositions;
1815 for (auto const& elemDecl : compositionStructure.at("elements")) {
1816 if (!allowRecursion) {
1817 STORM_LOG_THROW(elemDecl.count("automaton") == 1, storm::exceptions::InvalidJaniException, "Automaton must be given in the element");
1818 }
1819 compositions.push_back(parseComposition(elemDecl));
1820 }
1821
1822 STORM_LOG_THROW(compositionStructure.count("syncs") < 2, storm::exceptions::InvalidJaniException, "Sync vectors can be given at most once");
1823 std::vector<storm::jani::SynchronizationVector> syncVectors;
1824 if (compositionStructure.count("syncs") > 0) {
1825 syncVectors = parseSyncVectors<ValueType>(compositionStructure.at("syncs"));
1826 }
1827
1828 return std::shared_ptr<storm::jani::Composition>(new storm::jani::ParallelComposition(compositions, syncVectors));
1829}
1830
1831template class JaniParser<double>;
1832template class JaniParser<storm::RationalNumber>;
1833} // namespace parser
1834} // namespace storm
int_fast64_t evaluateAsInt(Valuation const *valuation=nullptr) const
Evaluates the expression under the valuation of variables given by the valuation and returns the resu...
bool isVariable() const
Retrieves whether the expression is a variable.
bool hasNumericalType() const
Retrieves whether the expression has a numerical return type, i.e., integer or double.
storm::RationalNumber evaluateAsRational() const
Evaluates the expression and returns the resulting rational number.
bool hasBooleanType() const
Retrieves whether the expression has a boolean return type.
bool containsVariables() const
Retrieves whether the expression contains a variable.
std::set< storm::expressions::Variable > getVariables() const
Retrieves the set of all variables that appear in the expression.
std::shared_ptr< BaseExpression const > const & getBaseExpressionPointer() const
Retrieves a pointer to the base expression underlying this expression object.
bool hasIntegerType() const
Retrieves whether the expression has an integral return type.
std::string toString() const
Converts the expression into a string.
Type const & getType() const
Retrieves the type of the expression.
ExpressionManager const & getManager() const
Retrieves the manager responsible for this expression.
bool isInitialized() const
Checks whether the object encapsulates a base-expression.
Expression substitute(Expression const &expression)
Substitutes the identifiers in the given expression according to the previously given map and returns...
Type getElementType() const
Retrieves the element type of the type, provided that it is an Array type.
Definition Type.cpp:210
bool isIntegerType() const
Checks whether this type is an integral type.
Definition Type.cpp:182
bool isNumericalType() const
Checks whether this type is a numerical type.
Definition Type.cpp:190
bool isArrayType() const
Checks whether this type is an array type.
Definition Type.cpp:194
storm::expressions::Expression getExpression() const
Retrieves an expression that represents the variable.
Definition Variable.cpp:34
void addEdge(Edge const &edge)
Adds an edge to the automaton.
void setInitialStatesRestriction(storm::expressions::Expression const &initialStatesRestriction)
Sets the expression restricting the legal initial values of the automaton's variables.
Variable const & addVariable(Variable const &variable)
Adds the given variable to this automaton.
Definition Automaton.cpp:51
FunctionDefinition const & addFunctionDefinition(FunctionDefinition const &functionDefinition)
Adds the given function definition.
Definition Automaton.cpp:79
void addInitialLocation(std::string const &name)
Adds the location with the given name to the initial locations.
uint64_t addLocation(Location const &location)
Adds the given location to the automaton.
std::string const & getName() const
Retrieves the name of the function.
storm::expressions::Type const & getType() const
Retrieves the type of the function.
bool isTransient() const
Definition LValue.cpp:70
Jani Location:
Definition Location.h:15
bool hasMultiObjectiveProperties() const
ModelFeatures & add(ModelFeature const &modelFeature)
void setInitialStatesRestriction(storm::expressions::Expression const &initialStatesRestriction)
Sets the expression restricting the legal initial values of the global variables.
Definition Model.cpp:1285
bool addNonTrivialRewardExpression(std::string const &identifier, storm::expressions::Expression const &rewardExpression)
Adds a reward expression, i.e., a reward model that does not consist of a single, global,...
Definition Model.cpp:801
void setSystemComposition(std::shared_ptr< Composition > const &composition)
Sets the system composition expression of the JANI model.
Definition Model.cpp:1018
std::vector< Constant > const & getConstants() const
Retrieves the constants of the model.
Definition Model.cpp:689
void addConstant(Constant const &constant)
Adds the given constant to the model.
Definition Model.cpp:654
uint64_t getJaniVersion() const
Retrieves the JANI-version of the model.
Definition Model.cpp:117
Variable const & addVariable(Variable const &variable)
Adds the given variable to this model.
Definition Model.cpp:717
FunctionDefinition const & addFunctionDefinition(FunctionDefinition const &functionDefinition)
Adds the given function definition.
Definition Model.cpp:774
static const std::string SILENT_ACTION_NAME
The name of the silent action.
Definition Model.h:639
uint64_t addAction(Action const &action)
Adds an action to the model.
Definition Model.cpp:617
std::size_t getNumberOfAutomata() const
Retrieves the number of automata in this model.
Definition Model.cpp:914
bool isDiscreteTimeModel() const
Determines whether this model is a discrete-time model.
Definition Model.cpp:1378
ModelFeatures const & getModelFeatures() const
Retrieves the enabled model features.
Definition Model.cpp:129
uint64_t addAutomaton(Automaton const &automaton)
Adds the given automaton to the automata of this model.
Definition Model.cpp:863
uint64_t getActionIndex(std::string const &name) const
Get the index of the action.
Definition Model.cpp:636
void finalize()
After adding all components to the model, this method has to be called.
Definition Model.cpp:1404
static const std::string NO_ACTION_INPUT
static std::shared_ptr< Variable > makeVariable(std::string const &name, JaniType const &type, storm::expressions::Variable const &variable, boost::optional< storm::expressions::Expression > const &initValue, bool transient)
Convenience functions to call the appropriate constructor and return a shared pointer to the variable...
Definition Variable.cpp:95
static std::shared_ptr< Formula const > getTrueFormula()
Definition Formula.cpp:213
std::shared_ptr< Formula > eliminateRewardAccumulations(Formula const &f) const
Eliminates any reward accumulations of the formula, where the presence of the reward accumulation doe...
storm::jani::Property parseProperty(storm::jani::Model &model, storm::json< ValueType > const &propertyStructure, Scope const &scope)
storm::json< ValueType > Json
Definition JaniParser.h:39
static std::pair< storm::jani::Model, std::vector< storm::jani::Property > > parseFromString(std::string const &jsonstring, bool parseProperties=true)
static std::pair< storm::jani::Model, std::vector< storm::jani::Property > > parse(std::string const &path, bool parseProperties=true)
void readFile(std::string const &path)
std::pair< std::unique_ptr< storm::jani::JaniType >, storm::expressions::Type > parseType(storm::json< ValueType > const &typeStructure, std::string variableName, Scope const &scope)
std::pair< storm::jani::Model, std::vector< storm::jani::Property > > parseModel(bool parseProperties=true)
std::unordered_map< std::string, storm::jani::Variable const * > VariablesMap
Definition JaniParser.h:36
std::unordered_map< std::string, storm::jani::Constant const * > ConstantsMap
Definition JaniParser.h:37
std::unordered_map< std::string, storm::jani::FunctionDefinition const * > FunctionsMap
Definition JaniParser.h:38
std::shared_ptr< storm::jani::Variable > parseVariable(storm::json< ValueType > const &variableStructure, Scope const &scope, std::string const &namePrefix="")
storm::expressions::Expression parseExpression(storm::json< ValueType > const &expressionStructure, Scope const &scope, bool returnNoneOnUnknownOpString=false, std::unordered_map< std::string, storm::expressions::Variable > const &auxiliaryVariables={})
storm::jani::Automaton parseAutomaton(storm::json< ValueType > const &automatonStructure, storm::jani::Model const &parentModel, Scope const &scope)
storm::jani::LValue parseLValue(storm::json< ValueType > const &lValueStructure, Scope const &scope)
#define STORM_LOG_WARN(message)
Definition logging.h:25
#define STORM_LOG_TRACE(message)
Definition logging.h:12
#define STORM_LOG_WARN_COND(cond, message)
Definition macros.h:38
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:30
Expression maximum(Expression const &first, Expression const &second)
Expression ceil(Expression const &first)
Expression ite(Expression const &condition, Expression const &thenExpression, Expression const &elseExpression)
Expression iff(Expression const &first, Expression const &second)
Expression abs(Expression const &first)
Expression minimum(Expression const &first, Expression const &second)
Expression floor(Expression const &first)
Expression sin(Expression const &first)
Expression sign(Expression const &first)
Expression truncate(Expression const &first)
Expression pow(Expression const &base, Expression const &exponent, bool allowIntegerType)
The type of the resulting expression is.
Expression xclusiveor(Expression const &first, Expression const &second)
Expression logarithm(Expression const &first, Expression const &second)
Expression cos(Expression const &first)
void closeFile(std::ofstream &stream)
Close the given file after writing.
Definition file.h:47
void openFile(std::string const &filepath, std::ofstream &filestream, bool append=false, bool silent=false)
Open the given file for writing.
Definition file.h:18
std::string toString(ModelFeature const &modelFeature)
ModelType getModelType(std::string const &input)
Definition ModelType.cpp:42
ModelFeatures getAllKnownModelFeatures()
void ensureNumberOfArguments(uint64_t expected, uint64_t actual, std::string const &opstring, std::string const &errorInfo)
Helper for parse expression.
void insertLowerUpperTimeBounds(std::vector< boost::optional< storm::logic::TimeBound > > &lowerBounds, std::vector< boost::optional< storm::logic::TimeBound > > &upperBounds, storm::jani::PropertyInterval const &pi)
std::string getString(typename JaniParser< ValueType >::Json const &structure, std::string const &errorInfo)
std::vector< storm::jani::SynchronizationVector > parseSyncVectors(typename JaniParser< ValueType >::Json const &syncVectorStructure)
int64_t getSignedInt(typename JaniParser< ValueType >::Json const &structure, std::string const &errorInfo)
bool getBoolean(typename JaniParser< ValueType >::Json const &structure, std::string const &errorInfo)
void ensureArrayType(storm::expressions::Expression const &expr, std::string const &opstring, unsigned argNr, std::string const &errorInfo)
Helper for parse expression.
void ensureBooleanType(storm::expressions::Expression const &expr, std::string const &opstring, unsigned argNr, std::string const &errorInfo)
Helper for parse expression.
void ensureIntegerType(storm::expressions::Expression const &expr, std::string const &opstring, unsigned argNr, std::string const &errorInfo)
Helper for parse expression.
const std::string VARIABLE_AUTOMATON_DELIMITER
uint64_t getUnsignedInt(typename JaniParser< ValueType >::Json const &structure, std::string const &errorInfo)
void ensureNumericalType(storm::expressions::Expression const &expr, std::string const &opstring, unsigned argNr, std::string const &errorInfo)
Helper for parse expression.
bool isOne(ValueType const &a)
Definition constants.cpp:34
bool isZero(ValueType const &a)
Definition constants.cpp:39
Property intervals as per Jani Specification.
Definition Property.h:21
storm::expressions::Expression upperBound
Definition Property.h:24
storm::expressions::Expression lowerBound
Definition Property.h:22
boost::optional< Bound > bound
boost::optional< storm::solver::OptimizationDirection > optimalityType
FunctionsMap const * globalFunctions
Definition JaniParser.h:63
VariablesMap const * localVars
Definition JaniParser.h:64
FunctionsMap const * localFunctions
Definition JaniParser.h:65
VariablesMap const * globalVars
Definition JaniParser.h:62
Scope refine(std::string const &prependedDescription="") const
Definition JaniParser.h:66
ConstantsMap const * constants
Definition JaniParser.h:61