Move function for making actions pure to simulator lib

This commit is contained in:
Joeri Exelmans 2024-10-29 11:36:42 +01:00
parent c738e8bcd1
commit 043f44d163
2 changed files with 63 additions and 52 deletions

View file

@ -5,9 +5,10 @@ import functools
import sys import sys
from framework.conformance import Conformance, render_conformance_check_result from framework.conformance import Conformance, render_conformance_check_result
from concrete_syntax.common import indent from concrete_syntax.common import indent
from concrete_syntax.textual_od.renderer import render_od from concrete_syntax.textual_od.renderer import render_od
from transformation.cloner import clone_od
from api.od import ODAPI
class DecisionMaker: class DecisionMaker:
@abc.abstractmethod @abc.abstractmethod
@ -37,6 +38,7 @@ class Simulator:
# Run simulation until termination condition satisfied # Run simulation until termination condition satisfied
def run(self, od): def run(self, od):
self.__print("Start simulation") self.__print("Start simulation")
self.__print(f"Decision maker: {self.decision_maker}")
step_counter = 0 step_counter = 0
while True: while True:
self.__print("--------------") self.__print("--------------")
@ -68,12 +70,23 @@ class Simulator:
self.__print(f"Executed {step_counter} steps.") self.__print(f"Executed {step_counter} steps.")
return od return od
def make_actions_pure(actions, od):
# Copy model before modifying it
def exec_pure(action, od):
cloned_rt_m = clone_od(od.state, od.m, od.mm)
new_od = ODAPI(od.state, cloned_rt_m, od.mm)
msgs = action(new_od)
return (new_od, msgs)
def filter_valid_actions(actions): for descr, action in actions:
yield (descr, functools.partial(exec_pure, action, od))
def filter_valid_actions(pure_actions):
result = {} result = {}
def make_tuple(new_od, msgs): def make_tuple(new_od, msgs):
return (new_od, msgs) return (new_od, msgs)
for name, callback in actions: for name, callback in pure_actions:
print(f"attempt '{name}' ...", end='\r') print(f"attempt '{name}' ...", end='\r')
(new_od, msgs) = callback() (new_od, msgs) = callback()
conf = Conformance(new_od.state, new_od.m, new_od.mm) conf = Conformance(new_od.state, new_od.m, new_od.mm)
@ -87,8 +100,12 @@ def filter_valid_actions(actions):
class RandomDecisionMaker(DecisionMaker): class RandomDecisionMaker(DecisionMaker):
def __init__(self, seed=0, verbose=True): def __init__(self, seed=0, verbose=True):
self.seed = seed
self.r = random.Random(seed) self.r = random.Random(seed)
def __str__(self):
return f"RandomDecisionMaker(seed={self.seed})"
def __call__(self, actions): def __call__(self, actions):
arr = [action for descr, action in actions] arr = [action for descr, action in actions]
i = math.floor(self.r.random()*len(arr)) i = math.floor(self.r.random()*len(arr))
@ -98,6 +115,9 @@ class InteractiveDecisionMaker(DecisionMaker):
def __init__(self, msg="Select action:"): def __init__(self, msg="Select action:"):
self.msg = msg self.msg = msg
def __str__(self):
return f"InteractiveDecisionMaker()"
def __call__(self, actions): def __call__(self, actions):
arr = [] arr = []
for i, (key, result) in enumerate(actions): for i, (key, result) in enumerate(actions):

View file

@ -9,15 +9,15 @@ from concrete_syntax.textual_od import parser, renderer
from concrete_syntax.common import indent from concrete_syntax.common import indent
from concrete_syntax.plantuml import renderer as plantuml from concrete_syntax.plantuml import renderer as plantuml
from util import prompt from util import prompt
from transformation.cloner import clone_od
from api.od import ODAPI from api.od import ODAPI
from examples.semantics.operational.simulator import Simulator, RandomDecisionMaker, InteractiveDecisionMaker, filter_valid_actions from examples.semantics.operational.simulator import Simulator, RandomDecisionMaker, InteractiveDecisionMaker, make_actions_pure, filter_valid_actions
state = DevState() state = DevState()
scd_mmm = bootstrap_scd(state) # Load meta-meta-model
# Load meta-meta-model ### Load (meta-)models ###
scd_mmm = bootstrap_scd(state)
# Design meta-model # Design meta-model
woods_mm_cs = """ woods_mm_cs = """
@ -46,15 +46,6 @@ woods_mm_cs = """
target_lower_cardinality = 1; target_lower_cardinality = 1;
} }
""" """
woods_mm = parser.parse_od(
state,
m_text=woods_mm_cs,
mm=scd_mmm)
conf = Conformance(state, woods_mm, scd_mmm)
print("MM ...", render_conformance_check_result(conf.check_nominal()))
# Runtime meta-model # Runtime meta-model
woods_rt_mm_cs = woods_mm_cs + """ woods_rt_mm_cs = woods_mm_cs + """
AnimalState:Class { AnimalState:Class {
@ -137,6 +128,14 @@ woods_rt_mm_cs = woods_mm_cs + """
} }
""" """
woods_mm = parser.parse_od(
state,
m_text=woods_mm_cs,
mm=scd_mmm)
conf = Conformance(state, woods_mm, scd_mmm)
print("MM ...", render_conformance_check_result(conf.check_nominal()))
woods_rt_mm = parser.parse_od( woods_rt_mm = parser.parse_od(
state, state,
m_text=woods_rt_mm_cs, m_text=woods_rt_mm_cs,
@ -145,14 +144,6 @@ woods_rt_mm = parser.parse_od(
conf = Conformance(state, woods_rt_mm, scd_mmm) conf = Conformance(state, woods_rt_mm, scd_mmm)
print("RT-MM ...", render_conformance_check_result(conf.check_nominal())) print("RT-MM ...", render_conformance_check_result(conf.check_nominal()))
# print("--------------")
# print(indent(
# renderer.render_od(state,
# m_id=woods_rt_mm,
# mm_id=scd_mmm),
# 4))
# print("--------------")
# Our design model - the part that doesn't change # Our design model - the part that doesn't change
woods_m_cs = """ woods_m_cs = """
george:Man { george:Man {
@ -174,14 +165,6 @@ woods_m_cs = """
:afraidOf (george -> bill) :afraidOf (george -> bill)
""" """
woods_m = parser.parse_od(
state,
m_text=woods_m_cs,
mm=woods_mm)
conf = Conformance(state, woods_m, woods_mm)
print("M ...", render_conformance_check_result(conf.check_nominal()))
# Our runtime model - the part that changes with every execution step # Our runtime model - the part that changes with every execution step
woods_rt_initial_m_cs = woods_m_cs + """ woods_rt_initial_m_cs = woods_m_cs + """
georgeState:ManState { georgeState:ManState {
@ -211,6 +194,14 @@ woods_rt_initial_m_cs = woods_m_cs + """
} }
""" """
woods_m = parser.parse_od(
state,
m_text=woods_m_cs,
mm=woods_mm)
conf = Conformance(state, woods_m, woods_mm)
print("M ...", render_conformance_check_result(conf.check_nominal()))
woods_rt_m = parser.parse_od( woods_rt_m = parser.parse_od(
state, state,
m_text=woods_rt_initial_m_cs, m_text=woods_rt_initial_m_cs,
@ -219,6 +210,10 @@ woods_rt_m = parser.parse_od(
conf = Conformance(state, woods_rt_m, woods_rt_mm) conf = Conformance(state, woods_rt_m, woods_rt_mm)
print("RT-M ...", render_conformance_check_result(conf.check_nominal())) print("RT-M ...", render_conformance_check_result(conf.check_nominal()))
print()
### Semantics ###
# Helpers # Helpers
def state_of(od, animal): def state_of(od, animal):
@ -229,7 +224,8 @@ def get_time(od):
_, clock = od.get_all_instances("Clock")[0] _, clock = od.get_all_instances("Clock")[0]
return clock, od.get_slot_value(clock, "time") return clock, od.get_slot_value(clock, "time")
def advance_time(od): # Action: Time advances, whoever is being attacked dies, bears become hungrier
def action_advance_time(od):
msgs = [] msgs = []
clock, old_time = get_time(od) clock, old_time = get_time(od)
new_time = old_time + 1 new_time = old_time + 1
@ -259,8 +255,9 @@ def advance_time(od):
msgs.append(f"Bear {bear_name}'s hunger level is now {new_hunger}.") msgs.append(f"Bear {bear_name}'s hunger level is now {new_hunger}.")
return msgs return msgs
# we must use the names of the objects as parameters, because when cloning, the IDs of objects change! # Action: Animal attacks Man
def attack(od, animal_name: str, man_name: str): # Note: We must use the names of the objects as parameters, because when cloning, the IDs of objects change!
def action_attack(od, animal_name: str, man_name: str):
msgs = [] msgs = []
animal = od.get(animal_name) animal = od.get(animal_name)
man = od.get(man_name) man = od.get(man_name)
@ -274,11 +271,11 @@ def attack(od, animal_name: str, man_name: str):
msgs.append(f"{animal_name} is now attacking {man_name}") msgs.append(f"{animal_name} is now attacking {man_name}")
return msgs return msgs
# Get all actions that can be performed (including those that bring us to a non-conforming state)
def get_all_actions(od): def get_all_actions(od):
def _get_actions(od): def _generate_actions(od):
# can always advance time: # can always advance time:
yield ("advance time", advance_time) yield ("advance time", action_advance_time)
# who can attack whom? # who can attack whom?
for _, afraid_link in od.get_all_instances("afraidOf"): for _, afraid_link in od.get_all_instances("afraidOf"):
@ -289,21 +286,15 @@ def get_all_actions(od):
man_state = state_of(od, man) man_state = state_of(od, man)
animal_state = state_of(od, animal) animal_state = state_of(od, animal)
descr = f"{animal_name} ({od.get_type_name(animal)}) attacks {man_name} ({od.get_type_name(man)})" descr = f"{animal_name} ({od.get_type_name(animal)}) attacks {man_name} ({od.get_type_name(man)})"
yield (descr, functools.partial(attack, animal_name=animal_name, man_name=man_name)) yield (descr, functools.partial(action_attack, animal_name=animal_name, man_name=man_name))
# Copy model before modifying it return make_actions_pure(_generate_actions(od), od)
def exec_pure(action, od):
cloned_rt_m = clone_od(state, od.m, od.mm)
new_od = ODAPI(state, cloned_rt_m, od.mm)
msgs = action(new_od)
return (new_od, msgs)
for descr, action in _get_actions(od):
yield (descr, functools.partial(exec_pure, action, od))
# Only get those actions that bring us to a conforming state
def get_valid_actions(od): def get_valid_actions(od):
return filter_valid_actions(get_all_actions(od)) return filter_valid_actions(get_all_actions(od))
# Render our run-time state to a string
def render_woods(od): def render_woods(od):
txt = "" txt = ""
_, time = get_time(od) _, time = get_time(od)
@ -336,7 +327,7 @@ def render_woods(od):
txt += f" 👨 {od.get_name(man)} ({render_dead(man_state)}) {render_attacking(man_state)}{being_attacked}\n" txt += f" 👨 {od.get_name(man)} ({render_dead(man_state)}) {render_attacking(man_state)}{being_attacked}\n"
return txt return txt
# When should simulation stop?
def termination_condition(od): def termination_condition(od):
_, time = get_time(od) _, time = get_time(od)
if time >= 10: if time >= 10:
@ -351,18 +342,18 @@ def termination_condition(od):
if len(who_is_dead) >= 2: if len(who_is_dead) >= 2:
return f"{' and '.join(who_is_dead)} are dead" return f"{' and '.join(who_is_dead)} are dead"
sim = Simulator( sim = Simulator(
action_generator=get_valid_actions, action_generator=get_valid_actions,
# action_generator=get_actions, # action_generator=get_all_actions,
decision_maker=RandomDecisionMaker(seed=0), decision_maker=RandomDecisionMaker(seed=0),
# decision_maker=InteractiveDecisionMaker(), # decision_maker=InteractiveDecisionMaker(),
termination_condition=termination_condition, termination_condition=termination_condition,
check_conformance=True, check_conformance=False,
verbose=True, verbose=True,
renderer=render_woods, renderer=render_woods,
) )
od = ODAPI(state, woods_rt_m, woods_rt_mm) od = ODAPI(state, woods_rt_m, woods_rt_mm)
print()
sim.run(od) sim.run(od)