diff --git a/src/cli/entrypoints.h b/src/cli/entrypoints.h index ba863af43..d14f8848c 100644 --- a/src/cli/entrypoints.h +++ b/src/cli/entrypoints.h @@ -64,7 +64,7 @@ namespace storm { storm::modelchecker::SparseMdpLearningModelChecker<ValueType> checker(program); std::unique_ptr<storm::modelchecker::CheckResult> result; if (checker.canHandle(task)) { - std::unique_ptr<storm::modelchecker::CheckResult> result = checker.check(task); + result = checker.check(task); } else { std::cout << " skipped, because the formula cannot be handled by the selected engine/method." << std::endl; } diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp index 46cf53011..f6ecc1055 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.cpp @@ -2,6 +2,7 @@ #include "src/storage/SparseMatrix.h" #include "src/storage/sparse/StateStorage.h" +#include "src/storage/MaximalEndComponentDecomposition.h" #include "src/generator/PrismNextStateGenerator.h" @@ -11,13 +12,16 @@ #include "src/modelchecker/results/ExplicitQuantitativeCheckResult.h" +#include "src/settings/SettingsManager.h" +#include "src/settings/modules/GeneralSettings.h" + #include "src/utility/macros.h" #include "src/exceptions/NotSupportedException.h" namespace storm { namespace modelchecker { template<typename ValueType> - SparseMdpLearningModelChecker<ValueType>::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram<ValueType>(program)), variableInformation(this->program) { + SparseMdpLearningModelChecker<ValueType>::SparseMdpLearningModelChecker(storm::prism::Program const& program) : program(storm::utility::prism::preprocessProgram<ValueType>(program)), variableInformation(this->program), randomGenerator(std::chrono::system_clock::now().time_since_epoch().count()), comparator(1e-9) { // Intentionally left empty. } @@ -104,7 +108,6 @@ namespace storm { max = std::max(max, current); } -// STORM_LOG_TRACE("Looking for action with value " << upperBoundsPerState[stateToRowGroupMapping[currentStateId]] << "."); STORM_LOG_TRACE("Looking for action with value " << max << "."); for (uint32_t row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { @@ -115,9 +118,7 @@ namespace storm { STORM_LOG_TRACE("Computed (upper) bound " << current << " for row " << row << "."); // If the action is one of the maximizing ones, insert it into our list. - // TODO: should this need to be an approximate check? -// if (current == upperBoundsPerState[stateToRowGroupMapping[currentStateId]]) { - if (current == max) { + if (comparator.isEqual(current, max)) { allMaxActions.push_back(row); } } @@ -126,99 +127,128 @@ namespace storm { // Now sample from all maximizing actions. std::uniform_int_distribution<uint32_t> distribution(0, allMaxActions.size() - 1); - return allMaxActions[distribution(generator)] - rowGroupIndices[rowGroup]; + return allMaxActions[distribution(randomGenerator)] - rowGroupIndices[rowGroup]; } template<typename ValueType> typename SparseMdpLearningModelChecker<ValueType>::StateType SparseMdpLearningModelChecker<ValueType>::sampleSuccessorFromAction(StateType currentStateId, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping) { uint32_t row = rowGroupIndices[stateToRowGroupMapping[currentStateId]]; - // TODO: this can be precomputed. + // TODO: precompute this? std::vector<ValueType> probabilities(transitionMatrix[row].size()); std::transform(transitionMatrix[row].begin(), transitionMatrix[row].end(), probabilities.begin(), [] (storm::storage::MatrixEntry<StateType, ValueType> const& entry) { return entry.getValue(); } ); // Now sample according to the probabilities. std::discrete_distribution<StateType> distribution(probabilities.begin(), probabilities.end()); - StateType offset = distribution(generator); + StateType offset = distribution(randomGenerator); STORM_LOG_TRACE("Sampled " << offset << " from " << probabilities.size() << " elements."); return transitionMatrix[row][offset].getColumn(); } template<typename ValueType> - std::unique_ptr<CheckResult> SparseMdpLearningModelChecker<ValueType>::computeReachabilityProbabilities(CheckTask<storm::logic::EventuallyFormula> const& checkTask) { - storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); - storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); - STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); - storm::expressions::Expression targetStateExpression; + storm::expressions::Expression SparseMdpLearningModelChecker<ValueType>::getTargetStateExpression(storm::logic::Formula const& subformula) { + storm::expressions::Expression result; if (subformula.isAtomicExpressionFormula()) { - targetStateExpression = subformula.asAtomicExpressionFormula().getExpression(); + result = subformula.asAtomicExpressionFormula().getExpression(); } else { - targetStateExpression = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); + result = program.getLabelExpression(subformula.asAtomicLabelFormula().getLabel()); } + return result; + } + + template<typename ValueType> + void SparseMdpLearningModelChecker<ValueType>::detectEndComponents(std::vector<std::pair<StateType, uint32_t>> const& stateActionStack, boost::container::flat_set<StateType> const& targetStates, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping, std::vector<ValueType>& lowerBoundsPerAction, std::vector<ValueType>& upperBoundsPerAction, std::vector<ValueType>& lowerBoundsPerState, std::vector<ValueType>& upperBoundsPerState, std::unordered_map<StateType, storm::generator::CompressedState>& unexploredStates, StateType const& unexploredMarker) const { - // A container for the encountered states. - storm::storage::sparse::StateStorage<StateType> stateStorage(variableInformation.getTotalBitOffset(true)); + // Outline: + // 1. construct a sparse transition matrix of the relevant part of the state space. + // 2. use this matrix to compute an MEC decomposition. + // 3. if non-empty analyze the decomposition for accepting/rejecting MECs. + // 4. modify matrix to account for the findings of 3. - // A generator used to explore the model. - storm::generator::PrismNextStateGenerator<ValueType, StateType> generator(program, variableInformation, false); + // Start with 1. + storm::storage::SparseMatrixBuilder<ValueType> builder(0, 0, 0, false, true, 0); + std::vector<StateType> relevantStates; + for (StateType state = 0; state < stateToRowGroupMapping.size(); ++state) { + if (stateToRowGroupMapping[state] != unexploredMarker) { + relevantStates.push_back(state); + } + } + StateType unexploredState = relevantStates.size(); - // A container that stores the transitions found so far. - std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> matrix; + std::unordered_map<StateType, StateType> relevantStateToNewRowGroupMapping; + for (StateType index = 0; index < relevantStates.size(); ++index) { + relevantStateToNewRowGroupMapping.emplace(relevantStates[index], index); + } - // A vector storing where the row group of each state starts. - std::vector<StateType> rowGroupIndices; - rowGroupIndices.push_back(0); + StateType currentRow = 0; + for (auto const& state : relevantStates) { + builder.newRowGroup(currentRow); + StateType rowGroup = stateToRowGroupMapping[state]; + for (auto row = rowGroupIndices[rowGroup]; row < rowGroupIndices[rowGroup + 1]; ++row) { + ValueType unexpandedProbability = storm::utility::zero<ValueType>(); + for (auto const& entry : transitionMatrix[row]) { + auto it = relevantStateToNewRowGroupMapping.find(entry.getColumn()); + if (it != relevantStateToNewRowGroupMapping.end()) { + // If the entry is a relevant state, we copy it over (and compensate for the offset change). + builder.addNextValue(currentRow, it->second, entry.getValue()); + } else { + // If the entry is an unexpanded state, we gather the probability to later redirect it to an unexpanded sink. + unexpandedProbability += entry.getValue(); + } + } + builder.addNextValue(currentRow, unexploredState, unexpandedProbability); + ++currentRow; + } + } + builder.newRowGroup(currentRow); - // A vector storing the mapping from state ids to row groups. - std::vector<StateType> stateToRowGroupMapping; - StateType unexploredMarker = std::numeric_limits<StateType>::max(); - // Vectors to store the lower/upper bounds for each action (in each state). - std::vector<ValueType> lowerBoundsPerAction; - std::vector<ValueType> upperBoundsPerAction; - std::vector<ValueType> lowerBoundsPerState; - std::vector<ValueType> upperBoundsPerState; + // Go on to step 2. + storm::storage::MaximalEndComponentDecomposition<ValueType> mecDecomposition; - // A mapping of unexplored IDs to their actual compressed states. - std::unordered_map<StateType, storm::generator::CompressedState> unexploredStates; + // 3. Analyze the MEC decomposition. - // Create a callback for the next-state generator to enable it to request the index of states. - std::function<StateType (storm::generator::CompressedState const&)> stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates, &unexploredMarker] (storm::generator::CompressedState const& state) -> StateType { - StateType newIndex = stateStorage.numberOfStates; - - // Check, if the state was already registered. - std::pair<uint32_t, std::size_t> actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); - - if (actualIndexBucketPair.first == newIndex) { - ++stateStorage.numberOfStates; - stateToRowGroupMapping.push_back(unexploredMarker); - unexploredStates[newIndex] = state; - } - - return actualIndexBucketPair.first; - }; + // 4. Finally modify the system + } + + template<typename ValueType> + std::tuple<typename SparseMdpLearningModelChecker<ValueType>::StateType, ValueType, ValueType> SparseMdpLearningModelChecker<ValueType>::performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage<StateType>& stateStorage, storm::generator::PrismNextStateGenerator<ValueType, StateType>& generator, std::function<StateType (storm::generator::CompressedState const&)> const& stateToIdCallback, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>>& matrix, std::vector<StateType>& rowGroupIndices, std::vector<StateType>& stateToRowGroupMapping, std::unordered_map<StateType, storm::generator::CompressedState>& unexploredStates, StateType const& unexploredMarker) { + // Generate the initial state so we know where to start the simulation. stateStorage.initialStateIndices = generator.getInitialStates(stateToIdCallback); STORM_LOG_THROW(stateStorage.initialStateIndices.size() == 1, storm::exceptions::NotSupportedException, "Currently only models with one initial state are supported by the learning engine."); + StateType initialStateIndex = stateStorage.initialStateIndices.front(); + + // A set storing all target states. + boost::container::flat_set<StateType> targetStates; + + // Vectors to store the lower/upper bounds for each action (in each state). + std::vector<ValueType> lowerBoundsPerAction; + std::vector<ValueType> upperBoundsPerAction; + std::vector<ValueType> lowerBoundsPerState; + std::vector<ValueType> upperBoundsPerState; // Now perform the actual sampling. std::vector<std::pair<StateType, uint32_t>> stateActionStack; - uint_fast64_t iteration = 0; + std::size_t iterations = 0; + std::size_t maxPathLength = 0; + std::size_t pathLengthUntilEndComponentDetection = 27; bool convergenceCriterionMet = false; while (!convergenceCriterionMet) { // Start the search from the initial state. - stateActionStack.push_back(std::make_pair(stateStorage.initialStateIndices.front(), 0)); - + stateActionStack.push_back(std::make_pair(initialStateIndex, 0)); + + bool foundTerminalState = false; bool foundTargetState = false; - while (!foundTargetState) { + while (!foundTerminalState) { StateType const& currentStateId = stateActionStack.back().first; STORM_LOG_TRACE("State on top of stack is: " << currentStateId << "."); // If the state is not yet expanded, we need to retrieve its behaviors. auto unexploredIt = unexploredStates.find(currentStateId); if (unexploredIt != unexploredStates.end()) { - STORM_LOG_TRACE("State was not yet expanded."); + STORM_LOG_TRACE("State was not yet explored."); // Map the unexplored state to a row group. stateToRowGroupMapping[currentStateId] = rowGroupIndices.size() - 1; @@ -235,60 +265,32 @@ namespace storm { generator.load(currentState); if (generator.satisfies(targetStateExpression)) { STORM_LOG_TRACE("State does not need to be expanded, because it is a target state."); - - // If it's in fact a goal state, we need to go backwards in the stack and update the probabilities. + targetStates.insert(currentStateId); foundTargetState = true; - lowerBoundsPerState.back() = storm::utility::one<ValueType>(); - - // Set the lower/upper bounds for the only (dummy) action. - lowerBoundsPerAction.push_back(storm::utility::one<ValueType>()); - upperBoundsPerAction.push_back(storm::utility::one<ValueType>()); - - // Increase the size of the matrix, but leave the row empty. - matrix.resize(matrix.size() + 1); - - STORM_LOG_TRACE("Updating probabilities along states in stack."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + foundTerminalState = true; } else { - STORM_LOG_TRACE("Expanding state."); + STORM_LOG_TRACE("Exploring state."); // If it needs to be expanded, we use the generator to retrieve the behavior of the new state. storm::generator::StateBehavior<ValueType, StateType> behavior = generator.expand(stateToIdCallback); STORM_LOG_TRACE("State has " << behavior.getNumberOfChoices() << " choices."); // Clumsily check whether we have found a state that forms a trivial BMEC. - bool isNonacceptingTerminalState = false; if (behavior.getNumberOfChoices() == 0) { - isNonacceptingTerminalState = true; + foundTerminalState = true; } else if (behavior.getNumberOfChoices() == 1) { auto const& onlyChoice = *behavior.begin(); if (onlyChoice.size() == 1) { auto const& onlyEntry = *onlyChoice.begin(); if (onlyEntry.first == currentStateId) { - isNonacceptingTerminalState = true; + foundTerminalState = true; } } } - if (isNonacceptingTerminalState) { - STORM_LOG_TRACE("State does not need to be expanded, because it forms a trivial BMEC."); - - foundTargetState = true; - upperBoundsPerState.back() = storm::utility::zero<ValueType>(); - - // Set the lower/upper bounds for the only (dummy) action. - lowerBoundsPerAction.push_back(storm::utility::zero<ValueType>()); - upperBoundsPerAction.push_back(storm::utility::zero<ValueType>()); - - // Increase the size of the matrix, but leave the row empty. - matrix.resize(matrix.size() + 1); - - STORM_LOG_TRACE("Updating probabilities along states in stack."); - updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); - } - // If the state was neither a trivial (non-accepting) BMEC nor a target state, we need to store - // its behavior. - if (!foundTargetState) { + // If the state was neither a trivial (non-accepting) terminal state nor a target state, we + // need to store its behavior. + if (!foundTerminalState) { // Next, we insert the behavior into our matrix structure. StateType startRow = matrix.size(); matrix.resize(startRow + behavior.getNumberOfChoices()); @@ -297,10 +299,10 @@ namespace storm { for (auto const& entry : choice) { matrix[startRow + currentAction].emplace_back(entry.first, entry.second); } - + lowerBoundsPerAction.push_back(storm::utility::zero<ValueType>()); upperBoundsPerAction.push_back(storm::utility::one<ValueType>()); - + // Update the bounds for the explored states to initially let them have the correct value. updateProbabilities(currentStateId, currentAction, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); @@ -309,6 +311,26 @@ namespace storm { } } + if (foundTerminalState) { + STORM_LOG_TRACE("State does not need to be explored, because it is " << (foundTargetState ? "a target state" : "a rejecting terminal state") << "."); + + if (foundTargetState) { + lowerBoundsPerState.back() = storm::utility::one<ValueType>(); + lowerBoundsPerAction.push_back(storm::utility::one<ValueType>()); + upperBoundsPerAction.push_back(storm::utility::one<ValueType>()); + } else { + upperBoundsPerState.back() = storm::utility::zero<ValueType>(); + lowerBoundsPerAction.push_back(storm::utility::zero<ValueType>()); + upperBoundsPerAction.push_back(storm::utility::zero<ValueType>()); + } + + // Increase the size of the matrix, but leave the row empty. + matrix.resize(matrix.size() + 1); + + STORM_LOG_TRACE("Updating probabilities along states in stack."); + updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); + } + // Now that we have explored the state, we can dispose of it. unexploredStates.erase(unexploredIt); @@ -318,14 +340,14 @@ namespace storm { // If the state was explored before, but determined to be a terminal state of the exploration, // we need to determine this now. if (matrix[rowGroupIndices[stateToRowGroupMapping[currentStateId]]].empty()) { - foundTargetState = true; + foundTerminalState = true; // Update the bounds along the path to the terminal state. updateProbabilitiesUsingStack(stateActionStack, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredMarker); } } - if (!foundTargetState) { + if (!foundTerminalState) { // At this point, we can be sure that the state was expanded and that we can sample according to the // probabilities in the matrix. uint32_t chosenAction = sampleFromMaxActions(currentStateId, matrix, rowGroupIndices, stateToRowGroupMapping, upperBoundsPerAction, unexploredMarker); @@ -337,20 +359,83 @@ namespace storm { // Put the successor state and a dummy action on top of the stack. stateActionStack.emplace_back(successor, 0); + maxPathLength = std::max(maxPathLength, stateActionStack.size()); + + // If the current path length exceeds the threshold, we perform an EC detection. + if (stateActionStack.size() > pathLengthUntilEndComponentDetection) { + detectEndComponents(stateActionStack, targetStates, matrix, rowGroupIndices, stateToRowGroupMapping, lowerBoundsPerAction, upperBoundsPerAction, lowerBoundsPerState, upperBoundsPerState, unexploredStates, unexploredMarker); + } } } STORM_LOG_DEBUG("Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)."); - STORM_LOG_DEBUG("Lower bound is " << lowerBoundsPerState[0] << "."); - STORM_LOG_DEBUG("Upper bound is " << upperBoundsPerState[0] << "."); - ValueType difference = upperBoundsPerState[0] - lowerBoundsPerState[0]; - STORM_LOG_DEBUG("Difference after iteration " << iteration << " is " << difference << "."); + STORM_LOG_DEBUG("Lower bound is " << lowerBoundsPerState[initialStateIndex] << "."); + STORM_LOG_DEBUG("Upper bound is " << upperBoundsPerState[initialStateIndex] << "."); + ValueType difference = upperBoundsPerState[initialStateIndex] - lowerBoundsPerState[initialStateIndex]; + STORM_LOG_DEBUG("Difference after iteration " << iterations << " is " << difference << "."); convergenceCriterionMet = difference < 1e-6; - ++iteration; + ++iterations; } - return nullptr; + if (storm::settings::generalSettings().isShowStatisticsSet()) { + std::cout << std::endl << "Learning summary -------------------------" << std::endl; + std::cout << "Discovered states: " << stateStorage.numberOfStates << " (" << unexploredStates.size() << " unexplored)" << std::endl; + std::cout << "Sampling iterations: " << iterations << std::endl; + std::cout << "Maximal path length: " << maxPathLength << std::endl; + } + + return std::make_tuple(initialStateIndex, lowerBoundsPerState[initialStateIndex], upperBoundsPerState[initialStateIndex]); + } + + template<typename ValueType> + std::unique_ptr<CheckResult> SparseMdpLearningModelChecker<ValueType>::computeReachabilityProbabilities(CheckTask<storm::logic::EventuallyFormula> const& checkTask) { + storm::logic::EventuallyFormula const& eventuallyFormula = checkTask.getFormula(); + storm::logic::Formula const& subformula = eventuallyFormula.getSubformula(); + STORM_LOG_THROW(subformula.isAtomicExpressionFormula() || subformula.isAtomicLabelFormula(), storm::exceptions::NotSupportedException, "Learning engine can only deal with formulas of the form 'F \"label\"' or 'F expression'."); + + // Derive the expression for the target states, so we know when to abort the learning run. + storm::expressions::Expression targetStateExpression = getTargetStateExpression(subformula); + + // A container for the encountered states. + storm::storage::sparse::StateStorage<StateType> stateStorage(variableInformation.getTotalBitOffset(true)); + + // A generator used to explore the model. + storm::generator::PrismNextStateGenerator<ValueType, StateType> generator(program, variableInformation, false); + + // A container that stores the transitions found so far. + std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> matrix; + + // A vector storing where the row group of each state starts. + std::vector<StateType> rowGroupIndices; + rowGroupIndices.push_back(0); + + // A vector storing the mapping from state ids to row groups. + std::vector<StateType> stateToRowGroupMapping; + StateType unexploredMarker = std::numeric_limits<StateType>::max(); + + // A mapping of unexplored IDs to their actual compressed states. + std::unordered_map<StateType, storm::generator::CompressedState> unexploredStates; + + // Create a callback for the next-state generator to enable it to request the index of states. + std::function<StateType (storm::generator::CompressedState const&)> stateToIdCallback = [&stateStorage, &stateToRowGroupMapping, &unexploredStates, &unexploredMarker] (storm::generator::CompressedState const& state) -> StateType { + StateType newIndex = stateStorage.numberOfStates; + + // Check, if the state was already registered. + std::pair<uint32_t, std::size_t> actualIndexBucketPair = stateStorage.stateToId.findOrAddAndGetBucket(state, newIndex); + + if (actualIndexBucketPair.first == newIndex) { + ++stateStorage.numberOfStates; + stateToRowGroupMapping.push_back(unexploredMarker); + unexploredStates[newIndex] = state; + } + + return actualIndexBucketPair.first; + }; + + // Compute and return result. + std::tuple<StateType, ValueType, ValueType> boundsForInitialState = performLearningProcedure(targetStateExpression, stateStorage, generator, stateToIdCallback, matrix, rowGroupIndices, stateToRowGroupMapping, unexploredStates, unexploredMarker); + return std::make_unique<ExplicitQuantitativeCheckResult<ValueType>>(std::get<0>(boundsForInitialState), std::get<1>(boundsForInitialState)); } template class SparseMdpLearningModelChecker<double>; diff --git a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h index 1d91a5d14..60dc25bc2 100644 --- a/src/modelchecker/reachability/SparseMdpLearningModelChecker.h +++ b/src/modelchecker/reachability/SparseMdpLearningModelChecker.h @@ -10,9 +10,22 @@ #include "src/generator/CompressedState.h" #include "src/generator/VariableInformation.h" +#include "src/utility/ConstantsComparator.h" #include "src/utility/constants.h" namespace storm { + namespace storage { + namespace sparse { + template<typename StateType> + class StateStorage; + } + } + + namespace generator { + template<typename ValueType, typename StateType> + class PrismNextStateGenerator; + } + namespace modelchecker { template<typename ValueType> @@ -27,6 +40,8 @@ namespace storm { virtual std::unique_ptr<CheckResult> computeReachabilityProbabilities(CheckTask<storm::logic::EventuallyFormula> const& checkTask) override; private: + std::tuple<StateType, ValueType, ValueType> performLearningProcedure(storm::expressions::Expression const& targetStateExpression, storm::storage::sparse::StateStorage<StateType>& stateStorage, storm::generator::PrismNextStateGenerator<ValueType, StateType>& generator, std::function<StateType (storm::generator::CompressedState const&)> const& stateToIdCallback, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>>& matrix, std::vector<StateType>& rowGroupIndices, std::vector<StateType>& stateToRowGroupMapping, std::unordered_map<StateType, storm::generator::CompressedState>& unexploredStates, StateType const& unexploredMarker); + void updateProbabilities(StateType const& sourceStateId, uint32_t action, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping, std::vector<ValueType>& lowerBounds, std::vector<ValueType>& upperBounds, std::vector<ValueType>& lowerBoundsPerState, std::vector<ValueType>& upperBoundsPerState, StateType const& unexploredMarker) const; void updateProbabilitiesUsingStack(std::vector<std::pair<StateType, uint32_t>>& stateActionStack, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping, std::vector<ValueType>& lowerBounds, std::vector<ValueType>& upperBounds, std::vector<ValueType>& lowerBoundsPerState, std::vector<ValueType>& upperBoundsPerState, StateType const& unexploredMarker) const; @@ -34,7 +49,11 @@ namespace storm { uint32_t sampleFromMaxActions(StateType currentStateId, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping, std::vector<ValueType>& upperBounds, StateType const& unexploredMarker); StateType sampleSuccessorFromAction(StateType currentStateId, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping); - + + void detectEndComponents(std::vector<std::pair<StateType, uint32_t>> const& stateActionStack, boost::container::flat_set<StateType> const& targetStates, std::vector<std::vector<storm::storage::MatrixEntry<StateType, ValueType>>> const& transitionMatrix, std::vector<StateType> const& rowGroupIndices, std::vector<StateType> const& stateToRowGroupMapping, std::vector<ValueType>& lowerBoundsPerAction, std::vector<ValueType>& upperBoundsPerAction, std::vector<ValueType>& lowerBoundsPerState, std::vector<ValueType>& upperBoundsPerState, std::unordered_map<StateType, storm::generator::CompressedState>& unexploredStates, StateType const& unexploredMarker) const; + + storm::expressions::Expression getTargetStateExpression(storm::logic::Formula const& subformula); + // The program that defines the model to check. storm::prism::Program program; @@ -42,7 +61,10 @@ namespace storm { storm::generator::VariableInformation variableInformation; // The random number generator. - std::default_random_engine generator; + std::default_random_engine randomGenerator; + + // A comparator used to determine whether values are equal. + storm::utility::ConstantsComparator<ValueType> comparator; }; } } diff --git a/src/parser/DeterministicModelParser.cpp b/src/parser/DeterministicModelParser.cpp index 18f14428a..2bc82362f 100644 --- a/src/parser/DeterministicModelParser.cpp +++ b/src/parser/DeterministicModelParser.cpp @@ -23,7 +23,7 @@ namespace storm { uint_fast64_t stateCount = transitions.getColumnCount(); // Parse the state labeling. - storm::models::sparse::StateLabeling labeling(std::move(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename))); + storm::models::sparse::StateLabeling labeling(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename)); // Construct the result. DeterministicModelParser<ValueType, RewardValueType>::Result result(std::move(transitions), std::move(labeling)); diff --git a/src/parser/NondeterministicModelParser.cpp b/src/parser/NondeterministicModelParser.cpp index b2d539c3a..b703937fd 100644 --- a/src/parser/NondeterministicModelParser.cpp +++ b/src/parser/NondeterministicModelParser.cpp @@ -25,7 +25,7 @@ namespace storm { uint_fast64_t stateCount = transitions.getColumnCount(); // Parse the state labeling. - storm::models::sparse::StateLabeling labeling(std::move(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename))); + storm::models::sparse::StateLabeling labeling(storm::parser::AtomicPropositionLabelingParser::parseAtomicPropositionLabeling(stateCount, labelingFilename)); // Only parse state rewards if a file is given. boost::optional<std::vector<RewardValueType>> stateRewards; diff --git a/src/storage/SparseMatrix.cpp b/src/storage/SparseMatrix.cpp index da441214e..907f97081 100644 --- a/src/storage/SparseMatrix.cpp +++ b/src/storage/SparseMatrix.cpp @@ -106,14 +106,11 @@ namespace storm { template<typename ValueType> void SparseMatrixBuilder<ValueType>::addNextValue(index_type row, index_type column, ValueType const& value) { // Check that we did not move backwards wrt. the row. - if (row < lastRow) { - throw storm::exceptions::InvalidArgumentException() << "Illegal call to SparseMatrixBuilder::addNextValue: adding an element in row " << row << ", but an element in row " << lastRow << " has already been added."; - } + STORM_LOG_THROW(row >= lastRow, storm::exceptions::InvalidArgumentException, "Adding an element in row " << row << ", but an element in row " << lastRow << " has already been added."); - // Check that we did not move backwards wrt. to column. - if (row == lastRow && column < lastColumn) { - throw storm::exceptions::InvalidArgumentException() << "Illegal call to SparseMatrixBuilder::addNextValue: adding an element in column " << column << " in row " << row << ", but an element in column " << lastColumn << " has already been added in that row."; - } + // If the element is in the same row, but was not inserted in the correct order, we need to fix the row after + // the insertion. + bool fixCurrentRow = row == lastRow && column < lastColumn; // If we switched to another row, we have to adjust the missing entries in the row indices vector. if (row != lastRow) { @@ -132,6 +129,27 @@ namespace storm { highestColumn = std::max(highestColumn, column); ++currentEntryCount; + // If we need to fix the row, do so now. + if (fixCurrentRow) { + // First, we sort according to columns. + std::sort(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry<index_type, ValueType> const& a, storm::storage::MatrixEntry<index_type, ValueType> const& b) { + return a.getColumn() < b.getColumn(); + }); + + // Then, we eliminate possible duplicate entries. + auto it = std::unique(columnsAndValues.begin() + rowIndications.back(), columnsAndValues.end(), [] (storm::storage::MatrixEntry<index_type, ValueType> const& a, storm::storage::MatrixEntry<index_type, ValueType> const& b) { + return a.getColumn() == b.getColumn(); + }); + + // Finally, remove the superfluous elements. + std::size_t elementsToRemove = std::distance(it, columnsAndValues.end()); + if (elementsToRemove > 0) { + STORM_LOG_WARN("Unordered insertion into matrix builder caused duplicate entries."); + currentEntryCount -= elementsToRemove; + columnsAndValues.resize(columnsAndValues.size() - elementsToRemove); + } + } + // In case we did not expect this value, we throw an exception. if (forceInitialDimensions) { STORM_LOG_THROW(!initialRowCountSet || lastRow < initialRowCount, storm::exceptions::OutOfRangeException, "Cannot insert value at illegal row " << lastRow << "."); diff --git a/src/storage/SparseMatrix.h b/src/storage/SparseMatrix.h index c5c7133c6..f1debced1 100644 --- a/src/storage/SparseMatrix.h +++ b/src/storage/SparseMatrix.h @@ -229,7 +229,7 @@ namespace storm { * @return True if replacement took place, False if nothing changed. */ bool replaceColumns(std::vector<index_type> const& replacements, index_type offset); - + private: // A flag indicating whether a row count was set upon construction. bool initialRowCountSet; diff --git a/src/storage/expressions/ToExprtkStringVisitor.cpp b/src/storage/expressions/ToExprtkStringVisitor.cpp index f3a89b2c5..c0cea43ac 100644 --- a/src/storage/expressions/ToExprtkStringVisitor.cpp +++ b/src/storage/expressions/ToExprtkStringVisitor.cpp @@ -10,7 +10,7 @@ namespace storm { stream.str(""); stream.clear(); expression->accept(*this); - return std::move(stream.str()); + return stream.str(); } boost::any ToExprtkStringVisitor::visit(IfThenElseExpression const& expression) {