Skip to content

Commit b55152a

Browse files
authored
Merge pull request #71 from steve-anunknown/improve-performance-of-w-methods
optimized the W and Wp method oracles
2 parents a752949 + 57d27c5 commit b55152a

File tree

3 files changed

+196
-66
lines changed

3 files changed

+196
-66
lines changed

aalpy/oracles/WMethodEqOracle.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
from aalpy.base.Oracle import Oracle
44
from aalpy.base.SUL import SUL
5-
from aalpy.utils.HelperFunctions import product_with_possible_empty_iterable
6-
5+
from itertools import product
76

87
class WMethodEqOracle(Oracle):
98
"""
@@ -26,34 +25,55 @@ def __init__(self, alphabet: list, sul: SUL, max_number_of_states, shuffle_test_
2625
self.shuffle = shuffle_test_set
2726
self.cache = set()
2827

28+
def test_suite(self, cover, depth, char_set):
29+
"""
30+
Construct the test suite for the W Method using
31+
the provided state cover and characterization set,
32+
exploring up to a given depth.
33+
Args:
34+
35+
cover: list of states to cover
36+
depth: maximum length of middle part
37+
char_set: characterization set
38+
"""
39+
# fix the length of the middle part per loop
40+
# to avoid generating large sequences early on
41+
char_set = char_set or [()]
42+
for d in range(depth):
43+
middle = product(self.alphabet, repeat=d)
44+
for m in middle:
45+
for (s, c) in product(cover, char_set):
46+
yield s + m + c
47+
48+
2949
def find_cex(self, hypothesis):
3050

3151
if not hypothesis.characterization_set:
3252
hypothesis.characterization_set = hypothesis.compute_characterization_set()
3353

3454
# covers every transition of the specification at least once.
35-
transition_cover = [state.prefix + (letter,) for state in hypothesis.states for letter in self.alphabet]
36-
37-
middle = []
38-
for i in range(self.m + 1 - len(hypothesis.states)):
39-
middle.extend(list(product_with_possible_empty_iterable(self.alphabet, repeat=i)))
40-
41-
for seq in product_with_possible_empty_iterable(transition_cover, middle, hypothesis.characterization_set):
42-
inp_seq = tuple([i for sub in seq for i in sub])
43-
if inp_seq not in self.cache:
55+
transition_cover = [
56+
state.prefix + (letter,)
57+
for state in hypothesis.states
58+
for letter in self.alphabet
59+
]
60+
61+
depth = self.m + 1 - len(hypothesis.states)
62+
for seq in self.test_suite(transition_cover, depth, hypothesis.characterization_set):
63+
if seq not in self.cache:
4464
self.reset_hyp_and_sul(hypothesis)
4565
outputs = []
4666

47-
for ind, letter in enumerate(inp_seq):
67+
for ind, letter in enumerate(seq):
4868
out_hyp = hypothesis.step(letter)
4969
out_sul = self.sul.step(letter)
5070
self.num_steps += 1
5171

5272
outputs.append(out_sul)
5373
if out_hyp != out_sul:
5474
self.sul.post()
55-
return inp_seq[:ind + 1]
56-
self.cache.add(inp_seq)
75+
return seq[:ind + 1]
76+
self.cache.add(seq)
5777

5878
return None
5979

aalpy/oracles/WpMethodEqOracle.py

Lines changed: 43 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
from itertools import chain, tee
1+
from itertools import chain, product
22

33
from aalpy.base.Oracle import Oracle
44
from aalpy.base.SUL import SUL
5-
from aalpy.utils.HelperFunctions import product_with_possible_empty_iterable
65

76

87
def state_characterization_set(hypothesis, alphabet, state):
@@ -23,34 +22,45 @@ def state_characterization_set(hypothesis, alphabet, state):
2322
return result
2423

2524

26-
def i_star(alphabet, max_seq_len):
25+
def first_phase_it(alphabet, state_cover, depth, char_set):
2726
"""
28-
Return an iterator that generates all possible sequences of length upto from the given alphabet.
27+
Return an iterator that generates all possible sequences for the first phase of the Wp-method.
2928
Args:
3029
alphabet: input alphabet
31-
max_seq_len: maximum length of the sequences
30+
state_cover: list of states to cover
31+
depth: maximum length of middle part
32+
char_set: characterization set
3233
"""
33-
return chain(*(product_with_possible_empty_iterable(alphabet, repeat=i) for i in range(max_seq_len)))
34+
char_set = char_set or [()]
35+
for d in range(depth):
36+
middle = product(alphabet, repeat=d)
37+
for m in middle:
38+
for s in state_cover:
39+
for c in char_set:
40+
yield s + m + c
3441

3542

36-
def second_phase_test_case_generator(hyp, alphabet, difference, middle):
43+
def second_phase_it(hyp, alphabet, difference, depth):
3744
"""
3845
Return an iterator that generates all possible sequences for the second phase of the Wp-method.
3946
Args:
4047
hyp: hypothesis automaton
4148
alphabet: input alphabet
4249
difference: set of sequences that are in the transition cover but not in the state cover
43-
middle: iterator that generates all possible sequences of length upto from the given alphabet
50+
depth: maximum length of middle part
4451
"""
4552
state_mapping = {}
46-
for t, mid in product_with_possible_empty_iterable(difference, middle):
47-
_ = hyp.execute_sequence(hyp.initial_state, t + mid)
48-
state = hyp.current_state
49-
if state not in state_mapping:
50-
state_mapping[state] = state_characterization_set(hyp, alphabet, state)
53+
for d in range(depth):
54+
middle = product(alphabet, repeat=d)
55+
for mid in middle:
56+
for t in difference:
57+
_ = hyp.execute_sequence(hyp.initial_state, t + mid)
58+
state = hyp.current_state
59+
if state not in state_mapping:
60+
state_mapping[state] = state_characterization_set(hyp, alphabet, state)
5161

52-
for sm in state_mapping[state]:
53-
yield t + mid + sm
62+
for sm in state_mapping[state]:
63+
yield t + mid + sm
5464

5565

5666
class WpMethodEqOracle(Oracle):
@@ -63,21 +73,6 @@ def __init__(self, alphabet: list, sul: SUL, max_number_of_states=4):
6373
self.m = max_number_of_states
6474
self.cache = set()
6575

66-
def test_sequance(self, hypothesis, seq_under_test):
67-
self.reset_hyp_and_sul(hypothesis)
68-
69-
for ind, letter in enumerate(seq_under_test):
70-
out_hyp = hypothesis.step(letter)
71-
out_sul = self.sul.step(letter)
72-
self.num_steps += 1
73-
74-
if out_hyp != out_sul:
75-
self.sul.post()
76-
return seq_under_test[: ind + 1]
77-
self.cache.add(seq_under_test)
78-
79-
return None
80-
8176
def find_cex(self, hypothesis):
8277
if not hypothesis.characterization_set:
8378
hypothesis.characterization_set = hypothesis.compute_characterization_set()
@@ -90,31 +85,27 @@ def find_cex(self, hypothesis):
9085

9186
state_cover = set(state.prefix for state in hypothesis.states)
9287
difference = transition_cover.difference(state_cover)
93-
94-
# two views of the same iterator
95-
middle_1, middle_2 = tee(i_star(self.alphabet, self.m - hypothesis.size + 1), 2)
96-
88+
depth = self.m + 1 - len(hypothesis.states)
9789
# first phase State Cover * Middle * Characterization Set
98-
state_cover = state_cover or [()]
99-
char_set = hypothesis.characterization_set or [()]
100-
101-
for sc in state_cover:
102-
for m in middle_1:
103-
for cs in char_set:
104-
test_seq = sc + m + cs
105-
if test_seq not in self.cache:
106-
counterexample = self.test_sequance(hypothesis, test_seq)
107-
if counterexample:
108-
return counterexample
90+
first_phase = first_phase_it(self.alphabet, state_cover, depth, hypothesis.characterization_set)
10991

11092
# second phase (Transition Cover - State Cover) * Middle * Characterization Set
11193
# of the state that the prefix leads to
112-
second_phase = second_phase_test_case_generator(hypothesis, self.alphabet, difference, middle_2)
113-
114-
for test_seq in second_phase:
115-
if test_seq not in self.cache:
116-
counterexample = self.test_sequance(hypothesis, test_seq)
117-
if counterexample:
118-
return counterexample
94+
second_phase = second_phase_it(hypothesis, self.alphabet, difference, depth)
95+
test_suite = chain(first_phase, second_phase)
96+
97+
for seq in test_suite:
98+
if seq not in self.cache:
99+
self.reset_hyp_and_sul(hypothesis)
100+
101+
for ind, letter in enumerate(seq):
102+
out_hyp = hypothesis.step(letter)
103+
out_sul = self.sul.step(letter)
104+
self.num_steps += 1
105+
106+
if out_hyp != out_sul:
107+
self.sul.post()
108+
return seq[: ind + 1]
109+
self.cache.add(seq)
119110

120111
return None

aalpy/oracles/WpMethodOther.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from itertools import chain, tee, product
2+
3+
from aalpy.base.Oracle import Oracle
4+
from aalpy.base.SUL import SUL
5+
from aalpy.utils.HelperFunctions import product_with_possible_empty_iterable
6+
7+
8+
def state_characterization_set(hypothesis, alphabet, state):
9+
"""
10+
Return a list of sequences that distinguish the given state from all other states in the hypothesis.
11+
Args:
12+
hypothesis: hypothesis automaton
13+
alphabet: input alphabet
14+
state: state for which to find distinguishing sequences
15+
"""
16+
result = []
17+
for i in range(len(hypothesis.states)):
18+
if hypothesis.states[i] == state:
19+
continue
20+
seq = hypothesis.find_distinguishing_seq(state, hypothesis.states[i], alphabet)
21+
if seq:
22+
result.append(tuple(seq))
23+
return result
24+
25+
26+
def i_star(alphabet, max_seq_len):
27+
"""
28+
Return an iterator that generates all possible sequences of length upto from the given alphabet.
29+
Args:
30+
alphabet: input alphabet
31+
max_seq_len: maximum length of the sequences
32+
"""
33+
return chain(*(product_with_possible_empty_iterable(alphabet, repeat=i) for i in range(max_seq_len)))
34+
35+
36+
def second_phase_test_case_generator(hyp, alphabet, difference, middle):
37+
"""
38+
Return an iterator that generates all possible sequences for the second phase of the Wp-method.
39+
Args:
40+
hyp: hypothesis automaton
41+
alphabet: input alphabet
42+
difference: set of sequences that are in the transition cover but not in the state cover
43+
middle: iterator that generates all possible sequences of length upto from the given alphabet
44+
"""
45+
state_mapping = {}
46+
for t, mid in product_with_possible_empty_iterable(difference, middle):
47+
_ = hyp.execute_sequence(hyp.initial_state, t + mid)
48+
state = hyp.current_state
49+
if state not in state_mapping:
50+
state_mapping[state] = state_characterization_set(hyp, alphabet, state)
51+
52+
for sm in state_mapping[state]:
53+
yield t + mid + sm
54+
55+
56+
class WpMethodOtherEqOracle(Oracle):
57+
"""
58+
Implements the Wp-method equivalence oracle.
59+
"""
60+
61+
def __init__(self, alphabet: list, sul: SUL, max_number_of_states=4):
62+
super().__init__(alphabet, sul)
63+
self.m = max_number_of_states
64+
self.cache = set()
65+
66+
def test_sequance(self, hypothesis, seq_under_test):
67+
self.reset_hyp_and_sul(hypothesis)
68+
69+
for ind, letter in enumerate(seq_under_test):
70+
out_hyp = hypothesis.step(letter)
71+
out_sul = self.sul.step(letter)
72+
self.num_steps += 1
73+
74+
if out_hyp != out_sul:
75+
self.sul.post()
76+
return seq_under_test[: ind + 1]
77+
self.cache.add(seq_under_test)
78+
79+
return None
80+
81+
def find_cex(self, hypothesis):
82+
if not hypothesis.characterization_set:
83+
hypothesis.characterization_set = hypothesis.compute_characterization_set()
84+
85+
transition_cover = set(
86+
state.prefix + (letter,)
87+
for state in hypothesis.states
88+
for letter in self.alphabet
89+
)
90+
91+
state_cover = set(state.prefix for state in hypothesis.states)
92+
difference = transition_cover.difference(state_cover)
93+
94+
# two views of the same iterator
95+
middle_1, middle_2 = tee(i_star(self.alphabet, self.m - hypothesis.size + 1), 2)
96+
97+
# first phase State Cover * Middle * Characterization Set
98+
state_cover = state_cover or [()]
99+
char_set = hypothesis.characterization_set or [()]
100+
101+
for sc in state_cover:
102+
for m in middle_1:
103+
for cs in char_set:
104+
test_seq = sc + m + cs
105+
if test_seq not in self.cache:
106+
counterexample = self.test_sequance(hypothesis, test_seq)
107+
if counterexample:
108+
return counterexample
109+
# second phase (Transition Cover - State Cover) * Middle * Characterization Set
110+
# of the state that the prefix leads to
111+
second_phase = second_phase_test_case_generator(hypothesis, self.alphabet, difference, middle_2)
112+
113+
for test_seq in second_phase:
114+
if test_seq not in self.cache:
115+
counterexample = self.test_sequance(hypothesis, test_seq)
116+
if counterexample:
117+
return counterexample
118+
119+
return None

0 commit comments

Comments
 (0)