Browse Source
add k-shortest paths binding (ShortestPathsGenerator)
add k-shortest paths binding (ShortestPathsGenerator)
Merge branch 'ksp_generator'refactoring
Tom Janson
8 years ago
12 changed files with 298 additions and 2 deletions
-
6CMakeLists.txt
-
2lib/stormpy/utility/__init__.py
-
4setup.py
-
10src/CMakeLists.txt
-
1src/common.h
-
2src/mod_storage.cpp
-
9src/mod_utility.cpp
-
39src/storage/bitvector.cpp
-
8src/storage/bitvector.h
-
66src/utility/shortestPaths.cpp
-
8src/utility/shortestPaths.h
-
145tests/utility/test_shortestpaths.py
@ -0,0 +1,2 @@ |
|||
from . import utility |
|||
from .utility import * |
@ -1,10 +1,12 @@ |
|||
#include "common.h"
|
|||
|
|||
#include "storage/bitvector.h"
|
|||
#include "storage/model.h"
|
|||
#include "storage/matrix.h"
|
|||
|
|||
PYBIND11_PLUGIN(storage) { |
|||
py::module m("storage"); |
|||
define_bitvector(m); |
|||
define_model(m); |
|||
define_sparse_matrix(m); |
|||
return m.ptr(); |
|||
|
@ -0,0 +1,9 @@ |
|||
#include "common.h"
|
|||
|
|||
#include "utility/shortestPaths.h"
|
|||
|
|||
PYBIND11_PLUGIN(utility) { |
|||
py::module m("utility", "Dumping ground of stuff that really should be somewhere more reasonable"); |
|||
define_ksp(m); |
|||
return m.ptr(); |
|||
} |
@ -0,0 +1,39 @@ |
|||
#include "bitvector.h"
|
|||
#include "storm/storage/BitVector.h"
|
|||
|
|||
#include <sstream>
|
|||
|
|||
void define_bitvector(py::module& m) { |
|||
using BitVector = storm::storage::BitVector; |
|||
|
|||
py::class_<BitVector>(m, "BitVector") |
|||
.def(py::init<>()) |
|||
.def(py::init<BitVector>(), "other"_a) |
|||
.def(py::init<uint_fast64_t>(), "length"_a) |
|||
.def(py::init<uint_fast64_t, bool>(), "length"_a, "init"_a) |
|||
.def(py::init<uint_fast64_t, std::vector<uint_fast64_t>>(), "length"_a, "set_entries"_a) |
|||
|
|||
.def("size", &BitVector::size) |
|||
.def("number_of_set_bits", &BitVector::getNumberOfSetBits) |
|||
//.def("get", &BitVector::get, "index"_a) // no idea why this does not work
|
|||
.def("get", [](BitVector const& b, uint_fast64_t i) { return b.get(i); }, "index"_a) |
|||
|
|||
.def(py::self == py::self) |
|||
.def(py::self != py::self) |
|||
|
|||
.def(py::self < py::self) |
|||
.def(py::self & py::self) |
|||
.def(py::self | py::self) |
|||
.def(py::self ^ py::self) |
|||
.def(py::self % py::self) |
|||
.def(~py::self) |
|||
|
|||
.def(py::self &= py::self) |
|||
.def(py::self |= py::self) |
|||
|
|||
.def("__repr__", [](BitVector const& b) { std::ostringstream oss; oss << b; return oss.str(); }) |
|||
|
|||
// TODO (when needed): iterator
|
|||
; |
|||
|
|||
} |
@ -0,0 +1,8 @@ |
|||
#ifndef PYTHON_STORAGE_BITVECTOR_H_ |
|||
#define PYTHON_STORAGE_BITVECTOR_H_ |
|||
|
|||
#include "common.h" |
|||
|
|||
void define_bitvector(py::module& m); |
|||
|
|||
#endif /* PYTHON_STORAGE_BITVECTOR_H_ */ |
@ -0,0 +1,66 @@ |
|||
#include "shortestPaths.h"
|
|||
#include "storm/utility/shortestPaths.h"
|
|||
|
|||
#include <sstream>
|
|||
#include <string>
|
|||
|
|||
#include <boost/optional/optional_io.hpp>
|
|||
|
|||
void define_ksp(py::module& m) { |
|||
|
|||
// long types shortened for readability
|
|||
//
|
|||
// this could be templated rather than hardcoding double, but the actual
|
|||
// bindings must refer to instantiated versions anyway (i.e., overloaded
|
|||
// for each template instantiation) -- and double is enough for me
|
|||
using Model = storm::models::sparse::Model<double>; |
|||
using BitVector = storm::storage::BitVector; |
|||
using Matrix = storm::storage::SparseMatrix<double>; |
|||
using MatrixFormat = storm::utility::ksp::MatrixFormat; |
|||
using Path = storm::utility::ksp::Path<double>; |
|||
using ShortestPathsGenerator = storm::utility::ksp::ShortestPathsGenerator<double>; |
|||
using state_t = storm::utility::ksp::state_t; |
|||
using StateProbMap = std::unordered_map<state_t, double>; |
|||
|
|||
py::class_<Path>(m, "Path") |
|||
// overload constructor rather than dealing with boost::optional
|
|||
.def("__init__", [](Path &instance, state_t preNode, unsigned long preK, double distance) { |
|||
new (&instance) Path { boost::optional<state_t>(preNode), preK, distance }; |
|||
}, "predecessorNode"_a, "predecessorK"_a, "distance"_a) |
|||
|
|||
.def("__init__", [](Path &instance, unsigned long preK, double distance) { |
|||
new (&instance) Path { boost::none, preK, distance }; |
|||
}, "predecessorK"_a, "distance"_a) |
|||
|
|||
.def(py::self == py::self, "Compares predecessor node and index, ignoring distance") |
|||
|
|||
.def("__repr__", [](Path const& p) { |
|||
std::ostringstream oss; |
|||
oss << "<Path with predecessorNode: '" << ((p.predecessorNode) ? std::to_string(p.predecessorNode.get()) : "None"); |
|||
oss << "' predecessorK: '" << p.predecessorK << "' distance: '" << p.distance << "'>"; |
|||
return oss.str(); |
|||
}) |
|||
|
|||
.def_readwrite("predecessorNode", &Path::predecessorNode) // TODO (un-)wrap boost::optional so it's usable
|
|||
.def_readwrite("predecessorK", &Path::predecessorK) |
|||
.def_readwrite("distance", &Path::distance) |
|||
; |
|||
|
|||
py::enum_<MatrixFormat>(m, "MatrixFormat") |
|||
.value("Straight", MatrixFormat::straight) |
|||
.value("I_Minus_P", MatrixFormat::iMinusP) |
|||
; |
|||
|
|||
py::class_<ShortestPathsGenerator>(m, "ShortestPathsGenerator") |
|||
.def(py::init<Model const&, BitVector>(), "model"_a, "target_bitvector"_a) |
|||
.def(py::init<Model const&, state_t>(), "model"_a, "target_state"_a) |
|||
.def(py::init<Model const&, std::vector<state_t> const&>(), "model"_a, "target_state_list"_a) |
|||
.def(py::init<Model const&, std::string>(), "model"_a, "target_label"_a) |
|||
.def(py::init<Matrix const&, std::vector<double> const&, BitVector const&, MatrixFormat>(), "transition_matrix"_a, "target_prob_vector"_a, "initial_states"_a, "matrix_format"_a) |
|||
.def(py::init<Matrix const&, StateProbMap const&, BitVector const&, MatrixFormat>(), "transition_matrix"_a, "target_prob_map"_a, "initial_states"_a, "matrix_format"_a) |
|||
|
|||
.def("get_distance", &ShortestPathsGenerator::getDistance, "k"_a) |
|||
.def("get_states", &ShortestPathsGenerator::getStates, "k"_a) |
|||
.def("get_path_as_list", &ShortestPathsGenerator::getPathAsList, "k"_a) |
|||
; |
|||
} |
@ -0,0 +1,8 @@ |
|||
#ifndef PYTHON_UTILITY_SHORTESTPATHS_H_ |
|||
#define PYTHON_UTILITY_SHORTESTPATHS_H_ |
|||
|
|||
#include "src/common.h" |
|||
|
|||
void define_ksp(py::module& m); |
|||
|
|||
#endif /* PYTHON_UTILITY_SHORTESTPATHS_H_ */ |
@ -0,0 +1,145 @@ |
|||
import stormpy |
|||
import stormpy.logic |
|||
from stormpy.storage import BitVector |
|||
from stormpy.utility import ShortestPathsGenerator |
|||
from stormpy.utility import MatrixFormat |
|||
from helpers.helper import get_example_path |
|||
|
|||
import pytest |
|||
import math |
|||
|
|||
|
|||
# this is admittedly slightly overengineered |
|||
|
|||
class ModelWithKnownShortestPaths: |
|||
"""Knuth's die model with reference kSP methods""" |
|||
def __init__(self): |
|||
self.target_label = "one" |
|||
|
|||
program_path = get_example_path("dtmc", "die.pm") |
|||
raw_formula = "P=? [ F \"" + self.target_label + "\" ]" |
|||
program = stormpy.parse_prism_program(program_path) |
|||
formulas = stormpy.parse_formulas_for_prism_program(raw_formula, program) |
|||
|
|||
self.model = stormpy.build_model(program, formulas[0]) |
|||
|
|||
def probability(self, k): |
|||
return (1/2)**((2 * k) + 1) |
|||
|
|||
def state_set(self, k): |
|||
return BitVector(self.model.nr_states, [0, 1, 3, 7]) |
|||
|
|||
def path(self, k): |
|||
path = [0] + k * [1, 3] + [7] |
|||
return list(reversed(path)) # SPG returns traversal from back |
|||
|
|||
|
|||
@pytest.fixture(scope="module", params=[1, 2, 3, 3000, 42]) |
|||
def index(request): |
|||
return request.param |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def model_with_known_shortest_paths(): |
|||
return ModelWithKnownShortestPaths() |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def model(model_with_known_shortest_paths): |
|||
return model_with_known_shortest_paths.model |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def expected_distance(model_with_known_shortest_paths): |
|||
return model_with_known_shortest_paths.probability |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def expected_state_set(model_with_known_shortest_paths): |
|||
return model_with_known_shortest_paths.state_set |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def expected_path(model_with_known_shortest_paths): |
|||
return model_with_known_shortest_paths.path |
|||
|
|||
|
|||
@pytest.fixture(scope="module") |
|||
def target_label(model_with_known_shortest_paths): |
|||
return model_with_known_shortest_paths.target_label |
|||
|
|||
|
|||
@pytest.fixture |
|||
def state(model): |
|||
some_state = 7 |
|||
assert model.nr_states > some_state, "test model too small" |
|||
return some_state |
|||
|
|||
|
|||
@pytest.fixture |
|||
def state_list(model): |
|||
some_state_list = [4, 5, 7] |
|||
assert model.nr_states > max(some_state_list), "test model too small" |
|||
return some_state_list |
|||
|
|||
|
|||
@pytest.fixture |
|||
def state_bitvector(model, state_list): |
|||
return BitVector(length=model.nr_states, set_entries=state_list) |
|||
|
|||
|
|||
@pytest.fixture |
|||
def transition_matrix(model): |
|||
return model.transition_matrix |
|||
|
|||
|
|||
@pytest.fixture |
|||
def target_prob_map(model, state_list): |
|||
return {i: (1.0 if i in state_list else 0.0) for i in range(model.nr_states)} |
|||
|
|||
|
|||
@pytest.fixture |
|||
def target_prob_list(target_prob_map): |
|||
return [target_prob_map[i] for i in range(max(target_prob_map.keys()))] |
|||
|
|||
|
|||
@pytest.fixture |
|||
def initial_states(model): |
|||
return BitVector(model.nr_states, model.initial_states) |
|||
|
|||
|
|||
@pytest.fixture |
|||
def matrix_format(): |
|||
return MatrixFormat.Straight |
|||
|
|||
|
|||
class TestShortestPaths: |
|||
def test_spg_ctor_bitvector_target(self, model, state_bitvector): |
|||
_ = ShortestPathsGenerator(model, state_bitvector) |
|||
|
|||
def test_spg_ctor_single_state_target(self, model, state): |
|||
_ = ShortestPathsGenerator(model, state) |
|||
|
|||
def test_spg_ctor_state_list_target(self, model, state_list): |
|||
_ = ShortestPathsGenerator(model, state_list) |
|||
|
|||
def test_spg_ctor_label_target(self, model, target_label): |
|||
_ = ShortestPathsGenerator(model, target_label) |
|||
|
|||
def test_spg_ctor_matrix_vector(self, transition_matrix, target_prob_list, initial_states, matrix_format): |
|||
_ = ShortestPathsGenerator(transition_matrix, target_prob_list, initial_states, matrix_format) |
|||
|
|||
def test_spg_ctor_matrix_map(self, transition_matrix, target_prob_map, initial_states, matrix_format): |
|||
_ = ShortestPathsGenerator(transition_matrix, target_prob_map, initial_states, matrix_format) |
|||
|
|||
def test_spg_distance(self, model, target_label, index, expected_distance): |
|||
spg = ShortestPathsGenerator(model, target_label) |
|||
assert math.isclose(spg.get_distance(index), expected_distance(index)) |
|||
|
|||
def test_spg_state_set(self, model, target_label, index, expected_state_set): |
|||
spg = ShortestPathsGenerator(model, target_label) |
|||
assert spg.get_states(index) == expected_state_set(index) |
|||
|
|||
def test_spg_state_list(self, model, target_label, index, expected_path): |
|||
spg = ShortestPathsGenerator(model, target_label) |
|||
assert spg.get_path_as_list(index) == expected_path(index) |
Write
Preview
Loading…
Cancel
Save
Reference in new issue