|
|
@ -24,93 +24,142 @@ extern "C" { |
|
|
|
namespace storm { |
|
|
|
namespace counterexamples { |
|
|
|
|
|
|
|
/*! |
|
|
|
* A helper class that provides the functionality to compute a hash value for pairs of indices. |
|
|
|
*/ |
|
|
|
class PairHash { |
|
|
|
public: |
|
|
|
std::size_t operator()(std::pair<uint_fast64_t, uint_fast64_t> const& pair) const { |
|
|
|
size_t seed = 0; |
|
|
|
boost::hash_combine(seed, pair.first); |
|
|
|
boost::hash_combine(seed, pair.second); |
|
|
|
return seed; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
/*! |
|
|
|
* This class provides functionality to generate a minimal counterexample to a probabilistic reachability |
|
|
|
* property in terms of used labels. |
|
|
|
*/ |
|
|
|
template <class T> |
|
|
|
class MinimalLabelSetGenerator { |
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
static std::unordered_set<uint_fast64_t> getMinimalLabelSet(storm::models::Mdp<T> const& labeledMdp, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, T lowerProbabilityBound, bool checkThresholdFeasible = false) { |
|
|
|
#ifdef HAVE_GUROBI |
|
|
|
// (0) Check whether the MDP is indeed labeled. |
|
|
|
if (!labeledMdp.hasChoiceLabels()) { |
|
|
|
throw storm::exceptions::InvalidArgumentException() << "Minimal label set generation is impossible for unlabeled model."; |
|
|
|
private: |
|
|
|
/*! |
|
|
|
* A helper class that provides the functionality to compute a hash value for pairs of state indices. |
|
|
|
*/ |
|
|
|
class PairHash { |
|
|
|
public: |
|
|
|
std::size_t operator()(std::pair<uint_fast64_t, uint_fast64_t> const& pair) const { |
|
|
|
size_t seed = 0; |
|
|
|
boost::hash_combine(seed, pair.first); |
|
|
|
boost::hash_combine(seed, pair.second); |
|
|
|
return seed; |
|
|
|
} |
|
|
|
|
|
|
|
// (1) TODO: check whether its possible to exceed the threshold if checkThresholdFeasible is set. |
|
|
|
|
|
|
|
// (2) Identify relevant and problematic states. |
|
|
|
storm::storage::SparseMatrix<bool> backwardTransitions = labeledMdp.getBackwardTransitions(); |
|
|
|
storm::storage::BitVector relevantStates = storm::utility::graph::performProbGreater0E(labeledMdp, backwardTransitions, phiStates, psiStates); |
|
|
|
relevantStates &= ~psiStates; |
|
|
|
storm::storage::BitVector problematicStates = storm::utility::graph::performProbGreater0E(labeledMdp, backwardTransitions, phiStates, psiStates); |
|
|
|
problematicStates.complement(); |
|
|
|
problematicStates &= relevantStates; |
|
|
|
LOG4CPLUS_INFO(logger, "Found " << phiStates.getNumberOfSetBits() << " filter states (" << phiStates.toString() << ")."); |
|
|
|
LOG4CPLUS_INFO(logger, "Found " << psiStates.getNumberOfSetBits() << " target states (" << psiStates.toString() << ")."); |
|
|
|
LOG4CPLUS_INFO(logger, "Found " << relevantStates.getNumberOfSetBits() << " relevant states (" << relevantStates.toString() << ")."); |
|
|
|
LOG4CPLUS_INFO(logger, "Found " << problematicStates.getNumberOfSetBits() << " problematic states (" << problematicStates.toString() << ")."); |
|
|
|
|
|
|
|
// (3) Determine sets of relevant labels and problematic choices. |
|
|
|
}; |
|
|
|
|
|
|
|
/*! |
|
|
|
* A helper struct storing which states are relevant or problematic. |
|
|
|
*/ |
|
|
|
struct StateInformation { |
|
|
|
storm::storage::BitVector relevantStates; |
|
|
|
storm::storage::BitVector problematicStates; |
|
|
|
}; |
|
|
|
|
|
|
|
/*! |
|
|
|
* A helper struct capturing information about relevant and problematic choices of states and which labels |
|
|
|
* are relevant. |
|
|
|
*/ |
|
|
|
struct ChoiceInformation { |
|
|
|
std::unordered_map<uint_fast64_t, std::list<uint_fast64_t>> relevantChoicesForRelevantStates; |
|
|
|
std::unordered_map<uint_fast64_t, std::list<uint_fast64_t>> problematicChoicesForProblematicStates; |
|
|
|
std::unordered_set<uint_fast64_t> relevantLabels; |
|
|
|
std::unordered_set<uint_fast64_t> allRelevantLabels; |
|
|
|
}; |
|
|
|
|
|
|
|
/*! |
|
|
|
* A helper struct capturing information about the variables of the MILP formulation. |
|
|
|
*/ |
|
|
|
struct VariableInformation { |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> labelToVariableIndexMap; |
|
|
|
std::unordered_map<uint_fast64_t, std::list<uint_fast64_t>> stateToChoiceVariablesIndexMap; |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> stateToProbabilityVariableIndexMap; |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> problematicStateToVariableIndexMap; |
|
|
|
std::unordered_map<std::pair<uint_fast64_t, uint_fast64_t>, uint_fast64_t, PairHash> problematicTransitionToVariableIndexMap; |
|
|
|
uint_fast64_t nextFreeVariableIndex = 0; |
|
|
|
}; |
|
|
|
|
|
|
|
/*! |
|
|
|
* Determines the relevant and the problematic states of the given MDP with respect to the given phi and psi |
|
|
|
* state sets. The relevant states are those for which there exists at least one scheduler that attains a |
|
|
|
* non-zero probability of satisfying phi until psi. Problematic states are relevant states that have at |
|
|
|
* least one scheduler such that the probability of satisfying phi until psi is zero. |
|
|
|
* |
|
|
|
* @param labeledMdp The MDP whose states to search. |
|
|
|
* @param phiStates A bit vector characterizing all states satisfying phi. |
|
|
|
* @param psiStates A bit vector characterizing all states satisfying psi. |
|
|
|
* @return A structure that stores the relevant and problematic states. |
|
|
|
*/ |
|
|
|
static struct StateInformation determineRelevantAndProblematicStates(storm::models::Mdp<T> const& labeledMdp, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates) { |
|
|
|
StateInformation result; |
|
|
|
storm::storage::SparseMatrix<bool> backwardTransitions = labeledMdp.getBackwardTransitions(); |
|
|
|
result.relevantStates = storm::utility::graph::performProbGreater0E(labeledMdp, backwardTransitions, phiStates, psiStates); |
|
|
|
result.relevantStates &= ~psiStates; |
|
|
|
storm::storage::BitVector problematicStates = storm::utility::graph::performProbGreater0E(labeledMdp, backwardTransitions, phiStates, psiStates); |
|
|
|
result.problematicStates.complement(); |
|
|
|
result.problematicStates &= result.relevantStates; |
|
|
|
LOG4CPLUS_DEBUG(logger, "Found " << phiStates.getNumberOfSetBits() << " filter states (" << phiStates.toString() << ")."); |
|
|
|
LOG4CPLUS_DEBUG(logger, "Found " << psiStates.getNumberOfSetBits() << " target states (" << psiStates.toString() << ")."); |
|
|
|
LOG4CPLUS_DEBUG(logger, "Found " << result.relevantStates.getNumberOfSetBits() << " relevant states (" << result.relevantStates.toString() << ")."); |
|
|
|
LOG4CPLUS_DEBUG(logger, "Found " << result.problematicStates.getNumberOfSetBits() << " problematic states (" << result.problematicStates.toString() << ")."); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Determines the relevant and problematic choices of the given MDP with respect to the given parameters. |
|
|
|
* |
|
|
|
* @param labeledMdp The MDP whose choices to search. |
|
|
|
* @param stateInformation The relevant and problematic states of the model. |
|
|
|
* @param psiStates A bit vector characterizing the psi states in the model. |
|
|
|
* @return A structure that stores the relevant and problematic choices in the model as well as the set |
|
|
|
* of relevant labels. |
|
|
|
*/ |
|
|
|
static struct ChoiceInformation determineRelevantAndProblematicChoices(storm::models::Mdp<T> const& labeledMdp, StateInformation const& stateInformation, storm::storage::BitVector const& psiStates) { |
|
|
|
// Create result and shortcuts to needed data for convenience. |
|
|
|
ChoiceInformation result; |
|
|
|
storm::storage::SparseMatrix<T> const& transitionMatrix = labeledMdp.getTransitionMatrix(); |
|
|
|
std::vector<uint_fast64_t> const& nondeterministicChoiceIndices = labeledMdp.getNondeterministicChoiceIndices(); |
|
|
|
std::vector<std::list<uint_fast64_t>> const& choiceLabeling = labeledMdp.getChoiceLabeling(); |
|
|
|
|
|
|
|
// Now traverse all choices of all relevant states and check whether there is a relevant target state. |
|
|
|
// If so, the associated labels become relevant. |
|
|
|
for (auto state : relevantStates) { |
|
|
|
relevantChoicesForRelevantStates.emplace(state, std::list<uint_fast64_t>()); |
|
|
|
if (problematicStates.get(state)) { |
|
|
|
problematicChoicesForProblematicStates.emplace(state, std::list<uint_fast64_t>()); |
|
|
|
// If so, the associated labels become relevant. Also, if a choice of relevant state has at least one |
|
|
|
// relevant successor, the choice is considered to be relevant. |
|
|
|
for (auto state : stateInformation.relevantStates) { |
|
|
|
result.relevantChoicesForRelevantStates.emplace(state, std::list<uint_fast64_t>()); |
|
|
|
if (stateInformation.problematicStates.get(state)) { |
|
|
|
result.problematicChoicesForProblematicStates.emplace(state, std::list<uint_fast64_t>()); |
|
|
|
} |
|
|
|
for (uint_fast64_t row = nondeterministicChoiceIndices[state]; row < nondeterministicChoiceIndices[state + 1]; ++row) { |
|
|
|
bool currentChoiceRelevant = false; |
|
|
|
bool allSuccessorsProblematic = true; |
|
|
|
for (typename storm::storage::SparseMatrix<T>::ConstIndexIterator successorIt = transitionMatrix.constColumnIteratorBegin(row); successorIt != transitionMatrix.constColumnIteratorEnd(row); ++successorIt) { |
|
|
|
// If there is a relevant successor, we need to add the labels of the current choice. |
|
|
|
if (relevantStates.get(*successorIt) || psiStates.get(*successorIt)) { |
|
|
|
if (stateInformation.relevantStates.get(*successorIt) || psiStates.get(*successorIt)) { |
|
|
|
for (auto const& label : choiceLabeling[row]) { |
|
|
|
relevantLabels.insert(label); |
|
|
|
result.relevantLabels.insert(label); |
|
|
|
} |
|
|
|
if (!currentChoiceRelevant) { |
|
|
|
currentChoiceRelevant = true; |
|
|
|
relevantChoicesForRelevantStates[state].emplace_back(row); |
|
|
|
result.relevantChoicesForRelevantStates[state].emplace_back(row); |
|
|
|
} |
|
|
|
} |
|
|
|
if (!problematicStates.get(*successorIt)) { |
|
|
|
if (!stateInformation.problematicStates.get(*successorIt)) { |
|
|
|
allSuccessorsProblematic = false; |
|
|
|
} |
|
|
|
} |
|
|
|
if (problematicStates.get(state) && allSuccessorsProblematic) { |
|
|
|
problematicChoicesForProblematicStates[state].emplace_back(row); |
|
|
|
|
|
|
|
// If all successors of a problematic state are problematic themselves, we record this choice |
|
|
|
// as being problematic. |
|
|
|
if (stateInformation.problematicStates.get(state) && allSuccessorsProblematic) { |
|
|
|
result.problematicChoicesForProblematicStates[state].emplace_back(row); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
LOG4CPLUS_INFO(logger, "Found " << relevantLabels.size() << " relevant labels."); |
|
|
|
for (auto label : relevantLabels) { |
|
|
|
LOG4CPLUS_INFO(logger, "Relevant label " << label << "."); |
|
|
|
} |
|
|
|
|
|
|
|
// (3) Encode resulting system as MILP problem. |
|
|
|
// (3.1) Initialize MILP solver and model. |
|
|
|
LOG4CPLUS_DEBUG(logger, "Found " << result.relevantLabels.size() << " relevant labels."); |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates a Gurobi environment and model and returns pointers to them. |
|
|
|
* |
|
|
|
* @return A pair of two pointers to a Gurobi environment and model, respectively. |
|
|
|
*/ |
|
|
|
static std::pair<GRBenv*, GRBmodel*> getGurobiEnvironmentAndModel() { |
|
|
|
GRBenv* env = nullptr; |
|
|
|
int error = GRBloadenv(&env, "storm_gurobi.log"); |
|
|
|
if (error || env == NULL) { |
|
|
@ -118,22 +167,29 @@ namespace storm { |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not initialize Gurobi (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
GRBmodel* model = nullptr; |
|
|
|
error = GRBnewmodel(env, &model, "storm_milp", 0, nullptr, nullptr, nullptr, nullptr, nullptr); |
|
|
|
error = GRBnewmodel(env, &model, "minimal_label_milp", 0, nullptr, nullptr, nullptr, nullptr, nullptr); |
|
|
|
if (error) { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not initialize Gurobi model (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not initialize Gurobi model (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
|
|
|
|
// (3.2) Create variables. |
|
|
|
|
|
|
|
// Prepare internal variables. |
|
|
|
return std::make_pair(env, model); |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates the variables for the labels of the model. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param relevantLabels The set of relevant labels of the model. |
|
|
|
* @param nextFreeVariableIndex A reference to the next free variable index. Note: when creating new |
|
|
|
* variables, this value is increased. |
|
|
|
* @return A mapping from labels to variable indices. |
|
|
|
*/ |
|
|
|
static std::unordered_map<uint_fast64_t, uint_fast64_t> createLabelVariables(GRBenv* env, GRBmodel* model, std::unordered_set<uint_fast64_t> const& relevantLabels, uint_fast64_t& nextFreeVariableIndex) { |
|
|
|
int error = 0; |
|
|
|
std::stringstream variableNameBuffer; |
|
|
|
uint_fast64_t nextLabelIndex = 0; |
|
|
|
|
|
|
|
// Create variables for involved labels. |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> labelToIndexMap; |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> resultingMap; |
|
|
|
for (auto const& label : relevantLabels) { |
|
|
|
// Reset stringstream properly to construct new variable name. |
|
|
|
variableNameBuffer.str(""); |
|
|
|
variableNameBuffer.clear(); |
|
|
|
variableNameBuffer << "label" << label; |
|
|
@ -142,17 +198,31 @@ namespace storm { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
labelToIndexMap[label] = nextLabelIndex; |
|
|
|
++nextLabelIndex; |
|
|
|
resultingMap[label] = nextFreeVariableIndex; |
|
|
|
++nextFreeVariableIndex; |
|
|
|
} |
|
|
|
|
|
|
|
// Create scheduler variables for relevant states and their actions. |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> stateToStartingIndexMap; |
|
|
|
for (auto state : relevantStates) { |
|
|
|
std::list<uint_fast64_t> const& relevantChoicesForState = relevantChoicesForRelevantStates[state]; |
|
|
|
stateToStartingIndexMap[state] = nextLabelIndex; |
|
|
|
return resultingMap; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates the variables for the relevant choices in the model. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param stateInformation The information about the states of the model. |
|
|
|
* @param choiceInformation The information about the choices of the model. |
|
|
|
* @param nextFreeVariableIndex A reference to the next free variable index. Note: when creating new |
|
|
|
* variables, this value is increased. |
|
|
|
* @return A mapping from states to a list of choice variable indices. |
|
|
|
*/ |
|
|
|
static std::unordered_map<uint_fast64_t, std::list<uint_fast64_t>> createSchedulerVariables(GRBenv* env, GRBmodel* model, StateInformation const& stateInformation, ChoiceInformation const& choiceInformation, uint_fast64_t& nextFreeVariableIndex) { |
|
|
|
int error = 0; |
|
|
|
std::stringstream variableNameBuffer; |
|
|
|
std::unordered_map<uint_fast64_t, std::list<uint_fast64_t>> resultingMap; |
|
|
|
for (auto state : stateInformation.relevantStates) { |
|
|
|
resultingMap.emplace(state, std::list<uint_fast64_t>()); |
|
|
|
std::list<uint_fast64_t> const& relevantChoicesForState = choiceInformation.relevantChoicesForRelevantStates[state]; |
|
|
|
for (uint_fast64_t row : relevantChoicesForState) { |
|
|
|
// Reset stringstream properly to construct new variable name. |
|
|
|
variableNameBuffer.str(""); |
|
|
|
variableNameBuffer.clear(); |
|
|
|
variableNameBuffer << "choice" << row << "in" << state; |
|
|
@ -161,14 +231,28 @@ namespace storm { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
++nextLabelIndex; |
|
|
|
resultingMap[state].emplace_back(nextFreeVariableIndex); |
|
|
|
++nextFreeVariableIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Create variables for probabilities for all relevant states. |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> stateToProbabilityVariableIndex; |
|
|
|
for (auto state : relevantStates) { |
|
|
|
// Reset stringstream properly to construct new variable name. |
|
|
|
return resultingMap; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates the variables for the probabilities in the model. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param stateInformation The information about the states in the model. |
|
|
|
* @param nextFreeVariableIndex A reference to the next free variable index. Note: when creating new |
|
|
|
* variables, this value is increased. |
|
|
|
* @return A mapping from states to the index of the corresponding probability variables. |
|
|
|
*/ |
|
|
|
static std::unordered_map<uint_fast64_t, uint_fast64_t> createProbabilityVariables(GRBenv* env, GRBmodel* model, StateInformation const& stateInformation, uint_fast64_t& nextFreeVariableIndex) { |
|
|
|
int error = 0; |
|
|
|
std::stringstream variableNameBuffer; |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> resultingMap; |
|
|
|
for (auto state : stateInformation.relevantStates) { |
|
|
|
variableNameBuffer.str(""); |
|
|
|
variableNameBuffer.clear(); |
|
|
|
variableNameBuffer << "p" << state; |
|
|
@ -177,36 +261,48 @@ namespace storm { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
stateToProbabilityVariableIndex[state] = nextLabelIndex; |
|
|
|
++nextLabelIndex; |
|
|
|
resultingMap[state] = nextFreeVariableIndex; |
|
|
|
++nextFreeVariableIndex; |
|
|
|
} |
|
|
|
|
|
|
|
// Create variables for problematic states, successors of problematic states and transitions of problematic states. |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> problematicStateVariablesToIndexMap; |
|
|
|
std::unordered_map<std::pair<uint_fast64_t, uint_fast64_t>, uint_fast64_t, PairHash> problematicTransitionVariables; |
|
|
|
for (auto state : problematicStates) { |
|
|
|
// First check whether there is not already a variable for this state and proceed with next state. |
|
|
|
if (problematicStateVariablesToIndexMap.find(state) == problematicStateVariablesToIndexMap.end()) { |
|
|
|
// Reset stringstream properly to construct new variable name. |
|
|
|
return resultingMap; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates the variables for the problematic states in the model. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param labeledMdp The labeled MDP. |
|
|
|
* @param stateInformation The information about the states in the model. |
|
|
|
* @param nextFreeVariableIndex A reference to the next free variable index. Note: when creating new |
|
|
|
* variables, this value is increased. |
|
|
|
* @return A mapping from problematic states to the index of the corresponding variables. |
|
|
|
*/ |
|
|
|
static std::unordered_map<uint_fast64_t, uint_fast64_t> createProblematicStateVariables(GRBenv* env, GRBmodel* model, storm::models::Mdp<T> const& labeledMdp, StateInformation const& stateInformation, ChoiceInformation const& choiceInformation, uint_fast64_t& nextFreeVariableIndex) { |
|
|
|
int error = 0; |
|
|
|
std::stringstream variableNameBuffer; |
|
|
|
std::unordered_map<uint_fast64_t, uint_fast64_t> resultingMap; |
|
|
|
for (auto state : stateInformation.problematicStates) { |
|
|
|
// First check whether there is not already a variable for this state and advance to the next state |
|
|
|
// in this case. |
|
|
|
if (resultingMap.find(state) == resultingMap.end()) { |
|
|
|
variableNameBuffer.str(""); |
|
|
|
variableNameBuffer.clear(); |
|
|
|
variableNameBuffer << "r" << state; |
|
|
|
std::cout << "Creating r variable" << std::endl; |
|
|
|
error = GRBaddvar(model, 0, nullptr, nullptr, 0.0, 0.0, 1.0, GRB_CONTINUOUS, variableNameBuffer.str().c_str()); |
|
|
|
if (error) { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
problematicStateVariablesToIndexMap[state] = nextLabelIndex; |
|
|
|
++nextLabelIndex; |
|
|
|
resultingMap[state] = nextFreeVariableIndex; |
|
|
|
++nextFreeVariableIndex; |
|
|
|
} |
|
|
|
|
|
|
|
std::list<uint_fast64_t> const& relevantChoicesForState = relevantChoicesForRelevantStates[state]; |
|
|
|
for (uint_fast64_t row : relevantChoicesForState) { |
|
|
|
for (typename storm::storage::SparseMatrix<T>::ConstIndexIterator successorIt = transitionMatrix.constColumnIteratorBegin(row); successorIt != transitionMatrix.constColumnIteratorEnd(row); ++successorIt) { |
|
|
|
if (relevantStates.get(*successorIt)) { |
|
|
|
if (problematicStateVariablesToIndexMap.find(*successorIt) == problematicStateVariablesToIndexMap.end()) { |
|
|
|
// Reset stringstream properly to construct new variable name. |
|
|
|
std::list<uint_fast64_t> const& relevantChoicesForState = choiceInformation.relevantChoicesForRelevantStates[state]; |
|
|
|
for (uint_fast64_t row : choiceInformation.relevantChoicesForState) { |
|
|
|
for (typename storm::storage::SparseMatrix<T>::ConstIndexIterator successorIt = labeledMdp.getTransitionMatrix().constColumnIteratorBegin(row); successorIt != labeledMdp.getTransitionMatrix().constColumnIteratorEnd(row); ++successorIt) { |
|
|
|
if (stateInformation.relevantStates.get(*successorIt)) { |
|
|
|
if (resultingMap.find(*successorIt) == resultingMap.end()) { |
|
|
|
variableNameBuffer.str(""); |
|
|
|
variableNameBuffer.clear(); |
|
|
|
variableNameBuffer << "r" << *successorIt; |
|
|
@ -215,9 +311,37 @@ namespace storm { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
problematicStateVariablesToIndexMap[state] = nextLabelIndex; |
|
|
|
++nextLabelIndex; |
|
|
|
resultingMap[state] = nextFreeVariableIndex; |
|
|
|
++nextFreeVariableIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return resultingMap; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates the variables for the problematic choices in the model. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param labeledMdp The labeled MDP. |
|
|
|
* @param stateInformation The information about the states in the model. |
|
|
|
* @param choiceInformation The information about the choices in the model. |
|
|
|
* @param nextFreeVariableIndex A reference to the next free variable index. Note: when creating new |
|
|
|
* variables, this value is increased. |
|
|
|
* @return A mapping from problematic choices to the index of the corresponding variables. |
|
|
|
*/ |
|
|
|
static std::unordered_map<std::pair<uint_fast64_t, uint_fast64_t>, uint_fast64_t, PairHash> createProblematicChoiceVariables(GRBenv* env, GRBmodel* model, storm::models::Mdp<T> const& labeledMdp, StateInformation const& stateInformation, ChoiceInformation const& choiceInformation, uint_fast64_t& nextFreeVariableIndex) { |
|
|
|
int error = 0; |
|
|
|
std::stringstream variableNameBuffer; |
|
|
|
std::unordered_map<std::pair<uint_fast64_t, uint_fast64_t>, uint_fast64_t, PairHash> resultingMap; |
|
|
|
for (auto state : stateInformation.problematicStates) { |
|
|
|
std::list<uint_fast64_t> const& relevantChoicesForState = choiceInformation.relevantChoicesForRelevantStates[state]; |
|
|
|
for (uint_fast64_t row : choiceInformation.relevantChoicesForState) { |
|
|
|
for (typename storm::storage::SparseMatrix<T>::ConstIndexIterator successorIt = labeledMdp.getTransitionMatrix().constColumnIteratorBegin(row); successorIt != labeledMdp.getTransitionMatrix().constColumnIteratorEnd(row); ++successorIt) { |
|
|
|
if (stateInformation.relevantStates.get(*successorIt)) { |
|
|
|
variableNameBuffer.str(""); |
|
|
|
variableNameBuffer.clear(); |
|
|
|
variableNameBuffer << "t" << state << "to" << *successorIt; |
|
|
@ -226,58 +350,134 @@ namespace storm { |
|
|
|
LOG4CPLUS_ERROR(logger, "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Could not create Gurobi variable (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
problematicTransitionVariables[std::make_pair(state, *successorIt)] = nextLabelIndex; |
|
|
|
++nextLabelIndex; |
|
|
|
resultingMap[std::make_pair(state, *successorIt)] = nextFreeVariableIndex; |
|
|
|
++nextFreeVariableIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return resultingMap; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Creates all variables needed to encode the problem as an MILP problem and returns a struct containing |
|
|
|
* information about the variables that were created. This implicitly establishes the objective function |
|
|
|
* passed to the solver. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param labeledMdp The labeled MDP. |
|
|
|
* @param stateInformation The information about the states in the model. |
|
|
|
* @param choiceInformation The information about the choices in the model. |
|
|
|
*/ |
|
|
|
static VariableInformation createVariables(GRBenv* env, GRBmodel* model, storm::models::Mdp<T> const& labeledMdp, StateInformation const& stateInformation, ChoiceInformation const& choiceInformation) { |
|
|
|
// Create a struct that stores all information about variables. |
|
|
|
VariableInformation result; |
|
|
|
|
|
|
|
LOG4CPLUS_INFO(logger, "Successfully created " << nextLabelIndex << " Gurobi variables."); |
|
|
|
// Create variables for involved labels. |
|
|
|
result.labelToVariableIndexMap = createLabelVariables(env, model, choiceInformation.relevantLabes, result.nextFreeVariableIndex); |
|
|
|
|
|
|
|
// Update model to incorporate prior changes. |
|
|
|
error = GRBupdatemodel(model); |
|
|
|
// Create scheduler variables for relevant states and their actions. |
|
|
|
result.stateToChoiceVariablesIndexMap = createSchedulerVariables(env, model, stateInformation, choiceInformation, result.nextFreeVariableIndex); |
|
|
|
|
|
|
|
// Create variables for probabilities for all relevant states. |
|
|
|
result.stateToProbabilityVariableIndexMap = createProbabilityVariables(env, model, stateInformation, result.nextFreeVariableIndex); |
|
|
|
|
|
|
|
// Create variables for problematic states. |
|
|
|
result.problematicStateToVariableIndexMap = createProblematicStateVariables(env, model, stateInformation, result.nextFreeVariableIndex); |
|
|
|
|
|
|
|
// Create variables for problematic choices. |
|
|
|
result.problematicTransitionToVariableIndexMap = createProblematicChoiceVariables(env, model, stateInformation, choiceInformation, result.nextFreeVariableIndex); |
|
|
|
|
|
|
|
LOG4CPLUS_INFO(logger, "Successfully created " << result.nextFreeVariableIndex << " Gurobi variables."); |
|
|
|
|
|
|
|
// Finally, return variable information struct. |
|
|
|
return result; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Updates the Gurobi model to incorporate any prior changes. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
*/ |
|
|
|
static void updateModel(GRBenv* env, GRBmodel* model) { |
|
|
|
int error = GRBupdatemodel(model); |
|
|
|
if (error) { |
|
|
|
LOG4CPLUS_ERROR(logger, "Unable to update Gurobi model (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Unable to update Gurobi model (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
|
|
|
|
// Make sure we have exactly one initial state and assert that it's probability is above the given threshold. |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Asserts a constraint in the MILP problem that makes sure the reachability probability in the subsystem |
|
|
|
* exceeds the given threshold. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param labeledMdp The labeled MDP. |
|
|
|
* @param variableInformation A struct with information about the variables of the model. |
|
|
|
* @param probabilityThreshold The probability that the subsystem must exceed. |
|
|
|
*/ |
|
|
|
static void assertProbabilityGreaterThanThreshold(GRBenv* env, GRBmodel* model, storm::models::Mdp<T> const& labeledMdp, VariableInformation const& variableInformation, T probabilityThreshold) { |
|
|
|
int error = 0; |
|
|
|
storm::storage::BitVector const& initialStates = labeledMdp.getLabeledStates("init"); |
|
|
|
if (initialStates.getNumberOfSetBits() != 1) { |
|
|
|
LOG4CPLUS_ERROR(logger, "Must have exactly one initial state, but got " << initialStates.getNumberOfSetBits() << "instead."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Must have exactly one initial state, but got " << initialStates.getNumberOfSetBits() << "instead."; |
|
|
|
} |
|
|
|
for (auto initialState : initialStates) { |
|
|
|
int variableIndex = static_cast<int>(stateToProbabilityVariableIndex[initialState]); |
|
|
|
int variableIndex = static_cast<int>(variableInformation.stateToProbabilityVariableIndexMap[initialState]); |
|
|
|
double coefficient = 1.0; |
|
|
|
error = GRBaddconstr(model, 1, &variableIndex, &coefficient, GRB_GREATER_EQUAL, lowerProbabilityBound + 1e-6, nullptr); |
|
|
|
error = GRBaddconstr(model, 1, &variableIndex, &coefficient, GRB_GREATER_EQUAL, probabilityThreshold + 1e-6, nullptr); |
|
|
|
if (error) { |
|
|
|
LOG4CPLUS_ERROR(logger, "Unable to assert constraint (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Unable to assert constraint (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Now add the constaints that ensure that the policy chooses at most one action in each state. |
|
|
|
for (auto state : relevantStates) { |
|
|
|
uint_fast64_t startingIndex = stateToStartingIndexMap[state]; |
|
|
|
std::list<uint_fast64_t> const& relevantChoicesForState = relevantChoicesForRelevantStates[state]; |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Asserts constraints that make sure the selected policy is valid, i.e. chooses at most one action in each state. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param stateInformation The information about the states in the model. |
|
|
|
* @param variableInformation A struct with information about the variables of the model. |
|
|
|
*/ |
|
|
|
static void assertValidPolicy(GRBenv* env, GRBmodel* model, StateInformation const& stateInformation, VariableInformation const& variableInformation) { |
|
|
|
int error = 0; |
|
|
|
for (auto state : stateInformation.relevantStates) { |
|
|
|
std::list<uint_fast64_t> const& choiceVariableIndices = variableInformation.stateToChoiceVariablesIndexMap[state]; |
|
|
|
std::vector<int> variables; |
|
|
|
std::vector<double> coefficients(relevantChoicesForState.size(), 1); |
|
|
|
variables.reserve(relevantChoicesForState.size()); |
|
|
|
for (auto choice : relevantChoicesForState) { |
|
|
|
variables.push_back(static_cast<int>(startingIndex++)); |
|
|
|
std::vector<double> coefficients(choiceVariableIndices.size(), 1); |
|
|
|
variables.reserve(choiceVariableIndices.size()); |
|
|
|
for (auto choiceVariableIndex : choiceVariableIndices) { |
|
|
|
variables.push_back(static_cast<int>(choiceVariableIndex)); |
|
|
|
} |
|
|
|
error = GRBaddconstr(model, relevantChoicesForState.size(), &variables[0], &coefficients[0], GRB_LESS_EQUAL, 1, nullptr); |
|
|
|
error = GRBaddconstr(model, choiceVariableIndices.size(), &variables[0], &coefficients[0], GRB_LESS_EQUAL, 1, nullptr); |
|
|
|
if (error) { |
|
|
|
LOG4CPLUS_ERROR(logger, "Unable to assert constraint (" << GRBgeterrormsg(env) << ")."); |
|
|
|
throw storm::exceptions::InvalidStateException() << "Unable to assert constraint (" << GRBgeterrormsg(env) << ")."; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Add constraints that enforce that certain labels are put into the label set when the corresponding |
|
|
|
// transitions get selected. |
|
|
|
for (auto state : relevantStates) { |
|
|
|
} |
|
|
|
|
|
|
|
/*! |
|
|
|
* Asserts constraints that make sure the labels are included in the solution set if the policy selects a |
|
|
|
* choice that is labeled with the label in question. |
|
|
|
* |
|
|
|
* @param env The Gurobi environment. |
|
|
|
* @param model The Gurobi model. |
|
|
|
* @param labeledMdp The labeled MDP. |
|
|
|
* @param stateInformation The information about the states in the model. |
|
|
|
* @param choiceInformation The information about the choices in the model. |
|
|
|
* @param variableInformation A struct with information about the variables of the model. |
|
|
|
*/ |
|
|
|
static void assertChoicesImplyLabels(GRBenv* env, GRBmodel* model, storm::models::Mdp<T> const& labeledMdp, StateInformation const& stateInformation, ChoiceInformation const& choiceInformation, VariableInformation const& variableInformation) { |
|
|
|
int error = 0; |
|
|
|
for (auto state : stateInformation.relevantStates) { |
|
|
|
std::list<uint_fast64_t> const& choiceVariableIndices = variableInformation.stateToChoiceVariablesIndexMap[state]; |
|
|
|
uint_fast64_t currentChoiceVariableIndex = stateToStartingIndexMap[state]; |
|
|
|
for (auto choice : relevantChoicesForRelevantStates[state]) { |
|
|
|
int indices[2]; indices[0] = 0; indices[1] = currentChoiceVariableIndex; |
|
|
@ -293,6 +493,53 @@ namespace storm { |
|
|
|
++currentChoiceVariableIndex; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static void buildConstraintSystem(GRBenv* env, GRBmodel* model, storm::models::Mdp<T> const& labeledMdp, StateInformation const& stateInformation, ChoiceInformation const& choiceInformation, VariableInformation const& variableInformation, T probabilityThreshold) { |
|
|
|
// Assert that the reachability probability in the subsystem exceeds the given threshold. |
|
|
|
assertProbabilityGreaterThanThreshold(env, model, labeledMdp, variableInformation, probabilityThreshold); |
|
|
|
|
|
|
|
// Add constraints that assert the policy takes at most one action in each state. |
|
|
|
assertValidPolicy(env, model, stateInformation, variableInformation); |
|
|
|
|
|
|
|
assertChoicesImplyLabels(env, model, labeledMdp, stateInformation, choiceInformation, variableInformation); |
|
|
|
} |
|
|
|
|
|
|
|
// computeLabelSetFromSolution |
|
|
|
|
|
|
|
public: |
|
|
|
|
|
|
|
static std::unordered_set<uint_fast64_t> getMinimalLabelSet(storm::models::Mdp<T> const& labeledMdp, storm::storage::BitVector const& phiStates, storm::storage::BitVector const& psiStates, T probabilityThreshold, bool checkThresholdFeasible = false) { |
|
|
|
#ifdef HAVE_GUROBI |
|
|
|
// (0) Check whether the MDP is indeed labeled. |
|
|
|
if (!labeledMdp.hasChoiceLabels()) { |
|
|
|
throw storm::exceptions::InvalidArgumentException() << "Minimal label set generation is impossible for unlabeled model."; |
|
|
|
} |
|
|
|
|
|
|
|
// (1) TODO: check whether its possible to exceed the threshold if checkThresholdFeasible is set. |
|
|
|
|
|
|
|
// (2) Identify relevant and problematic states. |
|
|
|
StateInformation stateInformation = determineRelevantAndProblematicStates(labeledMdp, phiStates, psiStates); |
|
|
|
|
|
|
|
// (3) Determine sets of relevant labels and problematic choices. |
|
|
|
ChoiceInformation choiceInformation = determineRelevantAndProblematicChoices(labeledMdp, stateInformation); |
|
|
|
|
|
|
|
// (4) Encode resulting system as MILP problem. |
|
|
|
// (4.1) Initialize MILP solver and model. |
|
|
|
std::pair<GRBenv*, GRBmodel*> environmentModelPair = getGurobiEnvironmentAndModel(); |
|
|
|
|
|
|
|
// (4.2) Create variables. |
|
|
|
VariableInformation variableInformation = createVariables(environmentModelPair.first, environmentModelPair.second, labeledMdp, stateInformation, choiceInformation); |
|
|
|
|
|
|
|
// Update model. |
|
|
|
updateModel(environmentModelPair.first, environmentModelPair.second); |
|
|
|
|
|
|
|
// Create all constraints. |
|
|
|
buildConstraintSystem(environmentModelPair.first, environmentModelPair.second, labeledMdp, stateInformation, choiceInformation, variableInformation, probabilityThreshold); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Add constraints that make sure the reachability probability for states that do not choose any action |
|
|
|
// is zero. |
|
|
@ -440,8 +687,8 @@ namespace storm { |
|
|
|
// (4) Read off result from MILP variables. |
|
|
|
|
|
|
|
// (5) Shutdown MILP solver. |
|
|
|
GRBfreemodel(model); |
|
|
|
GRBfreeenv(env); |
|
|
|
GRBfreemodel(environmentModelPair.second); |
|
|
|
GRBfreeenv(environmentModelPair.first); |
|
|
|
|
|
|
|
// (6) Potentially verify whether the resulting system exceeds the given threshold. |
|
|
|
// (7) Return result. |
|
|
|