#include "storm/utility/vector.h" #include "storm/storage/Scheduler.h" #include "storm/utility/macros.h" #include "storm/adapters/JsonAdapter.h" #include "storm/exceptions/NotImplementedException.h" #include <boost/algorithm/string/join.hpp> namespace storm { namespace storage { template <typename ValueType> Scheduler<ValueType>::Scheduler(uint_fast64_t numberOfModelStates, boost::optional<storm::storage::MemoryStructure> const& memoryStructure) : memoryStructure(memoryStructure) { uint_fast64_t numOfMemoryStates = memoryStructure ? memoryStructure->getNumberOfStates() : 1; schedulerChoices = std::vector<std::vector<SchedulerChoice<ValueType>>>(numOfMemoryStates, std::vector<SchedulerChoice<ValueType>>(numberOfModelStates)); dontCareStates = std::vector<storm::storage::BitVector>(numOfMemoryStates, storm::storage::BitVector(numberOfModelStates, false)); numOfUndefinedChoices = numOfMemoryStates * numberOfModelStates; numOfDeterministicChoices = 0; numOfDontCareStates = 0; } template <typename ValueType> Scheduler<ValueType>::Scheduler(uint_fast64_t numberOfModelStates, boost::optional<storm::storage::MemoryStructure>&& memoryStructure) : memoryStructure(std::move(memoryStructure)) { uint_fast64_t numOfMemoryStates = this->memoryStructure ? this->memoryStructure->getNumberOfStates() : 1; schedulerChoices = std::vector<std::vector<SchedulerChoice<ValueType>>>(numOfMemoryStates, std::vector<SchedulerChoice<ValueType>>(numberOfModelStates)); dontCareStates = std::vector<storm::storage::BitVector>(numOfMemoryStates, storm::storage::BitVector(numberOfModelStates, false)); numOfUndefinedChoices = numOfMemoryStates * numberOfModelStates; numOfDeterministicChoices = 0; numOfDontCareStates = 0; } template <typename ValueType> void Scheduler<ValueType>::setChoice(SchedulerChoice<ValueType> const& choice, uint_fast64_t modelState, uint_fast64_t memoryState) { STORM_LOG_ASSERT(memoryState < getNumberOfMemoryStates(), "Illegal memory state index"); STORM_LOG_ASSERT(modelState < schedulerChoices[memoryState].size(), "Illegal model state index"); auto& schedulerChoice = schedulerChoices[memoryState][modelState]; if (schedulerChoice.isDefined()) { if (!choice.isDefined()) { ++numOfUndefinedChoices; } } else { if (choice.isDefined()) { assert(numOfUndefinedChoices > 0); --numOfUndefinedChoices; } } if (schedulerChoice.isDeterministic()) { if (!choice.isDeterministic()) { assert(numOfDeterministicChoices > 0); --numOfDeterministicChoices; } } else { if (choice.isDeterministic()) { ++numOfDeterministicChoices; } } schedulerChoice = choice; } template <typename ValueType> bool Scheduler<ValueType>::isChoiceSelected(BitVector const& selectedStates, uint64_t memoryState) const { for (auto selectedState : selectedStates) { auto& schedulerChoice = schedulerChoices[memoryState][selectedState]; if (!schedulerChoice.isDefined()) { return false; } } return true; } template <typename ValueType> void Scheduler<ValueType>::clearChoice(uint_fast64_t modelState, uint_fast64_t memoryState) { STORM_LOG_ASSERT(memoryState < getNumberOfMemoryStates(), "Illegal memory state index"); STORM_LOG_ASSERT(modelState < schedulerChoices[memoryState].size(), "Illegal model state index"); setChoice(SchedulerChoice<ValueType>(), modelState, memoryState); } template <typename ValueType> SchedulerChoice<ValueType> const& Scheduler<ValueType>::getChoice(uint_fast64_t modelState, uint_fast64_t memoryState) const { STORM_LOG_ASSERT(memoryState < getNumberOfMemoryStates(), "Illegal memory state index"); STORM_LOG_ASSERT(modelState < schedulerChoices[memoryState].size(), "Illegal model state index"); return schedulerChoices[memoryState][modelState]; } template <typename ValueType> void Scheduler<ValueType>::setDontCare(uint_fast64_t modelState, uint_fast64_t memoryState, bool setArbitraryChoice) { STORM_LOG_ASSERT(memoryState < getNumberOfMemoryStates(), "Illegal memory state index"); STORM_LOG_ASSERT(modelState < schedulerChoices[memoryState].size(), "Illegal model state index"); if (!dontCareStates[memoryState].get(modelState)) { auto& schedulerChoice = schedulerChoices[memoryState][modelState]; if (!schedulerChoice.isDefined() && setArbitraryChoice) { // Set an arbitrary choice this->setChoice(0, modelState, memoryState); } dontCareStates[memoryState].set(modelState, true); ++numOfDontCareStates; } } template <typename ValueType> void Scheduler<ValueType>::unSetDontCare(uint_fast64_t modelState, uint_fast64_t memoryState) { STORM_LOG_ASSERT(memoryState < getNumberOfMemoryStates(), "Illegal memory state index"); STORM_LOG_ASSERT(modelState < schedulerChoices[memoryState].size(), "Illegal model state index"); if (dontCareStates[memoryState].get(modelState)) { dontCareStates[memoryState].set(modelState, false); --numOfDontCareStates; } } template <typename ValueType> bool Scheduler<ValueType>::isDontCare(uint_fast64_t modelState, uint64_t memoryState) const { return dontCareStates[memoryState].get(modelState); } template<typename ValueType> storm::storage::BitVector Scheduler<ValueType>::computeActionSupport(std::vector<uint_fast64_t> const& nondeterministicChoiceIndices) const { auto nrActions = nondeterministicChoiceIndices.back(); storm::storage::BitVector result(nrActions); for (auto const& choicesPerMemoryNode : schedulerChoices) { STORM_LOG_ASSERT(nondeterministicChoiceIndices.size()-2 < choicesPerMemoryNode.size(), "Illegal model state index"); for (uint64_t stateId = 0; stateId < nondeterministicChoiceIndices.size()-1; ++stateId) { for (auto const& schedChoice : choicesPerMemoryNode[stateId].getChoiceAsDistribution()) { STORM_LOG_ASSERT(schedChoice.first < nondeterministicChoiceIndices[stateId+1] - nondeterministicChoiceIndices[stateId], "Scheduler chooses action indexed " << schedChoice.first << " in state id " << stateId << " but state contains only " << nondeterministicChoiceIndices[stateId+1] - nondeterministicChoiceIndices[stateId] << " choices ."); result.set(nondeterministicChoiceIndices[stateId] + schedChoice.first); } } } return result; } template <typename ValueType> bool Scheduler<ValueType>::isPartialScheduler() const { return numOfUndefinedChoices != 0; } template <typename ValueType> bool Scheduler<ValueType>::isDeterministicScheduler() const { return numOfDeterministicChoices == (schedulerChoices.size() * schedulerChoices.begin()->size()) - numOfUndefinedChoices; } template <typename ValueType> bool Scheduler<ValueType>::isMemorylessScheduler() const { return getNumberOfMemoryStates() == 1; } template <typename ValueType> uint_fast64_t Scheduler<ValueType>::getNumberOfMemoryStates() const { return memoryStructure ? memoryStructure->getNumberOfStates() : 1; } template <typename ValueType> boost::optional<storm::storage::MemoryStructure> const& Scheduler<ValueType>::getMemoryStructure() const { return memoryStructure; } template <typename ValueType> void Scheduler<ValueType>::printToStream(std::ostream& out, std::shared_ptr<storm::models::sparse::Model<ValueType>> model, bool skipUniqueChoices, bool skipDontCareStates) const { STORM_LOG_THROW(model == nullptr || model->getNumberOfStates() == schedulerChoices.front().size(), storm::exceptions::InvalidOperationException, "The given model is not compatible with this scheduler."); bool const stateValuationsGiven = model != nullptr && model->hasStateValuations(); bool const choiceLabelsGiven = model != nullptr && model->hasChoiceLabeling(); bool const choiceOriginsGiven = model != nullptr && model->hasChoiceOrigins(); uint_fast64_t widthOfStates = std::to_string(schedulerChoices.front().size()).length(); if (stateValuationsGiven) { widthOfStates += model->getStateValuations().getStateInfo(schedulerChoices.front().size() - 1).length() + 5; } widthOfStates = std::max(widthOfStates, (uint_fast64_t)12); uint_fast64_t numOfSkippedStatesWithUniqueChoice = 0; out << "___________________________________________________________________" << std::endl; out << (isPartialScheduler() ? "Partially" : "Fully") << " defined "; out << (isMemorylessScheduler() ? "memoryless " : ""); out << (isDeterministicScheduler() ? "deterministic" : "randomized") << " scheduler"; if (!isMemorylessScheduler()) { out << " with " << getNumberOfMemoryStates() << " memory states"; } out << ":" << std::endl; STORM_LOG_WARN_COND(!(skipUniqueChoices && model == nullptr), "Can not skip unique choices if the model is not given."); out << std::setw(widthOfStates) << "model state:" << " " << (isMemorylessScheduler() ? "" : " memory: ") << "choice(s)" << (isMemorylessScheduler() ? "" : " memory updates: ") << std::endl; for (uint_fast64_t state = 0; state < schedulerChoices.front().size(); ++state) { std::stringstream stateString; // Check whether the state is skipped if (skipUniqueChoices && model != nullptr && model->getTransitionMatrix().getRowGroupSize(state) == 1) { ++numOfSkippedStatesWithUniqueChoice; continue; } // Print the state info if (stateValuationsGiven) { stateString << std::setw(widthOfStates) << (std::to_string(state) + ": " + model->getStateValuations().getStateInfo(state)); } else { stateString << std::setw(widthOfStates) << state; } stateString << " "; bool firstMemoryState = true; for (uint_fast64_t memoryState = 0; memoryState < getNumberOfMemoryStates(); ++memoryState) { // Ignore dontCare states if(skipDontCareStates && isDontCare(state, memoryState)) { continue; } // Indent if this is not the first memory state if (firstMemoryState) { firstMemoryState = false; } else { stateString << std::setw(widthOfStates) << ""; stateString << " "; } // Print the memory state info if (!isMemorylessScheduler()) { stateString << "m" << std::setw(8) << memoryState; } stateString << " "; // Print choice info SchedulerChoice<ValueType> const& choice = schedulerChoices[memoryState][state]; if (choice.isDefined()) { if (choice.isDeterministic()) { if (choiceOriginsGiven) { stateString << model->getChoiceOrigins()->getChoiceInfo(model->getTransitionMatrix().getRowGroupIndices()[state] + choice.getDeterministicChoice()); } else { stateString << choice.getDeterministicChoice(); } if (choiceLabelsGiven) { auto choiceLabels = model->getChoiceLabeling().getLabelsOfChoice(model->getTransitionMatrix().getRowGroupIndices()[state] + choice.getDeterministicChoice()); stateString << " {" << boost::join(choiceLabels, ", ") << "}"; } } else { bool firstChoice = true; for (auto const& choiceProbPair : choice.getChoiceAsDistribution()) { if (firstChoice) { firstChoice = false; } else { stateString << " + "; } stateString << choiceProbPair.second << ": ("; if (choiceOriginsGiven) { stateString << model->getChoiceOrigins()->getChoiceInfo(model->getTransitionMatrix().getRowGroupIndices()[state] + choiceProbPair.first); } else { stateString << choiceProbPair.first; } if (choiceLabelsGiven) { auto choiceLabels = model->getChoiceLabeling().getLabelsOfChoice(model->getTransitionMatrix().getRowGroupIndices()[state] + choiceProbPair.first); stateString << " {" << boost::join(choiceLabels, ", ") << "}"; } stateString << ")"; } } } else { if(!printUndefinedChoices) continue; stateString << "undefined."; } // Print memory updates if(!isMemorylessScheduler()) { stateString << std::setw(widthOfStates) << ""; // The memory updates do not depend on the actual choice, they only depend on the current model- and memory state as well as the successor model state. for (auto const& choiceProbPair : choice.getChoiceAsDistribution()) { uint64_t row = model->getTransitionMatrix().getRowGroupIndices()[state] + choiceProbPair.first; bool firstUpdate = true; for (auto entryIt = model->getTransitionMatrix().getRow(row).begin(); entryIt < model->getTransitionMatrix().getRow(row).end(); ++entryIt) { if (firstUpdate) { firstUpdate = false; } else { stateString << ", "; } stateString << "model state' = " << entryIt->getColumn() << ": -> " << "(m' = "<<this->memoryStructure->getSuccessorMemoryState(memoryState, entryIt - model->getTransitionMatrix().begin()) <<")"; } } } out << stateString.str(); out << std::endl; } } if (numOfSkippedStatesWithUniqueChoice > 0) { out << "Skipped " << numOfSkippedStatesWithUniqueChoice << " deterministic states with unique choice." << std::endl; } out << "___________________________________________________________________" << std::endl; } template <> void Scheduler<float>::printJsonToStream(std::ostream& out, std::shared_ptr<storm::models::sparse::Model<float>> model, bool skipUniqueChoices, bool skipDontCareStates) const { STORM_LOG_THROW(isMemorylessScheduler(), storm::exceptions::NotImplementedException, "Json export of schedulers not implemented for this value type."); } template <typename ValueType> void Scheduler<ValueType>::printJsonToStream(std::ostream& out, std::shared_ptr<storm::models::sparse::Model<ValueType>> model, bool skipUniqueChoices, bool skipDontCareStates) const { STORM_LOG_THROW(model == nullptr || model->getNumberOfStates() == schedulerChoices.front().size(), storm::exceptions::InvalidOperationException, "The given model is not compatible with this scheduler."); STORM_LOG_WARN_COND(!(skipUniqueChoices && model == nullptr), "Can not skip unique choices if the model is not given."); storm::json<storm::RationalNumber> output; for (uint64_t state = 0; state < schedulerChoices.front().size(); ++state) { // Check whether the state is skipped if (skipUniqueChoices && model != nullptr && model->getTransitionMatrix().getRowGroupSize(state) == 1) { continue; } for (uint_fast64_t memoryState = 0; memoryState < getNumberOfMemoryStates(); ++memoryState) { // Ignore dontCare states if (skipDontCareStates && isDontCare(state, memoryState)) { continue; } storm::json<storm::RationalNumber> stateChoicesJson; if (model && model->hasStateValuations()) { stateChoicesJson["s"] = model->getStateValuations().template toJson<storm::RationalNumber>(state); } else { stateChoicesJson["s"] = state; } if (!isMemorylessScheduler()) { stateChoicesJson["m"] = memoryState; } auto const &choice = schedulerChoices[memoryState][state]; storm::json<storm::RationalNumber> choicesJson; if (choice.isDefined()) { for (auto const &choiceProbPair : choice.getChoiceAsDistribution()) { uint64_t globalChoiceIndex = model->getTransitionMatrix().getRowGroupIndices()[state] + choiceProbPair.first; storm::json<storm::RationalNumber> choiceJson; if (model && model->hasChoiceOrigins() && model->getChoiceOrigins()->getIdentifier(globalChoiceIndex) != model->getChoiceOrigins()->getIdentifierForChoicesWithNoOrigin()) { choiceJson["origin"] = model->getChoiceOrigins()->getChoiceAsJson(globalChoiceIndex); } if (model && model->hasChoiceLabeling()) { auto choiceLabels = model->getChoiceLabeling().getLabelsOfChoice(globalChoiceIndex); choiceJson["labels"] = std::vector<std::string>(choiceLabels.begin(), choiceLabels.end()); } choiceJson["index"] = globalChoiceIndex; choiceJson["prob"] = storm::utility::convertNumber<storm::RationalNumber>( choiceProbPair.second); // Memory updates if(!isMemorylessScheduler()) { choiceJson["memory-updates"] = std::vector<storm::json<storm::RationalNumber>>(); uint64_t row = model->getTransitionMatrix().getRowGroupIndices()[state] + choiceProbPair.first; for (auto entryIt = model->getTransitionMatrix().getRow(row).begin(); entryIt < model->getTransitionMatrix().getRow(row).end(); ++entryIt) { storm::json<storm::RationalNumber> updateJson; // next model state if (model && model->hasStateValuations()) { updateJson["s'"] = model->getStateValuations().template toJson<storm::RationalNumber>(entryIt->getColumn()); } else { updateJson["s'"] = entryIt->getColumn(); } // next memory state updateJson["m'"] = this->memoryStructure->getSuccessorMemoryState(memoryState, entryIt - model->getTransitionMatrix().begin()); choiceJson["memory-updates"].push_back(std::move(updateJson)); } } choicesJson.push_back(std::move(choiceJson)); } } else { choicesJson = "undefined"; } stateChoicesJson["c"] = std::move(choicesJson); output.push_back(std::move(stateChoicesJson)); } } out << output.dump(4); } template <typename ValueType> void Scheduler<ValueType>::setPrintUndefinedChoices(bool value) { printUndefinedChoices = value; } template class Scheduler<double>; template class Scheduler<float>; template class Scheduler<storm::RationalNumber>; template class Scheduler<storm::RationalFunction>; } }