Преглед изворни кода

Move function for making actions pure to simulator lib

Joeri Exelmans пре 1 година
родитељ
комит
043f44d163
2 измењених фајлова са 64 додато и 53 уклоњено
  1. 23 3
      examples/semantics/operational/simulator.py
  2. 41 50
      examples/semantics/operational/woods_pysem.py

+ 23 - 3
examples/semantics/operational/simulator.py

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

+ 41 - 50
examples/semantics/operational/woods_pysem.py

@@ -9,15 +9,15 @@ from concrete_syntax.textual_od import parser, renderer
 from concrete_syntax.common import indent
 from concrete_syntax.plantuml import renderer as plantuml
 from util import prompt
-from transformation.cloner import clone_od
 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()
+scd_mmm = bootstrap_scd(state) # Load meta-meta-model
 
-# Load meta-meta-model
-scd_mmm = bootstrap_scd(state)
+### Load (meta-)models ###
 
 # Design meta-model
 woods_mm_cs = """
@@ -46,15 +46,6 @@ woods_mm_cs = """
         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
 woods_rt_mm_cs = woods_mm_cs + """
     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(
     state,
     m_text=woods_rt_mm_cs,
@@ -145,14 +144,6 @@ woods_rt_mm = parser.parse_od(
 conf = Conformance(state, woods_rt_mm, scd_mmm)
 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
 woods_m_cs = """
     george:Man {
@@ -174,14 +165,6 @@ woods_m_cs = """
     :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
 woods_rt_initial_m_cs = woods_m_cs + """
     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(
     state,
     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)
 print("RT-M ...", render_conformance_check_result(conf.check_nominal()))
 
+print()
+
+
+### Semantics ###
 
 # Helpers
 def state_of(od, animal):
@@ -229,7 +224,8 @@ def get_time(od):
     _, clock = od.get_all_instances("Clock")[0]
     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 = []
     clock, old_time = get_time(od)
     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}.")
     return msgs
 
-# we must use the names of the objects as parameters, because when cloning, the IDs of objects change!
-def attack(od, animal_name: str, man_name: str):
+# Action: Animal attacks Man
+# 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 = []
     animal = od.get(animal_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}")
     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_actions(od):
+    def _generate_actions(od):
         # can always advance time:
-        yield ("advance time", advance_time)
+        yield ("advance time", action_advance_time)
 
         # who can attack whom?
         for _, afraid_link in od.get_all_instances("afraidOf"):
@@ -289,21 +286,15 @@ def get_all_actions(od):
             man_state = state_of(od, man)
             animal_state = state_of(od, animal)
             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))
-            
-    # Copy model before modifying it
-    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))
+            yield (descr, functools.partial(action_attack, animal_name=animal_name, man_name=man_name))
+
+    return make_actions_pure(_generate_actions(od), od)
 
+# Only get those actions that bring us to a conforming state
 def get_valid_actions(od):
     return filter_valid_actions(get_all_actions(od))
 
+# Render our run-time state to a string
 def render_woods(od):
     txt = ""
     _, 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"
     return txt
 
-
+# When should simulation stop?
 def termination_condition(od):
     _, time = get_time(od)
     if time >= 10:
@@ -351,18 +342,18 @@ def termination_condition(od):
     if len(who_is_dead) >= 2:
         return f"{' and '.join(who_is_dead)} are dead"
 
+
 sim = Simulator(
     action_generator=get_valid_actions,
-    # action_generator=get_actions,
+    # action_generator=get_all_actions,
     decision_maker=RandomDecisionMaker(seed=0),
     # decision_maker=InteractiveDecisionMaker(),
     termination_condition=termination_condition,
-    check_conformance=True,
+    check_conformance=False,
     verbose=True,
     renderer=render_woods,
 )
 
 od = ODAPI(state, woods_rt_m, woods_rt_mm)
 
-print()
 sim.run(od)