36 std::set<uint64_t>
const expectedChoices(quotientChoices.begin(), quotientChoices.end());
37 std::set<uint64_t> mappedChoices;
41 EXPECT_EQ(expectedChoices, mappedChoices) <<
"unexpected quotient choices for the choices of state " << state;
52void testQuotient(std::string
const& prismFile, std::string
const& formulaString, uint64_t expectedModelStates, uint64_t expectedStates,
53 uint64_t expectedTransitions, uint64_t expectedChoices, Options
const options =
strongOptions(), BuildOptions buildOptions = {}) {
55 GTEST_SKIP() <<
"Z3 not available.";
57 if (options.stateLabelPreservation == StateLabelPreservation::All) {
58 buildOptions.allLabels =
true;
60 auto const input = buildFromPrism<ValueType>(prismFile, formulaString, buildOptions);
61 ASSERT_EQ(expectedModelStates, input.model->getNumberOfStates());
63 Options quotientOptions = options;
66 auto const& quotient = result.
quotient;
67 checkChoiceMapping(*input.model, result);
68 EXPECT_EQ(input.model->getType(), quotient->getType());
69 EXPECT_EQ(expectedStates, quotient->getNumberOfStates());
70 EXPECT_EQ(expectedTransitions, quotient->getNumberOfTransitions());
71 EXPECT_EQ(expectedChoices, quotient->getNumberOfChoices());
73 auto const choiceLabels = [](
auto const& model) {
74 std::set<std::string> result;
77 result.insert(labels.begin(), labels.end());
81 EXPECT_EQ(choiceLabels(input.model), choiceLabels(quotient));
82 EXPECT_EQ(input.model->hasChoiceOrigins(), quotient->hasChoiceOrigins());
85 for (
auto const& formula : input.formulas) {
86 EXPECT_NEAR(checkFormula<ValueType>(quotient, formula), checkFormula<ValueType>(input.model, formula), 1e-9) << *formula;
95 options.stateLabelPreservation = StateLabelPreservation::All;
116TEST(StrongBisimulationTest, SilentChainIsNotCollapsed) {
119 builder.addNextValue(0, 1, 1.0);
120 builder.addNextValue(1, 2, 1.0);
121 builder.addNextValue(2, 3, 0.5);
122 builder.addNextValue(2, 4, 0.5);
123 builder.addNextValue(3, 3, 1.0);
124 builder.addNextValue(4, 4, 1.0);
125 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {3}}});
128 EXPECT_EQ(5ull, quotient->getNumberOfStates());
129 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]"), 0.5, 1e-12);
135TEST(StrongBisimulationTest, IdenticalDistributions) {
138 builder.addNextValue(0, 1, 0.5);
139 builder.addNextValue(0, 2, 0.5);
140 builder.addNextValue(1, 3, 0.25);
141 builder.addNextValue(1, 4, 0.75);
142 builder.addNextValue(2, 3, 0.25);
143 builder.addNextValue(2, 4, 0.75);
144 builder.addNextValue(3, 3, 1.0);
145 builder.addNextValue(4, 4, 1.0);
146 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"goal", {3}}});
149 EXPECT_EQ(4ull, quotient->getNumberOfStates());
150 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"P=? [F \"goal\"]"), 0.25, 1e-12);
156TEST(StrongBisimulationTest, CtmcInternalRateIsObservable) {
159 builder.addNextValue(0, 1, 100.0);
160 builder.addNextValue(0, 2, 1.0);
161 builder.addNextValue(1, 2, 1.0);
162 builder.addNextValue(2, 2, 1.0);
163 auto const model = buildModel<storm::models::sparse::Ctmc<ValueType>>(builder.build(), {{
"goal", {2}}});
167 EXPECT_EQ(3ull, quotient->getNumberOfStates());
173TEST(StrongBisimulationTest, CtmcExitRateIsObservable) {
176 builder.addNextValue(0, 2, 1.0);
177 builder.addNextValue(1, 2, 2.0);
178 builder.addNextValue(2, 2, 1.0);
179 auto const model = buildModel<storm::models::sparse::Ctmc<ValueType>>(builder.build(), {{
"goal", {2}}});
182 EXPECT_EQ(3ull, quotient->getNumberOfStates());
189TEST(StrongBisimulationTest, PropositionalSubformulas) {
192 builder.addNextValue(0, 1, 1.0 / 3);
193 builder.addNextValue(0, 2, 1.0 / 3);
194 builder.addNextValue(0, 3, 1.0 / 3);
195 builder.addNextValue(1, 1, 1.0);
196 builder.addNextValue(2, 2, 1.0);
197 builder.addNextValue(3, 3, 1.0);
198 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"a", {1, 3}}, {
"b", {1}}});
200 individualOptions.stateLabelPreservation = StateLabelPreservation::FormulaIndividual;
203 for (std::string
const formulaString : {
"P=? [F \"a\" & !\"b\"]",
"P=? [F P>=0.5 [F \"a\" & !\"b\"]]"}) {
206 EXPECT_EQ(3ull, quotient->getNumberOfStates()) << formulaString;
207 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), 1.0 / 3, 1e-12) << formulaString;
210 EXPECT_EQ(4ull, individualQuotient->getNumberOfStates()) << formulaString;
211 EXPECT_NEAR(checkFormula<ValueType>(individualQuotient, formulaString), 1.0 / 3, 1e-12) << formulaString;
219TEST(StrongBisimulationTest, InitLabelInPropositionalSubformula) {
223 builder.addNextValue(0, 2, 1.0);
224 builder.addNextValue(1, 2, 1.0);
225 builder.addNextValue(2, 2, 1.0);
226 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"a", {2}}});
230 std::string
const formulaString =
"P=? [F \"a\" & !\"init\"]";
234 EXPECT_EQ(3ull, quotient->getNumberOfStates());
235 ASSERT_EQ(1ull, quotient->getInitialStates().getNumberOfSetBits());
236 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), 1.0, 1e-12);
243TEST(StrongBisimulationTest, InitLabelCombinedWithOtherLabel) {
247 builder.addNextValue(0, 2, 1.0);
248 builder.addNextValue(1, 2, 1.0);
249 builder.addNextValue(2, 2, 1.0);
250 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"a", {0}}});
254 std::string
const formulaString =
"P=? [F \"init\" & \"a\"]";
258 EXPECT_EQ(2ull, quotient->getNumberOfStates());
259 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), 0.0, 1e-12);
265TEST(StrongBisimulationTest, FloatingPointRoundingNeedsTolerance) {
268 builder.addNextValue(0, 1, 0.5);
269 builder.addNextValue(0, 2, 0.5);
270 builder.addNextValue(1, 3, 0.1);
271 builder.addNextValue(1, 4, 0.2);
272 builder.addNextValue(1, 5, 0.7);
273 builder.addNextValue(2, 3, 0.3);
274 builder.addNextValue(2, 5, 0.7);
275 builder.addNextValue(3, 3, 1.0);
276 builder.addNextValue(4, 4, 1.0);
277 builder.addNextValue(5, 5, 1.0);
278 auto const model = buildModel<storm::models::sparse::Dtmc<ValueType>>(builder.build(), {{
"target", {3, 4}}});
295TEST(StrongBisimulationTest, ChoiceSetsAreCompared) {
298 builder.newRowGroup(0);
299 builder.addNextValue(0, 2, 1.0);
300 builder.addNextValue(1, 2, 0.5);
301 builder.addNextValue(1, 3, 0.5);
302 builder.newRowGroup(2);
303 builder.addNextValue(2, 2, 0.5);
304 builder.addNextValue(2, 3, 0.5);
305 builder.addNextValue(3, 2, 1.0);
306 builder.addNextValue(4, 2, 1.0);
307 builder.newRowGroup(5);
308 builder.addNextValue(5, 2, 1.0);
309 builder.newRowGroup(6);
310 builder.addNextValue(6, 3, 1.0);
311 auto const model = buildModel<storm::models::sparse::Mdp<ValueType>>(builder.build(), {{
"goal", {2}}});
314 EXPECT_EQ(3ull, quotient->getNumberOfStates());
315 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Pmin=? [F \"goal\"]"), 0.5, 1e-12);
316 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Pmax=? [F \"goal\"]"), 1.0, 1e-12);
323TEST(StrongBisimulationTest, ChoiceOrderMattersIfActionSensitive) {
326 builder.newRowGroup(0);
327 builder.addNextValue(0, 2, 1.0);
328 builder.addNextValue(1, 2, 0.5);
329 builder.addNextValue(1, 3, 0.5);
330 builder.newRowGroup(2);
331 builder.addNextValue(2, 2, 0.5);
332 builder.addNextValue(2, 3, 0.5);
333 builder.addNextValue(3, 2, 1.0);
334 builder.newRowGroup(4);
335 builder.addNextValue(4, 2, 1.0);
336 builder.newRowGroup(5);
337 builder.addNextValue(5, 3, 1.0);
338 auto const model = buildModel<storm::models::sparse::Mdp<ValueType>>(builder.build(), {{
"goal", {2}}});
343 options.actionSensitive =
true;
345 EXPECT_EQ(4ull, quotient->getNumberOfStates());
346 EXPECT_EQ(6ull, quotient->getNumberOfChoices());
353TEST(StrongBisimulationTest, ApproximateChoicePartnersAtDifferentPositions) {
357 builder.newRowGroup(0);
358 builder.addNextValue(0, 2, 0.001);
359 builder.addNextValue(0, 3, 0.999);
360 builder.addNextValue(1, 2, 0.019);
361 builder.addNextValue(1, 3, 0.981);
362 builder.addNextValue(2, 2, 0.039);
363 builder.addNextValue(2, 3, 0.961);
364 builder.newRowGroup(3);
365 builder.addNextValue(3, 2, 0.010);
366 builder.addNextValue(3, 3, 0.990);
367 builder.addNextValue(4, 2, 0.030);
368 builder.addNextValue(4, 3, 0.970);
369 builder.addNextValue(5, 2, 0.048);
370 builder.addNextValue(5, 3, 0.952);
371 builder.newRowGroup(6);
372 builder.addNextValue(6, 2, 1.0);
373 builder.newRowGroup(7);
374 builder.addNextValue(7, 3, 1.0);
375 auto const model = buildModel<storm::models::sparse::Mdp<ValueType>>(builder.build(), {{
"goal", {2}}, {
"sink", {3}}});
384 options.createQuotientChoiceMapping =
true;
392TEST(StrongBisimulationTest, ApproximateChoiceMappingIsOneToOne) {
398 auto const addChoice = [&builder](uint64_t
const row,
ValueType const goalProbability) {
399 builder.addNextValue(row, 2, goalProbability);
402 builder.newRowGroup(0);
406 builder.newRowGroup(3);
410 builder.newRowGroup(6);
411 builder.addNextValue(6, 2, 1.0);
412 builder.newRowGroup(7);
413 builder.addNextValue(7, 3, 1.0);
414 builder.newRowGroup(8);
418 builder.newRowGroup(11);
422 auto const model = buildModel<storm::models::sparse::Mdp<ValueType>>(builder.build(), {{
"goal", {2}}, {
"sink", {3}}});
426 options.createQuotientChoiceMapping =
true;
428 ASSERT_EQ(4ull, result.quotient->getNumberOfStates());
429 ASSERT_EQ(8ull, result.quotient->getNumberOfChoices());
430 checkChoiceMapping(*model, result);
433 auto const& mapping = *result.toQuotientChoiceMapping;
434 for (uint64_t i = 0;
i < 3; ++
i) {
435 EXPECT_EQ(mapping[i], mapping[3 + i]) <<
"unexpected quotient choice for the choices " <<
i <<
" and " << (3 +
i);
436 EXPECT_EQ(mapping[8 + i], mapping[11 + i]) <<
"unexpected quotient choice for the choices " << (8 +
i) <<
" and " << (11 + i);
448TEST(StrongBisimulationTest, MdpQuotientIsNotMeasureDriven) {
451 builder.newRowGroup(0);
452 builder.addNextValue(0, 1, 1.0);
453 builder.addNextValue(1, 3, 1.0);
454 builder.newRowGroup(2);
455 builder.addNextValue(2, 2, 1.0);
456 builder.newRowGroup(3);
457 builder.addNextValue(3, 2, 1.0);
458 builder.newRowGroup(4);
459 builder.addNextValue(4, 1, 1.0);
460 auto const model = buildModel<storm::models::sparse::Mdp<ValueType>>(builder.build(), {{
"goal", {2}}});
463 std::vector<std::shared_ptr<storm::logic::Formula const>>
const formulas{formulaParser.parseSingleFormulaFromString(
"Pmax=? [F \"goal\"]")};
467 EXPECT_EQ(4ull, quotient->getNumberOfStates());
469 auto const& labeling = quotient->getStateLabeling();
470 ASSERT_TRUE(labeling.containsLabel(
"init"));
471 ASSERT_TRUE(labeling.containsLabel(
"goal"));
472 EXPECT_TRUE(labeling.getStates(
"init").isDisjointFrom(labeling.getStates(
"goal")));
474 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Pmax=? [F<=1 \"goal\"]"), checkFormula<ValueType>(model,
"Pmax=? [F<=1 \"goal\"]"), 1e-12);
475 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Pmax=? [F<=2 \"goal\"]"), checkFormula<ValueType>(model,
"Pmax=? [F<=2 \"goal\"]"), 1e-12);
482TEST(StrongBisimulationTest, MarkovAutomaton) {
487 std::vector<std::shared_ptr<storm::logic::Formula const>>
const formulas{formulaParser.
parseSingleFormulaFromString(
"Tmin=? [F \"all_jobs_finished\"]")};
492 auto const markovAutomaton = quotient->template as<storm::models::sparse::MarkovAutomaton<ValueType>>();
494 for (uint64_t state = 0; state < quotient->getNumberOfStates(); ++state) {
495 if (markovAutomaton->isMarkovianState(state)) {
496 EXPECT_EQ(1ull, quotient->getTransitionMatrix().getRowGroupSize(state));
499 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Tmin=? [F \"all_jobs_finished\"]"), checkFormula<ValueType>(model,
"Tmin=? [F \"all_jobs_finished\"]"),
502 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Tmax=? [F \"all_jobs_finished\"]"), checkFormula<ValueType>(model,
"Tmax=? [F \"all_jobs_finished\"]"),
512TEST(StrongBisimulationTest, MarkovAutomatonExpectedTime) {
515 builder.newRowGroup(0);
516 builder.addNextValue(0, 1, 1.0);
517 builder.addNextValue(1, 2, 1.0);
518 builder.newRowGroup(2);
519 builder.addNextValue(2, 3, 1.0);
520 builder.newRowGroup(3);
521 builder.addNextValue(3, 3, 2.0);
522 builder.newRowGroup(4);
523 builder.addNextValue(4, 3, 1.0);
524 auto const model = buildMarkovAutomaton<ValueType>(builder.build(),
storm::storage::BitVector(4, {1, 2, 3}), {{
"done", {3}}});
525 ASSERT_NEAR(0.5, checkFormula<ValueType>(model,
"Tmin=? [F \"done\"]"), 1e-12);
526 ASSERT_NEAR(1.0, checkFormula<ValueType>(model,
"Tmax=? [F \"done\"]"), 1e-12);
529 std::vector<std::shared_ptr<storm::logic::Formula const>>
const formulas{formulaParser.
parseSingleFormulaFromString(
"Tmin=? [F \"done\"]")};
533 EXPECT_EQ(4ull, quotient->getNumberOfStates());
534 EXPECT_NEAR(0.5, checkFormula<ValueType>(quotient,
"Tmin=? [F \"done\"]"), 1e-12);
535 EXPECT_NEAR(1.0, checkFormula<ValueType>(quotient,
"Tmax=? [F \"done\"]"), 1e-12);
542TEST(StrongBisimulationTest, MarkovAutomatonHybridStates) {
546 builder.newRowGroup(0);
547 builder.addNextValue(0, 2, 2.0);
548 builder.addNextValue(1, 2, 1.0);
549 builder.newRowGroup(2);
550 builder.addNextValue(2, 2, 2.0);
551 builder.addNextValue(3, 2, 1.0);
552 builder.newRowGroup(4);
553 builder.addNextValue(4, 3, 4.0);
554 builder.newRowGroup(5);
555 builder.addNextValue(5, 3, 1.0);
557 markovianStates.set(0);
558 markovianStates.set(1);
559 markovianStates.set(2);
560 auto const model = buildMarkovAutomaton<ValueType>(builder.build(), std::move(markovianStates), {{
"goal", {3}}});
561 ASSERT_FALSE(model->isClosed());
564 EXPECT_EQ(3ull, quotient->getNumberOfStates());
565 EXPECT_EQ(4ull, quotient->getNumberOfChoices());
566 auto const markovAutomaton = quotient->template as<storm::models::sparse::MarkovAutomaton<ValueType>>();
567 ASSERT_FALSE(markovAutomaton->isClosed());
568 auto const hybridState = *quotient->getInitialStates().begin();
569 EXPECT_TRUE(markovAutomaton->isMarkovianState(hybridState));
570 EXPECT_EQ(2ull, quotient->getTransitionMatrix().getRowGroupSize(hybridState));
571 EXPECT_EQ(2.0, markovAutomaton->getExitRate(hybridState));
576 markovAutomaton->close();
577 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Tmin=? [F \"goal\"]"), 0.25, 1e-9);
578 EXPECT_NEAR(checkFormula<ValueType>(model,
"Tmin=? [F \"goal\"]"), 0.25, 1e-9);
585TEST(StrongBisimulationTest, Die) {
586 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]", 13ull, 5ull, 8ull, 5ull);
589TEST(StrongBisimulationTest, DieAllLabels) {
590 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]", 13ull, 11ull, 17ull, 11ull, allLabelOptions());
596TEST(StrongBisimulationTest, DieUnnamedRewardModel) {
597 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"R=? [F \"done\"]", 13ull, 5ull, 7ull, 5ull);
603TEST(StrongBisimulationTest, DiePropositionalSubformulas) {
604 std::string
const formulaString =
"P=? [F (s=7 & d=1) | \"two\"]";
605 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm", formulaString, 13ull, 6ull, 10ull, 6ull);
607 options.stateLabelPreservation = StateLabelPreservation::FormulaIndividual;
608 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm", formulaString, 13ull, 7ull, 11ull, 7ull, options);
614TEST(StrongBisimulationTest, DieStateValuations) {
616 GTEST_SKIP() <<
"Z3 not available.";
618 auto const input = buildFromPrism<ValueType>(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]", {.stateValuations =
true});
619 ASSERT_TRUE(input.model->hasStateValuations());
622 ASSERT_TRUE(result.quotient->hasStateValuations());
623 ASSERT_EQ(result.quotient->getNumberOfStates(), result.quotient->getStateValuations().getNumberOfEntities());
625 std::set<uint64_t> quotientStatesWithRepresentedValuation;
626 for (uint64_t state = 0; state < input.model->getNumberOfStates(); ++state) {
627 uint64_t
const quotientState = result.toQuotientStateMapping[state];
628 if (input.model->getStateValuations().toString(state) == result.quotient->getStateValuations().toString(quotientState)) {
629 quotientStatesWithRepresentedValuation.insert(quotientState);
632 EXPECT_EQ(result.quotient->getNumberOfStates(), quotientStatesWithRepresentedValuation.size());
635TEST(StrongBisimulationTest, Crowds) {
636 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/crowds5_5.pm",
"P=? [F \"observe0Greater1\"]", 7403ull, 65ull, 105ull, 65ull);
639TEST(StrongBisimulationTest, CrowdsAllLabels) {
641 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/crowds5_5.pm",
"P=? [F \"observe0Greater1\"]", 8607ull, 2149ull, 3912ull, 2149ull, allLabelOptions());
644TEST(StrongBisimulationTest, CtmcEmbedded) {
645 testQuotient(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"P=? [F<=10000 \"down\"]", 2076ull, 310ull, 1752ull, 310ull, approximateOptions());
648TEST(StrongBisimulationTest, CtmcCluster) {
649 testQuotient(STORM_TEST_RESOURCES_DIR
"/ctmc/cluster2.sm",
"P=? [F<=100 !\"minimum\"]", 276ull, 147ull, 569ull, 147ull);
652TEST(StrongBisimulationTest, CtmcClusterChoiceLabelsAndOrigins) {
654 testQuotient(STORM_TEST_RESOURCES_DIR
"/ctmc/cluster2.sm",
"P=? [F<=100 !\"minimum\"]", 276ull, 276ull, 1120ull, 276ull,
strongOptions(),
655 {.choiceLabels =
true, .choiceOrigins =
true});
658TEST(StrongBisimulationTest, DtmcBrpChoiceLabelsAndOrigins) {
659 testQuotient(STORM_TEST_RESOURCES_DIR
"/dtmc/brp-16-2.pm",
"P=? [F \"target\"]", 613ull, 412ull, 572ull, 412ull,
strongOptions(),
660 {.choiceLabels =
true, .choiceOrigins =
true});
668TEST(StrongBisimulationTest, CtmcEmbeddedExact) {
670 GTEST_SKIP() <<
"Z3 not available.";
672 std::string
const formulaString =
"P=? [F<=10000 \"down\"]";
673 auto const exactInput = buildFromPrism<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm", formulaString, {.allLabels =
true});
674 ASSERT_EQ(3478ull, exactInput.model->getNumberOfStates());
675 ASSERT_EQ(14639ull, exactInput.model->getNumberOfTransitions());
678 options.stateLabelPreservation = StateLabelPreservation::All;
679 auto const labeledQuotient =
681 EXPECT_EQ(1127ull, labeledQuotient->getNumberOfStates());
682 EXPECT_EQ(5730ull, labeledQuotient->getNumberOfTransitions());
686 options.stateLabelPreservation = StateLabelPreservation::None;
688 EXPECT_EQ(98ull, unlabeledQuotient->getNumberOfStates());
689 EXPECT_EQ(539ull, unlabeledQuotient->getNumberOfTransitions());
696TEST(StrongBisimulationTest, CtmcEmbeddedWithTolerance) {
698 GTEST_SKIP() <<
"Z3 not available.";
700 std::string
const formulaString =
"P=? [F<=10000 \"down\"]";
701 auto const input = buildFromPrism<double>(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm", formulaString, {.allLabels =
true});
703 Options options = approximateOptions();
704 options.stateLabelPreservation = StateLabelPreservation::All;
706 EXPECT_EQ(1127ull, labeledQuotient->getNumberOfStates());
707 EXPECT_EQ(5730ull, labeledQuotient->getNumberOfTransitions());
709 options.stateLabelPreservation = StateLabelPreservation::None;
711 EXPECT_EQ(98ull, unlabeledQuotient->getNumberOfStates());
712 EXPECT_EQ(539ull, unlabeledQuotient->getNumberOfTransitions());
715TEST(StrongBisimulationTest, MdpTwoDice) {
716 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/two_dice.nm",
"Pmin=? [F \"two\"]", 169ull, 11ull, 26ull, 14ull);
719TEST(StrongBisimulationTest, MdpTwoDiceAllLabels) {
720 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/two_dice.nm",
"Pmin=? [F \"two\"]", 169ull, 77ull, 183ull, 97ull, allLabelOptions());
723TEST(StrongBisimulationTest, MdpCoin) {
724 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/coin2-2.nm",
"Pmin=? [F \"finished\"]", 272ull, 55ull, 96ull, 78ull);
731TEST(StrongBisimulationTest, MdpCoinQuotientIsNotMeasureDriven) {
733 GTEST_SKIP() <<
"Z3 not available.";
735 std::string
const formulaString =
"Pmax=? [F \"finished\"]";
736 auto const input = buildFromPrism<ValueType>(STORM_TEST_RESOURCES_DIR
"/mdp/coin2-2.nm", formulaString);
737 ASSERT_NEAR(1.0, checkFormula<ValueType>(input.model, formulaString), 1e-9);
740 EXPECT_EQ(55ull, quotient->getNumberOfStates());
741 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), 1.0, 1e-9);
743 EXPECT_NEAR(checkFormula<ValueType>(quotient,
"Pmax=? [F<=10 \"finished\"]"), checkFormula<ValueType>(input.model,
"Pmax=? [F<=10 \"finished\"]"), 1e-9);
746TEST(StrongBisimulationTest, MdpTwoDiceActionSensitive) {
748 options.actionSensitive =
true;
749 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/two_dice.nm",
"Pmin=? [F \"two\"]", 169ull, 33ull, 97ull, 58ull, options);
752TEST(StrongBisimulationTest, MdpCoinChoiceOrigins) {
753 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/coin2-2.nm",
"Pmin=? [F \"finished\"]", 272ull, 195ull, 399ull, 321ull,
strongOptions(),
754 {.choiceOrigins =
true});
757TEST(StrongBisimulationTest, MdpLeaderChoiceLabels) {
758 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/leader3.nm",
"Pmin=? [F \"elected\"]", 364ull, 169ull, 325ull, 262ull,
strongOptions(), {.choiceLabels =
true});
761TEST(StrongBisimulationTest, MdpLeaderChoiceLabelsAndOriginsActionSensitive) {
763 options.actionSensitive =
true;
764 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/leader3.nm",
"Pmin=? [F \"elected\"]", 364ull, 169ull, 325ull, 262ull, options,
765 {.choiceLabels =
true, .choiceOrigins =
true});
771TEST(StrongBisimulationTest, MdpTwoDiceApproximative) {
772 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/two_dice.nm",
"Pmin=? [F \"two\"]", 169ull, 11ull, 26ull, 14ull, approximateOptions());
775TEST(StrongBisimulationTest, MdpCoinApproximative) {
776 testQuotient(STORM_TEST_RESOURCES_DIR
"/mdp/coin2-2.nm",
"Pmin=? [F \"finished\"]", 272ull, 55ull, 96ull, 78ull, approximateOptions());
787void testRefinementStrategiesAgree(std::string
const& prismFile, std::string
const& formulaString) {
789 GTEST_SKIP() <<
"Z3 not available.";
791 auto const input = buildFromPrism<VT>(prismFile, formulaString);
794 splitterOptions.preferSignatureRefinement =
false;
797 auto signatureOptions = splitterOptions;
798 signatureOptions.preferSignatureRefinement =
true;
801 EXPECT_EQ(splitterQuotient->getNumberOfStates(), signatureQuotient->getNumberOfStates());
802 EXPECT_EQ(splitterQuotient->getNumberOfTransitions(), signatureQuotient->getNumberOfTransitions());
805TEST(StrongBisimulationTest, RefinementStrategiesAgreeOnDie) {
806 testRefinementStrategiesAgree<ValueType>(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]");
809TEST(StrongBisimulationTest, RefinementStrategiesAgreeOnCrowds) {
810 testRefinementStrategiesAgree<ValueType>(STORM_TEST_RESOURCES_DIR
"/dtmc/crowds5_5.pm",
"P=? [F \"observe0Greater1\"]");
817TEST(StrongBisimulationTest, RefinementStrategiesAgreeOnCtmc) {
818 testRefinementStrategiesAgree<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"P=? [F<=10000 \"down\"]");
824void testExactAgreesWithApproximate(std::string
const& prismFile, std::string
const& formulaString) {
826 GTEST_SKIP() <<
"Z3 not available.";
828 auto const doubleInput = buildFromPrism<double>(prismFile, formulaString);
829 auto const exactInput = buildFromPrism<storm::RationalNumber>(prismFile, formulaString);
831 auto const doubleQuotient =
833 auto const exactQuotient =
836 EXPECT_EQ(doubleQuotient->getNumberOfStates(), exactQuotient->getNumberOfStates());
837 EXPECT_EQ(doubleQuotient->getNumberOfTransitions(), exactQuotient->getNumberOfTransitions());
840TEST(StrongBisimulationTest, ExactAgreesWithApproximateOnDie) {
841 testExactAgreesWithApproximate(STORM_TEST_RESOURCES_DIR
"/dtmc/die.pm",
"P=? [F \"one\"]");
844TEST(StrongBisimulationTest, ExactAgreesWithApproximateOnTwoDice) {
845 testExactAgreesWithApproximate(STORM_TEST_RESOURCES_DIR
"/mdp/two_dice.nm",
"Pmin=? [F \"two\"]");
854void testPermutationInvariance(std::string
const& prismFile, std::string
const& formulaString, uint64_t
const numSeeds, Options options =
strongOptions()) {
856 GTEST_SKIP() <<
"Z3 not available.";
858 options.stateLabelPreservation = StateLabelPreservation::All;
859 auto const input = buildFromPrism<VT>(prismFile, formulaString, {.allLabels =
true});
862 std::vector<uint64_t> permutation(input.model->getNumberOfStates());
863 std::iota(permutation.begin(), permutation.end(), 0ull);
864 for (uint64_t seed = 0; seed < numSeeds; ++seed) {
865 std::mt19937_64 rng(seed);
866 std::shuffle(permutation.begin(), permutation.end(), rng);
869 EXPECT_EQ(quotient->getNumberOfStates(), permutedQuotient->getNumberOfStates()) <<
"Seed " << seed <<
".";
870 EXPECT_EQ(quotient->getNumberOfTransitions(), permutedQuotient->getNumberOfTransitions()) <<
"Seed " << seed <<
".";
874TEST(StrongBisimulationTest, PermutationInvarianceExactCtmc) {
875 testPermutationInvariance<storm::RationalNumber>(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"P=? [F<=10000 \"down\"]", 3ull);
878TEST(StrongBisimulationTest, PermutationInvarianceCtmc) {
879 testPermutationInvariance<double>(STORM_TEST_RESOURCES_DIR
"/ctmc/embedded2.sm",
"P=? [F<=10000 \"down\"]", 3ull, approximateOptions());
882TEST(StrongBisimulationTest, PermutationInvarianceMdp) {
883 testPermutationInvariance<double>(STORM_TEST_RESOURCES_DIR
"/mdp/two_dice.nm",
"Pmin=? [F \"two\"]", 3ull, approximateOptions());
894template<
typename ModelType>
895std::shared_ptr<ModelType> buildRandomModel(uint64_t
const numStates, uint64_t
const seed) {
896 bool constexpr isNondeterministic = std::is_same_v<ModelType, storm::models::sparse::Mdp<ValueType>>;
897 std::mt19937_64 rng(seed);
900 for (uint64_t state = 0; state < numStates; ++state) {
901 if constexpr (isNondeterministic) {
902 builder.newRowGroup(row);
904 uint64_t
const numChoices = isNondeterministic ? 1 + rng() % 2 : 1;
905 for (uint64_t choice = 0; choice < numChoices; ++choice, ++row) {
906 std::map<uint64_t, ValueType> distribution;
907 uint64_t
const numSuccessors = 1 + rng() % 3;
908 for (uint64_t i = 0;
i < numSuccessors; ++
i) {
910 distribution[rng() % numStates] +=
static_cast<ValueType>(1 + rng() % 4);
913 for (
auto const& [_, value] : distribution) {
916 for (
auto const& [column, value] : distribution) {
917 builder.addNextValue(row, column, value / sum);
921 std::map<std::string, std::vector<uint64_t>> labels{{
"a", {}}, {
"b", {}}};
922 for (uint64_t state = 0; state < numStates; ++state) {
923 if (rng() % 3 == 0) {
924 labels[
"a"].push_back(state);
926 if (rng() % 4 == 0) {
927 labels[
"b"].push_back(state);
930 return buildModel<ModelType>(builder.build(row, numStates, numStates), labels);
940template<
typename ModelType>
941void testIdempotence(uint64_t
const numStates, uint64_t
const numSeeds, std::string
const& formulaString) {
942 for (uint64_t seed = 0; seed < numSeeds; ++seed) {
943 auto const model = buildRandomModel<ModelType>(numStates, seed);
946 ASSERT_LE(quotient->getNumberOfStates(), model->
getNumberOfStates()) <<
"Seed " << seed <<
".";
948 EXPECT_EQ(quotient->getNumberOfStates(), quotientOfQuotient->getNumberOfStates()) <<
"Not idempotent for seed " << seed <<
".";
949 EXPECT_EQ(quotient->getNumberOfTransitions(), quotientOfQuotient->getNumberOfTransitions()) <<
"Not idempotent for seed " << seed <<
".";
951 EXPECT_NEAR(checkFormula<ValueType>(quotient, formulaString), checkFormula<ValueType>(model, formulaString), 1e-4) <<
"Seed " << seed <<
".";
955TEST(StrongBisimulationTest, RandomDtmcs) {
956 testIdempotence<storm::models::sparse::Dtmc<ValueType>>(12, 500,
"P=? [F \"a\"]");
957 testIdempotence<storm::models::sparse::Dtmc<ValueType>>(60, 200,
"P=? [(!\"b\") U \"a\"]");
960TEST(StrongBisimulationTest, RandomCtmcs) {
961 testIdempotence<storm::models::sparse::Ctmc<ValueType>>(12, 500,
"P=? [F \"a\"]");
962 testIdempotence<storm::models::sparse::Ctmc<ValueType>>(60, 200,
"P=? [(!\"b\") U \"a\"]");
965TEST(StrongBisimulationTest, RandomMdps) {
966 testIdempotence<storm::models::sparse::Mdp<ValueType>>(12, 500,
"Pmin=? [F \"a\"]");
967 testIdempotence<storm::models::sparse::Mdp<ValueType>>(60, 200,
"Pmax=? [(!\"b\") U \"a\"]");
973template<
typename ModelType>
974void testRefinementStrategiesAgreeOnRandomModels(uint64_t
const numStates, uint64_t
const numSeeds) {
975 for (uint64_t seed = 0; seed < numSeeds; ++seed) {
976 auto const model = buildRandomModel<ModelType>(numStates, seed);
978 splitterOptions.preferSignatureRefinement =
false;
980 auto signatureOptions = splitterOptions;
981 signatureOptions.preferSignatureRefinement =
true;
983 EXPECT_EQ(splitterQuotient->getNumberOfStates(), signatureQuotient->getNumberOfStates()) <<
"Seed " << seed <<
".";
984 EXPECT_EQ(splitterQuotient->getNumberOfTransitions(), signatureQuotient->getNumberOfTransitions()) <<
"Seed " << seed <<
".";
988TEST(StrongBisimulationTest, RefinementStrategiesAgreeOnRandomDtmcs) {
989 testRefinementStrategiesAgreeOnRandomModels<storm::models::sparse::Dtmc<ValueType>>(12, 500);
990 testRefinementStrategiesAgreeOnRandomModels<storm::models::sparse::Dtmc<ValueType>>(60, 200);
993TEST(StrongBisimulationTest, RefinementStrategiesAgreeOnRandomCtmcs) {
994 testRefinementStrategiesAgreeOnRandomModels<storm::models::sparse::Ctmc<ValueType>>(12, 500);
995 testRefinementStrategiesAgreeOnRandomModels<storm::models::sparse::Ctmc<ValueType>>(60, 200);
TEST(RegionConstraintCheckerTest, AffineConstraintHoldsThroughout)
static constexpr double DefaultModelTolerance
The default tolerance up to which probabilities and rates are considered equal.
virtual ModelType getType() const
Return the actual type of the model.
std::set< std::string > getLabels() const
Retrieves the set of labels contained in this labeling.
Base class for all sparse models.
storm::models::sparse::ChoiceLabeling const & getChoiceLabeling() const
Retrieves the labels for the choices of the model.
storm::storage::SparseMatrix< ValueType > const & getTransitionMatrix() const
Retrieves the matrix representing the transitions of the model.
virtual uint_fast64_t getNumberOfChoices() const override
Returns the number of choices ine the model.
ValueType const & getStochasticTolerance() const
Retrieves the tolerance up to which the model components are considered valid.
bool hasChoiceLabeling() const
Retrieves whether this model has a labeling of the choices.
storm::models::sparse::StateLabeling const & getStateLabeling() const
Returns the state labeling associated with this model.
virtual uint_fast64_t getNumberOfStates() const override
Returns the number of states of the model.
void addLabelToState(std::string const &label, storm::storage::sparse::state_type state)
Adds a label to a given state.
void removeLabelFromState(std::string const &label, storm::storage::sparse::state_type state)
Removes a label from a given state.
A bit vector that is internally represented as a vector of 64-bit values.
A class that can be used to build a sparse matrix by adding value by value.
std::shared_ptr< storm::models::sparse::Model< ValueType > > buildExplicitDRNModel(std::string const &drnFile, storm::parser::DirectEncodingParserOptions const &options=storm::parser::DirectEncodingParserOptions())
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< storm::models::sparse::MarkovAutomaton< ValueType > > buildMarkovAutomaton(storm::storage::SparseMatrix< ValueType > matrix, storm::storage::BitVector markovianStates, std::map< std::string, std::vector< uint64_t > > const &labels)
Builds a Markov automaton.
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)
#define STORM_SILENT_EXPECT_THROW(statement, expected_exception)
bool createQuotientChoiceMapping
std::optional< std::vector< uint64_t > > toQuotientChoiceMapping
std::shared_ptr< storm::models::sparse::Model< ValueType > > quotient
std::vector< uint64_t > toQuotientStateMapping
What to build from a PRISM program in addition to what the formulas require.