31TEST(WeakBisimulationTest, SilentChain) {
33 builder.addNextValue(0, 1, 1.0);
34 builder.addNextValue(1, 2, 1.0);
35 builder.addNextValue(2, 3, 0.5);
36 builder.addNextValue(2, 4, 0.5);
37 builder.addNextValue(3, 3, 1.0);
38 builder.addNextValue(4, 4, 1.0);
39 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {3}}});
42 EXPECT_EQ(5ull, strongQuotient->getNumberOfStates());
46 EXPECT_EQ(3ull, quotient->getNumberOfStates());
47 EXPECT_EQ(4ull, quotient->getNumberOfTransitions());
48 EXPECT_NEAR(checkFormula<ValueType>(model,
"P=? [F \"goal\"]"), 0.5, 1e-12);
49 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]"), 0.5, 1e-12);
56TEST(WeakBisimulationTest, SilentStatesReachingDifferentClasses) {
59 builder.addNextValue(0, 2, 0.5);
60 builder.addNextValue(0, 3, 0.5);
61 builder.addNextValue(1, 2, 1.0);
62 builder.addNextValue(2, 4, 0.5);
63 builder.addNextValue(2, 5, 0.5);
64 builder.addNextValue(3, 5, 1.0);
65 builder.addNextValue(4, 4, 1.0);
66 builder.addNextValue(5, 5, 1.0);
67 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"c", {4}}, {
"d", {5}}});
71 EXPECT_EQ(5ull, quotient->getNumberOfStates());
72 EXPECT_NEAR(checkFormula<ValueType>(model,
"P=? [F \"c\"]"), 0.25, 1e-12);
73 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"c\"]"), 0.25, 1e-12);
81TEST(WeakBisimulationTest, ClassSilentOnlyWithinBlock) {
83 builder.addNextValue(0, 2, 0.5);
84 builder.addNextValue(0, 3, 0.5);
85 builder.addNextValue(1, 0, 1.0);
86 builder.addNextValue(2, 4, 1.0);
87 builder.addNextValue(3, 5, 1.0);
88 builder.addNextValue(4, 4, 1.0);
89 builder.addNextValue(5, 5, 1.0);
90 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"g", {4}}, {
"h", {5}}});
93 EXPECT_EQ(5ull, quotient->getNumberOfStates());
94 EXPECT_NEAR(checkFormula<ValueType>(model,
"P=? [F \"g\"]"), 0.5, 1e-12);
95 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"g\"]"), 0.5, 1e-12);
101TEST(WeakBisimulationTest, DivergentStates) {
104 builder.addNextValue(0, 1, 0.5);
105 builder.addNextValue(0, 3, 0.5);
106 builder.addNextValue(1, 2, 1.0);
107 builder.addNextValue(2, 1, 1.0);
108 builder.addNextValue(3, 3, 1.0);
109 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {3}}});
113 EXPECT_EQ(3ull, quotient->getNumberOfStates());
114 EXPECT_NEAR(checkFormula<ValueType>(model,
"P=? [F \"goal\"]"), 0.5, 1e-12);
115 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]"), 0.5, 1e-12);
121TEST(WeakBisimulationTest, ZeroProbabilityIsNeverGroupedWithNonZeroProbability) {
124 builder.addNextValue(0, 1, 0.5);
125 builder.addNextValue(0, 2, 0.5);
126 builder.addNextValue(1, 3, 1e-10);
127 builder.addNextValue(1, 4, 1.0 - 1e-10);
128 builder.addNextValue(2, 4, 1.0);
129 builder.addNextValue(3, 3, 1.0);
130 builder.addNextValue(4, 4, 1.0);
131 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {3}}, {
"sink", {4}}});
136 EXPECT_EQ(5ull, quotient->getNumberOfStates());
137 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]") / 5e-11, 1.0, 1e-6);
147TEST(WeakBisimulationTest, CtmcInternalRate) {
150 builder.addNextValue(0, 1, 100.0);
151 builder.addNextValue(0, 2, 1.0);
152 builder.addNextValue(1, 2, 1.0);
153 builder.addNextValue(2, 2, 1.0);
154 auto const model = buildModel<storm::models::sparse::Ctmc<ValueType>>(builder.build(), {{
"goal", {2}}});
157 EXPECT_EQ(3ull, strongQuotient->getNumberOfStates());
160 EXPECT_EQ(2ull, quotient->getNumberOfStates());
162 ValueType const expected = 1.0 - std::exp(-1.0);
163 EXPECT_NEAR(checkFormula<ValueType>(model,
"P=? [F<=1 \"goal\"]"), expected, 1e-6);
164 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F<=1 \"goal\"]"), expected, 1e-6);
171TEST(WeakBisimulationTest, CtmcSplitChangesSilence) {
174 builder.addNextValue(0, 1, 1.0);
175 builder.addNextValue(1, 2, 1.0);
176 builder.addNextValue(2, 2, 1.0);
177 auto const model = buildModel<storm::models::sparse::Ctmc<ValueType>>(builder.build(), {{
"goal", {2}}});
180 EXPECT_EQ(3ull, quotient->getNumberOfStates());
181 ValueType const expected = checkFormula<ValueType>(model,
"P=? [F<=1 \"goal\"]");
182 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F<=1 \"goal\"]"), expected, 1e-9);
189TEST(WeakBisimulationTest, CtmcRateRewards) {
192 builder.addNextValue(0, 1, 100.0);
193 builder.addNextValue(0, 2, 1.0);
194 builder.addNextValue(1, 2, 1.0);
195 builder.addNextValue(2, 2, 1.0);
196 auto const model = buildModel<storm::models::sparse::Ctmc<ValueType>>(builder.build(), {{
"goal", {2}}}, {1.0, 1.0, 0.0});
200 EXPECT_EQ(2ull, quotient->getNumberOfStates());
202 EXPECT_NEAR(checkFormula<ValueType>(model,
"R{\"rew\"}=? [F \"goal\"]"), 1.0, 1e-9);
203 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"R{\"rew\"}=? [F \"goal\"]"), 1.0, 1e-9);
209TEST(WeakBisimulationTest, CtmcExpectedTime) {
212 builder.addNextValue(0, 1, 100.0);
213 builder.addNextValue(0, 2, 1.0);
214 builder.addNextValue(1, 2, 1.0);
215 builder.addNextValue(2, 2, 1.0);
216 auto const model = buildModel<storm::models::sparse::Ctmc<ValueType>>(builder.build(), {{
"goal", {2}}});
219 std::vector<std::shared_ptr<storm::logic::Formula const>>
const formulas{formulaParser.parseSingleFormulaFromString(
"T=? [F \"goal\"]")};
221 EXPECT_EQ(2ull, quotient->getNumberOfStates());
222 EXPECT_NEAR(checkFormula<ValueType>(model,
"T=? [F \"goal\"]"), 1.0, 1e-9);
223 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"T=? [F \"goal\"]"), 1.0, 1e-9);
234TEST(WeakBisimulationTest, Rewards) {
237 builder.addNextValue(0, 0, 0.5);
238 builder.addNextValue(0, 2, 0.5);
239 builder.addNextValue(1, 2, 1.0);
240 builder.addNextValue(2, 2, 1.0);
241 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {2}}}, {1.0, 1.0, 0.0});
244 optionsWithoutRewards.preserveAllRewards =
false;
246 EXPECT_EQ(2ull, quotient->getNumberOfStates());
247 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]"), 1.0, 1e-12);
250 EXPECT_EQ(3ull, rewardQuotient->getNumberOfStates());
252 EXPECT_NEAR(checkFormula<ValueType>(model,
"R{\"rew\"}=? [F \"goal\"]"), 2.0, 1e-12);
253 EXPECT_NEAR(checkFormula<ValueType>(rewardQuotient,
"R{\"rew\"}=? [F \"goal\"]"), 2.0, 1e-12);
259TEST(WeakBisimulationTest, StateActionRewards) {
262 builder.addNextValue(0, 0, 0.5);
263 builder.addNextValue(0, 2, 0.5);
264 builder.addNextValue(1, 2, 1.0);
265 builder.addNextValue(2, 2, 1.0);
266 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {2}}}, {}, {1.0, 1.0, 0.0});
269 EXPECT_EQ(3ull, quotient->getNumberOfStates());
271 EXPECT_NEAR(checkFormula<ValueType>(model,
"R{\"rew\"}=? [F \"goal\"]"), 2.0, 1e-12);
272 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"R{\"rew\"}=? [F \"goal\"]"), 2.0, 1e-12);
279TEST(WeakBisimulationTest, ZeroRewardIsNeverGroupedWithNonZeroReward) {
282 builder.addNextValue(0, 2, 1.0);
283 builder.addNextValue(1, 2, 1.0);
284 builder.addNextValue(2, 2, 1.0);
285 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {2}}}, {0.0, 1e-9, 0.0});
290 EXPECT_EQ(3ull, quotient->getNumberOfStates());
293 options.preserveAllRewards =
false;
295 EXPECT_EQ(2ull, rewardlessQuotient->getNumberOfStates());
302TEST(WeakBisimulationTest, DivergentStepSensitiveBlock) {
305 builder.addNextValue(0, 1, 0.5);
306 builder.addNextValue(0, 3, 0.5);
307 builder.addNextValue(1, 1, 0.5);
308 builder.addNextValue(1, 2, 0.5);
309 builder.addNextValue(2, 2, 1.0);
310 builder.addNextValue(3, 3, 1.0);
311 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"d", {1, 2}}, {
"goal", {3}}}, {0.0, 1.0, 1.0, 0.0});
314 EXPECT_EQ(3ull, quotient->getNumberOfStates());
315 EXPECT_NEAR(checkFormula<ValueType>(model,
"P=? [F \"goal\"]"), 0.5, 1e-12);
316 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]"), 0.5, 1e-12);
318 EXPECT_TRUE(std::isinf(checkFormula<ValueType>(model,
"R{\"rew\"}=? [F \"goal\"]")));
319 EXPECT_TRUE(std::isinf(checkFormula<ValueType>(quotient,
"R{\"rew\"}=? [F \"goal\"]")));
326TEST(WeakBisimulationTest, StepSensitiveSplitChangesSilence) {
329 builder.addNextValue(0, 1, 1.0);
330 builder.addNextValue(1, 1, 0.5);
331 builder.addNextValue(1, 2, 0.5);
332 builder.addNextValue(2, 2, 1.0);
333 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {2}}}, {1.0, 1.0, 0.0});
336 EXPECT_EQ(3ull, quotient->getNumberOfStates());
337 EXPECT_NEAR(checkFormula<ValueType>(model,
"R{\"rew\"}=? [F \"goal\"]"), 3.0, 1e-9);
338 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"R{\"rew\"}=? [F \"goal\"]"), 3.0, 1e-9);
346TEST(WeakBisimulationTest, SplitterSplitDuringItsOwnRound) {
348 for (uint64_t b = 0; b < 3; ++b) {
349 builder.addNextValue(b, 4, 0.5);
350 builder.addNextValue(b, 8, 0.5);
352 builder.addNextValue(3, 3, 0.5);
353 builder.addNextValue(3, 5, 0.5);
354 builder.addNextValue(4, 4, 0.25);
355 builder.addNextValue(4, 5, 0.75);
356 for (uint64_t e = 5; e < 8; ++e) {
357 builder.addNextValue(e, 8, 1.0);
359 builder.addNextValue(8, 8, 1.0);
360 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"b", {0, 1, 2}}, {
"c", {3, 4}}, {
"e", {5, 6, 7}}, {
"t", {8}}},
361 {0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0});
365 EXPECT_EQ(quotient->getNumberOfStates(), requotient->getNumberOfStates());
366 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"t\"]"), checkFormula<ValueType>(model,
"P=? [F \"t\"]"), 1e-9);
376TEST(WeakBisimulationTest, PropositionalSubformulas) {
379 builder.addNextValue(0, 1, 1.0);
380 builder.addNextValue(1, 2, 1.0);
381 builder.addNextValue(2, 3, 0.25);
382 builder.addNextValue(2, 4, 0.25);
383 builder.addNextValue(2, 5, 0.5);
384 builder.addNextValue(3, 3, 1.0);
385 builder.addNextValue(4, 4, 1.0);
386 builder.addNextValue(5, 5, 1.0);
387 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"a", {3, 5}}, {
"b", {3}}});
388 std::string
const formulaString =
"P=? [F \"a\" & !\"b\"]";
393 EXPECT_EQ(3ull, quotient->getNumberOfStates());
394 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), 0.5, 1e-12);
397 options.stateLabelPreservation = StateLabelPreservation::FormulaIndividual;
399 EXPECT_EQ(4ull, individualQuotient->getNumberOfStates());
400 EXPECT_NEAR(checkFormula<ValueType>(individualQuotient, formulaString), 0.5, 1e-12);
411template<
typename ModelType>
412std::shared_ptr<ModelType> buildRandomModel(uint64_t
const numStates, uint64_t
const seed) {
413 std::mt19937_64 rng(seed);
415 for (uint64_t state = 0; state < numStates; ++state) {
416 uint64_t
const numSuccessors = 1 + rng() % 3;
417 std::map<uint64_t, ValueType> row;
418 for (uint64_t i = 0;
i < numSuccessors; ++
i) {
420 row[rng() % numStates] +=
static_cast<ValueType>(1 + rng() % 4);
422 if constexpr (std::is_same_v<ModelType, storm::models::sparse::Dtmc<ValueType>>) {
424 for (
auto const& [_, value] : row) {
427 for (
auto& [_, value] : row) {
431 for (
auto const& [column, value] : row) {
432 builder.addNextValue(state, column, value);
435 std::map<std::string, std::vector<uint64_t>> labels{{
"a", {}}, {
"b", {}}};
436 for (uint64_t state = 0; state < numStates; ++state) {
437 if (rng() % 3 == 0) {
438 labels[
"a"].push_back(state);
440 if (rng() % 4 == 0) {
441 labels[
"b"].push_back(state);
444 return buildModel<ModelType>(builder.build(), labels);
455template<
typename ModelType>
456void testIdempotence(uint64_t
const numStates, uint64_t
const numSeeds, std::string
const& formulaString) {
457 for (uint64_t seed = 0; seed < numSeeds; ++seed) {
458 auto const model = buildRandomModel<ModelType>(numStates, seed);
462 ASSERT_LE(quotient->getNumberOfStates(), model->getNumberOfStates()) <<
"Seed " << seed <<
".";
464 EXPECT_EQ(quotient->getNumberOfStates(), quotientOfQuotient->getNumberOfStates()) <<
"Weak bisimulation is not idempotent for seed " << seed <<
".";
465 EXPECT_EQ(quotient->getNumberOfTransitions(), quotientOfQuotient->getNumberOfTransitions())
466 <<
"Weak bisimulation is not idempotent for seed " << seed <<
".";
468 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), checkFormula<ValueType>(model, formulaString), 1e-6) <<
"Seed " << seed <<
".";
472TEST(WeakBisimulationTest, RandomDtmcs) {
473 testIdempotence<storm::models::sparse::Dtmc<ValueType>>(12, 500,
"P=? [F \"a\"]");
474 testIdempotence<storm::models::sparse::Dtmc<ValueType>>(60, 200,
"P=? [(!\"b\") U \"a\"]");
477TEST(WeakBisimulationTest, RandomCtmcs) {
478 testIdempotence<storm::models::sparse::Ctmc<ValueType>>(12, 500,
"P=? [F \"a\"]");
479 testIdempotence<storm::models::sparse::Ctmc<ValueType>>(60, 200,
"P=? [(!\"b\") U \"a\"]");
493void testAgainstOriginal(std::string
const& prismFile, std::string
const& formulaString, uint64_t expectedModelStates, uint64_t expectedStrongStates,
494 uint64_t expectedWeakStates, uint64_t expectedWeakTransitions, BuildOptions
const& buildOptions = {},
double const tolerance = 0.0) {
496 GTEST_SKIP() <<
"Z3 not available.";
498 auto const [model, formulas] = buildFromPrism<ValueType>(prismFile, formulaString, buildOptions);
499 ASSERT_EQ(expectedModelStates, model->getNumberOfStates());
504 weak.tolerance = strong.tolerance;
505 if (buildOptions.allLabels) {
506 strong.stateLabelPreservation = StateLabelPreservation::All;
507 weak.stateLabelPreservation = StateLabelPreservation::All;
511 EXPECT_EQ(model->getType(), strongQuotient->getType());
512 EXPECT_EQ(expectedStrongStates, strongQuotient->getNumberOfStates());
514 EXPECT_EQ(model->getType(), weakQuotient->getType());
515 EXPECT_EQ(expectedWeakStates, weakQuotient->getNumberOfStates());
516 EXPECT_EQ(expectedWeakTransitions, weakQuotient->getNumberOfTransitions());
518 ValueType const expected = checkFormula<ValueType>(model, formulaString);
519 EXPECT_NEAR(checkFormula<ValueType>(strongQuotient, formulaString), expected, 1e-9);
520 EXPECT_NEAR(checkFormula<ValueType>(weakQuotient, formulaString), expected, 1e-9);
523TEST(WeakBisimulationTest, Die) {
524 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]", 13ull, 5ull, 5ull, 8ull);
527TEST(WeakBisimulationTest, DieAllLabels) {
528 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]", 13ull, 11ull, 9ull, 13ull, {.allLabels =
true});
531TEST(WeakBisimulationTest, Crowds) {
532 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/dtmc/crowds5_5.pm",
"P=? [F \"observe0Greater1\"]", 7403ull, 65ull, 43ull, 83ull);
535TEST(WeakBisimulationTest, CrowdsAllLabels) {
537 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/dtmc/crowds5_5.pm",
"P=? [F \"observe0Greater1\"]", 8607ull, 2149ull, 1556ull, 3287ull, {.allLabels =
true});
545TEST(WeakBisimulationTest, CtmcEmbedded) {
546 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"P=? [F<=10000 \"down\"]", 2076ull, 310ull, 158ull, 898ull, {}, 1e-9);
549TEST(WeakBisimulationTest, CtmcEmbeddedAllLabels) {
550 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"P=? [F<=10000 \"down\"]", 3478ull, 1127ull, 659ull, 3392ull, {.allLabels =
true}, 1e-9);
557TEST(WeakBisimulationTest, CtmcClusterChoiceLabelsAndOrigins) {
558 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/ctmc/cluster2.sm",
"P=? [F<=100 !\"minimum\"]", 276ull, 276ull, 276ull, 1120ull,
559 {.choiceLabels =
true, .choiceOrigins =
true});
562TEST(WeakBisimulationTest, DtmcBrpChoiceLabelsAndOrigins) {
563 testAgainstOriginal(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"P=? [F \"target\"]", 613ull, 412ull, 412ull, 572ull,
564 {.choiceLabels =
true, .choiceOrigins =
true});
TEST(RegionConstraintCheckerTest, AffineConstraintHoldsThroughout)
A class that can be used to build a sparse matrix by adding value by value.
ReturnType< ValueType > performBisimulationMinimization(storm::models::sparse::Model< ValueType > const &model, std::vector< std::shared_ptr< storm::logic::Formula const > > const &formulas, Options options)
Computes the bisimulation quotient of the given model.
SFTBDDChecker::ValueType ValueType
Expression sum(std::vector< storm::expressions::Expression > const &expressions)
storm::bisimulation::StateLabelPreservation StateLabelPreservation
ValueType checkFormula(std::shared_ptr< storm::models::sparse::Model< ValueType > > const &model, std::shared_ptr< storm::logic::Formula const > const &formula)
storm::bisimulation::Options Options
std::shared_ptr< ModelType > buildModel(storm::storage::SparseMatrix< ValueType > matrix, std::map< std::string, std::vector< uint64_t > > const &labels, std::vector< ValueType > const &stateRewards={}, std::vector< ValueType > const &stateActionRewards={})
Builds a deterministic model with the given (probability or rate) matrix.
PrismInput< ValueType > buildFromPrism(std::string const &prismFile, std::string const &formulaString, BuildOptions const &buildOptions={})
TargetType convertNumber(SourceType const &number)
What to build from a PRISM program in addition to what the formulas require.