Browse Source

kSP: accept matrix-vector input; various other stuff

tempestpy_adaptions
Tom Janson 8 years ago
parent
commit
daa76f7eb4
  1. 118
      src/utility/shortestPaths.cpp
  2. 120
      src/utility/shortestPaths.h
  3. 34
      src/utility/shortestPaths.md
  4. 32
      test/functional/utility/KSPTest.cpp

118
src/utility/shortestPaths.cpp

@ -1,20 +1,30 @@
#include <queue> #include <queue>
#include <set> #include <set>
#include <string>
#include "macros.h"
#include "shortestPaths.h" #include "shortestPaths.h"
#include "graph.h" #include "graph.h"
// FIXME: I've accidentally used k=0 *twice* now without realizing that k>=1 is required!
// Accessing zero should trigger a warning!
// (Also, did I document this? I think so, somewhere. I went with k>=1 because
// that's what the KSP paper used, but in retrospect k>=0 seems more intuitive ...)
namespace storm { namespace storm {
namespace utility { namespace utility {
namespace ksp { namespace ksp {
template <typename T> template <typename T>
ShortestPathsGenerator<T>::ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model,
state_list_t const& targets) : model(model), targets(targets) {
// FIXME: does this create a copy? I don't need one, so I should avoid that
transitionMatrix = model->getTransitionMatrix();
numStates = model->getNumberOfStates() + 1; // one more for meta-target
metaTarget = numStates - 1; // first unused state number
targetSet = std::unordered_set<state_t>(targets.begin(), targets.end());
ShortestPathsGenerator<T>::ShortestPathsGenerator(storage::SparseMatrix<T> const& transitionMatrix,
std::unordered_map<state_t, T> const& targetProbMap,
BitVector const& initialStates,
MatrixFormat matrixFormat) :
transitionMatrix(transitionMatrix),
numStates(transitionMatrix.getColumnCount() + 1), // one more for meta-target
metaTarget(transitionMatrix.getColumnCount()), // first unused state index
initialStates(initialStates),
targetProbMap(targetProbMap),
matrixFormat(matrixFormat) {
computePredecessors(); computePredecessors();
@ -29,20 +39,30 @@ namespace storm {
candidatePaths.resize(numStates); candidatePaths.resize(numStates);
} }
template <typename T>
ShortestPathsGenerator<T>::ShortestPathsGenerator(storage::SparseMatrix<T> const& transitionMatrix,
std::vector<T> const& targetProbVector, BitVector const& initialStates, MatrixFormat matrixFormat)
: ShortestPathsGenerator<T>(transitionMatrix, vectorToMap(targetProbVector), initialStates, matrixFormat) {}
// extracts the relevant info from the model and delegates to ctor above
template <typename T>
ShortestPathsGenerator<T>::ShortestPathsGenerator(Model const& model, BitVector const& targetBV)
: ShortestPathsGenerator<T>(model.getTransitionMatrix(), allProbOneMap(targetBV), model.getInitialStates(), MatrixFormat::straight) {}
// several alternative ways to specify the targets are provided, // several alternative ways to specify the targets are provided,
// each converts the targets and delegates to the ctor above // each converts the targets and delegates to the ctor above
// I admit it's kind of ugly, but actually pretty convenient (and I've wanted to try C++11 delegation) // I admit it's kind of ugly, but actually pretty convenient (and I've wanted to try C++11 delegation)
template <typename T> template <typename T>
ShortestPathsGenerator<T>::ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model,
state_t singleTarget) : ShortestPathsGenerator<T>(model, std::vector<state_t>{singleTarget}) {}
ShortestPathsGenerator<T>::ShortestPathsGenerator(Model const& model, state_t singleTarget)
: ShortestPathsGenerator<T>(model, std::vector<state_t>{singleTarget}) {}
template <typename T> template <typename T>
ShortestPathsGenerator<T>::ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model,
storage::BitVector const& targetBV) : ShortestPathsGenerator<T>(model, bitvectorToList(targetBV)) {}
ShortestPathsGenerator<T>::ShortestPathsGenerator(Model const& model, std::vector<state_t> const& targetList)
: ShortestPathsGenerator<T>(model, BitVector(model.getNumberOfStates(), targetList)) {}
template <typename T> template <typename T>
ShortestPathsGenerator<T>::ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model,
std::string const& targetLabel) : ShortestPathsGenerator<T>(model, bitvectorToList(model->getStates(targetLabel))) {}
ShortestPathsGenerator<T>::ShortestPathsGenerator(Model const& model, std::string const& targetLabel)
: ShortestPathsGenerator<T>(model, model.getStates(targetLabel)) {}
template <typename T> template <typename T>
T ShortestPathsGenerator<T>::getDistance(unsigned long k) { T ShortestPathsGenerator<T>::getDistance(unsigned long k) {
@ -51,9 +71,9 @@ namespace storm {
} }
template <typename T> template <typename T>
storage::BitVector ShortestPathsGenerator<T>::getStates(unsigned long k) {
BitVector ShortestPathsGenerator<T>::getStates(unsigned long k) {
computeKSP(k); computeKSP(k);
storage::BitVector stateSet(numStates - 1, false); // no meta-target
BitVector stateSet(numStates - 1, false); // no meta-target
Path<T> currentPath = kShortestPaths[metaTarget][k - 1]; Path<T> currentPath = kShortestPaths[metaTarget][k - 1];
boost::optional<state_t> maybePredecessor = currentPath.predecessorNode; boost::optional<state_t> maybePredecessor = currentPath.predecessorNode;
@ -71,10 +91,10 @@ namespace storm {
} }
template <typename T> template <typename T>
state_list_t ShortestPathsGenerator<T>::getPathAsList(unsigned long k) {
std::vector<state_t> ShortestPathsGenerator<T>::getPathAsList(unsigned long k) {
computeKSP(k); computeKSP(k);
state_list_t backToFrontList;
std::vector<state_t> backToFrontList;
Path<T> currentPath = kShortestPaths[metaTarget][k - 1]; Path<T> currentPath = kShortestPaths[metaTarget][k - 1];
boost::optional<state_t> maybePredecessor = currentPath.predecessorNode; boost::optional<state_t> maybePredecessor = currentPath.predecessorNode;
@ -100,16 +120,20 @@ namespace storm {
for (state_t i = 0; i < numStates - 1; i++) { for (state_t i = 0; i < numStates - 1; i++) {
for (auto const& transition : transitionMatrix.getRowGroup(i)) { for (auto const& transition : transitionMatrix.getRowGroup(i)) {
// to avoid non-minimal paths, the target states are
// to avoid non-minimal paths, the meta-target-predecessors are
// *not* predecessors of any state but the meta-target // *not* predecessors of any state but the meta-target
if (std::find(targets.begin(), targets.end(), i) == targets.end()) {
if (!isMetaTargetPredecessor(i)) {
graphPredecessors[transition.getColumn()].push_back(i); graphPredecessors[transition.getColumn()].push_back(i);
} }
} }
} }
// meta-target has exactly the target states as predecessors
graphPredecessors[metaTarget] = targets;
// meta-target has exactly the meta-target-predecessors as predecessors
// (duh. note that the meta-target-predecessors used to be called target,
// but that's not necessarily true in the matrix/value invocation case)
for (auto targetProbPair : targetProbMap) {
graphPredecessors[metaTarget].push_back(targetProbPair.first);
}
} }
template <typename T> template <typename T>
@ -117,8 +141,8 @@ namespace storm {
// the existing Dijkstra isn't working anyway AND // the existing Dijkstra isn't working anyway AND
// doesn't fully meet our requirements, so let's roll our own // doesn't fully meet our requirements, so let's roll our own
T inftyDistance = utility::zero<T>();
T zeroDistance = utility::one<T>();
T inftyDistance = zero<T>();
T zeroDistance = one<T>();
shortestPathDistances.resize(numStates, inftyDistance); shortestPathDistances.resize(numStates, inftyDistance);
shortestPathPredecessors.resize(numStates, boost::optional<state_t>()); shortestPathPredecessors.resize(numStates, boost::optional<state_t>());
@ -126,7 +150,7 @@ namespace storm {
// default comparison on pair actually works fine if distance is the first entry // default comparison on pair actually works fine if distance is the first entry
std::set<std::pair<T, state_t>, std::greater<std::pair<T, state_t>>> dijkstraQueue; std::set<std::pair<T, state_t>, std::greater<std::pair<T, state_t>>> dijkstraQueue;
for (state_t initialState : model->getInitialStates()) {
for (state_t initialState : initialStates) {
shortestPathDistances[initialState] = zeroDistance; shortestPathDistances[initialState] = zeroDistance;
dijkstraQueue.emplace(zeroDistance, initialState); dijkstraQueue.emplace(zeroDistance, initialState);
} }
@ -135,13 +159,14 @@ namespace storm {
state_t currentNode = (*dijkstraQueue.begin()).second; state_t currentNode = (*dijkstraQueue.begin()).second;
dijkstraQueue.erase(dijkstraQueue.begin()); dijkstraQueue.erase(dijkstraQueue.begin());
if (targetSet.count(currentNode) == 0) {
if (!isMetaTargetPredecessor(currentNode)) {
// non-target node, treated normally // non-target node, treated normally
for (auto const& transition : transitionMatrix.getRowGroup(currentNode)) { for (auto const& transition : transitionMatrix.getRowGroup(currentNode)) {
state_t otherNode = transition.getColumn(); state_t otherNode = transition.getColumn();
// note that distances are probabilities, thus they are multiplied and larger is better // note that distances are probabilities, thus they are multiplied and larger is better
T alternateDistance = shortestPathDistances[currentNode] * transition.getValue();
T alternateDistance = shortestPathDistances[currentNode] * convertDistance(currentNode, otherNode, transition.getValue());
assert(zero<T>() <= alternateDistance <= one<T>()); // FIXME: there is a negative transition! SM gives us a placeholder!
if (alternateDistance > shortestPathDistances[otherNode]) { if (alternateDistance > shortestPathDistances[otherNode]) {
shortestPathDistances[otherNode] = alternateDistance; shortestPathDistances[otherNode] = alternateDistance;
shortestPathPredecessors[otherNode] = boost::optional<state_t>(currentNode); shortestPathPredecessors[otherNode] = boost::optional<state_t>(currentNode);
@ -149,9 +174,9 @@ namespace storm {
} }
} }
} else { } else {
// target node has only "virtual edge" (with prob 1) to meta-target
// no multiplication necessary
T alternateDistance = shortestPathDistances[currentNode];
// node only has one "virtual edge" (with prob as per targetProbMap) to meta-target
// FIXME: double check
T alternateDistance = shortestPathDistances[currentNode] * targetProbMap[currentNode];
if (alternateDistance > shortestPathDistances[metaTarget]) { if (alternateDistance > shortestPathDistances[metaTarget]) {
shortestPathDistances[metaTarget] = alternateDistance; shortestPathDistances[metaTarget] = alternateDistance;
shortestPathPredecessors[metaTarget] = boost::optional<state_t>(currentNode); shortestPathPredecessors[metaTarget] = boost::optional<state_t>(currentNode);
@ -179,7 +204,7 @@ namespace storm {
// BFS in Dijkstra-SP order // BFS in Dijkstra-SP order
std::queue<state_t> bfsQueue; std::queue<state_t> bfsQueue;
for (state_t initialState : model->getInitialStates()) {
for (state_t initialState : initialStates) {
bfsQueue.push(initialState); bfsQueue.push(initialState);
} }
@ -214,27 +239,31 @@ namespace storm {
for (auto const& transition : transitionMatrix.getRowGroup(tailNode)) { for (auto const& transition : transitionMatrix.getRowGroup(tailNode)) {
if (transition.getColumn() == headNode) { if (transition.getColumn() == headNode) {
return transition.getValue();
return convertDistance(tailNode, headNode, transition.getValue());
} }
} }
// there is no such edge // there is no such edge
// let's disallow that for now, because I'm not expecting it to happen // let's disallow that for now, because I'm not expecting it to happen
assert(false); assert(false);
return utility::zero<T>();
return zero<T>();
} else { } else {
// edge must be "virtual edge" from target state to meta-target
assert(targetSet.count(tailNode) == 1);
return utility::one<T>();
// edge must be "virtual edge" to meta-target
assert(isMetaTargetPredecessor(tailNode));
return targetProbMap.at(tailNode); // FIXME double check
} }
} }
template <typename T> template <typename T>
void ShortestPathsGenerator<T>::computeNextPath(state_t node, unsigned long k) { void ShortestPathsGenerator<T>::computeNextPath(state_t node, unsigned long k) {
assert(k >= 2); // Dijkstra is used for k=1
assert(kShortestPaths[node].size() == k - 1); // if not, the previous SP must not exist assert(kShortestPaths[node].size() == k - 1); // if not, the previous SP must not exist
// TODO: I could extract the candidate generation to make this function more succinct
if (k == 2) { if (k == 2) {
// Step B.1 in J&M paper
Path<T> shortestPathToNode = kShortestPaths[node][1 - 1]; // never forget index shift :-| Path<T> shortestPathToNode = kShortestPaths[node][1 - 1]; // never forget index shift :-|
for (state_t predecessor : graphPredecessors[node]) { for (state_t predecessor : graphPredecessors[node]) {
@ -254,7 +283,9 @@ namespace storm {
} }
} }
if (k > 2 || !isInitialState(node)) {
if (not (k == 2 && isInitialState(node))) {
// Steps B.2-5 in J&M paper
// the (k-1)th shortest path (i.e., one better than the one we want to compute) // the (k-1)th shortest path (i.e., one better than the one we want to compute)
Path<T> previousShortestPath = kShortestPaths[node][k - 1 - 1]; // oh god, I forgot index shift AGAIN Path<T> previousShortestPath = kShortestPaths[node][k - 1 - 1]; // oh god, I forgot index shift AGAIN
@ -280,9 +311,10 @@ namespace storm {
}; };
candidatePaths[node].insert(pathToPredecessorPlusEdge); candidatePaths[node].insert(pathToPredecessorPlusEdge);
} }
// else there was no path; TODO: does this need handling?
// else there was no path; TODO: does this need handling? -- yes, but not here (because the step B.1 may have added candidates)
} }
// Step B.6 in J&M paper
if (!candidatePaths[node].empty()) { if (!candidatePaths[node].empty()) {
Path<T> minDistanceCandidate = *(candidatePaths[node].begin()); Path<T> minDistanceCandidate = *(candidatePaths[node].begin());
for (auto path : candidatePaths[node]) { for (auto path : candidatePaths[node]) {
@ -293,6 +325,9 @@ namespace storm {
candidatePaths[node].erase(std::find(candidatePaths[node].begin(), candidatePaths[node].end(), minDistanceCandidate)); candidatePaths[node].erase(std::find(candidatePaths[node].begin(), candidatePaths[node].end(), minDistanceCandidate));
kShortestPaths[node].push_back(minDistanceCandidate); kShortestPaths[node].push_back(minDistanceCandidate);
} else {
// TODO: kSP does not exist. this is handled later, but it would be nice to catch it as early as possble, wouldn't it?
STORM_LOG_TRACE("KSP: no candidates, this will trigger nonexisting ksp after exiting these recursions. TODO: handle here");
} }
} }
@ -303,9 +338,10 @@ namespace storm {
for (unsigned long nextK = alreadyComputedK + 1; nextK <= k; nextK++) { for (unsigned long nextK = alreadyComputedK + 1; nextK <= k; nextK++) {
computeNextPath(metaTarget, nextK); computeNextPath(metaTarget, nextK);
if (kShortestPaths[metaTarget].size() < nextK) { if (kShortestPaths[metaTarget].size() < nextK) {
//std::cout << std::endl << "--> DEBUG: Last path: k=" << (nextK - 1) << ":" << std::endl;
//printKShortestPath(metaTarget, nextK - 1);
//std::cout << "---------" << "No other path exists!" << std::endl;
unsigned long lastExistingK = nextK - 1;
STORM_LOG_DEBUG("KSP throws (as expected) due to nonexistence -- maybe this is unhandled and causes the Python interface to segfault?");
STORM_LOG_DEBUG("last existing k-SP has k=" + std::to_string(lastExistingK));
STORM_LOG_DEBUG("maybe this is unhandled and causes the Python interface to segfault?");
throw std::invalid_argument("k-SP does not exist for k=" + std::to_string(k)); throw std::invalid_argument("k-SP does not exist for k=" + std::to_string(k));
} }
} }

120
src/utility/shortestPaths.h

@ -12,20 +12,22 @@
// for more information about the purpose, design decisions, // for more information about the purpose, design decisions,
// etc., you may consult `shortestPath.md`. - Tom // etc., you may consult `shortestPath.md`. - Tom
/*
* TODO:
* - take care of self-loops of target states
* - implement target group
* - think about how to get paths with new nodes, rather than different
* paths over the same set of states (which happens often)
*/
// TODO: test whether using BitVector instead of vector<state_t> is
// faster for storing predecessors etc.
// Now using BitVectors instead of vector<state_t> in the API because
// BitVectors are used throughout Storm to represent a (unordered) list
// of states.
// (Even though both initialStates and targets are probably very sparse.)
namespace storm { namespace storm {
namespace utility { namespace utility {
namespace ksp { namespace ksp {
typedef storage::sparse::state_type state_t;
typedef std::vector<state_t> state_list_t;
using state_t = storage::sparse::state_type;
using OrderedStateList = std::vector<state_t>;
using BitVector = storage::BitVector;
// -- helper structs/classes -----------------------------------------------------------------------------
template <typename T> template <typename T>
struct Path { struct Path {
@ -46,24 +48,39 @@ namespace storm {
} }
}; };
// when using the raw matrix/vector invocation, this enum parameter
// forces the caller to declare whether the matrix has the evil I-P
// format, which requires back-conversion of the entries
enum class MatrixFormat { straight, iMinusP };
// ------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------
template <typename T> template <typename T>
class ShortestPathsGenerator { class ShortestPathsGenerator {
public: public:
using Matrix = storage::SparseMatrix<T>;
using StateProbMap = std::unordered_map<state_t, T>;
using Model = models::sparse::Model<T>;
/*! /*!
* Performs precomputations (including meta-target insertion and Dijkstra). * Performs precomputations (including meta-target insertion and Dijkstra).
* Modifications are done locally, `model` remains unchanged. * Modifications are done locally, `model` remains unchanged.
* Target (group) cannot be changed. * Target (group) cannot be changed.
*/ */
// FIXME: this shared_ptr-passing business might be a bad idea
ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model, state_list_t const& targets);
ShortestPathsGenerator(Model const& model, BitVector const& targetBV);
// allow alternative ways of specifying the target, // allow alternative ways of specifying the target,
// all of which will be converted to list and delegated to constructor above
ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model, state_t singleTarget);
ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model, storage::BitVector const& targetBV);
ShortestPathsGenerator(std::shared_ptr<models::sparse::Model<T>> model, std::string const& targetLabel);
// all of which will be converted to BitVector and delegated to constructor above
ShortestPathsGenerator(Model const& model, state_t singleTarget);
ShortestPathsGenerator(Model const& model, std::vector<state_t> const& targetList);
ShortestPathsGenerator(Model const& model, std::string const& targetLabel = "target");
// a further alternative: use transition matrix of maybe-states
// combined with target vector (e.g., the instantiated matrix/vector from SamplingModel);
// in this case separately specifying a target makes no sense
ShortestPathsGenerator(Matrix const& transitionMatrix, std::vector<T> const& targetProbVector, BitVector const& initialStates, MatrixFormat matrixFormat);
ShortestPathsGenerator(Matrix const& maybeTransitionMatrix, StateProbMap const& targetProbMap, BitVector const& initialStates, MatrixFormat matrixFormat);
inline ~ShortestPathsGenerator(){} inline ~ShortestPathsGenerator(){}
@ -87,21 +104,21 @@ namespace storm {
* Computes KSP if not yet computed. * Computes KSP if not yet computed.
* @throws std::invalid_argument if no such k-shortest path exists * @throws std::invalid_argument if no such k-shortest path exists
*/ */
state_list_t getPathAsList(unsigned long k);
OrderedStateList getPathAsList(unsigned long k);
private: private:
std::shared_ptr<models::sparse::Model<T>> model;
storage::SparseMatrix<T> transitionMatrix;
Matrix const& transitionMatrix; // FIXME: store reference instead (?)
state_t numStates; // includes meta-target, i.e. states in model + 1 state_t numStates; // includes meta-target, i.e. states in model + 1
state_list_t targets;
std::unordered_set<state_t> targetSet;
state_t metaTarget; state_t metaTarget;
BitVector initialStates;
StateProbMap targetProbMap;
MatrixFormat matrixFormat;
std::vector<state_list_t> graphPredecessors;
std::vector<OrderedStateList> graphPredecessors;
std::vector<boost::optional<state_t>> shortestPathPredecessors; std::vector<boost::optional<state_t>> shortestPathPredecessors;
std::vector<state_list_t> shortestPathSuccessors;
std::vector<OrderedStateList> shortestPathSuccessors;
std::vector<T> shortestPathDistances; std::vector<T> shortestPathDistances;
std::vector<std::vector<Path<T>>> kShortestPaths; std::vector<std::vector<Path<T>>> kShortestPaths;
@ -159,18 +176,63 @@ namespace storm {
// --- tiny helper fcts --- // --- tiny helper fcts ---
inline bool isInitialState(state_t node) const { inline bool isInitialState(state_t node) const {
auto initialStates = model->getInitialStates();
return find(initialStates.begin(), initialStates.end(), node) != initialStates.end(); return find(initialStates.begin(), initialStates.end(), node) != initialStates.end();
} }
inline state_list_t bitvectorToList(storage::BitVector const& bv) const {
state_list_t list;
for (state_t state : bv) {
list.push_back(state);
inline bool isMetaTargetPredecessor(state_t node) const {
return targetProbMap.count(node) == 1;
}
// I dislike this. But it is necessary if we want to handle those ugly I-P matrices
inline T convertDistance(state_t tailNode, state_t headNode, T distance) const {
if (matrixFormat == MatrixFormat::straight) {
return distance;
} else {
if (tailNode == headNode) {
// diagonal: 1-p = dist
return one<T>() - distance;
} else {
// non-diag: -p = dist
return zero<T>() - distance;
}
} }
return list;
} }
/*!
* Returns a map where each state of the input BitVector is mapped to 1 (`one<T>`).
*/
inline StateProbMap allProbOneMap(BitVector bitVector) const {
StateProbMap stateProbMap;
for (state_t node : bitVector) {
stateProbMap.emplace(node, one<T>()); // FIXME check rvalue warning (here and below)
}
return stateProbMap;
}
/*!
* Given a vector of probabilities so that the `i`th entry corresponds to the
* probability of state `i`, returns an equivalent map of only the non-zero entries.
*/
inline std::unordered_map<state_t, T> vectorToMap(std::vector<T> probVector) const {
//assert(probVector.size() == numStates); // numStates may not yet be initialized! // still true?
std::unordered_map<state_t, T> stateProbMap;
for (state_t i = 0; i < probVector.size(); i++) {
T probEntry = probVector[i];
// only non-zero entries (i.e. true transitions) are added to the map
if (probEntry != 0) {
assert(0 < probEntry <= 1);
stateProbMap.emplace(i, probEntry);
}
}
return stateProbMap;
}
// ----------------------- // -----------------------
}; };
} }

34
src/utility/shortestPaths.md

@ -39,6 +39,27 @@ target group is required in the constructor. (It would have been possible to
allow for interchangeable target groups, but I don't anticipate that use allow for interchangeable target groups, but I don't anticipate that use
case.) case.)
#### Special case: Using Matrix/Vector from SamplingModel
The class has been updated to support the matrix/vector that `SamplingModel`
generates (as an instance of a PDTMC) as input. This is in fact closely
related to the target groups, since it works as follows:
The input is a (sub-stochastic) transition matrix of the maybe-states (only!)
and a vector (again over the maybe-states) with the probabilities to an
implied target state.
This naturally corresponds to having a meta-target, except the probability
of its incoming edges range over $(0,1]$ rather than being $1$.
Thus, applying the term "target group" to the set of states with non-zero
transitions to the meta-target is now misleading[^1], but nevertheless it
should work exactly the same. [Right?]
In terms of implementation, in `getEdgeDistance` as well as in the loop of
the Dijkstra, the "virtual" edges to the meta-target were checked for and
set to probability $1$; this must now be changed to use the probability as
indicated in the `targetProbVector` if this input format is used.
### Minimality of paths ### Minimality of paths
Secondly, we define shortest paths as "minimal" shortest paths in the Secondly, we define shortest paths as "minimal" shortest paths in the
following sense: The path may not visit any target state except at the following sense: The path may not visit any target state except at the
@ -54,7 +75,7 @@ This is a common feature if the target state is a sink; but we are not
interested in such paths. interested in such paths.
(In fact, ideally we'd like to see paths whose node-intersection with all (In fact, ideally we'd like to see paths whose node-intersection with all
shorter paths is non-empty (which is an even stronger statement than
shorter paths is non-empty [^2] (which is an even stronger statement than
loop-free-ness of paths), because we want to take a union of those node loop-free-ness of paths), because we want to take a union of those node
sets. But that's a different matter.) sets. But that's a different matter.)
@ -76,11 +97,18 @@ path to some node `u` plus an edge to `t`:
Further, the shortest paths to some node are always computed in order and Further, the shortest paths to some node are always computed in order and
without gaps, e.g., the 1, 2, 3-shortest paths to `t` will be computed without gaps, e.g., the 1, 2, 3-shortest paths to `t` will be computed
before the 4-SP. Thus, we store the SPs in a linked list for each node, before the 4-SP. Thus, we store the SPs in a linked list for each node,
with the k-th entry[^1] being the k-th SP to that node.
with the k-th entry[^3] being the k-th SP to that node.
Thus for an SP as shown above we simply store the predecessor node (`u`) Thus for an SP as shown above we simply store the predecessor node (`u`)
and the `k`, which allows us to look up the tail of the SP. and the `k`, which allows us to look up the tail of the SP.
By recursively looking up the tail (until it's empty), we reconstruct By recursively looking up the tail (until it's empty), we reconstruct
the entire path back-to-front. the entire path back-to-front.
[^1]: Which due to 0-based indexing has index `k-1`, of course! Damn it.
[^1]: I suppose the correct term would now be "meta-target predecessors".
In fact, I will rename all occurences of `target` in the source to
`metaTargetPredecessors` – clumsy but accurate.
[^2]: (2016-08-20:) Is this correct? Didn't I mean that the path should
contain new nodes, i.e., non-emptiness of
((nodes in path) set-minus (union(nodes in shorter paths)))?
Yeah, I'm pretty sure that's what I meant.
[^3]: Which due to 0-based indexing has index `k-1`, of course! Damn it.

32
test/functional/utility/KSPTest.cpp

@ -25,8 +25,8 @@ std::shared_ptr<storm::models::sparse::Model<double>> buildExampleModel() {
TEST(KSPTest, dijkstra) { TEST(KSPTest, dijkstra) {
auto model = buildExampleModel(); auto model = buildExampleModel();
storm::storage::sparse::state_type testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(model, testState);
storm::utility::ksp::state_t testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(*model, testState);
double dist = spg.getDistance(1); double dist = spg.getDistance(1);
EXPECT_DOUBLE_EQ(0.015859334652581887, dist); EXPECT_DOUBLE_EQ(0.015859334652581887, dist);
@ -35,8 +35,8 @@ TEST(KSPTest, dijkstra) {
TEST(KSPTest, singleTarget) { TEST(KSPTest, singleTarget) {
auto model = buildExampleModel(); auto model = buildExampleModel();
storm::storage::sparse::state_type testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(model, testState);
storm::utility::ksp::state_t testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(*model, testState);
double dist = spg.getDistance(100); double dist = spg.getDistance(100);
EXPECT_DOUBLE_EQ(1.5231305000339649e-06, dist); EXPECT_DOUBLE_EQ(1.5231305000339649e-06, dist);
@ -45,8 +45,8 @@ TEST(KSPTest, singleTarget) {
TEST(KSPTest, reentry) { TEST(KSPTest, reentry) {
auto model = buildExampleModel(); auto model = buildExampleModel();
storm::storage::sparse::state_type testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(model, testState);
storm::utility::ksp::state_t testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(*model, testState);
double dist = spg.getDistance(100); double dist = spg.getDistance(100);
EXPECT_DOUBLE_EQ(1.5231305000339649e-06, dist); EXPECT_DOUBLE_EQ(1.5231305000339649e-06, dist);
@ -59,8 +59,8 @@ TEST(KSPTest, reentry) {
TEST(KSPTest, groupTarget) { TEST(KSPTest, groupTarget) {
auto model = buildExampleModel(); auto model = buildExampleModel();
auto groupTarget = storm::utility::ksp::state_list_t{50, 90};
auto spg = storm::utility::ksp::ShortestPathsGenerator<double>(model, groupTarget);
auto groupTarget = std::vector<storm::utility::ksp::state_t>{50, 90};
auto spg = storm::utility::ksp::ShortestPathsGenerator<double>(*model, groupTarget);
// this path should lead to 90 // this path should lead to 90
double dist1 = spg.getDistance(8); double dist1 = spg.getDistance(8);
@ -78,8 +78,8 @@ TEST(KSPTest, groupTarget) {
TEST(KSPTest, kTooLargeException) { TEST(KSPTest, kTooLargeException) {
auto model = buildExampleModel(); auto model = buildExampleModel();
storm::storage::sparse::state_type testState = 1;
storm::utility::ksp::ShortestPathsGenerator<double> spg(model, testState);
storm::utility::ksp::state_t testState = 1;
storm::utility::ksp::ShortestPathsGenerator<double> spg(*model, testState);
ASSERT_THROW(spg.getDistance(2), std::invalid_argument); ASSERT_THROW(spg.getDistance(2), std::invalid_argument);
} }
@ -87,11 +87,11 @@ TEST(KSPTest, kTooLargeException) {
TEST(KSPTest, kspStateSet) { TEST(KSPTest, kspStateSet) {
auto model = buildExampleModel(); auto model = buildExampleModel();
storm::storage::sparse::state_type testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(model, testState);
storm::utility::ksp::state_t testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(*model, testState);
storm::storage::BitVector referenceBV(model->getNumberOfStates(), false); storm::storage::BitVector referenceBV(model->getNumberOfStates(), false);
for (auto s : storm::utility::ksp::state_list_t{300, 293, 285, 279, 271, 265, 259, 252, 244, 237, 229, 223, 217, 210, 202, 195, 187, 181, 175, 168, 160, 153, 145, 139, 133, 126, 118, 111, 103, 97, 91, 84, 76, 69, 61, 55, 49, 43, 35, 41, 32, 25, 19, 14, 10, 7, 4, 2, 1, 0}) {
for (auto s : std::vector<storm::utility::ksp::state_t>{300, 293, 285, 279, 271, 265, 259, 252, 244, 237, 229, 223, 217, 210, 202, 195, 187, 181, 175, 168, 160, 153, 145, 139, 133, 126, 118, 111, 103, 97, 91, 84, 76, 69, 61, 55, 49, 43, 35, 41, 32, 25, 19, 14, 10, 7, 4, 2, 1, 0}) {
referenceBV.set(s, true); referenceBV.set(s, true);
} }
@ -103,11 +103,11 @@ TEST(KSPTest, kspStateSet) {
TEST(KSPTest, kspPathAsList) { TEST(KSPTest, kspPathAsList) {
auto model = buildExampleModel(); auto model = buildExampleModel();
storm::storage::sparse::state_type testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(model, testState);
storm::utility::ksp::state_t testState = 300;
storm::utility::ksp::ShortestPathsGenerator<double> spg(*model, testState);
// TODO: use path that actually has a loop or something to make this more interesting // TODO: use path that actually has a loop or something to make this more interesting
auto reference = storm::utility::ksp::state_list_t{300, 293, 285, 279, 271, 265, 259, 252, 244, 237, 229, 223, 217, 210, 202, 195, 187, 181, 175, 168, 160, 153, 145, 139, 133, 126, 118, 111, 103, 97, 91, 84, 76, 69, 61, 55, 49, 43, 35, 41, 32, 25, 19, 14, 10, 7, 4, 2, 1, 0};
auto reference = storm::utility::ksp::OrderedStateList{300, 293, 285, 279, 271, 265, 259, 252, 244, 237, 229, 223, 217, 210, 202, 195, 187, 181, 175, 168, 160, 153, 145, 139, 133, 126, 118, 111, 103, 97, 91, 84, 76, 69, 61, 55, 49, 43, 35, 41, 32, 25, 19, 14, 10, 7, 4, 2, 1, 0};
auto list = spg.getPathAsList(7); auto list = spg.getPathAsList(7);
EXPECT_EQ(list, reference); EXPECT_EQ(list, reference);

Loading…
Cancel
Save