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
447 lines
24 KiB
#include "prism.h"
|
|
#include <storm/storage/prism/Program.h>
|
|
#include <boost/variant.hpp>
|
|
#include <random>
|
|
#include "src/helpers.h"
|
|
#include <storm/storage/expressions/ExpressionManager.h>
|
|
#include <storm/storage/jani/Model.h>
|
|
#include <storm/storage/jani/Property.h>
|
|
#include <storm/generator/NextStateGenerator.h>
|
|
#include <storm/generator/Choice.h>
|
|
#include <storm/generator/StateBehavior.h>
|
|
#include <storm/generator/PrismNextStateGenerator.h>
|
|
#include "storm/exceptions/NotSupportedException.h"
|
|
#include <storm/storage/expressions/SimpleValuation.h>
|
|
#include "storm/exceptions/InvalidTypeException.h"
|
|
#include "storm/exceptions/InvalidStateException.h"
|
|
#include "storm/exceptions/InvalidAccessException.h"
|
|
#include <storm/utility/solver.h>
|
|
|
|
|
|
using namespace storm::prism;
|
|
|
|
template <typename StateType, typename ValueType>
|
|
void define_stateGeneration(py::module& m);
|
|
|
|
void define_prism(py::module& m) {
|
|
py::class_<storm::prism::Program, std::shared_ptr<storm::prism::Program>> program(m, "PrismProgram", "A Prism Program");
|
|
program.def_property_readonly("constants", &Program::getConstants, "Get Program Constants")
|
|
.def_property_readonly("global_boolean_variables", &Program::getGlobalBooleanVariables, "Retrieves the global boolean variables of the program")
|
|
.def_property_readonly("global_integer_variables", &Program::getGlobalIntegerVariables, "Retrieves the global integer variables of the program")
|
|
.def_property_readonly("nr_modules", &storm::prism::Program::getNumberOfModules, "Number of modules")
|
|
.def_property_readonly("modules", &storm::prism::Program::getModules, "Modules in the program")
|
|
.def_property_readonly("model_type", &storm::prism::Program::getModelType, "Model type")
|
|
.def_property_readonly("has_undefined_constants", &storm::prism::Program::hasUndefinedConstants, "Flag if program has undefined constants")
|
|
.def_property_readonly("undefined_constants_are_graph_preserving", &storm::prism::Program::undefinedConstantsAreGraphPreserving, "Flag if the undefined constants do not change the graph structure")
|
|
.def("substitute_constants", &Program::substituteConstants, "Substitute constants within program")
|
|
.def("substitute_formulas", &Program::substituteFormulas, "Substitute formulas within program")
|
|
.def("define_constants", &Program::defineUndefinedConstants, "Define constants")
|
|
.def("restrict_commands", &Program::restrictCommands, "Restrict commands")
|
|
.def("simplify", &Program::simplify, "Simplify")
|
|
.def("used_constants",&Program::usedConstants, "Compute Used Constants")
|
|
.def("get_constant", &Program::getConstant, py::arg("name"))
|
|
.def_property_readonly("reward_models", &Program::getRewardModels, "The defined reward models")
|
|
.def("get_module", [](Program const& prog, std::string const& name) {return prog.getModule(name);}, py::arg("module_name"))
|
|
// TODO the following is a duplicate and should be deprecated.
|
|
.def_property_readonly("hasUndefinedConstants", &Program::hasUndefinedConstants, "Does the program have undefined constants?")
|
|
.def_property_readonly("isDeterministicModel", &Program::isDeterministicModel, "Does the program describe a deterministic model?")
|
|
.def_property_readonly("expression_manager", &Program::getManager, "Get the expression manager for expressions in this program")
|
|
.def("get_synchronizing_action_indices", &Program::getSynchronizingActionIndices, "Get the synchronizing action indices")
|
|
.def("get_action_name", &Program::getActionName, py::arg("action_index"), "Get the action name for a given action index")
|
|
.def("get_module_indices_by_action_index", &Program::getModuleIndicesByActionIndex, py::arg("action_index"), "get all modules that have a particular action index")
|
|
.def_property_readonly("number_of_unlabeled_commands", &Program::getNumberOfUnlabeledCommands, "Gets the number of commands that are not labelled")
|
|
.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()))
|
|
.def("to_jani", [](storm::prism::Program const& program, std::vector<storm::jani::Property> const& properties, bool allVariablesGlobal, std::string suffix) {
|
|
return program.toJani(properties, allVariablesGlobal, suffix);
|
|
}, "Transform to Jani program", py::arg("properties"), py::arg("all_variables_global") = true, py::arg("suffix") = "")
|
|
.def("__str__", &streamToString<storm::prism::Program>)
|
|
.def_property_readonly("variables", &Program::getAllExpressionVariables, "Get all Expression Variables used by the program")
|
|
.def("get_label_expression", [](storm::prism::Program const& program, std::string const& label){
|
|
return program.getLabelExpression(label);
|
|
}, "Get the expression of the given label.", py::arg("label"))
|
|
.def_property_readonly("labels", &Program::getLabels, "Get all labels in the program")
|
|
;
|
|
|
|
py::class_<Module> module(m, "PrismModule", "A module in a Prism program");
|
|
module.def_property_readonly("commands", [](Module const& module) {return module.getCommands();}, "Commands in the module")
|
|
.def_property_readonly("name", &Module::getName, "Name of the module")
|
|
.def_property_readonly("integer_variables", &Module::getIntegerVariables, "All integer Variables of this module")
|
|
.def_property_readonly("boolean_variables", &Module::getBooleanVariables, "All boolean Variables of this module")
|
|
.def("get_integer_variable", &Module::getIntegerVariable, py::arg("variable_name"))
|
|
.def("get_boolean_variable", &Module::getBooleanVariable, py::arg("variable_name"))
|
|
.def("get_command_indices_by_action_index", &Module::getCommandIndicesByActionIndex, py::arg("action_index"))
|
|
.def("__str__", &streamToString<Module>)
|
|
;
|
|
|
|
py::class_<Command> command(m, "PrismCommand", "A command in a Prism program");
|
|
command.def_property_readonly("global_index", &Command::getGlobalIndex, "Get global index")
|
|
.def_property_readonly("labeled", &Command::isLabeled, "Is the command labeled")
|
|
.def_property_readonly("action_index", &Command::getActionIndex, "What is the action index of the command")
|
|
.def_property_readonly("guard_expression", &Command::getGuardExpression, "Get guard expression")
|
|
.def_property_readonly("is_labeled", &Command::isLabeled, "Retrieves whether the command possesses a synchronization label")
|
|
.def_property_readonly("action_name", &Command::getActionName, "Retrieves the action name of this command")
|
|
.def_property_readonly("updates", [](Command const& command) {
|
|
return command.getUpdates();
|
|
}, "Updates in the command")
|
|
.def("__str__", &streamToString<Command>)
|
|
;
|
|
|
|
py::class_<Update> update(m, "PrismUpdate", "An update in a Prism command");
|
|
update.def(py::init<uint_fast64_t, storm::expressions::Expression const&, std::vector<storm::prism::Assignment> const&>())
|
|
.def_property_readonly("assignments", [](Update const& update) {
|
|
return update.getAssignments();
|
|
}, "Assignments in the update")
|
|
.def_property_readonly("probability_expression", &Update::getLikelihoodExpression, "The probability expression for this update")
|
|
.def_property_readonly("global_index", &Update::getGlobalIndex, "Retrieves the global index of the update, that is, a unique index over all modules")
|
|
.def("substitute", &Update::substitute, "Substitutes all identifiers in the update according to the given map")
|
|
.def("simplify", &Update::simplify, "Simplifies the update in various ways (also removes identity assignments)")
|
|
.def("get_assignment", &Update::getAssignment, py::arg("variable_name"), "Retrieves a reference to the assignment for the variable with the given name")
|
|
.def("get_as_variable_to_expression_map", &Update::getAsVariableToExpressionMap, "Creates a mapping representation of this update")
|
|
.def("__str__", &streamToString<Update>)
|
|
;
|
|
|
|
py::class_<Assignment> assignment(m, "PrismAssignment", "An assignment in prism");
|
|
assignment.def(py::init<storm::expressions::Variable const&, storm::expressions::Expression const&>())
|
|
.def_property_readonly("variable", &Assignment::getVariable, "Variable that is updated")
|
|
.def_property_readonly("expression", &Assignment::getExpression, "Expression for the update")
|
|
.def("__str__", &streamToString<Assignment>)
|
|
;
|
|
|
|
py::class_<Label> label(m, "PrismLabel", "A label in prism");
|
|
label.def_property_readonly("name", &Label::getName);
|
|
label.def_property_readonly("expression", &Label::getStatePredicateExpression);
|
|
|
|
// PrismType
|
|
py::enum_<storm::prism::Program::ModelType>(m, "PrismModelType", "Type of the prism model")
|
|
.value("DTMC", storm::prism::Program::ModelType::DTMC)
|
|
.value("CTMC", storm::prism::Program::ModelType::CTMC)
|
|
.value("MDP", storm::prism::Program::ModelType::MDP)
|
|
.value("CTMDP", storm::prism::Program::ModelType::CTMDP)
|
|
.value("MA", storm::prism::Program::ModelType::MA)
|
|
.value("UNDEFINED", storm::prism::Program::ModelType::UNDEFINED)
|
|
;
|
|
|
|
py::class_<Constant, std::shared_ptr<Constant>> constant(m, "PrismConstant", "A constant in a Prism program");
|
|
constant.def_property_readonly("name", &Constant::getName, "Constant name")
|
|
.def_property_readonly("defined", &Constant::isDefined, "Is the constant defined?")
|
|
.def_property_readonly("type", &Constant::getType, "The type of the constant")
|
|
.def_property_readonly("expression_variable", &Constant::getExpressionVariable, "Expression variable")
|
|
.def_property_readonly("definition", &Constant::getExpression, "Defining expression")
|
|
;
|
|
|
|
py::class_<Variable, std::shared_ptr<Variable>> variable(m, "PrismVariable", "A program variable in a Prism program");
|
|
variable.def_property_readonly("name", &Variable::getName, "Variable name")
|
|
.def_property_readonly("initial_value_expression", &Variable::getInitialValueExpression, "The expression represented the initial value of the variable")
|
|
.def_property_readonly("expression_variable", &Variable::getExpressionVariable, "The expression variable corresponding to the variable")
|
|
;
|
|
|
|
py::class_<IntegerVariable, std::shared_ptr<IntegerVariable>> integer_variable(m, "PrismIntegerVariable", variable, "A program integer variable in a Prism program");
|
|
integer_variable.def_property_readonly("lower_bound_expression", &IntegerVariable::getLowerBoundExpression, "The the lower bound expression of this integer variable")
|
|
.def_property_readonly("upper_bound_expression", &IntegerVariable::getUpperBoundExpression, "The the upper bound expression of this integer variable")
|
|
.def("__str__", &streamToString<IntegerVariable>)
|
|
;
|
|
|
|
py::class_<BooleanVariable, std::shared_ptr<BooleanVariable>> boolean_variable(m, "PrismBooleanVariable", variable, "A program boolean variable in a Prism program");
|
|
boolean_variable.def("__str__", &streamToString<BooleanVariable>);
|
|
|
|
py::class_<RewardModel, std::shared_ptr<RewardModel>> rewardModel(m, "PrismRewardModel", "Reward declaration in prism");
|
|
rewardModel.def_property_readonly("name", &RewardModel::getName, "get name of the reward model");
|
|
|
|
//define_stateGeneration<uint32_t, storm::RationalNumber>(m);
|
|
}
|
|
|
|
class ValuationMapping {
|
|
public:
|
|
std::map<storm::expressions::Variable, bool> booleanValues;
|
|
std::map<storm::expressions::Variable, int_fast64_t> integerValues;
|
|
std::map<storm::expressions::Variable, double> rationalValues;
|
|
|
|
ValuationMapping(storm::prism::Program const& program,
|
|
storm::expressions::SimpleValuation valuation) {
|
|
auto const& variables = program.getManager().getVariables();
|
|
for (auto const& variable : variables) {
|
|
if (variable.hasBooleanType()) {
|
|
booleanValues[variable] = valuation.getBooleanValue(variable);
|
|
} else if (variable.hasIntegerType()) {
|
|
integerValues[variable] = valuation.getIntegerValue(variable);
|
|
} else if (variable.hasRationalType()) {
|
|
rationalValues[variable] = valuation.getRationalValue(variable);
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidTypeException,
|
|
"Unexpected variable type.");
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string toString() const {
|
|
std::vector<std::string> strs;
|
|
for (auto const& value : booleanValues) {
|
|
std::stringstream sstr;
|
|
sstr << value.first.getName() + "=";
|
|
sstr << value.second;
|
|
strs.push_back(sstr.str());
|
|
}
|
|
for (auto const& value : integerValues) {
|
|
std::stringstream sstr;
|
|
sstr << value.first.getName() + "=";
|
|
sstr << value.second;
|
|
strs.push_back(sstr.str());
|
|
}
|
|
for (auto const& value : rationalValues) {
|
|
std::stringstream sstr;
|
|
sstr << value.first.getName() + "=";
|
|
sstr << value.second;
|
|
strs.push_back(sstr.str());
|
|
}
|
|
return "[" + boost::join(strs, ",") + "]";
|
|
}
|
|
};
|
|
|
|
template <typename StateType, typename ValueType>
|
|
class GeneratorChoice {
|
|
public:
|
|
typedef std::vector<uint_fast64_t> origins_type;
|
|
typedef std::vector<std::pair<StateType, ValueType>> distribution_type;
|
|
|
|
origins_type origins;
|
|
distribution_type distribution;
|
|
|
|
private:
|
|
static origins_type getOriginsVector(storm::generator::Choice<ValueType, StateType> &choice) {
|
|
auto originsSet = boost::any_cast<storm::storage::FlatSet<uint_fast64_t>>(&choice.getOriginData());
|
|
if (originsSet != nullptr) {
|
|
return origins_type(originsSet->begin(), originsSet->end());
|
|
} else {
|
|
STORM_LOG_THROW(false, storm::exceptions::NotImplementedException,
|
|
"Type of choice origin data (aka "
|
|
<< choice.getOriginData().type().name()
|
|
<< ") is not implemented.");
|
|
}
|
|
}
|
|
|
|
public:
|
|
GeneratorChoice(storm::generator::Choice<ValueType, StateType> &choice) : origins(getOriginsVector(choice)), distribution(choice.begin(), choice.end()) {}
|
|
};
|
|
|
|
template <typename StateType, typename ValueType>
|
|
class StateGenerator {
|
|
public:
|
|
typedef std::unordered_map<StateType, storm::generator::CompressedState> IdToStateMap;
|
|
typedef std::vector<GeneratorChoice<StateType, ValueType>> choice_list_type;
|
|
|
|
private:
|
|
storm::prism::Program const& program;
|
|
storm::generator::PrismNextStateGenerator<ValueType, StateType> generator;
|
|
std::function<StateType (storm::generator::CompressedState const&)> stateToIdCallback;
|
|
// this needs to be below the generator attribute,
|
|
// because its initialization depends on the generator being initialized.
|
|
// #justcppthings
|
|
storm::storage::sparse::StateStorage<StateType> stateStorage;
|
|
bool hasComputedInitialStates = false;
|
|
IdToStateMap stateMap;
|
|
boost::optional<StateType> currentStateIndex;
|
|
|
|
static storm::generator::NextStateGeneratorOptions makeNextStateGeneratorOptions() {
|
|
storm::generator::NextStateGeneratorOptions options;
|
|
options.setBuildChoiceOrigins(true);
|
|
return options;
|
|
}
|
|
|
|
public:
|
|
StateGenerator(storm::prism::Program const& program_) : program(program_), generator(program_, StateGenerator<StateType, ValueType>::makeNextStateGeneratorOptions()), stateStorage(generator.getStateSize()) {
|
|
stateToIdCallback = [this] (storm::generator::CompressedState const& state) -> StateType {
|
|
StateType newIndex = stateStorage.getNumberOfStates();
|
|
std::pair<StateType, std::size_t> indexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex);
|
|
StateType index = indexBucketPair.first;
|
|
stateMap[index] = state;
|
|
return index;
|
|
};
|
|
}
|
|
|
|
StateType loadInitialState() {
|
|
if (!hasComputedInitialStates) {
|
|
stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback);
|
|
hasComputedInitialStates = true;
|
|
}
|
|
STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported.");
|
|
StateType initialStateIndex = stateStorage.initialStateIndices.front();
|
|
load(initialStateIndex);
|
|
return initialStateIndex;
|
|
}
|
|
|
|
void load(StateType stateIndex) {
|
|
if (currentStateIndex && *currentStateIndex == stateIndex) {
|
|
return;
|
|
}
|
|
auto search = stateMap.find(stateIndex);
|
|
if (search == stateMap.end()) {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidAccessException,
|
|
"state id not found");
|
|
}
|
|
generator.load(search->second);
|
|
currentStateIndex = stateIndex;
|
|
}
|
|
|
|
ValuationMapping currentStateToValuation() {
|
|
if (!currentStateIndex) {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidStateException,
|
|
"Initial state not initialized");
|
|
}
|
|
auto valuation = generator.toValuation(stateMap[*currentStateIndex]);
|
|
return ValuationMapping(program, valuation);
|
|
}
|
|
|
|
bool satisfies(storm::expressions::Expression const& expression) {
|
|
return generator.satisfies(expression);
|
|
}
|
|
|
|
storm::generator::StateBehavior<ValueType, StateType> expandBehavior() {
|
|
if (!hasComputedInitialStates) {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidStateException,
|
|
"Initial state not initialized");
|
|
}
|
|
return generator.expand(stateToIdCallback);
|
|
}
|
|
|
|
choice_list_type expand() {
|
|
auto behavior = expandBehavior();
|
|
choice_list_type choices_result;
|
|
for (auto choice : behavior.getChoices()) {
|
|
choices_result.push_back(GeneratorChoice<StateType, ValueType>(choice));
|
|
}
|
|
|
|
return choices_result;
|
|
}
|
|
|
|
bool isTerminal() {
|
|
if (!hasComputedInitialStates) {
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidStateException,
|
|
"Initial state not initialized");
|
|
}
|
|
choice_list_type choices_result;
|
|
auto behavior = generator.expand(stateToIdCallback);
|
|
return behavior.getChoices().empty();
|
|
}
|
|
|
|
};
|
|
|
|
std::map<uint32_t, std::pair<storm::RationalNumber, storm::RationalNumber>> simulate(storm::prism::Program const& program, uint64_t totalSamples, uint64_t maxSteps) {
|
|
using StateType = uint32_t;
|
|
using ValueType = double;
|
|
|
|
StateGenerator<StateType, ValueType> generator(program);
|
|
std::map<StateType, std::pair<storm::RationalNumber, storm::RationalNumber>> result;
|
|
std::set<StateType> visited;
|
|
auto goal = program.getLabelExpression("goal");
|
|
|
|
std::random_device rd;
|
|
std::mt19937 gen(rd());
|
|
std::uniform_real_distribution<> dis(0.0, 1.0);
|
|
|
|
const auto addSample = [&result](StateType state, bool isGoal) {
|
|
auto hitsVisits = result.count(state) != 0 ? result[state] : std::make_pair(0, 0);
|
|
if (isGoal) hitsVisits.first++;
|
|
hitsVisits.second++;
|
|
result[state] = hitsVisits;
|
|
};
|
|
|
|
const auto sampleBehavior = [&gen, &dis](std::vector<storm::generator::Choice<ValueType, StateType>> const& choices) -> StateType {
|
|
ValueType rnd = dis(gen);
|
|
STORM_LOG_THROW(choices.size() == 1, storm::exceptions::InvalidStateException, "nondeterminism");
|
|
auto choice = choices[0];
|
|
for (auto entry : choice) {
|
|
if (rnd <= entry.second) {
|
|
return entry.first;
|
|
}
|
|
rnd -= entry.second;
|
|
}
|
|
STORM_LOG_THROW(false, storm::exceptions::InvalidStateException, "unreachable");
|
|
};
|
|
|
|
for (unsigned int i = 0; i <= totalSamples; i++) {
|
|
StateType state = generator.loadInitialState();
|
|
uint64_t steps = 0;
|
|
bool hitGoal = false;
|
|
visited.clear();
|
|
|
|
while (steps <= maxSteps) {
|
|
steps++;
|
|
visited.insert(state);
|
|
|
|
if (generator.satisfies(goal)) {
|
|
addSample(state, true);
|
|
break;
|
|
}
|
|
|
|
auto behavior = generator.expandBehavior();
|
|
auto choices = behavior.getChoices();
|
|
if (choices.empty()) {
|
|
addSample(state, false);
|
|
break;
|
|
}
|
|
|
|
state = sampleBehavior(choices);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
template <typename StateType, typename ValueType>
|
|
void define_stateGeneration(py::module& m) {
|
|
// 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.");
|
|
// valuation_mapping
|
|
// .def_readonly("boolean_values", &ValuationMapping::booleanValues)
|
|
// .def_readonly("integer_values", &ValuationMapping::integerValues)
|
|
// .def_readonly("rational_values", &ValuationMapping::rationalValues)
|
|
// .def("__str__", &ValuationMapping::toString);
|
|
//
|
|
// py::class_<GeneratorChoice<StateType, ValueType>,
|
|
// std::shared_ptr<GeneratorChoice<StateType, ValueType>>>
|
|
// generator_choice(m, "GeneratorChoice", R"doc(
|
|
// Representation of a choice taken by the generator.
|
|
//
|
|
// :ivar origins: A list of command ids that generated this choice.
|
|
// :vartype origins: List[int]
|
|
// :ivar distribution: The probability distribution of this choice.
|
|
// :vartype distribution: List[Pair[StateId, Probability]]
|
|
// )doc");
|
|
// generator_choice
|
|
// .def_readonly("origins", &GeneratorChoice<StateType, ValueType>::origins)
|
|
// .def_readonly("distribution", &GeneratorChoice<StateType, ValueType>::distribution);
|
|
//
|
|
// py::class_<StateGenerator<StateType, ValueType>, std::shared_ptr<StateGenerator<StateType, ValueType>>> state_generator(m, "StateGenerator", R"doc(
|
|
// Interactively explore states using Storm's PrismNextStateGenerator.
|
|
//
|
|
// :ivar program: A PRISM program.
|
|
// )doc");
|
|
// state_generator
|
|
// .def(py::init<storm::prism::Program const&>())
|
|
// .def("load_initial_state", &StateGenerator<StateType, ValueType>::loadInitialState, R"doc(
|
|
// Loads the (unique) initial state.
|
|
// Multiple initial states are not supported.
|
|
//
|
|
// :rtype: the ID of the initial state.
|
|
// )doc")
|
|
// .def("load", &StateGenerator<StateType, ValueType>::load, R"doc(
|
|
// :param state_id: The ID of the state to load.
|
|
// )doc")
|
|
// .def("current_state_to_valuation", &StateGenerator<StateType, ValueType>::currentStateToValuation, R"doc(
|
|
// Return a valuation for the currently loaded state.
|
|
//
|
|
// :rtype: stormpy.ValuationMapping
|
|
// )doc")
|
|
// .def("current_state_satisfies", &StateGenerator<StateType, ValueType>::satisfies, R"doc(
|
|
// Check if the currently loaded state satisfies the given expression.
|
|
//
|
|
// :param stormpy.Expression expression: The expression to check against.
|
|
// :rtype: bool
|
|
// )doc")
|
|
// .def("expand", &StateGenerator<StateType, ValueType>::expand, R"doc(
|
|
// Expand the currently loaded state and return its successors.
|
|
//
|
|
// :rtype: [GeneratorChoice]
|
|
// )doc");
|
|
//
|
|
// m.def("simulate", &simulate);
|
|
}
|