add examples
This commit is contained in:
parent
8504ba52f6
commit
42757ddc4f
35 changed files with 1104 additions and 609 deletions
|
|
@ -98,7 +98,14 @@ port_rt_mm_cs = port_mm_cs + """
|
|||
|
||||
BerthState:Class {
|
||||
# status == empty <=> numShips == 0
|
||||
constraint = `(get_slot_value(this, "numShips") == 0) == (get_slot_value(this, "status") == "empty")`;
|
||||
constraint = ```
|
||||
errors = []
|
||||
numShips = get_slot_value(this, "numShips")
|
||||
status = get_slot_value(this, "status")
|
||||
if (numShips == 0) != (status == "empty"):
|
||||
errors.append(f"Inconsistent: numShips = {numShips}, but status = {status}")
|
||||
errors
|
||||
```;
|
||||
}
|
||||
:Inheritance (BerthState -> PlaceState)
|
||||
|
||||
|
|
|
|||
62
examples/semantics/operational/port/rulebased_runner.py
Normal file
62
examples/semantics/operational/port/rulebased_runner.py
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import urllib.parse
|
||||
|
||||
from state.devstate import DevState
|
||||
from bootstrap.scd import bootstrap_scd
|
||||
from framework.conformance import Conformance, render_conformance_check_result
|
||||
from concrete_syntax.textual_od import parser
|
||||
from concrete_syntax.plantuml.renderer import render_object_diagram, render_class_diagram
|
||||
from api.od import ODAPI
|
||||
|
||||
from transformation.ramify import ramify
|
||||
|
||||
from examples.semantics.operational.simulator import Simulator, RandomDecisionMaker, InteractiveDecisionMaker
|
||||
from examples.semantics.operational.port import models
|
||||
from examples.semantics.operational.port.helpers import design_to_state, state_to_design, get_time
|
||||
from examples.semantics.operational.port.renderer import render_port_textual, render_port_graphviz
|
||||
|
||||
from examples.semantics.operational.port import rulebased_sem
|
||||
|
||||
state = DevState()
|
||||
scd_mmm = bootstrap_scd(state) # Load meta-meta-model
|
||||
|
||||
### Load (meta-)models ###
|
||||
|
||||
def parse_and_check(m_cs: str, mm, descr: str):
|
||||
m = parser.parse_od(
|
||||
state,
|
||||
m_text=m_cs,
|
||||
mm=mm)
|
||||
conf = Conformance(state, m, mm)
|
||||
print(descr, "...", render_conformance_check_result(conf.check_nominal()))
|
||||
return m
|
||||
|
||||
port_mm = parse_and_check(models.port_mm_cs, scd_mmm, "MM")
|
||||
port_m = parse_and_check(models.port_m_cs, port_mm, "M")
|
||||
port_rt_mm = parse_and_check(models.port_rt_mm_cs, scd_mmm, "RT-MM")
|
||||
port_rt_m = parse_and_check(models.port_rt_m_cs, port_rt_mm, "RT-M")
|
||||
|
||||
print()
|
||||
|
||||
# print(render_class_diagram(state, port_rt_mm))
|
||||
|
||||
### Simulate ###
|
||||
|
||||
port_rt_mm_ramified = ramify(state, port_rt_mm)
|
||||
|
||||
rulebased_action_generator = rulebased_sem.get_action_generator(state, port_rt_mm, port_rt_mm_ramified)
|
||||
termination_condition = rulebased_sem.TerminationCondition(state, port_rt_mm_ramified)
|
||||
|
||||
sim = Simulator(
|
||||
action_generator=rulebased_action_generator,
|
||||
# decision_maker=RandomDecisionMaker(seed=2),
|
||||
decision_maker=InteractiveDecisionMaker(),
|
||||
termination_condition=termination_condition,
|
||||
check_conformance=True,
|
||||
verbose=True,
|
||||
renderer=render_port_textual,
|
||||
# renderer=render_port_graphviz,
|
||||
)
|
||||
|
||||
od = ODAPI(state, port_rt_m, port_rt_mm)
|
||||
|
||||
sim.run(od)
|
||||
67
examples/semantics/operational/port/rulebased_sem.py
Normal file
67
examples/semantics/operational/port/rulebased_sem.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
### Operational Semantics - defined by rule-based model transformation ###
|
||||
|
||||
from concrete_syntax.textual_od.parser import parse_od
|
||||
from transformation.rule import Rule, RuleMatcherRewriter, PriorityActionGenerator
|
||||
from transformation.matcher.mvs_adapter import match_od
|
||||
from util import loader
|
||||
|
||||
import os
|
||||
THIS_DIR = os.path.dirname(__file__)
|
||||
|
||||
# kind: lhs, rhs, nac
|
||||
get_filename = lambda rule_name, kind: f"{THIS_DIR}/rules/r_{rule_name}_{kind}.od"
|
||||
|
||||
|
||||
def get_action_generator(state, rt_mm, rt_mm_ramified):
|
||||
matcher_rewriter = RuleMatcherRewriter(state, rt_mm, rt_mm_ramified)
|
||||
|
||||
#############################################################################
|
||||
# TO IMPLEMENT: Full semantics as a set of rule-based model transformations #
|
||||
|
||||
rules0_dict = loader.load_rules(state, get_filename, rt_mm_ramified,
|
||||
["ship_sinks"] # <- list of rule_name of equal priority
|
||||
)
|
||||
rules1_dict = loader.load_rules(state, get_filename, rt_mm_ramified,
|
||||
["ship_appears_in_berth"]
|
||||
)
|
||||
# rules2_dict = ...
|
||||
|
||||
generator = PriorityActionGenerator(matcher_rewriter, [
|
||||
rules0_dict, # highest priority
|
||||
rules1_dict, # lower priority
|
||||
# rules2_dict, # lowest priority
|
||||
])
|
||||
|
||||
# TO IMPLEMENT: Full semantics as a set of rule-based model transformations #
|
||||
#############################################################################
|
||||
|
||||
return generator
|
||||
|
||||
|
||||
|
||||
|
||||
# The termination condition can also be specified as a pattern:
|
||||
class TerminationCondition:
|
||||
def __init__(self, state, rt_mm_ramified):
|
||||
self.state = state
|
||||
self.rt_mm_ramified = rt_mm_ramified
|
||||
|
||||
# TO IMPLEMENT: terminate simulation when the place 'served' contains 2 ships.
|
||||
|
||||
########################################
|
||||
# You should only edit the pattern below
|
||||
pattern_cs = """
|
||||
# Placeholder to make the termination condition never hold:
|
||||
:GlobalCondition {
|
||||
condition = `False`;
|
||||
}
|
||||
"""
|
||||
# You should only edit the pattern above
|
||||
########################################
|
||||
|
||||
self.pattern = parse_od(state, pattern_cs, rt_mm_ramified)
|
||||
|
||||
def __call__(self, od):
|
||||
for match in match_od(self.state, od.m, od.mm, self.pattern, self.rt_mm_ramified):
|
||||
# stop after the first match (no need to look for more matches):
|
||||
return "There are 2 ships served." # Termination condition statisfied
|
||||
13
examples/semantics/operational/port/rules/README.txt
Normal file
13
examples/semantics/operational/port/rules/README.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
The names of the files in this directory are important.
|
||||
|
||||
A rule must always be named:
|
||||
r_<rule_name>_<lhs|rhs|nac>.od
|
||||
|
||||
It is allowed to have more than one NAC. In this case, the NACs must be named:
|
||||
r_<rule_name>_nac.od
|
||||
r_<rule_name>_nac2.od
|
||||
r_<rule_name>_nac3.od
|
||||
...
|
||||
|
||||
|
||||
For the assignment, you can delete the existing rules (they are nonsense) and start fresh.
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
berthState:RAM_BerthState {
|
||||
RAM_numShips = `get_value(this) == 0`;
|
||||
RAM_status = `get_value(this) == "empty"`;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
berthState:RAM_BerthState {
|
||||
RAM_numShips = `1`;
|
||||
RAM_status = `"served"`;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# Find any place that has at least one ship:
|
||||
|
||||
placeState:RAM_PlaceState {
|
||||
RAM_numShips = `get_value(this) > 0`;
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
placeState:RAM_PlaceState {
|
||||
# Decrement number of ships:
|
||||
RAM_numShips = `get_value(this) - 1`;
|
||||
}
|
||||
|
|
@ -10,12 +10,10 @@ from concrete_syntax.textual_od.renderer import render_od
|
|||
from transformation.cloner import clone_od
|
||||
from api.od import ODAPI
|
||||
|
||||
class DecisionMaker:
|
||||
@abc.abstractmethod
|
||||
def __call__(self, actions):
|
||||
pass
|
||||
from util.simulator import MinimalSimulator, DecisionMaker, RandomDecisionMaker, InteractiveDecisionMaker
|
||||
|
||||
class Simulator:
|
||||
|
||||
class Simulator(MinimalSimulator):
|
||||
def __init__(self,
|
||||
action_generator,
|
||||
decision_maker: DecisionMaker,
|
||||
|
|
@ -24,51 +22,26 @@ class Simulator:
|
|||
verbose=True,
|
||||
renderer=lambda od: render_od(od.state, od.m, od.mm),
|
||||
):
|
||||
self.action_generator = action_generator
|
||||
self.decision_maker = decision_maker
|
||||
self.termination_condition = termination_condition
|
||||
super().__init__(
|
||||
action_generator=action_generator,
|
||||
decision_maker=decision_maker,
|
||||
termination_condition=lambda od: self.check_render_termination_condition(od),
|
||||
verbose=verbose,
|
||||
)
|
||||
self.check_conformance = check_conformance
|
||||
self.verbose = verbose
|
||||
self.actual_termination_condition = termination_condition
|
||||
self.renderer = renderer
|
||||
|
||||
def __print(self, *args):
|
||||
if self.verbose:
|
||||
print(*args)
|
||||
|
||||
# Run simulation until termination condition satisfied
|
||||
def run(self, od: ODAPI):
|
||||
self.__print("Start simulation")
|
||||
self.__print(f"Decision maker: {self.decision_maker}")
|
||||
step_counter = 0
|
||||
while True:
|
||||
self.__print("--------------")
|
||||
self.__print(indent(self.renderer(od), 4))
|
||||
self.__print("--------------")
|
||||
|
||||
termination_reason = self.termination_condition(od)
|
||||
if termination_reason != None:
|
||||
self.__print(f"Termination condition satisfied.\nReason: {termination_reason}.")
|
||||
break
|
||||
|
||||
actions = self.action_generator(od)
|
||||
|
||||
chosen_action = self.decision_maker(actions)
|
||||
|
||||
if chosen_action == None:
|
||||
self.__print(f"No enabled actions.")
|
||||
break
|
||||
|
||||
(od, msgs) = chosen_action()
|
||||
self.__print(indent('\n'.join(f"▸ {msg}" for msg in msgs), 2))
|
||||
|
||||
step_counter += 1
|
||||
|
||||
if self.check_conformance:
|
||||
self.__print()
|
||||
conf = Conformance(od.state, od.m, od.mm)
|
||||
self.__print(render_conformance_check_result(conf.check_nominal()))
|
||||
self.__print(f"Executed {step_counter} steps.")
|
||||
return od
|
||||
def check_render_termination_condition(self, od):
|
||||
# A termination condition checker that also renders the model, and performs conformance check
|
||||
self._print("--------------")
|
||||
self._print(indent(self.renderer(od), 2))
|
||||
self._print("--------------")
|
||||
if self.check_conformance:
|
||||
conf = Conformance(od.state, od.m, od.mm)
|
||||
self._print(render_conformance_check_result(conf.check_nominal()))
|
||||
self._print()
|
||||
return self.actual_termination_condition(od)
|
||||
|
||||
def make_actions_pure(actions, od):
|
||||
# Copy model before modifying it
|
||||
|
|
@ -81,65 +54,17 @@ def make_actions_pure(actions, od):
|
|||
for descr, action in actions:
|
||||
yield (descr, functools.partial(exec_pure, action, od))
|
||||
|
||||
|
||||
def filter_valid_actions(pure_actions):
|
||||
result = {}
|
||||
def make_tuple(new_od, msgs):
|
||||
return (new_od, msgs)
|
||||
for name, callback in pure_actions:
|
||||
print(f"attempt '{name}' ...", end='\r')
|
||||
# print(f"attempt '{name}' ...", end='\r')
|
||||
(new_od, msgs) = callback()
|
||||
conf = Conformance(new_od.state, new_od.m, new_od.mm)
|
||||
errors = conf.check_nominal()
|
||||
# erase current line:
|
||||
print(" ", end='\r')
|
||||
# print(" ", end='\r')
|
||||
if len(errors) == 0:
|
||||
# updated RT-M is conform, we have a valid action:
|
||||
yield (name, functools.partial(make_tuple, new_od, msgs))
|
||||
|
||||
|
||||
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))
|
||||
return arr[i]
|
||||
|
||||
class InteractiveDecisionMaker(DecisionMaker):
|
||||
# auto_proceed: whether to prompt if there is only one enabled action
|
||||
def __init__(self, msg="Select action:", auto_proceed=False):
|
||||
self.msg = msg
|
||||
self.auto_proceed = auto_proceed
|
||||
|
||||
def __str__(self):
|
||||
return f"InteractiveDecisionMaker()"
|
||||
|
||||
def __call__(self, actions):
|
||||
arr = []
|
||||
for i, (key, result) in enumerate(actions):
|
||||
print(f" {i}. {key}")
|
||||
arr.append(result)
|
||||
if len(arr) == 0:
|
||||
return
|
||||
if len(arr) == 1 and self.auto_proceed:
|
||||
return arr[0]
|
||||
|
||||
def __choose():
|
||||
sys.stdout.write(f"{self.msg} ")
|
||||
try:
|
||||
raw = input()
|
||||
choice = int(raw) # may raise ValueError
|
||||
if choice >= 0 and choice < len(arr):
|
||||
return arr[choice]
|
||||
except ValueError:
|
||||
pass
|
||||
print("Invalid option")
|
||||
return __choose()
|
||||
|
||||
return __choose()
|
||||
|
|
|
|||
|
|
@ -1,337 +0,0 @@
|
|||
import functools
|
||||
|
||||
from state.devstate import DevState
|
||||
from bootstrap.scd import bootstrap_scd
|
||||
from framework.conformance import Conformance, render_conformance_check_result
|
||||
from concrete_syntax.textual_od import parser, renderer
|
||||
from concrete_syntax.plantuml import renderer as plantuml
|
||||
from api.od import ODAPI
|
||||
|
||||
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-)models ###
|
||||
|
||||
# Design meta-model
|
||||
woods_mm_cs = """
|
||||
Animal:Class {
|
||||
abstract = True;
|
||||
}
|
||||
|
||||
Bear:Class
|
||||
:Inheritance (Bear -> Animal)
|
||||
|
||||
Man:Class {
|
||||
lower_cardinality = 1;
|
||||
upper_cardinality = 2;
|
||||
constraint = `get_value(get_slot(this, "weight")) > 20`;
|
||||
}
|
||||
:Inheritance (Man -> Animal)
|
||||
|
||||
|
||||
Man_weight:AttributeLink (Man -> Integer) {
|
||||
name = "weight";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
afraidOf:Association (Man -> Animal) {
|
||||
source_upper_cardinality = 6;
|
||||
target_lower_cardinality = 1;
|
||||
}
|
||||
"""
|
||||
# Runtime meta-model
|
||||
woods_rt_mm_cs = woods_mm_cs + """
|
||||
AnimalState:Class {
|
||||
abstract = True;
|
||||
}
|
||||
AnimalState_dead:AttributeLink (AnimalState -> Boolean) {
|
||||
name = "dead";
|
||||
optional = False;
|
||||
}
|
||||
of:Association (AnimalState -> Animal) {
|
||||
source_lower_cardinality = 1;
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
BearState:Class {
|
||||
constraint = `get_type_name(get_target(get_outgoing(this, "of")[0])) == "Bear"`;
|
||||
}
|
||||
:Inheritance (BearState -> AnimalState)
|
||||
BearState_hunger:AttributeLink (BearState -> Integer) {
|
||||
name = "hunger";
|
||||
optional = False;
|
||||
constraint = ```
|
||||
val = get_value(get_target(this))
|
||||
val >= 0 and val <= 100
|
||||
```;
|
||||
}
|
||||
|
||||
ManState:Class {
|
||||
constraint = `get_type_name(get_target(get_outgoing(this, "of")[0])) == "Man"`;
|
||||
}
|
||||
:Inheritance (ManState -> AnimalState)
|
||||
|
||||
attacking:Association (AnimalState -> ManState) {
|
||||
# Animal can only attack one Man at a time
|
||||
target_upper_cardinality = 1;
|
||||
|
||||
# Man can only be attacked by one Animal at a time
|
||||
source_upper_cardinality = 1;
|
||||
|
||||
constraint = ```
|
||||
attacker = get_source(this)
|
||||
if get_type_name(attacker) == "BearState":
|
||||
# only BearState has 'hunger' attribute
|
||||
hunger = get_value(get_slot(attacker, "hunger"))
|
||||
else:
|
||||
hunger = 100 # Man can always attack
|
||||
attacker_dead = get_value(get_slot(attacker, "dead"))
|
||||
attacked_state = get_target(this)
|
||||
attacked_dead = get_value(get_slot(attacked_state, "dead"))
|
||||
(
|
||||
hunger >= 50
|
||||
and not attacker_dead # cannot attack while dead
|
||||
and not attacked_dead # cannot attack whoever is dead
|
||||
)
|
||||
```;
|
||||
}
|
||||
|
||||
attacking_starttime:AttributeLink (attacking -> Integer) {
|
||||
name = "starttime";
|
||||
optional = False;
|
||||
constraint = ```
|
||||
val = get_value(get_target(this))
|
||||
_, clock = get_all_instances("Clock")[0]
|
||||
current_time = get_slot_value(clock, "time")
|
||||
val >= 0 and val <= current_time
|
||||
```;
|
||||
}
|
||||
|
||||
# Just a clock singleton for keeping the time
|
||||
Clock:Class {
|
||||
lower_cardinality = 1;
|
||||
upper_cardinality = 1;
|
||||
}
|
||||
Clock_time:AttributeLink (Clock -> Integer) {
|
||||
name = "time";
|
||||
optional = False;
|
||||
constraint = `get_value(get_target(this)) >= 0`;
|
||||
}
|
||||
"""
|
||||
|
||||
# Our design model - the part that doesn't change
|
||||
woods_m_cs = """
|
||||
george:Man {
|
||||
weight = 80;
|
||||
}
|
||||
bill:Man {
|
||||
weight = 70;
|
||||
}
|
||||
|
||||
teddy:Bear
|
||||
mrBrown:Bear
|
||||
|
||||
# george is afraid of both bears
|
||||
:afraidOf (george -> teddy)
|
||||
:afraidOf (george -> mrBrown)
|
||||
|
||||
# the men are afraid of each other
|
||||
:afraidOf (bill -> george)
|
||||
:afraidOf (george -> bill)
|
||||
"""
|
||||
|
||||
# Our runtime model - the part that changes with every execution step
|
||||
woods_rt_initial_m_cs = woods_m_cs + """
|
||||
georgeState:ManState {
|
||||
dead = False;
|
||||
}
|
||||
:of (georgeState -> george)
|
||||
|
||||
billState:ManState {
|
||||
dead = False;
|
||||
}
|
||||
:of (billState -> bill)
|
||||
|
||||
teddyState:BearState {
|
||||
dead = False;
|
||||
hunger = 40;
|
||||
}
|
||||
:of (teddyState -> teddy)
|
||||
|
||||
mrBrownState:BearState {
|
||||
dead = False;
|
||||
hunger = 80;
|
||||
}
|
||||
:of (mrBrownState -> mrBrown)
|
||||
|
||||
clock:Clock {
|
||||
time = 0;
|
||||
}
|
||||
"""
|
||||
|
||||
def parse_and_check(m_cs: str, mm, descr: str):
|
||||
m = parser.parse_od(
|
||||
state,
|
||||
m_text=m_cs,
|
||||
mm=mm)
|
||||
conf = Conformance(state, m, mm)
|
||||
print(descr, "...", render_conformance_check_result(conf.check_nominal()))
|
||||
return m
|
||||
|
||||
woods_mm = parse_and_check(woods_mm_cs, scd_mmm, "MM")
|
||||
woods_rt_mm = parse_and_check(woods_rt_mm_cs, scd_mmm, "RT-MM")
|
||||
woods_m = parse_and_check(woods_m_cs, woods_mm, "M")
|
||||
woods_rt_m = parse_and_check(woods_rt_initial_m_cs, woods_rt_mm, "RT-M")
|
||||
|
||||
print()
|
||||
|
||||
|
||||
### Semantics ###
|
||||
|
||||
# Helpers
|
||||
def state_of(od, animal):
|
||||
return od.get_source(od.get_incoming(animal, "of")[0])
|
||||
def animal_of(od, state):
|
||||
return od.get_target(od.get_outgoing(state, "of")[0])
|
||||
def get_time(od):
|
||||
_, clock = od.get_all_instances("Clock")[0]
|
||||
return clock, od.get_slot_value(clock, "time")
|
||||
|
||||
# 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
|
||||
od.set_slot_value(clock, "time", new_time)
|
||||
|
||||
for _, attacking_link in od.get_all_instances("attacking"):
|
||||
man_state = od.get_target(attacking_link)
|
||||
animal_state = od.get_source(attacking_link)
|
||||
if od.get_type_name(animal_state) == "BearState":
|
||||
od.set_slot_value(animal_state, "hunger", max(od.get_slot_value(animal_state, "hunger") - 50, 0))
|
||||
od.set_slot_value(man_state, "dead", True)
|
||||
od.delete(attacking_link)
|
||||
msgs.append(f"{od.get_name(animal_of(od, animal_state))} kills {od.get_name(animal_of(od, man_state))}.")
|
||||
|
||||
for _, bear_state in od.get_all_instances("BearState"):
|
||||
if od.get_slot_value(bear_state, "dead"):
|
||||
continue # bear already dead
|
||||
old_hunger = od.get_slot_value(bear_state, "hunger")
|
||||
new_hunger = min(old_hunger + 10, 100)
|
||||
od.set_slot_value(bear_state, "hunger", new_hunger)
|
||||
bear = od.get_target(od.get_outgoing(bear_state, "of")[0])
|
||||
bear_name = od.get_name(bear)
|
||||
if new_hunger == 100:
|
||||
od.set_slot_value(bear_state, "dead", True)
|
||||
msgs.append(f"Bear {bear_name} dies of hunger.")
|
||||
else:
|
||||
msgs.append(f"Bear {bear_name}'s hunger level is now {new_hunger}.")
|
||||
return msgs
|
||||
|
||||
# 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)
|
||||
animal_state = state_of(od, animal)
|
||||
man_state = state_of(od, man)
|
||||
attack_link = od.create_link(None, # auto-generate link name
|
||||
"attacking", animal_state, man_state)
|
||||
_, clock = od.get_all_instances("Clock")[0]
|
||||
current_time = od.get_slot_value(clock, "time")
|
||||
od.set_slot_value(attack_link, "starttime", current_time)
|
||||
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 _generate_actions(od):
|
||||
# can always advance time:
|
||||
yield ("advance time", action_advance_time)
|
||||
|
||||
# if A is afraid of B, then B can attack A:
|
||||
for _, afraid_link in od.get_all_instances("afraidOf"):
|
||||
man = od.get_source(afraid_link)
|
||||
animal = od.get_target(afraid_link)
|
||||
animal_name = od.get_name(animal)
|
||||
man_name = od.get_name(man)
|
||||
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(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)
|
||||
txt += f"T = {time}.\n"
|
||||
txt += "Bears:\n"
|
||||
def render_attacking(animal_state):
|
||||
attacking = od.get_outgoing(animal_state, "attacking")
|
||||
if len(attacking) == 1:
|
||||
whom_state = od.get_target(attacking[0])
|
||||
whom_name = od.get_name(animal_of(od, whom_state))
|
||||
return f" attacking {whom_name}"
|
||||
else:
|
||||
return ""
|
||||
def render_dead(animal_state):
|
||||
return 'dead' if od.get_slot_value(animal_state, 'dead') else 'alive'
|
||||
for _, bear_state in od.get_all_instances("BearState"):
|
||||
bear = animal_of(od, bear_state)
|
||||
hunger = od.get_slot_value(bear_state, "hunger")
|
||||
txt += f" 🐻 {od.get_name(bear)} (hunger: {hunger}, {render_dead(bear_state)}) {render_attacking(bear_state)}\n"
|
||||
txt += "Men:\n"
|
||||
for _, man_state in od.get_all_instances("ManState"):
|
||||
man = animal_of(od, man_state)
|
||||
attacked_by = od.get_incoming(man_state, "attacking")
|
||||
if len(attacked_by) == 1:
|
||||
whom_state = od.get_source(attacked_by[0])
|
||||
whom_name = od.get_name(animal_of(od, whom_state))
|
||||
being_attacked = f" being attacked by {whom_name}"
|
||||
else:
|
||||
being_attacked = ""
|
||||
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:
|
||||
return "Took too long"
|
||||
|
||||
# End simulation when 2 animals are dead
|
||||
who_is_dead = []
|
||||
for _, animal_state in od.get_all_instances("AnimalState"):
|
||||
if od.get_slot_value(animal_state, "dead"):
|
||||
animal_name = od.get_name(animal_of(od, animal_state))
|
||||
who_is_dead.append(animal_name)
|
||||
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_all_actions,
|
||||
decision_maker=RandomDecisionMaker(seed=0),
|
||||
# decision_maker=InteractiveDecisionMaker(),
|
||||
termination_condition=termination_condition,
|
||||
check_conformance=False,
|
||||
verbose=True,
|
||||
renderer=render_woods,
|
||||
)
|
||||
|
||||
od = ODAPI(state, woods_rt_m, woods_rt_mm)
|
||||
|
||||
sim.run(od)
|
||||
Loading…
Add table
Add a link
Reference in a new issue