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

447 lines
24 KiB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
  1. #include "prism.h"
  2. #include <storm/storage/prism/Program.h>
  3. #include <boost/variant.hpp>
  4. #include <random>
  5. #include "src/helpers.h"
  6. #include <storm/storage/expressions/ExpressionManager.h>
  7. #include <storm/storage/jani/Model.h>
  8. #include <storm/storage/jani/Property.h>
  9. #include <storm/generator/NextStateGenerator.h>
  10. #include <storm/generator/Choice.h>
  11. #include <storm/generator/StateBehavior.h>
  12. #include <storm/generator/PrismNextStateGenerator.h>
  13. #include "storm/exceptions/NotSupportedException.h"
  14. #include <storm/storage/expressions/SimpleValuation.h>
  15. #include "storm/exceptions/InvalidTypeException.h"
  16. #include "storm/exceptions/InvalidStateException.h"
  17. #include "storm/exceptions/InvalidAccessException.h"
  18. #include <storm/utility/solver.h>
  19. using namespace storm::prism;
  20. template <typename StateType, typename ValueType>
  21. void define_stateGeneration(py::module& m);
  22. void define_prism(py::module& m) {
  23. py::class_<storm::prism::Program, std::shared_ptr<storm::prism::Program>> program(m, "PrismProgram", "A Prism Program");
  24. program.def_property_readonly("constants", &Program::getConstants, "Get Program Constants")
  25. .def_property_readonly("global_boolean_variables", &Program::getGlobalBooleanVariables, "Retrieves the global boolean variables of the program")
  26. .def_property_readonly("global_integer_variables", &Program::getGlobalIntegerVariables, "Retrieves the global integer variables of the program")
  27. .def_property_readonly("nr_modules", &storm::prism::Program::getNumberOfModules, "Number of modules")
  28. .def_property_readonly("modules", &storm::prism::Program::getModules, "Modules in the program")
  29. .def_property_readonly("model_type", &storm::prism::Program::getModelType, "Model type")
  30. .def_property_readonly("has_undefined_constants", &storm::prism::Program::hasUndefinedConstants, "Flag if program has undefined constants")
  31. .def_property_readonly("undefined_constants_are_graph_preserving", &storm::prism::Program::undefinedConstantsAreGraphPreserving, "Flag if the undefined constants do not change the graph structure")
  32. .def("substitute_constants", &Program::substituteConstants, "Substitute constants within program")
  33. .def("substitute_formulas", &Program::substituteFormulas, "Substitute formulas within program")
  34. .def("define_constants", &Program::defineUndefinedConstants, "Define constants")
  35. .def("restrict_commands", &Program::restrictCommands, "Restrict commands")
  36. .def("simplify", &Program::simplify, "Simplify")
  37. .def("used_constants",&Program::usedConstants, "Compute Used Constants")
  38. .def("get_constant", &Program::getConstant, py::arg("name"))
  39. .def_property_readonly("reward_models", &Program::getRewardModels, "The defined reward models")
  40. .def("get_module", [](Program const& prog, std::string const& name) {return prog.getModule(name);}, py::arg("module_name"))
  41. // TODO the following is a duplicate and should be deprecated.
  42. .def_property_readonly("hasUndefinedConstants", &Program::hasUndefinedConstants, "Does the program have undefined constants?")
  43. .def_property_readonly("isDeterministicModel", &Program::isDeterministicModel, "Does the program describe a deterministic model?")
  44. .def_property_readonly("expression_manager", &Program::getManager, "Get the expression manager for expressions in this program")
  45. .def("get_synchronizing_action_indices", &Program::getSynchronizingActionIndices, "Get the synchronizing action indices")
  46. .def("get_action_name", &Program::getActionName, py::arg("action_index"), "Get the action name for a given action index")
  47. .def("get_module_indices_by_action_index", &Program::getModuleIndicesByActionIndex, py::arg("action_index"), "get all modules that have a particular action index")
  48. .def_property_readonly("number_of_unlabeled_commands", &Program::getNumberOfUnlabeledCommands, "Gets the number of commands that are not labelled")
  49. .def("flatten", &Program::flattenModules, "Put program into a single module", py::arg("smt_factory")=std::shared_ptr<storm::utility::solver::SmtSolverFactory>(new storm::utility::solver::SmtSolverFactory()))
  50. .def("to_jani", [](storm::prism::Program const& program, std::vector<storm::jani::Property> const& properties, bool allVariablesGlobal, std::string suffix) {
  51. return program.toJani(properties, allVariablesGlobal, suffix);
  52. }, "Transform to Jani program", py::arg("properties"), py::arg("all_variables_global") = true, py::arg("suffix") = "")
  53. .def("__str__", &streamToString<storm::prism::Program>)
  54. .def_property_readonly("variables", &Program::getAllExpressionVariables, "Get all Expression Variables used by the program")
  55. .def("get_label_expression", [](storm::prism::Program const& program, std::string const& label){
  56. return program.getLabelExpression(label);
  57. }, "Get the expression of the given label.", py::arg("label"))
  58. .def_property_readonly("labels", &Program::getLabels, "Get all labels in the program")
  59. ;
  60. py::class_<Module> module(m, "PrismModule", "A module in a Prism program");
  61. module.def_property_readonly("commands", [](Module const& module) {return module.getCommands();}, "Commands in the module")
  62. .def_property_readonly("name", &Module::getName, "Name of the module")
  63. .def_property_readonly("integer_variables", &Module::getIntegerVariables, "All integer Variables of this module")
  64. .def_property_readonly("boolean_variables", &Module::getBooleanVariables, "All boolean Variables of this module")
  65. .def("get_integer_variable", &Module::getIntegerVariable, py::arg("variable_name"))
  66. .def("get_boolean_variable", &Module::getBooleanVariable, py::arg("variable_name"))
  67. .def("get_command_indices_by_action_index", &Module::getCommandIndicesByActionIndex, py::arg("action_index"))
  68. .def("__str__", &streamToString<Module>)
  69. ;
  70. py::class_<Command> command(m, "PrismCommand", "A command in a Prism program");
  71. command.def_property_readonly("global_index", &Command::getGlobalIndex, "Get global index")
  72. .def_property_readonly("labeled", &Command::isLabeled, "Is the command labeled")
  73. .def_property_readonly("action_index", &Command::getActionIndex, "What is the action index of the command")
  74. .def_property_readonly("guard_expression", &Command::getGuardExpression, "Get guard expression")
  75. .def_property_readonly("is_labeled", &Command::isLabeled, "Retrieves whether the command possesses a synchronization label")
  76. .def_property_readonly("action_name", &Command::getActionName, "Retrieves the action name of this command")
  77. .def_property_readonly("updates", [](Command const& command) {
  78. return command.getUpdates();
  79. }, "Updates in the command")
  80. .def("__str__", &streamToString<Command>)
  81. ;
  82. py::class_<Update> update(m, "PrismUpdate", "An update in a Prism command");
  83. update.def(py::init<uint_fast64_t, storm::expressions::Expression const&, std::vector<storm::prism::Assignment> const&>())
  84. .def_property_readonly("assignments", [](Update const& update) {
  85. return update.getAssignments();
  86. }, "Assignments in the update")
  87. .def_property_readonly("probability_expression", &Update::getLikelihoodExpression, "The probability expression for this update")
  88. .def_property_readonly("global_index", &Update::getGlobalIndex, "Retrieves the global index of the update, that is, a unique index over all modules")
  89. .def("substitute", &Update::substitute, "Substitutes all identifiers in the update according to the given map")
  90. .def("simplify", &Update::simplify, "Simplifies the update in various ways (also removes identity assignments)")
  91. .def("get_assignment", &Update::getAssignment, py::arg("variable_name"), "Retrieves a reference to the assignment for the variable with the given name")
  92. .def("get_as_variable_to_expression_map", &Update::getAsVariableToExpressionMap, "Creates a mapping representation of this update")
  93. .def("__str__", &streamToString<Update>)
  94. ;
  95. py::class_<Assignment> assignment(m, "PrismAssignment", "An assignment in prism");
  96. assignment.def(py::init<storm::expressions::Variable const&, storm::expressions::Expression const&>())
  97. .def_property_readonly("variable", &Assignment::getVariable, "Variable that is updated")
  98. .def_property_readonly("expression", &Assignment::getExpression, "Expression for the update")
  99. .def("__str__", &streamToString<Assignment>)
  100. ;
  101. py::class_<Label> label(m, "PrismLabel", "A label in prism");
  102. label.def_property_readonly("name", &Label::getName);
  103. label.def_property_readonly("expression", &Label::getStatePredicateExpression);
  104. // PrismType
  105. py::enum_<storm::prism::Program::ModelType>(m, "PrismModelType", "Type of the prism model")
  106. .value("DTMC", storm::prism::Program::ModelType::DTMC)
  107. .value("CTMC", storm::prism::Program::ModelType::CTMC)
  108. .value("MDP", storm::prism::Program::ModelType::MDP)
  109. .value("CTMDP", storm::prism::Program::ModelType::CTMDP)
  110. .value("MA", storm::prism::Program::ModelType::MA)
  111. .value("UNDEFINED", storm::prism::Program::ModelType::UNDEFINED)
  112. ;
  113. py::class_<Constant, std::shared_ptr<Constant>> constant(m, "PrismConstant", "A constant in a Prism program");
  114. constant.def_property_readonly("name", &Constant::getName, "Constant name")
  115. .def_property_readonly("defined", &Constant::isDefined, "Is the constant defined?")
  116. .def_property_readonly("type", &Constant::getType, "The type of the constant")
  117. .def_property_readonly("expression_variable", &Constant::getExpressionVariable, "Expression variable")
  118. .def_property_readonly("definition", &Constant::getExpression, "Defining expression")
  119. ;
  120. py::class_<Variable, std::shared_ptr<Variable>> variable(m, "PrismVariable", "A program variable in a Prism program");
  121. variable.def_property_readonly("name", &Variable::getName, "Variable name")
  122. .def_property_readonly("initial_value_expression", &Variable::getInitialValueExpression, "The expression represented the initial value of the variable")
  123. .def_property_readonly("expression_variable", &Variable::getExpressionVariable, "The expression variable corresponding to the variable")
  124. ;
  125. py::class_<IntegerVariable, std::shared_ptr<IntegerVariable>> integer_variable(m, "PrismIntegerVariable", variable, "A program integer variable in a Prism program");
  126. integer_variable.def_property_readonly("lower_bound_expression", &IntegerVariable::getLowerBoundExpression, "The the lower bound expression of this integer variable")
  127. .def_property_readonly("upper_bound_expression", &IntegerVariable::getUpperBoundExpression, "The the upper bound expression of this integer variable")
  128. .def("__str__", &streamToString<IntegerVariable>)
  129. ;
  130. py::class_<BooleanVariable, std::shared_ptr<BooleanVariable>> boolean_variable(m, "PrismBooleanVariable", variable, "A program boolean variable in a Prism program");
  131. boolean_variable.def("__str__", &streamToString<BooleanVariable>);
  132. py::class_<RewardModel, std::shared_ptr<RewardModel>> rewardModel(m, "PrismRewardModel", "Reward declaration in prism");
  133. rewardModel.def_property_readonly("name", &RewardModel::getName, "get name of the reward model");
  134. //define_stateGeneration<uint32_t, storm::RationalNumber>(m);
  135. }
  136. class ValuationMapping {
  137. public:
  138. std::map<storm::expressions::Variable, bool> booleanValues;
  139. std::map<storm::expressions::Variable, int_fast64_t> integerValues;
  140. std::map<storm::expressions::Variable, double> rationalValues;
  141. ValuationMapping(storm::prism::Program const& program,
  142. storm::expressions::SimpleValuation valuation) {
  143. auto const& variables = program.getManager().getVariables();
  144. for (auto const& variable : variables) {
  145. if (variable.hasBooleanType()) {
  146. booleanValues[variable] = valuation.getBooleanValue(variable);
  147. } else if (variable.hasIntegerType()) {
  148. integerValues[variable] = valuation.getIntegerValue(variable);
  149. } else if (variable.hasRationalType()) {
  150. rationalValues[variable] = valuation.getRationalValue(variable);
  151. } else {
  152. STORM_LOG_THROW(false, storm::exceptions::InvalidTypeException,
  153. "Unexpected variable type.");
  154. }
  155. }
  156. }
  157. std::string toString() const {
  158. std::vector<std::string> strs;
  159. for (auto const& value : booleanValues) {
  160. std::stringstream sstr;
  161. sstr << value.first.getName() + "=";
  162. sstr << value.second;
  163. strs.push_back(sstr.str());
  164. }
  165. for (auto const& value : integerValues) {
  166. std::stringstream sstr;
  167. sstr << value.first.getName() + "=";
  168. sstr << value.second;
  169. strs.push_back(sstr.str());
  170. }
  171. for (auto const& value : rationalValues) {
  172. std::stringstream sstr;
  173. sstr << value.first.getName() + "=";
  174. sstr << value.second;
  175. strs.push_back(sstr.str());
  176. }
  177. return "[" + boost::join(strs, ",") + "]";
  178. }
  179. };
  180. template <typename StateType, typename ValueType>
  181. class GeneratorChoice {
  182. public:
  183. typedef std::vector<uint_fast64_t> origins_type;
  184. typedef std::vector<std::pair<StateType, ValueType>> distribution_type;
  185. origins_type origins;
  186. distribution_type distribution;
  187. private:
  188. static origins_type getOriginsVector(storm::generator::Choice<ValueType, StateType> &choice) {
  189. auto originsSet = boost::any_cast<storm::storage::FlatSet<uint_fast64_t>>(&choice.getOriginData());
  190. if (originsSet != nullptr) {
  191. return origins_type(originsSet->begin(), originsSet->end());
  192. } else {
  193. STORM_LOG_THROW(false, storm::exceptions::NotImplementedException,
  194. "Type of choice origin data (aka "
  195. << choice.getOriginData().type().name()
  196. << ") is not implemented.");
  197. }
  198. }
  199. public:
  200. GeneratorChoice(storm::generator::Choice<ValueType, StateType> &choice) : origins(getOriginsVector(choice)), distribution(choice.begin(), choice.end()) {}
  201. };
  202. template <typename StateType, typename ValueType>
  203. class StateGenerator {
  204. public:
  205. typedef std::unordered_map<StateType, storm::generator::CompressedState> IdToStateMap;
  206. typedef std::vector<GeneratorChoice<StateType, ValueType>> choice_list_type;
  207. private:
  208. storm::prism::Program const& program;
  209. storm::generator::PrismNextStateGenerator<ValueType, StateType> generator;
  210. std::function<StateType (storm::generator::CompressedState const&)> stateToIdCallback;
  211. // this needs to be below the generator attribute,
  212. // because its initialization depends on the generator being initialized.
  213. // #justcppthings
  214. storm::storage::sparse::StateStorage<StateType> stateStorage;
  215. bool hasComputedInitialStates = false;
  216. IdToStateMap stateMap;
  217. boost::optional<StateType> currentStateIndex;
  218. static storm::generator::NextStateGeneratorOptions makeNextStateGeneratorOptions() {
  219. storm::generator::NextStateGeneratorOptions options;
  220. options.setBuildChoiceOrigins(true);
  221. return options;
  222. }
  223. public:
  224. StateGenerator(storm::prism::Program const& program_) : program(program_), generator(program_, StateGenerator<StateType, ValueType>::makeNextStateGeneratorOptions()), stateStorage(generator.getStateSize()) {
  225. stateToIdCallback = [this] (storm::generator::CompressedState const& state) -> StateType {
  226. StateType newIndex = stateStorage.getNumberOfStates();
  227. std::pair<StateType, std::size_t> indexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex);
  228. StateType index = indexBucketPair.first;
  229. stateMap[index] = state;
  230. return index;
  231. };
  232. }
  233. StateType loadInitialState() {
  234. if (!hasComputedInitialStates) {
  235. stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback);
  236. hasComputedInitialStates = true;
  237. }
  238. STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported.");
  239. StateType initialStateIndex = stateStorage.initialStateIndices.front();
  240. load(initialStateIndex);
  241. return initialStateIndex;
  242. }
  243. void load(StateType stateIndex) {
  244. if (currentStateIndex && *currentStateIndex == stateIndex) {
  245. return;
  246. }
  247. auto search = stateMap.find(stateIndex);
  248. if (search == stateMap.end()) {
  249. STORM_LOG_THROW(false, storm::exceptions::InvalidAccessException,
  250. "state id not found");
  251. }
  252. generator.load(search->second);
  253. currentStateIndex = stateIndex;
  254. }
  255. ValuationMapping currentStateToValuation() {
  256. if (!currentStateIndex) {
  257. STORM_LOG_THROW(false, storm::exceptions::InvalidStateException,
  258. "Initial state not initialized");
  259. }
  260. auto valuation = generator.toValuation(stateMap[*currentStateIndex]);
  261. return ValuationMapping(program, valuation);
  262. }
  263. bool satisfies(storm::expressions::Expression const& expression) {
  264. return generator.satisfies(expression);
  265. }
  266. storm::generator::StateBehavior<ValueType, StateType> expandBehavior() {
  267. if (!hasComputedInitialStates) {
  268. STORM_LOG_THROW(false, storm::exceptions::InvalidStateException,
  269. "Initial state not initialized");
  270. }
  271. return generator.expand(stateToIdCallback);
  272. }
  273. choice_list_type expand() {
  274. auto behavior = expandBehavior();
  275. choice_list_type choices_result;
  276. for (auto choice : behavior.getChoices()) {
  277. choices_result.push_back(GeneratorChoice<StateType, ValueType>(choice));
  278. }
  279. return choices_result;
  280. }
  281. bool isTerminal() {
  282. if (!hasComputedInitialStates) {
  283. STORM_LOG_THROW(false, storm::exceptions::InvalidStateException,
  284. "Initial state not initialized");
  285. }
  286. choice_list_type choices_result;
  287. auto behavior = generator.expand(stateToIdCallback);
  288. return behavior.getChoices().empty();
  289. }
  290. };
  291. std::map<uint32_t, std::pair<storm::RationalNumber, storm::RationalNumber>> simulate(storm::prism::Program const& program, uint64_t totalSamples, uint64_t maxSteps) {
  292. using StateType = uint32_t;
  293. using ValueType = double;
  294. StateGenerator<StateType, ValueType> generator(program);
  295. std::map<StateType, std::pair<storm::RationalNumber, storm::RationalNumber>> result;
  296. std::set<StateType> visited;
  297. auto goal = program.getLabelExpression("goal");
  298. std::random_device rd;
  299. std::mt19937 gen(rd());
  300. std::uniform_real_distribution<> dis(0.0, 1.0);
  301. const auto addSample = [&result](StateType state, bool isGoal) {
  302. auto hitsVisits = result.count(state) != 0 ? result[state] : std::make_pair(0, 0);
  303. if (isGoal) hitsVisits.first++;
  304. hitsVisits.second++;
  305. result[state] = hitsVisits;
  306. };
  307. const auto sampleBehavior = [&gen, &dis](std::vector<storm::generator::Choice<ValueType, StateType>> const& choices) -> StateType {
  308. ValueType rnd = dis(gen);
  309. STORM_LOG_THROW(choices.size() == 1, storm::exceptions::InvalidStateException, "nondeterminism");
  310. auto choice = choices[0];
  311. for (auto entry : choice) {
  312. if (rnd <= entry.second) {
  313. return entry.first;
  314. }
  315. rnd -= entry.second;
  316. }
  317. STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "unreachable");
  318. };
  319. for (unsigned int i = 0; i <= totalSamples; i++) {
  320. StateType state = generator.loadInitialState();
  321. uint64_t steps = 0;
  322. bool hitGoal = false;
  323. visited.clear();
  324. while (steps <= maxSteps) {
  325. steps++;
  326. visited.insert(state);
  327. if (generator.satisfies(goal)) {
  328. addSample(state, true);
  329. break;
  330. }
  331. auto behavior = generator.expandBehavior();
  332. auto choices = behavior.getChoices();
  333. if (choices.empty()) {
  334. addSample(state, false);
  335. break;
  336. }
  337. state = sampleBehavior(choices);
  338. }
  339. }
  340. return result;
  341. }
  342. template <typename StateType, typename ValueType>
  343. void define_stateGeneration(py::module& m) {
  344. // py::class_<ValuationMapping, std::shared_ptr<ValuationMapping>> valuation_mapping(m, "ValuationMapping", "A valuation mapping for a state consists of a mapping from variable to value for each of the three types.");
  345. // valuation_mapping
  346. // .def_readonly("boolean_values", &ValuationMapping::booleanValues)
  347. // .def_readonly("integer_values", &ValuationMapping::integerValues)
  348. // .def_readonly("rational_values", &ValuationMapping::rationalValues)
  349. // .def("__str__", &ValuationMapping::toString);
  350. //
  351. // py::class_<GeneratorChoice<StateType, ValueType>,
  352. // std::shared_ptr<GeneratorChoice<StateType, ValueType>>>
  353. // generator_choice(m, "GeneratorChoice", R"doc(
  354. // Representation of a choice taken by the generator.
  355. //
  356. // :ivar origins: A list of command ids that generated this choice.
  357. // :vartype origins: List[int]
  358. // :ivar distribution: The probability distribution of this choice.
  359. // :vartype distribution: List[Pair[StateId, Probability]]
  360. // )doc");
  361. // generator_choice
  362. // .def_readonly("origins", &GeneratorChoice<StateType, ValueType>::origins)
  363. // .def_readonly("distribution", &GeneratorChoice<StateType, ValueType>::distribution);
  364. //
  365. // py::class_<StateGenerator<StateType, ValueType>, std::shared_ptr<StateGenerator<StateType, ValueType>>> state_generator(m, "StateGenerator", R"doc(
  366. // Interactively explore states using Storm's PrismNextStateGenerator.
  367. //
  368. // :ivar program: A PRISM program.
  369. // )doc");
  370. // state_generator
  371. // .def(py::init<storm::prism::Program const&>())
  372. // .def("load_initial_state", &StateGenerator<StateType, ValueType>::loadInitialState, R"doc(
  373. // Loads the (unique) initial state.
  374. // Multiple initial states are not supported.
  375. //
  376. // :rtype: the ID of the initial state.
  377. // )doc")
  378. // .def("load", &StateGenerator<StateType, ValueType>::load, R"doc(
  379. // :param state_id: The ID of the state to load.
  380. // )doc")
  381. // .def("current_state_to_valuation", &StateGenerator<StateType, ValueType>::currentStateToValuation, R"doc(
  382. // Return a valuation for the currently loaded state.
  383. //
  384. // :rtype: stormpy.ValuationMapping
  385. // )doc")
  386. // .def("current_state_satisfies", &StateGenerator<StateType, ValueType>::satisfies, R"doc(
  387. // Check if the currently loaded state satisfies the given expression.
  388. //
  389. // :param stormpy.Expression expression: The expression to check against.
  390. // :rtype: bool
  391. // )doc")
  392. // .def("expand", &StateGenerator<StateType, ValueType>::expand, R"doc(
  393. // Expand the currently loaded state and return its successors.
  394. //
  395. // :rtype: [GeneratorChoice]
  396. // )doc");
  397. //
  398. // m.def("simulate", &simulate);
  399. }