276 lines
15 KiB
276 lines
15 KiB
#include "storm-parsers/parser/ImcaMarkovAutomatonParser.h"
|
|
|
|
#include "storm/settings/SettingsManager.h"
|
|
#include "storm/settings/modules/BuildSettings.h"
|
|
#include "storm/settings/modules/CoreSettings.h"
|
|
#include "storm/utility/file.h"
|
|
#include "storm/utility/builder.h"
|
|
|
|
namespace storm {
|
|
namespace parser {
|
|
|
|
template <typename ValueType, typename StateType>
|
|
ImcaParserGrammar<ValueType, StateType>::ImcaParserGrammar() : ImcaParserGrammar<ValueType, StateType>::base_type(start), numStates(0), numChoices(0), numTransitions(0), hasStateReward(false), hasActionReward(false) {
|
|
buildChoiceLabels = storm::settings::getModule<storm::settings::modules::BuildSettings>().isBuildChoiceLabelsSet();
|
|
initialize();
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
void ImcaParserGrammar<ValueType, StateType>::initialize() {
|
|
value = qi::double_[qi::_val = qi::_1];
|
|
value.name("value");
|
|
|
|
// We assume here that imca files are alphanumeric strings, If we restrict ourselves to the 's12345' representation, we could also do:
|
|
// state = (qi::lit("s") > qi::ulong_)[qi::_val = qi::_1];
|
|
state = qi::as_string[qi::raw[qi::lexeme[(qi::alnum | qi::char_('_')) % qi::eps]]][qi::_val = phoenix::bind(&ImcaParserGrammar<ValueType, StateType>::getStateIndex, phoenix::ref(*this), qi::_1)];
|
|
state.name("state");
|
|
|
|
reward = (-qi::lit("R") >> value)[qi::_val = qi::_1];
|
|
reward.name("reward");
|
|
|
|
transition = (qi::lit("*") >> state >> value)[qi::_val = phoenix::bind(&ImcaParserGrammar<ValueType, StateType>::createStateValuePair, phoenix::ref(*this), qi::_1, qi::_2)];
|
|
transition.name("transition");
|
|
|
|
choicelabel = qi::as_string[qi::raw[qi::lexeme[((qi::alpha | qi::char_('_')) >> *(qi::alnum | qi::char_('_')) | qi::lit("!"))]]];
|
|
choicelabel.name("choicelabel");
|
|
|
|
choice = (state >> choicelabel >> -reward >> *(transition >> qi::eps))[phoenix::bind(&ImcaParserGrammar<ValueType, StateType>::addChoiceToStateBehavior, phoenix::ref(*this), qi::_1, qi::_2, qi::_4, qi::_3)];
|
|
choice.name("choice");
|
|
|
|
transitions = qi::lit("#TRANSITIONS") >> *(choice);
|
|
transitions.name("TRANSITIONS");
|
|
|
|
initials = qi::lit("#INITIALS") >> *((state >> qi::eps)[phoenix::bind(&ImcaParserGrammar<ValueType, StateType>::addInitialState, phoenix::ref(*this), qi::_1)]);
|
|
initials.name("INITIALS");
|
|
|
|
goals = qi::lit("#GOALS") >> *((state >> qi::eps)[phoenix::bind(&ImcaParserGrammar<ValueType, StateType>::addGoalState, phoenix::ref(*this), qi::_1)]);
|
|
goals.name("GOALS");
|
|
|
|
start = (initials >> goals >> transitions)[qi::_val = phoenix::bind(&ImcaParserGrammar<ValueType, StateType>::createModelComponents, phoenix::ref(*this))];
|
|
start.name("start");
|
|
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
StateType ImcaParserGrammar<ValueType, StateType>::getStateIndex(std::string const& stateString) {
|
|
auto it = stateIndices.find(stateString);
|
|
if (it == stateIndices.end()) {
|
|
this->stateIndices.emplace_hint(it, stateString, numStates);
|
|
++numStates;
|
|
initialStates.grow(numStates);
|
|
goalStates.grow(numStates);
|
|
markovianStates.grow(numStates);
|
|
stateBehaviors.resize(numStates);
|
|
return numStates - 1;
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
std::pair<StateType, ValueType> ImcaParserGrammar<ValueType, StateType>::createStateValuePair(StateType const& state, ValueType const& value) {
|
|
return std::pair<StateType, ValueType>(state, value);
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
void ImcaParserGrammar<ValueType, StateType>::addInitialState(StateType const& state) {
|
|
initialStates.set(state);
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
void ImcaParserGrammar<ValueType, StateType>::addGoalState(StateType const& state) {
|
|
goalStates.set(state);
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
void ImcaParserGrammar<ValueType, StateType>::addChoiceToStateBehavior(StateType const& state, std::string const& label, std::vector<std::pair<StateType, ValueType>> const& transitions, boost::optional<ValueType> const& reward) {
|
|
bool isMarkovian = label == "!";
|
|
storm::generator::Choice<ValueType, StateType> choice(0, isMarkovian);
|
|
STORM_LOG_THROW(!transitions.empty(), storm::exceptions::WrongFormatException, "Empty choice defined for state s" << state << ".");
|
|
if (buildChoiceLabels && !isMarkovian) {
|
|
choice.addLabel(label);
|
|
}
|
|
if (reward && !isMarkovian) {
|
|
hasActionReward = true;
|
|
choice.addReward(reward.get());
|
|
}
|
|
for (auto const& t : transitions) {
|
|
STORM_LOG_THROW(t.second > storm::utility::zero<ValueType>(), storm::exceptions::WrongFormatException, "Probabilities and rates have to be positive. got " << t.second << " at state s" << state << ".");
|
|
choice.addProbability(t.first, t.second);
|
|
}
|
|
STORM_LOG_THROW(isMarkovian || storm::utility::isOne(choice.getTotalMass()), storm::exceptions::WrongFormatException, "Probability for choice " << label << " on state s" << state << " does not sum up to one.");
|
|
|
|
++numChoices;
|
|
numTransitions += choice.size();
|
|
auto& behavior = stateBehaviors[state];
|
|
behavior.setExpanded(true);
|
|
behavior.addChoice(std::move(choice));
|
|
if (isMarkovian) {
|
|
markovianStates.set(state);
|
|
if (reward) {
|
|
hasStateReward = true;
|
|
behavior.addStateReward(reward.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename ValueType, typename StateType>
|
|
storm::storage::sparse::ModelComponents<ValueType> ImcaParserGrammar<ValueType, StateType>::createModelComponents() {
|
|
|
|
// Prepare the statelabeling
|
|
initialStates.resize(numStates);
|
|
goalStates.resize(numStates);
|
|
markovianStates.resize(numStates);
|
|
storm::models::sparse::StateLabeling stateLabeling(numStates);
|
|
stateLabeling.addLabel("init", std::move(initialStates));
|
|
stateLabeling.addLabel("goal", std::move(goalStates));
|
|
|
|
// Fix deadlocks (if required)
|
|
assert(stateBehaviors.size() == numStates);
|
|
if (!storm::settings::getModule<storm::settings::modules::CoreSettings>().isDontFixDeadlocksSet()) {
|
|
StateType state = 0;
|
|
for (auto& behavior : stateBehaviors) {
|
|
if (!behavior.wasExpanded()) {
|
|
storm::generator::Choice<ValueType, StateType> choice(0, true);
|
|
choice.addProbability(state, storm::utility::one<ValueType>());
|
|
behavior.setExpanded(true);
|
|
behavior.addChoice(std::move(choice));
|
|
markovianStates.set(state);
|
|
++numChoices;
|
|
++numTransitions;
|
|
}
|
|
++state;
|
|
}
|
|
}
|
|
|
|
// Build the transition matrix together with exit rates, reward models, and choice labeling
|
|
storm::storage::SparseMatrixBuilder<ValueType> matrixBuilder(numChoices, numStates, numTransitions, true, true, numStates);
|
|
std::vector<ValueType> exitRates;
|
|
exitRates.reserve(numStates);
|
|
boost::optional<std::vector<ValueType>> stateRewards, actionRewards;
|
|
if (hasStateReward) {
|
|
stateRewards = std::vector<ValueType>(numStates, storm::utility::zero<ValueType>());
|
|
}
|
|
if (hasActionReward) {
|
|
actionRewards = std::vector<ValueType>(numChoices, storm::utility::zero<ValueType>());
|
|
}
|
|
boost::optional<storm::models::sparse::ChoiceLabeling> choiceLabeling;
|
|
if (buildChoiceLabels) {
|
|
choiceLabeling = storm::models::sparse::ChoiceLabeling(numChoices);
|
|
}
|
|
StateType state = 0;
|
|
StateType row = 0;
|
|
for (auto const& behavior : stateBehaviors) {
|
|
matrixBuilder.newRowGroup(row);
|
|
if (!behavior.getStateRewards().empty()) {
|
|
assert(behavior.getStateRewards().size() == 1);
|
|
stateRewards.get()[state] = behavior.getStateRewards().front();
|
|
}
|
|
if (markovianStates.get(state)) {
|
|
//For Markovian states, the Markovian choice has to be the first one in the resulting transition matrix.
|
|
bool markovianChoiceFound = false;
|
|
for (auto const& choice : behavior) {
|
|
if (choice.isMarkovian()) {
|
|
STORM_LOG_THROW(!markovianChoiceFound, storm::exceptions::WrongFormatException, "Multiple Markovian choices defined for state " << state << ".");
|
|
markovianChoiceFound = true;
|
|
if (!choice.getRewards().empty()) {
|
|
assert(choice.getRewards().size() == 1);
|
|
actionRewards.get()[row] = choice.getRewards().front();
|
|
}
|
|
if (buildChoiceLabels && choice.hasLabels()) {
|
|
assert(choice.getLabels().size() == 1);
|
|
std::string const& label = *choice.getLabels().begin();
|
|
if (!choiceLabeling->containsLabel(label)) {
|
|
choiceLabeling->addLabel(label);
|
|
}
|
|
choiceLabeling->addLabelToChoice(label, row);
|
|
}
|
|
exitRates.push_back(choice.getTotalMass());
|
|
for (auto const& transition : choice) {
|
|
matrixBuilder.addNextValue(row, transition.first, static_cast<ValueType>(transition.second / exitRates.back()));
|
|
}
|
|
++row;
|
|
}
|
|
}
|
|
} else {
|
|
exitRates.push_back(storm::utility::zero<ValueType>());
|
|
}
|
|
// Now add all probabilistic choices.
|
|
for (auto const& choice : behavior) {
|
|
if (!choice.isMarkovian()) {
|
|
if (!choice.getRewards().empty()) {
|
|
assert(choice.getRewards().size() == 1);
|
|
actionRewards.get()[row] = choice.getRewards().front();
|
|
}
|
|
if (buildChoiceLabels && choice.hasLabels()) {
|
|
assert(choice.getLabels().size() == 1);
|
|
std::string const& label = *choice.getLabels().begin();
|
|
if (!choiceLabeling->containsLabel(label)) {
|
|
choiceLabeling->addLabel(label);
|
|
}
|
|
choiceLabeling->addLabelToChoice(label, row);
|
|
}
|
|
for (auto const& transition : choice) {
|
|
matrixBuilder.addNextValue(row, transition.first, transition.second);
|
|
}
|
|
++row;
|
|
}
|
|
}
|
|
++state;
|
|
}
|
|
|
|
// Finalize the model components
|
|
std::unordered_map<std::string, storm::models::sparse::StandardRewardModel<ValueType>> rewardModel;
|
|
if (hasStateReward || hasActionReward) {
|
|
rewardModel.insert(std::make_pair("", storm::models::sparse::StandardRewardModel<ValueType>(stateRewards, actionRewards)));
|
|
}
|
|
storm::storage::sparse::ModelComponents<ValueType> components(matrixBuilder.build(), std::move(stateLabeling), std::move(rewardModel), false, std::move(markovianStates));
|
|
components.exitRates = std::move(exitRates);
|
|
components.choiceLabeling = std::move(choiceLabeling);
|
|
|
|
return components;
|
|
}
|
|
|
|
template<typename ValueType>
|
|
std::shared_ptr<storm::models::sparse::MarkovAutomaton<ValueType>> ImcaMarkovAutomatonParser<ValueType>::parseImcaFile(std::string const& filename) {
|
|
// Open file and initialize result.
|
|
std::ifstream inputFileStream;
|
|
storm::utility::openFile(filename, inputFileStream);
|
|
|
|
storm::storage::sparse::ModelComponents<ValueType> components;
|
|
|
|
// Now try to parse the contents of the file.
|
|
std::string fileContent((std::istreambuf_iterator<char>(inputFileStream)), (std::istreambuf_iterator<char>()));
|
|
PositionIteratorType first(fileContent.begin());
|
|
PositionIteratorType iter = first;
|
|
PositionIteratorType last(fileContent.end());
|
|
|
|
try {
|
|
// Start parsing.
|
|
ImcaParserGrammar<ValueType> grammar;
|
|
bool succeeded = qi::phrase_parse(iter, last, grammar, storm::spirit_encoding::space_type() | qi::lit("//") >> *(qi::char_ - (qi::eol | qi::eoi)) >> (qi::eol | qi::eoi), components);
|
|
STORM_LOG_THROW(succeeded, storm::exceptions::WrongFormatException, "Could not parse imca file.");
|
|
STORM_LOG_DEBUG("Parsed imca file successfully.");
|
|
} catch (qi::expectation_failure<PositionIteratorType> const& e) {
|
|
STORM_LOG_THROW(false, storm::exceptions::WrongFormatException, e.what_);
|
|
storm::utility::closeFile(inputFileStream);
|
|
} catch(std::exception& e) {
|
|
// In case of an exception properly close the file before passing exception.
|
|
storm::utility::closeFile(inputFileStream);
|
|
throw e;
|
|
}
|
|
|
|
// Close the stream in case everything went smoothly
|
|
storm::utility::closeFile(inputFileStream);
|
|
|
|
// Build the model from the obtained model components
|
|
return storm::utility::builder::buildModelFromComponents(storm::models::ModelType::MarkovAutomaton, std::move(components))->template as<storm::models::sparse::MarkovAutomaton<ValueType>>();
|
|
}
|
|
|
|
|
|
|
|
template class ImcaParserGrammar<double>;
|
|
template class ImcaMarkovAutomatonParser<double>;
|
|
} // namespace parser
|
|
} // namespace storm
|
|
|