Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
Quotient.cpp
Go to the documentation of this file.
2
3#include <map>
4#include <optional>
5
20
21namespace storm::bisimulation {
22
23template<typename ValueType>
25 storm::bisimulation::PreservationInformation const& preservationInformation,
26 QuotientData<ValueType> const& quotientData) -> std::shared_ptr<storm::models::sparse::Model<ValueType>> {
27 auto const& weakData = quotientData.weakData;
28 bool const isWeak = options.bisimulationType == BisimulationType::Weak;
29 STORM_LOG_ASSERT(isWeak == weakData.has_value(), "Weak bisimulation data must be given for (and only for) weak bisimulation.");
30 bool const useSignature = quotientData.signatureData.has_value();
31 bool const isNondeterministic = model.isNondeterministicModel();
32 STORM_LOG_ASSERT(useSignature || !isNondeterministic, "Signature data is required for nondeterministic models.");
33 auto const& toQuotientState = quotientData.toQuotientState;
34 auto const& toRepresentativeState = quotientData.toRepresentativeState;
35 auto const& toRepresentativeChoice = useSignature ? quotientData.signatureData->toRepresentativeChoice : quotientData.toRepresentativeState;
36
37 uint64_t const numberOfQuotientStates = toRepresentativeState.size();
38 uint64_t const numberOfQuotientChoices = toRepresentativeChoice.size();
39 STORM_LOG_ASSERT(isNondeterministic || numberOfQuotientStates == numberOfQuotientChoices, "Unexpected choice count.");
40
41 // Now build the model components one after the other
43
44 // Build the transition matrix
45 {
46 // Helper function to get the distribution over successor quotient states for a given quotient choice.
47 auto getQuotientRow = [&model, &useSignature, &quotientData, &toRepresentativeChoice, &toQuotientState, &isWeak,
48 &weakData](uint64_t const quotientChoice) -> std::map<uint64_t, ValueType> {
49 if (useSignature) {
50 return quotientData.signatureData->quotientChoiceDistributions[quotientChoice];
51 }
52 uint64_t const representative = toRepresentativeChoice[quotientChoice];
53 uint64_t const ownQuotientState = toQuotientState[representative];
54 if (isWeak) {
55 if (weakData->divergentStates.get(representative)) {
56 // The states of this block can never leave it, so the quotient state is absorbing. For a CTMC the rate of the self-loop is irrelevant.
57 return {{ownQuotientState, storm::utility::one<ValueType>()}};
58 }
59 // Non-divergent, representative states must not be silent because we have to represent the probability of exiting a block.
60 // It is ruled out by the caller passing the non-silent states as the preferred representatives in the constructor of QuotientData.
61 STORM_LOG_ASSERT(!weakData->silentStates.get(representative),
62 "Weak bisimulation quotient: The representative of a non-divergent block is silent.");
63 }
64 std::map<uint64_t, ValueType> quotientRow;
65 for (auto const& entry : model.getTransitionMatrix().getRow(representative)) {
66 if (auto const ret = quotientRow.emplace(toQuotientState[entry.getColumn()], entry.getValue()); !ret.second) {
67 ret.first->second += entry.getValue();
68 }
69 }
70 if constexpr (!storm::IsIntervalType<ValueType>) {
71 if (isWeak && !weakData->stepSensitiveStates.get(representative)) {
72 // Moves within the own block are unobservable, so they are dropped.
73 if (auto const ownBlockIt = quotientRow.find(ownQuotientState); ownBlockIt != quotientRow.end()) {
74 quotientRow.erase(ownBlockIt);
75 // If the quotientRow is now empty, it means that the representative state of this non-divergent block is silent.
76 // We already ruled that out above but check here again for consistency.
77 STORM_LOG_THROW(!quotientRow.empty(), storm::exceptions::UnexpectedException,
78 "Weak bisimulation quotient: the representative of a non-divergent block cannot leave its block.");
79 if (model.isDiscreteTimeModel()) {
80 // For a discrete-time model the remaining probabilities have to be renormalized.
81 // Note that we deliberately renormalize by the sum of the remaining entries rather than by 1 - ownBlockIt->second
82 // to avoid numerical issues if the two values are not exactly equal.
83 ValueType escapeValue = storm::utility::zero<ValueType>();
84 for (auto const& [_, value] : quotientRow) {
85 escapeValue += value;
86 }
87 for (auto& [_, value] : quotientRow) {
88 value /= escapeValue;
89 }
90 }
91 }
92 }
93 }
94 return quotientRow;
95 };
96
97 storm::storage::SparseMatrixBuilder<ValueType> builder(numberOfQuotientChoices, numberOfQuotientStates, 0, true, isNondeterministic,
98 isNondeterministic ? numberOfQuotientStates : 0);
99 for (uint64_t quotientState = 0, quotientChoice = 0; quotientState < numberOfQuotientStates; ++quotientState) {
100 if (isNondeterministic) {
101 builder.newRowGroup(quotientChoice);
102 }
103 uint64_t const quotientChoiceEnd = useSignature ? quotientData.signatureData->quotientChoiceGroupIndices[quotientState + 1] : quotientChoice + 1;
104 for (; quotientChoice < quotientChoiceEnd; ++quotientChoice) {
105 for (auto const& [column, value] : getQuotientRow(quotientChoice)) {
106 builder.addNextValue(quotientChoice, column, value);
107 }
108 }
109 }
110 components.transitionMatrix = builder.build();
111 }
112
113 // build state labeling
114 {
115 auto& stateLabeling = components.stateLabeling;
116 // Each quotient state that represents some initial state gets the "init" label
117 stateLabeling = storm::models::sparse::StateLabeling(numberOfQuotientStates);
118 storm::storage::BitVector init(numberOfQuotientStates, false);
119 for (auto const i : model.getStateLabeling().getStates("init")) {
120 init.set(toQuotientState[i], true);
121 }
122 stateLabeling.addLabel("init", std::move(init));
123 // The other labels are assigned based on the representative state.
124 // Note that we might only preserve propositional combinations of the labels, e.g., a quotient state representing "a" | "b" might get label "a", or "b",
125 // or both, depending on the representative state.
126 for (auto const& l : preservationInformation.preservedStateLabels) {
127 if (l == "init") {
128 continue; // see above.
129 }
130 auto const& in = model.getStateLabeling().getStates(l);
131 storm::storage::BitVector out(numberOfQuotientStates, false);
132 for (uint64_t quotientState = 0; quotientState < numberOfQuotientStates; ++quotientState) {
133 if (in.get(toRepresentativeState[quotientState])) {
134 out.set(quotientState, true);
135 }
136 }
137 stateLabeling.addLabel(l, std::move(out));
138 }
139 }
140
141 // build state valuations
142 if (model.hasStateValuations()) {
143 components.stateValuations = model.getStateValuations().selectEntities(toRepresentativeState);
144 }
145
146 // build choice labeling
147 if (!preservationInformation.preservedChoiceLabels.empty()) {
148 STORM_LOG_ASSERT(model.hasChoiceLabeling(), "Model has no choice labeling but bisimulation preserved some.");
149 components.choiceLabeling.emplace(numberOfQuotientChoices);
150 for (auto const& l : preservationInformation.preservedChoiceLabels) {
151 auto const& in = model.getChoiceLabeling().getChoices(l);
152 storm::storage::BitVector out(numberOfQuotientChoices, false);
153 for (uint64_t quotientChoice = 0; quotientChoice < numberOfQuotientChoices; ++quotientChoice) {
154 if (in.get(toRepresentativeChoice[quotientChoice])) {
155 out.set(quotientChoice, true);
156 }
157 }
158 components.choiceLabeling->addLabel(l, std::move(out));
159 }
160 }
161
162 // build choice origins
163 if (model.hasChoiceOrigins() && options.preserveChoiceOrigins) {
164 components.choiceOrigins = model.getChoiceOrigins()->selectChoices(toRepresentativeChoice);
165 }
166
167 // build reward models
168 for (auto const& r : preservationInformation.preservedRewardModels) {
169 std::optional<std::vector<ValueType>> stateRewards, stateActionRewards;
170 auto const& rm = model.getRewardModel(r);
171 if (rm.hasStateRewards()) {
172 stateRewards.emplace();
173 stateRewards->reserve(numberOfQuotientStates);
174 for (auto const representativeState : toRepresentativeState) {
175 stateRewards->push_back(rm.getStateReward(representativeState));
176 }
177 }
178 if (rm.hasStateActionRewards()) {
179 stateActionRewards.emplace();
180 stateActionRewards->reserve(numberOfQuotientChoices);
181 for (auto const representativeChoice : toRepresentativeChoice) {
182 stateActionRewards->push_back(rm.getStateActionReward(representativeChoice));
183 }
184 }
185 STORM_LOG_THROW(!rm.hasTransitionRewards(), storm::exceptions::NotSupportedException, "Transition rewards are not supported for quotient construction");
186 components.rewardModels.emplace(r, storm::models::sparse::StandardRewardModel<ValueType>(std::move(stateRewards), std::move(stateActionRewards)));
187 }
188
189 // build model type specific components
190 using enum storm::models::ModelType;
191 if (model.isOfType(Ctmc)) {
192 components.rateTransitions = true;
193 } else if (model.isOfType(MarkovAutomaton)) {
194 auto const& ma = model.template as<storm::models::sparse::MarkovAutomaton<ValueType>>();
195 components.markovianStates.emplace(numberOfQuotientStates, false);
196 components.exitRates.emplace(numberOfQuotientStates, storm::utility::zero<ValueType>());
197 for (uint64_t quotientState = 0; quotientState < numberOfQuotientStates; ++quotientState) {
198 auto const representativeState = toRepresentativeState[quotientState];
199 if (ma->isMarkovianState(representativeState)) {
200 // Note that a hybrid state (i.e. a Markovian state with further, probabilistic choices) keeps all of its choices here. As in the input
201 // model, the Markovian choice is the first one of the state, since the quotient choices follow the order of the representative state.
202 components.markovianStates->set(quotientState, true);
203 components.exitRates.value()[quotientState] = ma->getExitRate(representativeState);
204 }
205 }
206 } else {
207 STORM_LOG_THROW(model.isOfType(Dtmc) || model.isOfType(Mdp), storm::exceptions::NotSupportedException,
208 "The model type " << model.getType() << " is not supported for quotient construction.");
209 }
210 return storm::utility::builder::buildModelFromComponents(model.getType(), std::move(components));
211}
212
213template class Quotient<double>;
216template class Quotient<storm::Interval>;
218
219} // namespace storm::bisimulation
storm::models::sparse::Dtmc< double > Dtmc
storm::models::sparse::Mdp< double > Mdp
static std::shared_ptr< storm::models::sparse::Model< ValueType > > buildFromPartition(storm::models::sparse::Model< ValueType > const &model, storm::bisimulation::Options const &options, storm::bisimulation::PreservationInformation const &preservationInformation, QuotientData< ValueType > const &quotientData)
Builds the quotient model from the given partition (represented by quotientData), preserving the give...
Definition Quotient.cpp:24
Base class for all sparse models.
Definition Model.h:34
This class manages the labeling of the state space with a number of (atomic) labels.
A bit vector that is internally represented as a vector of 64-bit values.
Definition BitVector.h:16
void set(uint64_t index, bool value=true)
Sets the given truth value at the given index.
A class that can be used to build a sparse matrix by adding value by value.
#define STORM_LOG_ASSERT(cond, message)
Definition macros.h:9
#define STORM_LOG_THROW(cond, exception, message)
Definition macros.h:39
std::shared_ptr< storm::models::sparse::Model< ValueType, RewardModelType > > buildModelFromComponents(storm::models::ModelType modelType, storm::storage::sparse::ModelComponents< ValueType, RewardModelType > &&components)
Definition builder.cpp:20
ValueType zero()
Definition constants.cpp:26
ValueType one()
Definition constants.cpp:21
constexpr bool IsIntervalType
Helper to check if a type is an interval.
The index mappings between an input model and its bisimulation quotient, derived from a partition of ...
std::unordered_map< std::string, RewardModelType > rewardModels
storm::storage::SparseMatrix< ValueType > transitionMatrix
boost::optional< storm::storage::BitVector > markovianStates
std::optional< std::shared_ptr< storm::storage::sparse::ChoiceOrigins > > choiceOrigins
std::optional< storm::models::sparse::ChoiceLabeling > choiceLabeling
storm::models::sparse::StateLabeling stateLabeling
std::optional< storm::storage::sparse::Valuations > stateValuations
boost::optional< std::vector< ValueType > > exitRates