Storm 1.14.0.1
A Modern Probabilistic Model Checker
Loading...
Searching...
No Matches
HashVisitorTest.cpp
Go to the documentation of this file.
1#include "test/storm_gtest.h"
2
6
7namespace {
8
9class HashVisitorTest : public ::testing::Test {
10 public:
11 std::size_t hash(storm::expressions::Expression const& expression) {
12 storm::expressions::HashVisitor visitor;
13 return visitor.hash(expression);
14 }
15};
16
17} // namespace
18
19TEST_F(HashVisitorTest, SyntacticallyEqualExpressionsHashEqually) {
20 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
21 auto x = manager->declareBooleanVariable("x");
22 auto p = manager->declareRationalVariable("p");
23 auto q = manager->declareRationalVariable("q");
24
25 // Each of these pairs is syntactically equal, so the hashes must agree.
26 EXPECT_EQ(hash(x && x), hash(x && x));
27 EXPECT_EQ(hash((x && !x) || x), hash((x && !x) || x));
28 EXPECT_EQ(hash(p <= q), hash(p <= q));
29 EXPECT_EQ(hash(manager->integer(3) + manager->integer(4)), hash(manager->integer(3) + manager->integer(4)));
30 EXPECT_EQ(hash(manager->rational(3.5)), hash(manager->rational(3.5)));
31 EXPECT_EQ(hash(manager->boolean(true)), hash(manager->boolean(true)));
32
33 // Syntactic equality is insensitive to expression identity: two separately built but
34 // structurally identical expressions must still hash equally.
35 auto left = (x && x);
36 auto right = (x && x);
37 ASSERT_TRUE(left.isSyntacticallyEqual(right));
38 EXPECT_EQ(hash(left), hash(right));
39}
40
41TEST_F(HashVisitorTest, DifferentExpressionsHashDifferently) {
42 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
43 auto x = manager->declareBooleanVariable("x");
44 auto y = manager->declareBooleanVariable("y");
45 auto p = manager->declareRationalVariable("p");
46 auto q = manager->declareRationalVariable("q");
47
48 // Distinct variables.
49 EXPECT_NE(hash(x), hash(y));
50
51 // Distinct operators over the same operands.
52 EXPECT_NE(hash(x && x), hash(x || x));
53
54 // Same operator, different operand.
55 EXPECT_NE(hash(x && x), hash(x && y));
56
57 // Different literals of the same type.
58 EXPECT_NE(hash(manager->integer(3)), hash(manager->integer(4)));
59 EXPECT_NE(hash(manager->boolean(true)), hash(manager->boolean(false)));
60 EXPECT_NE(hash(manager->rational(3.5)), hash(manager->rational(4.5)));
61
62 // Different types that could otherwise coincide: an integer and a rational literal, and a
63 // variable against a literal.
64 EXPECT_NE(hash(manager->integer(2)), hash(manager->rational(2.0)));
65 EXPECT_NE(hash(x), hash(manager->boolean(true)));
66
67 // Same shape but a different relation type.
68 EXPECT_NE(hash(p <= q), hash(p >= q));
69
70 // The two arguments of a relation are not interchangeable, so the order matters.
71 EXPECT_NE(hash(p <= q), hash(q <= p));
72}
73
74TEST_F(HashVisitorTest, NestedExpressionsPropagateChangesFromChildren) {
75 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
76 auto x = manager->declareBooleanVariable("x");
77 auto y = manager->declareBooleanVariable("y");
78
79 // A change deep inside must change the hash of the enclosing expression; otherwise a container
80 // keyed on the hash would wrongly report two different expressions as equal.
81 EXPECT_NE(hash((x && x) || (x && x)), hash((x && y) || (x && y)));
82}
83
84TEST_F(HashVisitorTest, PredicateExpressionsAreHashed) {
85 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
86 auto x = manager->declareBooleanVariable("x");
87 auto y = manager->declareBooleanVariable("y");
88 auto p = manager->declareRationalVariable("p");
89
90 // Syntactically equal predicates must hash equally, also across distinct nodes.
91 EXPECT_EQ(hash(storm::expressions::atLeastOneOf({x, y})), hash(storm::expressions::atLeastOneOf({x, y})));
92 EXPECT_EQ(hash(storm::expressions::exactlyOneOf({x, y})), hash(storm::expressions::exactlyOneOf({x, y})));
93
94 // The predicate type is part of the hash.
95 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x, y})), hash(storm::expressions::atMostOneOf({x, y})));
96 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x, y})), hash(storm::expressions::exactlyOneOf({x, y})));
97
98 // The operands are part of the hash, and their order matters.
99 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x, y})), hash(storm::expressions::atLeastOneOf({y, x})));
100 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x, y})), hash(storm::expressions::atLeastOneOf({x, p})));
101
102 // The arity is part of the hash.
103 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x})), hash(storm::expressions::atLeastOneOf({x, y})));
104
105 // A predicate must not collide with a plain variable or literal of the same operands.
106 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x, y})), hash(x));
107}
108
109TEST_F(HashVisitorTest, PredicateExpressionsCompareSyntactically) {
110 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
111 auto x = manager->declareBooleanVariable("x");
112 auto y = manager->declareBooleanVariable("y");
113
114 // SyntacticalEqualityCheckVisitor must support predicates as well; otherwise the hash above is
115 // unusable, because the two must agree.
116 EXPECT_TRUE(storm::expressions::atLeastOneOf({x, y}).isSyntacticallyEqual(storm::expressions::atLeastOneOf({x, y})));
117 EXPECT_FALSE(storm::expressions::atLeastOneOf({x, y}).isSyntacticallyEqual(storm::expressions::atMostOneOf({x, y})));
118 EXPECT_FALSE(storm::expressions::atLeastOneOf({x, y}).isSyntacticallyEqual(storm::expressions::atLeastOneOf({y, x})));
119 EXPECT_FALSE(storm::expressions::atLeastOneOf({x, y}).isSyntacticallyEqual(storm::expressions::atLeastOneOf({x})));
120 EXPECT_FALSE(storm::expressions::atLeastOneOf({x, y}).isSyntacticallyEqual(x));
121
122 // Predicates nested inside other expressions work too.
123 EXPECT_TRUE((x && storm::expressions::atLeastOneOf({x, y})).isSyntacticallyEqual(x && storm::expressions::atLeastOneOf({x, y})));
124}
125
126TEST_F(HashVisitorTest, PredicatesNestedInExpressionsAreHashed) {
127 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
128 auto x = manager->declareBooleanVariable("x");
129 auto y = manager->declareBooleanVariable("y");
130 auto z = manager->declareBooleanVariable("z");
131 auto p = manager->declareRationalVariable("p");
132
133 // A predicate that is reached through the recursion instead of being the root of the traversal must
134 // be hashed as well, otherwise the hash would still reach the unimplemented base visitor here.
135 EXPECT_EQ(hash(x && storm::expressions::atLeastOneOf({x, y})), hash(x && storm::expressions::atLeastOneOf({x, y})));
136 EXPECT_EQ(hash((x && storm::expressions::atLeastOneOf({x, y})) || z), hash((x && storm::expressions::atLeastOneOf({x, y})) || z));
137
138 // The nested predicate keeps contributing its type, its operands and their order.
139 EXPECT_NE(hash(x && storm::expressions::atLeastOneOf({x, y})), hash(x && storm::expressions::atMostOneOf({x, y})));
140 EXPECT_NE(hash(x && storm::expressions::atLeastOneOf({x, y})), hash(x && storm::expressions::atLeastOneOf({y, x})));
141 EXPECT_NE(hash(x && storm::expressions::atLeastOneOf({x, y})), hash(x && storm::expressions::atLeastOneOf({x, z})));
142
143 // Higher arity.
144 EXPECT_NE(hash(x && storm::expressions::atLeastOneOf({x, y, z})), hash(x && storm::expressions::atLeastOneOf({x, y})));
145
146 // A predicate nested inside a predicate is not expressible, because the factories only accept plain boolean
147 // operands. What matters here is that the operands are hashed recursively, which the arithmetic operand
148 // already covers.
149 EXPECT_EQ(hash(storm::expressions::atLeastOneOf({x && (p <= manager->integer(1)), y})),
150 hash(storm::expressions::atLeastOneOf({x && (p <= manager->integer(1)), y})));
151 EXPECT_NE(hash(storm::expressions::atLeastOneOf({x && (p <= manager->integer(1)), y})),
152 hash(storm::expressions::atLeastOneOf({x && (p <= manager->integer(2)), y})));
153}
154
155TEST_F(HashVisitorTest, HashIsStableAcrossCalls) {
156 auto manager = std::make_shared<storm::expressions::ExpressionManager>();
157 auto p = manager->declareRationalVariable("p");
158
159 // The hash must not depend on the visitor's state, so that it can be used to size or partition
160 // containers across separate traversals.
161 auto expression = (p <= manager->integer(1));
162 auto const first = hash(expression);
163 for (int i = 0; i < 5; ++i) {
164 EXPECT_EQ(first, hash(expression));
165 }
166
167 // A fresh visitor must agree with a reused one.
169 EXPECT_EQ(first, fresh.hash(expression));
170}
TEST_F(HashVisitorTest, SyntacticallyEqualExpressionsHashEqually)
Computes a hash of an expression such that syntactically equal expressions hash to the same value.
Definition HashVisitor.h:21
std::size_t hash(storm::expressions::Expression const &expression)
Computes the hash of the given expression.
Expression atLeastOneOf(std::vector< Expression > const &expressions)
Expression exactlyOneOf(std::vector< Expression > const &expressions)
Expression atMostOneOf(std::vector< Expression > const &expressions)