Skip to content

Instantly share code, notes, and snippets.

@emuskardin
Created November 8, 2024 08:51
Show Gist options
  • Save emuskardin/28e543f942f8b720b75ef7c57aac9199 to your computer and use it in GitHub Desktop.
Save emuskardin/28e543f942f8b720b75ef7c57aac9199 to your computer and use it in GitHub Desktop.
import random
from abc import ABC, abstractmethod
from aalpy import SUL, RandomWordEqOracle, run_Lstar, run_RPNI, bisimilar, compare_automata
class AbstractVendingMachine(ABC):
@abstractmethod
def add_coin(self, coin):
pass
@abstractmethod
def push_button(self, order):
pass
@abstractmethod
def reset(self):
pass
# for all intents and purposes the behaviour of this class is hidden from us
class VendingMachine(AbstractVendingMachine):
def __init__(self, max_coins=5):
self.coin_count = 0
self.max_coins = max_coins
def add_coin(self, coin):
if self.coin_count + coin > self.max_coins:
return 'coin_returned'
self.coin_count += coin
return 'coin_added'
def push_button(self, order):
if order == 'coke':
if self.coin_count >= 2:
self.coin_count -= 2
return 'Coke'
else:
return 'No_Action'
if order == 'peanuts':
if self.coin_count >= 3:
self.coin_count -= 3
return 'Peanuts'
else:
return 'No_Action'
if order == 'water':
if self.coin_count >= 1:
self.coin_count -= 1
return 'Water'
else:
return 'No_Action'
def reset(self):
self.coin_count = 0
# for all intents and purposes the behaviour of this class is hidden from us
class VendingMachineMutant1(AbstractVendingMachine):
def __init__(self, max_coins=5):
self.coin_count = 0
self.max_coins = max_coins
def add_coin(self, coin):
if self.coin_count + coin > self.max_coins:
return 'coin_returned'
self.coin_count += coin
return 'coin_added'
def push_button(self, order):
if order == 'coke':
if self.coin_count >= 2:
self.coin_count -= 2
return 'Coke'
else:
return 'No_Action'
if order == 'peanuts':
if self.coin_count >= 3:
self.coin_count -= 3
return 'Peanuts'
else:
return 'No_Action'
if order == 'water':
if self.coin_count >= 1:
return 'Water'
else:
return 'No_Action'
def reset(self):
self.coin_count = 0
# for all intents and purposes the behaviour of this class is hidden from us
class VendingMachineMutant2(AbstractVendingMachine):
def __init__(self, max_coins=5):
self.coin_count = 0
self.max_coins = max_coins
def add_coin(self, coin):
if self.coin_count + coin > self.max_coins:
return 'coin_returned'
self.coin_count += coin
return 'coin_added'
def push_button(self, order):
if order == 'coke':
if self.coin_count >= 2:
self.coin_count -= 2
return 'Coke'
else:
return 'No_Action'
if order == 'peanuts':
if self.coin_count >= 3:
self.coin_count -= 1
return 'Peanuts'
else:
return 'No_Action'
if order == 'water':
if self.coin_count >= 1:
self.coin_count -= 1
return 'Water'
else:
return 'No_Action'
def reset(self):
self.coin_count = 0
class VendingMachineSUL(SUL):
def __init__(self, vending_machine):
super().__init__()
self.vm = vending_machine
def pre(self):
self.vm.reset()
def post(self):
pass
def step(self, letter):
if letter[0] == 'coin':
return self.vm.add_coin(letter[1])
if letter[0] == 'order':
return self.vm.push_button(letter[1])
raise RuntimeError('Invalid input')
def active_learning(vending_machine, input_alphabet, visualize_model=True):
# define the input alphabet
# wrap the vending machine in SUL interface
sul = VendingMachineSUL(vending_machine)
# define the equivalence oracle used for conformance testing during learning
eq_oracle = RandomWordEqOracle(input_alphabet, sul, num_walks=1000, min_walk_len=3, max_walk_len=12)
# eq_oracle = RandomWMethodEqOracle(input_alphabet, sul, walks_per_state=10, walk_len=8)
# learn the mealy machine capturing the input-output behaviour of the vending machine
learned_model = run_Lstar(input_alphabet, sul, eq_oracle, 'mealy')
# visualize the model
if visualize_model:
learned_model.visualize("LearnedVendingMachine")
return learned_model
def passive_learning(vending_machine, input_alphabet, visualize_model=True):
# generate some data for passive automata learning
input_alphabet = [('coin', 1), ('coin', 2), ('order', 'coke'), ('order', 'water'),
('order', 'peanuts')]
sul = VendingMachineSUL(vending_machine)
# generate 200 random sequences of SUL's input-output behaviour
data = []
for _ in range(200):
# generate random input sequance
inputs = random.choices(input_alphabet, k=random.randint(2, 10))
# get an output
output = sul.query(inputs)[-1]
# add to the dataset
data.append((inputs, output))
# print 10 data entries
for d in data[:10]:
print(d)
learned_model = run_RPNI(data, automaton_type='moore')
if learned_model:
learned_model.visualize("PassiveLearning")
return learned_model
def learning_based_testing(vm1, vm2, input_alphabet):
learned_model_1 = active_learning(vm1, input_alphabet, visualize_model=False)
learned_model_2 = active_learning(vm2, input_alphabet, visualize_model=False)
# find a shortest differance between models
shortest_counterexample = bisimilar(learned_model_1, learned_model_2, return_cex=True)
# if a shortest counterexample is found, print the differences
if shortest_counterexample:
shortest_counterexample = tuple(shortest_counterexample)
output_vm1 = learned_model_1.execute_sequence(learned_model_1.initial_state, shortest_counterexample)
output_vm2 = learned_model_2.execute_sequence(learned_model_2.initial_state, shortest_counterexample)
print('CEX:', shortest_counterexample)
print(f'Vending Machine 1: {output_vm1}')
print(f'Vending Machine 2: {output_vm2}')
# if we want more counterexamples we can use following function
additional_counterexamples = compare_automata(learned_model_1, learned_model_2, num_cex=10)
# use the same input alphabet and SUL implementation from the previous example
input_alphabet = [('coin', 1), ('coin', 2), ('order', 'coke'), ('order', 'water'),
('order', 'peanuts')]
print('Active learning example')
vending_machine = VendingMachine(max_coins=5)
active_learning(vending_machine, input_alphabet, visualize_model=True)
print('Passive learning example')
vending_machine = VendingMachine(max_coins=5)
passive_learning(vending_machine, input_alphabet, visualize_model=True)
print('Learning-based testing')
# initialize 2 different vending machines
vm1 = VendingMachine(max_coins=5)
vm2 = VendingMachineMutant2(max_coins=5)
learning_based_testing(vm1, vm2, input_alphabet)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment