Skip to content

Observationtraceunfolder update #629

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions src/storm-pomdp/generator/NondeterministicBeliefTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ NondeterministicBeliefTracker<ValueType, BeliefState>::NondeterministicBeliefTra
template<typename ValueType, typename BeliefState>
bool NondeterministicBeliefTracker<ValueType, BeliefState>::reset(uint32_t observation) {
bool hit = false;
beliefs.clear();
for (auto state : pomdp.getInitialStates()) {
if (observation == pomdp.getObservation(state)) {
hit = true;
Expand Down Expand Up @@ -492,9 +493,6 @@ bool NondeterministicBeliefTracker<ValueType, BeliefState>::hasTimedOut() const
template class SparseBeliefState<double>;
template bool operator==(SparseBeliefState<double> const&, SparseBeliefState<double> const&);
template class NondeterministicBeliefTracker<double, SparseBeliefState<double>>;
// template class ObservationDenseBeliefState<double>;
// template bool operator==(ObservationDenseBeliefState<double> const&, ObservationDenseBeliefState<double> const&);
// template class NondeterministicBeliefTracker<double, ObservationDenseBeliefState<double>>;

template class SparseBeliefState<storm::RationalNumber>;
template bool operator==(SparseBeliefState<storm::RationalNumber> const&, SparseBeliefState<storm::RationalNumber> const&);
Expand Down
86 changes: 59 additions & 27 deletions src/storm-pomdp/transformer/ObservationTraceUnfolder.cpp
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
#include "storm-pomdp/transformer/ObservationTraceUnfolder.h"

#include "storm/adapters/RationalFunctionAdapter.h"
#include "storm/exceptions/InvalidArgumentException.h"
#include "storm/storage/expressions/ExpressionManager.h"
#include "storm/utility/ConstantsComparator.h"

#include "storm/adapters/RationalFunctionAdapter.h"

#undef _VERBOSE_OBSERVATION_UNFOLDING

namespace storm {
namespace pomdp {
template<typename ValueType>
ObservationTraceUnfolder<ValueType>::ObservationTraceUnfolder(storm::models::sparse::Pomdp<ValueType> const& model, std::vector<ValueType> const& risk,
std::shared_ptr<storm::expressions::ExpressionManager>& exprManager)
: model(model), risk(risk), exprManager(exprManager) {
std::shared_ptr<storm::expressions::ExpressionManager>& exprManager,
ObservationTraceUnfolderOptions const& options)
: model(model), risk(risk), exprManager(exprManager), options(options) {
statesPerObservation = std::vector<storm::storage::BitVector>(model.getNrObservations() + 1, storm::storage::BitVector(model.getNumberOfStates()));
for (uint64_t state = 0; state < model.getNumberOfStates(); ++state) {
statesPerObservation[model.getObservation(state)].set(state, true);
}
svvar = exprManager->declareFreshIntegerVariable(false, "_s");
tsvar = exprManager->declareFreshIntegerVariable(false, "_t");
}

template<typename ValueType>
std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<ValueType>::transform(const std::vector<uint32_t>& observations) {
std::vector<uint32_t> modifiedObservations = observations;
// First observation should be special.
// This just makes the algorithm simpler because we do not treat the first step as a special case later.
// We overwrite the observation with a non-existing obs z*
modifiedObservations[0] = model.getNrObservations();

storm::storage::BitVector initialStates = model.getInitialStates();
Expand All @@ -36,32 +39,52 @@ std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<
}
STORM_LOG_THROW(actualInitialStates.getNumberOfSetBits() == 1, storm::exceptions::InvalidArgumentException,
"Must have unique initial state matching the observation");
//
// For this z* that only exists in the initial state, we now also define the states for this observation.
statesPerObservation[model.getNrObservations()] = actualInitialStates;

#ifdef _VERBOSE_OBSERVATION_UNFOLDING
std::cout << "build valution builder..\n";
#endif
storm::storage::sparse::StateValuationsBuilder svbuilder;
svbuilder.addVariable(svvar);
svbuilder.addVariable(tsvar);

std::map<uint64_t, uint64_t> unfoldedToOld;
std::map<uint64_t, uint64_t> unfoldedToOldNextStep;
std::map<uint64_t, uint64_t> oldToUnfolded;
std::unordered_map<uint64_t, uint64_t> unfoldedToOld;
std::unordered_map<uint64_t, uint64_t> unfoldedToOldNextStep;
std::unordered_map<uint64_t, uint64_t> oldToUnfolded;

#ifdef _VERBOSE_OBSERVATION_UNFOLDING
std::cout << "start buildiing matrix...\n";
#endif

// Add this initial state state:
unfoldedToOldNextStep[0] = actualInitialStates.getNextSetIndex(0);
uint64_t newStateIndex = 0;
uint64_t const violatedState = newStateIndex;
if (!options.rejectionSampling) {
// The violated state is only used if we do no use the rejection semantics.
++newStateIndex;
}
// Add this initial state:
uint64_t const initialState = newStateIndex;
++newStateIndex;

unfoldedToOldNextStep[initialState] = actualInitialStates.getNextSetIndex(0);

uint64_t const resetDestination = options.rejectionSampling ? initialState : violatedState; // Should be initial state for the standard semantics.
storm::storage::SparseMatrixBuilder<ValueType> transitionMatrixBuilder(0, 0, 0, true, true);
uint64_t newStateIndex = 1;
uint64_t newRowGroupStart = 0;
uint64_t newRowCount = 0;
// Notice that we are going to use a special last step

// TODO only add this state if it is actually reachable / rejection sampling
if (!options.rejectionSampling) {
// the violated state (only used when no rejection sampling) is a sink state
transitionMatrixBuilder.newRowGroup(violatedState);
transitionMatrixBuilder.addNextValue(violatedState, violatedState, storm::utility::one<ValueType>());
svbuilder.addState(violatedState, {}, {-1, -1});
}

// Now we are starting to build the MDP from the initial state onwards.
uint64_t newRowGroupStart = initialState;
uint64_t newRowCount = initialState;

// Notice that we are going to use a special last step
for (uint64_t step = 0; step < observations.size() - 1; ++step) {
oldToUnfolded.clear();
unfoldedToOld = unfoldedToOldNextStep;
Expand All @@ -73,7 +96,7 @@ std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<
std::cout << "\tconsider new state " << unfoldedToOldEntry.first << '\n';
#endif
assert(step == 0 || newRowCount == transitionMatrixBuilder.getLastRow() + 1);
svbuilder.addState(unfoldedToOldEntry.first, {}, {static_cast<int64_t>(unfoldedToOldEntry.second)});
svbuilder.addState(unfoldedToOldEntry.first, {}, {static_cast<int64_t>(unfoldedToOldEntry.second), static_cast<int64_t>(step)});
uint64_t oldRowIndexStart = model.getNondeterministicChoiceIndices()[unfoldedToOldEntry.second];
uint64_t oldRowIndexEnd = model.getNondeterministicChoiceIndices()[unfoldedToOldEntry.second + 1];

Expand All @@ -96,7 +119,7 @@ std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<

// Add the resets
if (resetProb != storm::utility::zero<ValueType>()) {
transitionMatrixBuilder.addNextValue(newRowCount, 0, resetProb);
transitionMatrixBuilder.addNextValue(newRowCount, resetDestination, resetProb);
}
#ifdef _VERBOSE_OBSERVATION_UNFOLDING
std::cout << "\t\t\t add other transitions...\n";
Expand Down Expand Up @@ -125,22 +148,20 @@ std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<
}
newRowCount++;
}

newRowGroupStart = transitionMatrixBuilder.getLastRow() + 1;
}
}
// Now, take care of the last step.
uint64_t sinkState = newStateIndex;
uint64_t targetState = newStateIndex + 1;
auto cc = storm::utility::ConstantsComparator<ValueType>();
[[maybe_unused]] auto cc = storm::utility::ConstantsComparator<ValueType>();
for (auto const& unfoldedToOldEntry : unfoldedToOldNextStep) {
svbuilder.addState(unfoldedToOldEntry.first, {}, {static_cast<int64_t>(unfoldedToOldEntry.second)});
svbuilder.addState(unfoldedToOldEntry.first, {}, {static_cast<int64_t>(unfoldedToOldEntry.second), static_cast<int64_t>(observations.size() - 1)});

transitionMatrixBuilder.newRowGroup(newRowGroupStart);
STORM_LOG_ASSERT(risk.size() > unfoldedToOldEntry.second, "Must be a state");
STORM_LOG_ASSERT(!cc.isLess(storm::utility::one<ValueType>(), risk[unfoldedToOldEntry.second]), "Risk must be a probability");
STORM_LOG_ASSERT(!cc.isLess(risk[unfoldedToOldEntry.second], storm::utility::zero<ValueType>()), "Risk must be a probability");
// std::cout << "risk is" << risk[unfoldedToOldEntry.second] << '\n';
if (!storm::utility::isOne(risk[unfoldedToOldEntry.second])) {
transitionMatrixBuilder.addNextValue(newRowGroupStart, sinkState, storm::utility::one<ValueType>() - risk[unfoldedToOldEntry.second]);
}
Expand All @@ -152,13 +173,13 @@ std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<
// sink state
transitionMatrixBuilder.newRowGroup(newRowGroupStart);
transitionMatrixBuilder.addNextValue(newRowGroupStart, sinkState, storm::utility::one<ValueType>());
svbuilder.addState(sinkState, {}, {-1});
svbuilder.addState(sinkState, {}, {-1, -1});

newRowGroupStart++;
transitionMatrixBuilder.newRowGroup(newRowGroupStart);
// target state
transitionMatrixBuilder.addNextValue(newRowGroupStart, targetState, storm::utility::one<ValueType>());
svbuilder.addState(targetState, {}, {-1});
svbuilder.addState(targetState, {}, {-1, -1});
#ifdef _VERBOSE_OBSERVATION_UNFOLDING
std::cout << "build matrix...\n";
#endif
Expand All @@ -168,22 +189,27 @@ std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<
#ifdef _VERBOSE_OBSERVATION_UNFOLDING
std::cout << components.transitionMatrix << '\n';
#endif
STORM_LOG_ASSERT(components.transitionMatrix.getRowGroupCount() == targetState + 1,
"Expect row group count (" << components.transitionMatrix.getRowGroupCount() << ") one more as target state index " << targetState << ")");

storm::models::sparse::StateLabeling labeling(components.transitionMatrix.getRowGroupCount());
labeling.addLabel("_goal");
labeling.addLabelToState("_goal", targetState);
if (!options.rejectionSampling) {
labeling.addLabel("_violated");
labeling.addLabelToState("_violated", violatedState);
}
labeling.addLabel("_end");
labeling.addLabelToState("_end", sinkState);
labeling.addLabelToState("_end", targetState);
labeling.addLabel("init");
labeling.addLabelToState("init", 0);
labeling.addLabelToState("init", initialState);
components.stateLabeling = labeling;
components.stateValuations = svbuilder.build();
return std::make_shared<storm::models::sparse::Mdp<ValueType>>(std::move(components));
}

template<typename ValueType>
std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<ValueType>::extend(uint32_t observation) {
traceSoFar.push_back(observation);
std::shared_ptr<storm::models::sparse::Mdp<ValueType>> ObservationTraceUnfolder<ValueType>::extend(std::vector<uint32_t> const& observations) {
traceSoFar.insert(traceSoFar.end(), observations.begin(), observations.end());
return transform(traceSoFar);
}

Expand All @@ -192,8 +218,14 @@ void ObservationTraceUnfolder<ValueType>::reset(uint32_t observation) {
traceSoFar = {observation};
}

template<typename ValueType>
bool ObservationTraceUnfolder<ValueType>::isRejectionSamplingSet() const {
return options.rejectionSampling;
}

template class ObservationTraceUnfolder<double>;
template class ObservationTraceUnfolder<storm::RationalNumber>;
template class ObservationTraceUnfolder<storm::RationalFunction>;
template class ObservationTraceUnfolder<storm::Interval>;
} // namespace pomdp
} // namespace storm
16 changes: 13 additions & 3 deletions src/storm-pomdp/transformer/ObservationTraceUnfolder.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

namespace storm {
namespace pomdp {

class ObservationTraceUnfolderOptions {
public:
bool rejectionSampling = true;
};

/**
* Observation-trace unrolling to allow model checking for monitoring.
* This approach is outlined in Junges, Hazem, Seshia -- Runtime Monitoring for Markov Decision Processes
Expand All @@ -17,7 +23,7 @@ class ObservationTraceUnfolder {
* @param exprManager an Expression Manager
*/
ObservationTraceUnfolder(storm::models::sparse::Pomdp<ValueType> const& model, std::vector<ValueType> const& risk,
std::shared_ptr<storm::expressions::ExpressionManager>& exprManager);
std::shared_ptr<storm::expressions::ExpressionManager>& exprManager, ObservationTraceUnfolderOptions const& options);
/**
* Transform in one shot
* @param observations
Expand All @@ -29,20 +35,24 @@ class ObservationTraceUnfolder {
* @param observation
* @return
*/
std::shared_ptr<storm::models::sparse::Mdp<ValueType>> extend(uint32_t observation);
std::shared_ptr<storm::models::sparse::Mdp<ValueType>> extend(std::vector<uint32_t> const& observations);
/**
* When using the incremental approach, reset the observations made so far.
* @param observation The new initial observation
*/
void reset(uint32_t observation);

bool isRejectionSamplingSet() const;

private:
storm::models::sparse::Pomdp<ValueType> const& model;
std::vector<ValueType> risk; // TODO reconsider holding this as a reference, but there were some strange bugs
std::shared_ptr<storm::expressions::ExpressionManager>& exprManager;
std::vector<storm::storage::BitVector> statesPerObservation;
std::vector<uint32_t> traceSoFar;
storm::expressions::Variable svvar;
storm::expressions::Variable svvar; // Maps to the old state (explicit encoding)
storm::expressions::Variable tsvar; // Maps to the time step
ObservationTraceUnfolderOptions options;
};

} // namespace pomdp
Expand Down
Loading