From 1d03337a61f50743408748109d985ec19492c2e4 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 27 Jan 2025 16:53:39 +0100 Subject: [PATCH 01/10] update readme --- README.md | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3c0638f..bd8c2a4 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,22 @@ -# MV2 +# muMLE -This repository contains the code for my take on (a part of) the [Modelverse](https://msdl.uantwerpen.be/git/yentl/modelverse) for my Master's thesis. +Tiny (meta-)modeling framework. -## Development packages +Features: -Some packages were used during development, but are not needed for succesful runtime (e.g. linter, autoformatter). These can be found under `requirements_dev.txt`. + * mostly textual concrete syntax + * meta-modeling & constraint writing + * conformance checking + * model transformation primitives (match and rewrite) + * rule-based model transformation + * examples included: + - Class Diagrams (self-conforming) + - Causal Block Diagrams language + - Petri Net language -## Mandatory packages +## Dependencies -Python packages required to succesfully run/test the code in this repository can be found under `requirements.txt`. \ No newline at end of file + * Python 3.? + * Python libraries: + - Lark (for textual parsing) + - Jinja2 (not a hard requirement, only for model-to-text transformation) From 7f5c2f39fcfb4ffbc7d1d810efffab4325a42a2b Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Mon, 27 Jan 2025 17:07:36 +0100 Subject: [PATCH 02/10] update readme --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index bd8c2a4..54639b5 100644 --- a/README.md +++ b/README.md @@ -20,3 +20,13 @@ Features: * Python libraries: - Lark (for textual parsing) - Jinja2 (not a hard requirement, only for model-to-text transformation) + + +## Development + +The following branches exist: + + * `mde2425` - the branch containing a snapshot of the repo used for the MDE assignments 24-25. No breaking changes will be pushed here. After the re-exams (Sep 2025), this branch will be frozen. + * `master` - currently equivalent to `mde2425` (this is the branch that was cloned by the students). This branch will be deleted after Sep 2025, because the name is too vague. + * `cleaning` - in this branch, new development will occur, primarily cleaning up the code to prepare for next year's MDE classes. + From 70c53a9aef81caaa333889552f024b6a8e21c1e7 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Wed, 5 Feb 2025 10:40:40 +0100 Subject: [PATCH 03/10] rename branch --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54639b5..1171f46 100644 --- a/README.md +++ b/README.md @@ -28,5 +28,5 @@ The following branches exist: * `mde2425` - the branch containing a snapshot of the repo used for the MDE assignments 24-25. No breaking changes will be pushed here. After the re-exams (Sep 2025), this branch will be frozen. * `master` - currently equivalent to `mde2425` (this is the branch that was cloned by the students). This branch will be deleted after Sep 2025, because the name is too vague. - * `cleaning` - in this branch, new development will occur, primarily cleaning up the code to prepare for next year's MDE classes. + * `development` - in this branch, new development will occur, primarily cleaning up the code to prepare for next year's MDE classes. From 2c64ebda6743b2ecc9756b8618e53839f84cc93e Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 12:23:07 +0200 Subject: [PATCH 04/10] Scheduler first commit --- examples/schedule/RuleExecuter.py | 49 +++++++ examples/schedule/ScheduledActionGenerator.py | 104 ++++++++++++++ examples/schedule/__init__.py | 0 examples/schedule/generator.py | 129 ++++++++++++++++++ examples/schedule/models/README.md | 26 ++++ examples/schedule/models/scheduling_MM.od | 46 +++++++ examples/schedule/schedule_lib/__init__.py | 12 ++ examples/schedule/schedule_lib/data.py | 63 +++++++++ examples/schedule/schedule_lib/data_modify.py | 26 ++++ examples/schedule/schedule_lib/data_node.py | 47 +++++++ examples/schedule/schedule_lib/end.py | 21 +++ examples/schedule/schedule_lib/exec_node.py | 34 +++++ examples/schedule/schedule_lib/funcs.py | 10 ++ .../schedule/schedule_lib/id_generator.py | 8 ++ examples/schedule/schedule_lib/loop.py | 57 ++++++++ examples/schedule/schedule_lib/match.py | 42 ++++++ examples/schedule/schedule_lib/null_node.py | 25 ++++ examples/schedule/schedule_lib/print.py | 28 ++++ examples/schedule/schedule_lib/rewrite.py | 38 ++++++ examples/schedule/schedule_lib/singleton.py | 8 ++ examples/schedule/schedule_lib/start.py | 16 +++ examples/schedule/templates/schedule_dot.j2 | 9 ++ .../schedule/templates/schedule_template.j2 | 35 +++++ .../templates/schedule_template_wrap.j2 | 47 +++++++ 24 files changed, 880 insertions(+) create mode 100644 examples/schedule/RuleExecuter.py create mode 100644 examples/schedule/ScheduledActionGenerator.py create mode 100644 examples/schedule/__init__.py create mode 100644 examples/schedule/generator.py create mode 100644 examples/schedule/models/README.md create mode 100644 examples/schedule/models/scheduling_MM.od create mode 100644 examples/schedule/schedule_lib/__init__.py create mode 100644 examples/schedule/schedule_lib/data.py create mode 100644 examples/schedule/schedule_lib/data_modify.py create mode 100644 examples/schedule/schedule_lib/data_node.py create mode 100644 examples/schedule/schedule_lib/end.py create mode 100644 examples/schedule/schedule_lib/exec_node.py create mode 100644 examples/schedule/schedule_lib/funcs.py create mode 100644 examples/schedule/schedule_lib/id_generator.py create mode 100644 examples/schedule/schedule_lib/loop.py create mode 100644 examples/schedule/schedule_lib/match.py create mode 100644 examples/schedule/schedule_lib/null_node.py create mode 100644 examples/schedule/schedule_lib/print.py create mode 100644 examples/schedule/schedule_lib/rewrite.py create mode 100644 examples/schedule/schedule_lib/singleton.py create mode 100644 examples/schedule/schedule_lib/start.py create mode 100644 examples/schedule/templates/schedule_dot.j2 create mode 100644 examples/schedule/templates/schedule_template.j2 create mode 100644 examples/schedule/templates/schedule_template_wrap.j2 diff --git a/examples/schedule/RuleExecuter.py b/examples/schedule/RuleExecuter.py new file mode 100644 index 0000000..8566d10 --- /dev/null +++ b/examples/schedule/RuleExecuter.py @@ -0,0 +1,49 @@ +from concrete_syntax.textual_od.renderer import render_od + +import pprint +from typing import Generator, Callable, Any +from uuid import UUID +import functools + +from api.od import ODAPI +from concrete_syntax.common import indent +from transformation.matcher import match_od +from transformation.rewriter import rewrite +from transformation.cloner import clone_od +from util.timer import Timer +from util.loader import parse_and_check + +class RuleExecuter: + def __init__(self, state, mm: UUID, mm_ramified: UUID, eval_context={}): + self.state = state + self.mm = mm + self.mm_ramified = mm_ramified + self.eval_context = eval_context + + # Generates matches. + # Every match is a dictionary with entries LHS_element_name -> model_element_name + def match_rule(self, m: UUID, lhs: UUID, *, pivot:dict[Any, Any]): + lhs_matcher = match_od(self.state, + host_m=m, + host_mm=self.mm, + pattern_m=lhs, + pattern_mm=self.mm_ramified, + eval_context=self.eval_context, + pivot= pivot, + ) + return lhs_matcher + + def rewrite_rule(self, m: UUID, rhs: UUID, *, pivot:dict[Any, Any]): + yield rewrite(self.state, + rhs_m=rhs, + pattern_mm=self.mm_ramified, + lhs_match=pivot, + host_m=m, + host_mm=self.mm, + eval_context=self.eval_context, + ) + + + def load_match(self, file: str): + with open(file, "r") as f: + return parse_and_check(self.state, f.read(), self.mm_ramified, file) diff --git a/examples/schedule/ScheduledActionGenerator.py b/examples/schedule/ScheduledActionGenerator.py new file mode 100644 index 0000000..34b1b96 --- /dev/null +++ b/examples/schedule/ScheduledActionGenerator.py @@ -0,0 +1,104 @@ +import importlib.util +import io +import os + +from jinja2 import FileSystemLoader, Environment + +from concrete_syntax.textual_od import parser as parser_od +from concrete_syntax.textual_cd import parser as parser_cd +from api.od import ODAPI +from bootstrap.scd import bootstrap_scd +from examples.petrinet.schedule import Schedule +from examples.schedule.generator import schedule_generator +from examples.schedule.schedule_lib import End, NullNode +from framework.conformance import Conformance, render_conformance_check_result +from state.devstate import DevState + +class ScheduleActionGenerator: + def __init__(self, rule_executer, schedulefile:str): + self.rule_executer = rule_executer + self.rule_dict = {} + self.schedule: Schedule + + + self.state = DevState() + self.load_schedule(schedulefile) + + def load_schedule(self, filename): + print("Loading schedule ...") + scd_mmm = bootstrap_scd(self.state) + with open("../schedule/models/scheduling_MM.od", "r") as f_MM: + mm_cs = f_MM.read() + with open(f"{filename}", "r") as f_M: + m_cs = f_M.read() + print("OK") + + print("\nParsing models") + + print(f"\tParsing meta model") + scheduling_mm = parser_cd.parse_cd( + self.state, + m_text=mm_cs, + ) + print(f"\tParsing '{filename}_M.od' model") + scheduling_m = parser_od.parse_od( + self.state, + m_text=m_cs, + mm=scheduling_mm + ) + print(f"OK") + + print("\tmeta-meta-model a valid class diagram") + conf = Conformance(self.state, scd_mmm, scd_mmm) + print(render_conformance_check_result(conf.check_nominal())) + print(f"Is our '{filename}_M.od' model a valid '{filename}_MM.od' diagram?") + conf = Conformance(self.state, scheduling_m, scheduling_mm) + print(render_conformance_check_result(conf.check_nominal())) + print("OK") + + od = ODAPI(self.state, scheduling_m, scheduling_mm) + g = schedule_generator(od) + + output_buffer = io.StringIO() + g.generate_schedule(output_buffer) + open(f"schedule.py", "w").write(output_buffer.getvalue()) + spec = importlib.util.spec_from_file_location("schedule", "schedule.py") + scedule_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(scedule_module) + self.schedule = scedule_module.Schedule(self.rule_executer) + self.load_matchers() + + def load_matchers(self): + matchers = dict() + for file in self.schedule.get_matchers(): + matchers[file] = self.rule_executer.load_match(file) + self.schedule.init_schedule(matchers) + + def __call__(self, api: ODAPI): + exec_op = self.schedule(api) + yield from exec_op + + def termination_condition(self, api: ODAPI): + if type(self.schedule.cur) == End: + return "jay" + if type(self.schedule.cur) == NullNode: + return "RRRR" + return None + + def generate_dot(self): + env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates'))) + env.trim_blocks = True + env.lstrip_blocks = True + template_dot = env.get_template('schedule_dot.j2') + + nodes = [] + edges = [] + visit = set() + self.schedule.generate_dot(nodes, edges, visit) + print("Nodes:") + print(nodes) + print("\nEdges:") + print(edges) + + with open("test.dot", "w") as f_dot: + f_dot.write(template_dot.render({"nodes": nodes, "edges": edges})) \ No newline at end of file diff --git a/examples/schedule/__init__.py b/examples/schedule/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/examples/schedule/generator.py b/examples/schedule/generator.py new file mode 100644 index 0000000..ed8a111 --- /dev/null +++ b/examples/schedule/generator.py @@ -0,0 +1,129 @@ +import sys +import os +import json +from uuid import UUID + +from jinja2.runtime import Macro + +from api.od import ODAPI +from jinja2 import Environment, FileSystemLoader, meta + + +class schedule_generator: + def __init__(self, odApi:ODAPI): + self.env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), 'templates'))) + self.env.trim_blocks = True + self.env.lstrip_blocks = True + self.template = self.env.get_template('schedule_template.j2') + self.template_wrap = self.env.get_template('schedule_template_wrap.j2') + self.api = odApi + + def get_slot_value_default(item: UUID, slot:str, default): + if slot in self.api.get_slots(item): + return self.api.get_slot_value(item, slot) + return default + + name_dict = lambda item: {"name": self.api.get_name(item)} + conn_dict = lambda item: {"name_from": self.api.get_name(self.api.get_source(item)), + "name_to": self.api.get_name(self.api.get_target(item)), + "gate_from": self.api.get_slot_value(item, "gate_from"), + "gate_to": self.api.get_slot_value(item, "gate_to"), + } + + conn_data_event = {"Match": lambda item: False, + "Rewrite": lambda item: False, + "Data_modify": lambda item: True, + "Loop": lambda item: True, + "Print": lambda item: get_slot_value_default(item, "event", False) + } + conn_data_dict = lambda item: {"name_from": self.api.get_name(self.api.get_source(item)), + "name_to": self.api.get_name(self.api.get_target(item)), + "event": conn_data_event[self.api.get_type_name(target := self.api.get_target(item))](target) + } + rewrite_dict = lambda item: {"name": self.api.get_name(item), + "file": self.api.get_slot_value(item, "file"), + } + match_dict = lambda item: {"name": self.api.get_name(item), + "file": self.api.get_slot_value(item, "file"), + "n": self.api.get_slot_value(item, "n") \ + if "n" in self.api.get_slots(item) else 'float("inf")' + } + data_modify_dict = lambda item: {"name": self.api.get_name(item), + "dict": json.loads(self.api.get_slot_value(item, "modify_dict")) + } + loop_dict = lambda item: {"name": self.api.get_name(item), + "choise": get_slot_value_default(item, "choise", False)} + print_dict = lambda item: {"name": self.api.get_name(item), + "label": get_slot_value_default(item, "label", "")} + arg_map = {"Start": name_dict, "End": name_dict, + "Match": match_dict, "Rewrite": rewrite_dict, + "Data_modify": data_modify_dict, "Loop": loop_dict, + "Exec_con": conn_dict, "Data_con": conn_data_dict, + "Print": print_dict} + self.macro_args = {tp: (macro, arg_map.get(tp)) for tp, macro in self.template.module.__dict__.items() + if type(macro) == Macro} + + def _render(self, item): + type_name = self.api.get_type_name(item) + macro, arg_gen = self.macro_args[type_name] + return macro(**arg_gen(item)) + + def generate_schedule(self, stream = sys.stdout): + start = self.api.get_all_instances("Start")[0][1] + stack = [start] + out = {"blocks":[], "exec_conn":[], "data_conn":[], "match_files":set(), "matchers":[], "start":self.api.get_name(start)} + execBlocks = set() + exec_conn = list() + + while len(stack) > 0: + exec_obj = stack.pop() + if exec_obj in execBlocks: + continue + execBlocks.add(exec_obj) + for conn in self.api.get_outgoing(exec_obj, "Exec_con"): + exec_conn.append(conn) + stack.append(self.api.get_target(conn)) + + stack = list(execBlocks) + data_blocks = set() + for name, p in self.api.get_all_instances("Print"): + if "event" in (event := self.api.get_slots(p)) and event: + stack.append(p) + execBlocks.add(p) + + + data_conn = set() + while len(stack) > 0: + obj = stack.pop() + for data_c in self.api.get_incoming(obj, "Data_con"): + data_conn.add(data_c) + source = self.api.get_source(data_c) + if not self.api.is_instance(source, "Exec") and \ + source not in execBlocks and \ + source not in data_blocks: + stack.append(source) + data_blocks.add(source) + + for exec_item in execBlocks: + out["blocks"].append(self._render(exec_item)) + if self.api.is_instance(exec_item, "Rule"): + d = self.macro_args[self.api.get_type_name(exec_item)][1](exec_item) + out["match_files"].add(d["file"]) + out["matchers"].append(d) + for exec_c in exec_conn: + out["exec_conn"].append(self._render(exec_c)) + + for data_c in data_conn: + out["data_conn"].append(self._render(data_c)) + + for data_b in data_blocks: + out["blocks"].append(self._render(data_b)) + + print(self.template_wrap.render(out), file=stream) + + + + + + # print("with open('test.dot', 'w') as f:", file=stream) + # print(f"\tf.write({self.api.get_name(start)}.generate_dot())", file=stream) \ No newline at end of file diff --git a/examples/schedule/models/README.md b/examples/schedule/models/README.md new file mode 100644 index 0000000..5767d48 --- /dev/null +++ b/examples/schedule/models/README.md @@ -0,0 +1,26 @@ + +### association Exec_con + Integer gate_from; + Integer gate_to; + +### association Data_con + +### class Start [1..1] +### class End [1..*] + + +### class Match + optional Integer n; + +### class Rewrite + +### class Data_modify + String modify_dict; + +### class Loop + optional Boolean choise; + +## debugging tools + +### class Print(In_Exec, Out_Exec, In_Data) + optional Boolean event; \ No newline at end of file diff --git a/examples/schedule/models/scheduling_MM.od b/examples/schedule/models/scheduling_MM.od new file mode 100644 index 0000000..533d8bc --- /dev/null +++ b/examples/schedule/models/scheduling_MM.od @@ -0,0 +1,46 @@ +abstract class Exec +abstract class In_Exec(Exec) +abstract class Out_Exec(Exec) + +association Exec_con [0..*] Out_Exec -> In_Exec [0..*] { + Integer gate_from; + Integer gate_to; +} + +abstract class Data +abstract class In_Data(Data) +abstract class Out_Data(Data) +association Data_con [0..*] Out_Data -> In_Data [0..*] + +class Start [1..1] (Out_Exec) +class End [1..*] (In_Exec) + + +abstract class Rule (In_Exec, Out_Exec, In_Data, Out_Data) +{ + String file; +} +class Match (Rule) +{ + optional Integer n; +} + +class Rewrite (Rule) + +class Data_modify(In_Data, Out_Data) +{ + String modify_dict; +} + +class Loop(In_Exec, Out_Exec, In_Data, Out_Data) +{ + optional Boolean choise; +} + +# debugging tools + +class Print(In_Exec, Out_Exec, In_Data) +{ + optional Boolean event; + optional String label; +} \ No newline at end of file diff --git a/examples/schedule/schedule_lib/__init__.py b/examples/schedule/schedule_lib/__init__.py new file mode 100644 index 0000000..0b826ab --- /dev/null +++ b/examples/schedule/schedule_lib/__init__.py @@ -0,0 +1,12 @@ +from .data_node import DataNode +from .data_modify import DataModify +from .end import End +from .exec_node import ExecNode +from .loop import Loop +from .match import Match +from .null_node import NullNode +from .print import Print +from .rewrite import Rewrite +from .start import Start + +__all__ = ["DataNode", "End", "ExecNode", "Loop", "Match", "NullNode", "Rewrite", "Print", "DataModify", "Start"] \ No newline at end of file diff --git a/examples/schedule/schedule_lib/data.py b/examples/schedule/schedule_lib/data.py new file mode 100644 index 0000000..88bcb42 --- /dev/null +++ b/examples/schedule/schedule_lib/data.py @@ -0,0 +1,63 @@ +import functools +from typing import Any, Generator, Callable + + +class Data: + def __init__(self, super) -> None: + self.data: list[dict[Any, Any]] = list() + self.success: bool = False + self.super = super + + @staticmethod + def store_output(func: Callable) -> Callable: + def wrapper(self, *args, **kwargs) -> Any: + output = func(self, *args, **kwargs) + self.success = output + return output + return wrapper + + @store_output + def store_data(self, data_gen: Generator, n: int) -> bool: + self.data.clear() + if n == 0: + return True + i: int = 0 + while (match := next(data_gen, None)) is not None: + self.data.append(match) + i+=1 + if i >= n: + break + else: + if n == float("inf"): + return bool(len(self.data)) + self.data.clear() + return False + return True + + def get_super(self) -> int: + return self.super + + def replace(self, data: "Data") -> None: + self.data.clear() + self.data.extend(data.data) + + def append(self, data: Any) -> None: + self.data.append(data) + + def clear(self) -> None: + self.data.clear() + + def pop(self, index = -1) -> Any: + return self.data.pop(index) + + def empty(self) -> bool: + return len(self.data) == 0 + + def __getitem__(self, index): + return self.data[index] + + def __iter__(self): + return self.data.__iter__() + + def __len__(self): + return self.data.__len__() \ No newline at end of file diff --git a/examples/schedule/schedule_lib/data_modify.py b/examples/schedule/schedule_lib/data_modify.py new file mode 100644 index 0000000..0df6cba --- /dev/null +++ b/examples/schedule/schedule_lib/data_modify.py @@ -0,0 +1,26 @@ +import functools +from typing import TYPE_CHECKING, Callable, List + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode + + +class DataModify(DataNode): + def __init__(self, modify_dict: dict[str,str]) -> None: + DataNode.__init__(self) + self.modify_dict: dict[str,str] = modify_dict + + def input_event(self, success: bool) -> None: + if success or self.data_out.success: + self.data_out.data.clear() + for data in self.data_in.data: + self.data_out.append({self.modify_dict[key]: value for key, value in data.items() if key in self.modify_dict.keys()}) + DataNode.input_event(self, success) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=modify]") + super().generate_dot(nodes, edges, visited) diff --git a/examples/schedule/schedule_lib/data_node.py b/examples/schedule/schedule_lib/data_node.py new file mode 100644 index 0000000..557f297 --- /dev/null +++ b/examples/schedule/schedule_lib/data_node.py @@ -0,0 +1,47 @@ +from typing import Any, Generator, List + +from examples.schedule.schedule_lib.id_generator import IdGenerator +from .data import Data + +class DataNode: + def __init__(self) -> None: + if not hasattr(self, 'id'): + self.id = IdGenerator().generate_id() + self.data_out : Data = Data(self) + self.data_in: Data | None = None + self.eventsub: list[DataNode] = list() + + def connect_data(self, data_node: "DataNode", eventsub=True) -> None: + data_node.data_in = self.data_out + if eventsub: + self.eventsub.append(data_node) + + def store_data(self, data_gen: Generator, n: int) -> None: + success: bool = self.data_out.store_data(data_gen, n) + for sub in self.eventsub: + sub.input_event(success) + + def get_input_data(self) -> list[dict[Any, Any]]: + if not self.data_in.success: + raise Exception("Invalid input data: matching has failed") + data = self.data_in.data + if len(data) == 0: + raise Exception("Invalid input data: no data present") + return data + + def input_event(self, success: bool) -> None: + self.data_out.success = success + for sub in self.eventsub: + sub.input_event(success) + + def get_id(self) -> int: + return self.id + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + visited.add(self.id) + if self.data_in is not None: + edges.append(f"{self.data_in.get_super().get_id()} -> {self.get_id()} [color = green]") + self.data_in.get_super().generate_dot(nodes, edges, visited) + for sub in self.eventsub: + sub.generate_dot(nodes, edges, visited) + diff --git a/examples/schedule/schedule_lib/end.py b/examples/schedule/schedule_lib/end.py new file mode 100644 index 0000000..2a008c4 --- /dev/null +++ b/examples/schedule/schedule_lib/end.py @@ -0,0 +1,21 @@ +import functools +from typing import TYPE_CHECKING, List, Callable, Generator + +from api.od import ODAPI +from .exec_node import ExecNode + +class End(ExecNode): + def __init__(self) -> None: + super().__init__(out_connections=1) + + def execute(self, od: ODAPI) -> Generator | None: + return self.terminate(od) + + @staticmethod + def terminate(od: ODAPI) -> Generator: + yield f"end:", functools.partial(lambda od:(od, ""), od) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=end]") \ No newline at end of file diff --git a/examples/schedule/schedule_lib/exec_node.py b/examples/schedule/schedule_lib/exec_node.py new file mode 100644 index 0000000..c5d2d04 --- /dev/null +++ b/examples/schedule/schedule_lib/exec_node.py @@ -0,0 +1,34 @@ +from typing import TYPE_CHECKING, List, Callable, Generator +from api.od import ODAPI + +from .id_generator import IdGenerator + +class ExecNode: + def __init__(self, out_connections: int = 1) -> None: + from .null_node import NullNode + self.next_state: list[ExecNode] = [] + if out_connections > 0: + self.next_state = [NullNode()]*out_connections + self.id: int = IdGenerator().generate_id() + + def nextState(self) -> "ExecNode": + return self.next_state[0] + + def connect(self, next_state: "ExecNode", from_gate: int = 0, to_gate: int = 0) -> None: + if from_gate >= len(self.next_state): + raise IndexError + self.next_state[from_gate] = next_state + + def execute(self, od: ODAPI) -> Generator | None: + return None + + def get_id(self) -> int: + return self.id + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + visited.add(self.id) + for edge in self.next_state: + edges.append(f"{self.id} -> {edge.get_id()}") + for next in self.next_state: + next.generate_dot(nodes, edges, visited) + diff --git a/examples/schedule/schedule_lib/funcs.py b/examples/schedule/schedule_lib/funcs.py new file mode 100644 index 0000000..0b19b99 --- /dev/null +++ b/examples/schedule/schedule_lib/funcs.py @@ -0,0 +1,10 @@ +from typing import Callable + +def generate_dot_wrap(func) -> Callable: + def wrapper(self, *args, **kwargs) -> str: + nodes = [] + edges = [] + self.reset_visited() + func(self, nodes, edges, *args, **kwargs) + return f"digraph G {{\n\t{"\n\t".join(nodes)}\n\t{"\n\t".join(edges)}\n}}" + return wrapper diff --git a/examples/schedule/schedule_lib/id_generator.py b/examples/schedule/schedule_lib/id_generator.py new file mode 100644 index 0000000..d1f4b25 --- /dev/null +++ b/examples/schedule/schedule_lib/id_generator.py @@ -0,0 +1,8 @@ +from .singleton import Singleton + +class IdGenerator(metaclass=Singleton): + def __init__(self): + self.id = -1 + def generate_id(self) -> int: + self.id += 1 + return self.id \ No newline at end of file diff --git a/examples/schedule/schedule_lib/loop.py b/examples/schedule/schedule_lib/loop.py new file mode 100644 index 0000000..44ec5c5 --- /dev/null +++ b/examples/schedule/schedule_lib/loop.py @@ -0,0 +1,57 @@ +import functools +from random import choice +from typing import TYPE_CHECKING, Callable, List, Generator + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode +from .data_node import Data + + +class Loop(ExecNode, DataNode): + def __init__(self, choice) -> None: + ExecNode.__init__(self, out_connections=2) + DataNode.__init__(self) + self.choice: bool = choice + self.cur_data: Data = Data(-1) + + def nextState(self) -> ExecNode: + return self.next_state[not self.data_out.success] + + def execute(self, od: ODAPI) -> Generator | None: + if self.cur_data.empty(): + self.data_out.clear() + self.data_out.success = False + DataNode.input_event(self, False) + return None + + if self.choice: + def select_data() -> Generator: + for i in range(len(self.cur_data)): + yield f"choice: {self.cur_data[i]}", functools.partial(self.select_next,od, i) + return select_data() + else: + self.select_next(od, -1) + return None + + def input_event(self, success: bool) -> None: + if (b := self.data_out.success) or success: + self.cur_data.replace(self.data_in) + self.data_out.clear() + self.data_out.success = False + if b: + DataNode.input_event(self, False) + + def select_next(self,od: ODAPI, index: int) -> tuple[ODAPI, list[str]]: + self.data_out.clear() + self.data_out.append(self.cur_data.pop(index)) + DataNode.input_event(self, True) + return (od, ["data selected"]) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=Loop]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/match.py b/examples/schedule/schedule_lib/match.py new file mode 100644 index 0000000..f350ba6 --- /dev/null +++ b/examples/schedule/schedule_lib/match.py @@ -0,0 +1,42 @@ +import functools +from typing import TYPE_CHECKING, Callable, List, Generator + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode + + +class Match(ExecNode, DataNode): + def __init__(self, label: str, n: int | float) -> None: + ExecNode.__init__(self, out_connections=2) + DataNode.__init__(self) + self.label: str = label + self.n:int = n + self.rule = None + self.rule_executer : RuleExecuter + + def nextState(self) -> ExecNode: + return self.next_state[not self.data_out.success] + + def execute(self, od: ODAPI) -> Generator | None: + self.match(od) + return None + + def init_rule(self, rule, rule_executer): + self.rule = rule + self.rule_executer = rule_executer + + def match(self, od: ODAPI) -> None: + pivot = {} + if self.data_in is not None: + pivot = self.get_input_data()[0] + print(f"matching: {self.label}\n\tpivot: {pivot}") + self.store_data(self.rule_executer.match_rule(od.m, self.rule, pivot=pivot), self.n) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=M_{self.label.split("/")[-1]}_{self.n}]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/null_node.py b/examples/schedule/schedule_lib/null_node.py new file mode 100644 index 0000000..2d322bb --- /dev/null +++ b/examples/schedule/schedule_lib/null_node.py @@ -0,0 +1,25 @@ +import functools +from symtable import Function +from typing import List, Callable, Generator + +from api.od import ODAPI +from .singleton import Singleton + +from .exec_node import ExecNode + +class NullNode(ExecNode, metaclass=Singleton): + def __init__(self): + ExecNode.__init__(self, out_connections=0) + + def execute(self, od: ODAPI) -> Generator | None: + raise Exception('Null node should already have terminated the schedule') + + @staticmethod + def terminate(od: ODAPI): + return None + yield # verrrry important line, dont remove this unreachable code + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=Null]") \ No newline at end of file diff --git a/examples/schedule/schedule_lib/print.py b/examples/schedule/schedule_lib/print.py new file mode 100644 index 0000000..ed0bbc6 --- /dev/null +++ b/examples/schedule/schedule_lib/print.py @@ -0,0 +1,28 @@ +import functools +from typing import TYPE_CHECKING, Callable, List, Generator + +from api.od import ODAPI +from examples.schedule.RuleExecuter import RuleExecuter +from .exec_node import ExecNode +from .data_node import DataNode + + +class Print(ExecNode, DataNode): + def __init__(self, label: str = "") -> None: + ExecNode.__init__(self, out_connections=1) + DataNode.__init__(self) + self.label = label + + def execute(self, od: ODAPI) -> Generator | None: + self.input_event(True) + return None + + def input_event(self, success: bool) -> None: + print(f"{self.label}{self.data_in.data}") + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=Print_{self.label.replace(":", "")}]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/rewrite.py b/examples/schedule/schedule_lib/rewrite.py new file mode 100644 index 0000000..c00ee8e --- /dev/null +++ b/examples/schedule/schedule_lib/rewrite.py @@ -0,0 +1,38 @@ +import functools +from typing import List, Callable, Generator + +from api.od import ODAPI +from .exec_node import ExecNode +from .data_node import DataNode +from ..RuleExecuter import RuleExecuter + + +class Rewrite(ExecNode, DataNode): + def __init__(self, label: str) -> None: + ExecNode.__init__(self, out_connections=1) + DataNode.__init__(self) + self.label = label + self.rule = None + self.rule_executer : RuleExecuter + + def init_rule(self, rule, rule_executer): + self.rule = rule + self.rule_executer= rule_executer + + def execute(self, od: ODAPI) -> Generator | None: + yield "ghello", functools.partial(self.rewrite, od) + + def rewrite(self, od): + print("rewrite" + self.label) + pivot = {} + if self.data_in is not None: + pivot = self.get_input_data()[0] + self.store_data(self.rule_executer.rewrite_rule(od.m, self.rule, pivot=pivot), 1) + return ODAPI(od.state, od.m, od.mm),[f"rewrite {self.label}\n\tpivot: {pivot}\n\t{"success" if self.data_out.success else "failure"}\n"] + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=R_{self.label.split("/")[-1]}]") + ExecNode.generate_dot(self, nodes, edges, visited) + DataNode.generate_dot(self, nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/schedule_lib/singleton.py b/examples/schedule/schedule_lib/singleton.py new file mode 100644 index 0000000..31955e3 --- /dev/null +++ b/examples/schedule/schedule_lib/singleton.py @@ -0,0 +1,8 @@ +from abc import ABCMeta + +class Singleton(ABCMeta): + _instances = {} + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/examples/schedule/schedule_lib/start.py b/examples/schedule/schedule_lib/start.py new file mode 100644 index 0000000..44ed1e1 --- /dev/null +++ b/examples/schedule/schedule_lib/start.py @@ -0,0 +1,16 @@ +from typing import TYPE_CHECKING, Callable, List, Any + +from .funcs import generate_dot_wrap + +from .exec_node import ExecNode + + +class Start(ExecNode): + def __init__(self) -> None: + ExecNode.__init__(self, out_connections=1) + + def generate_dot(self, nodes: List[str], edges: List[str], visited: set[int]) -> None: + if self.id in visited: + return + nodes.append(f"{self.id}[label=start]") + super().generate_dot(nodes, edges, visited) \ No newline at end of file diff --git a/examples/schedule/templates/schedule_dot.j2 b/examples/schedule/templates/schedule_dot.j2 new file mode 100644 index 0000000..39d2672 --- /dev/null +++ b/examples/schedule/templates/schedule_dot.j2 @@ -0,0 +1,9 @@ +digraph G { +{% for node in nodes %} + {{ node }} +{% endfor %} + +{% for edge in edges %} + {{ edge }} +{% endfor %} +} \ No newline at end of file diff --git a/examples/schedule/templates/schedule_template.j2 b/examples/schedule/templates/schedule_template.j2 new file mode 100644 index 0000000..a0c251c --- /dev/null +++ b/examples/schedule/templates/schedule_template.j2 @@ -0,0 +1,35 @@ +{% macro Start(name) %} +{{ name }} = Start() +{%- endmacro %} + +{% macro End(name) %} +{{ name }} = End() +{%- endmacro %} + +{% macro Match(name, file, n) %} +{{ name }} = Match("{{ file }}", {{ n }}) +{%- endmacro %} + +{% macro Rewrite(name, file) %} +{{ name }} = Rewrite("{{ file }}") +{%- endmacro %} + +{% macro Data_modify(name, dict) %} +{{ name }} = DataModify({{ dict }}) +{%- endmacro %} + +{% macro Exec_con(name_from, name_to, gate_from, gate_to) %} +{{ name_from }}.connect({{ name_to }},{{ gate_from }},{{ gate_to }}) +{%- endmacro %} + +{% macro Data_con(name_from, name_to, event) %} +{{ name_from }}.connect_data({{ name_to }}, {{ event }}) +{%- endmacro %} + +{% macro Loop(name, choise) %} +{{ name }} = Loop({{ choise }}) +{%- endmacro %} + +{% macro Print(name, label) %} +{{ name }} = Print("{{ label }}") +{%- endmacro %} \ No newline at end of file diff --git a/examples/schedule/templates/schedule_template_wrap.j2 b/examples/schedule/templates/schedule_template_wrap.j2 new file mode 100644 index 0000000..389f2c2 --- /dev/null +++ b/examples/schedule/templates/schedule_template_wrap.j2 @@ -0,0 +1,47 @@ +from examples.schedule.schedule_lib import * + +class Schedule: + def __init__(self, rule_executer): + self.start: Start + self.cur: ExecNode = None + self.rule_executer = rule_executer + + def __call__(self, od): + self.cur = self.cur.nextState() + while not isinstance(self.cur, NullNode): + action_gen = self.cur.execute(od) + if action_gen is not None: + # if (action_gen := self.cur.execute(od)) is not None: + return action_gen + self.cur = self.cur.nextState() + return NullNode.terminate(od) + + @staticmethod + def get_matchers(): + return [ + {% for file in match_files %} + "{{ file }}.od", + {% endfor %} + ] + + def init_schedule(self, matchers): + {% for block in blocks%} + {{ block }} + {% endfor %} + + {% for conn in exec_conn%} + {{ conn }} + {% endfor %} + {% for conn_d in data_conn%} + {{ conn_d }} + {% endfor %} + self.start = {{ start }} + self.cur = {{ start }} + + {% for match in matchers %} + {{ match["name"] }}.init_rule(matchers["{{ match["file"] }}.od"], self.rule_executer) + {% endfor %} + return None + + def generate_dot(self, *args, **kwargs): + return self.start.generate_dot(*args, **kwargs) \ No newline at end of file From 87fc7362dba5be1c8039e3da517839daac9d94ee Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 12:23:29 +0200 Subject: [PATCH 05/10] Scheduler petrinet example --- examples/petrinet/models/schedule.od | 70 +++++++++++++++++++ .../all_input_have_token.od | 13 ++++ .../operational_semantics/all_inputs.od | 13 ++++ .../all_output_places.od | 13 ++++ .../all_output_places_update.od | 13 ++++ .../operational_semantics/delete_all.od | 0 .../r_fire_transition_lhs.od | 2 +- .../operational_semantics/transition.od | 1 + examples/petrinet/runner.py | 20 ++++-- 9 files changed, 139 insertions(+), 6 deletions(-) create mode 100644 examples/petrinet/models/schedule.od create mode 100644 examples/petrinet/operational_semantics/all_input_have_token.od create mode 100644 examples/petrinet/operational_semantics/all_inputs.od create mode 100644 examples/petrinet/operational_semantics/all_output_places.od create mode 100644 examples/petrinet/operational_semantics/all_output_places_update.od create mode 100644 examples/petrinet/operational_semantics/delete_all.od create mode 100644 examples/petrinet/operational_semantics/transition.od diff --git a/examples/petrinet/models/schedule.od b/examples/petrinet/models/schedule.od new file mode 100644 index 0000000..3dce69d --- /dev/null +++ b/examples/petrinet/models/schedule.od @@ -0,0 +1,70 @@ +start:Start +end:End + +transitions:Match{ + file = "operational_semantics/transition"; +} + + +d:Data_modify +{ + rename = ' + { + "tr": null + }'; + delete = ' + { + "tr": null + }'; +} + +nac_input_without:Match{ + file = "operational_semantics/all_input_have_token"; + n = "1"; +} + +inputs:Match{ + file = "operational_semantics/all_inputs"; +} + +rewrite_incoming:Rewrite +{ + file = "operational_semantics/remove_incoming"; +} + +loop_trans:Loop +loop_input:Loop + +p:Print +{ +event = True; +label = "transition: "; +} + +p2:Print +{ +event = True; +label = "inputs: "; +} + +:Exec_con(start -> transitions){gate_from = 0;gate_to = 0;} +:Exec_con(transitions -> end){gate_from = 1;gate_to = 0;} +:Exec_con(transitions -> loop_trans){gate_from = 0;gate_to = 0;} +:Exec_con(loop_trans -> nac_input_without){gate_from = 0;gate_to = 0;} + +[//]: # (:Exec_con(nac_input_without -> loop_trans){gate_from = 0;gate_to = 0;}) +:Exec_con(nac_input_without -> inputs){gate_from = 1;gate_to = 0;} +:Exec_con(inputs -> loop_input){gate_from = 0;gate_to = 0;} +:Exec_con(inputs -> loop_trans){gate_from = 1;gate_to = 0;} + +:Exec_con(loop_trans -> end){gate_from = 1;gate_to = 0;} + +:Data_con(transitions -> loop_trans) +:Data_con(nac_input_without -> p) +:Data_con(d -> nac_input_without) +:Data_con(loop_trans -> d) +:Data_con(loop_trans -> rewrite_incoming) + + + + diff --git a/examples/petrinet/operational_semantics/all_input_have_token.od b/examples/petrinet/operational_semantics/all_input_have_token.od new file mode 100644 index 0000000..9207ce2 --- /dev/null +++ b/examples/petrinet/operational_semantics/all_input_have_token.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `get_value(this) == 0`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) diff --git a/examples/petrinet/operational_semantics/all_inputs.od b/examples/petrinet/operational_semantics/all_inputs.od new file mode 100644 index 0000000..1b87f1d --- /dev/null +++ b/examples/petrinet/operational_semantics/all_inputs.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (p -> t) diff --git a/examples/petrinet/operational_semantics/all_output_places.od b/examples/petrinet/operational_semantics/all_output_places.od new file mode 100644 index 0000000..ab431cc --- /dev/null +++ b/examples/petrinet/operational_semantics/all_output_places.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `True`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/operational_semantics/all_output_places_update.od b/examples/petrinet/operational_semantics/all_output_places_update.od new file mode 100644 index 0000000..8d2908e --- /dev/null +++ b/examples/petrinet/operational_semantics/all_output_places_update.od @@ -0,0 +1,13 @@ +# A place with no tokens: + +p:RAM_PNPlace +ps:RAM_PNPlaceState { + RAM_numTokens = `set_value(this, get_value(this) + 1)`; +} +:RAM_pn_of (ps -> p) + +# An incoming arc from that place to our transition: + +t:RAM_PNTransition + +:RAM_arc (t -> p) diff --git a/examples/petrinet/operational_semantics/delete_all.od b/examples/petrinet/operational_semantics/delete_all.od new file mode 100644 index 0000000..e69de29 diff --git a/examples/petrinet/operational_semantics/r_fire_transition_lhs.od b/examples/petrinet/operational_semantics/r_fire_transition_lhs.od index c3bd82c..c05515b 100644 --- a/examples/petrinet/operational_semantics/r_fire_transition_lhs.od +++ b/examples/petrinet/operational_semantics/r_fire_transition_lhs.od @@ -1 +1 @@ -t:RAM_PNTransition \ No newline at end of file +t:RAM_PNTransition diff --git a/examples/petrinet/operational_semantics/transition.od b/examples/petrinet/operational_semantics/transition.od new file mode 100644 index 0000000..c7c8203 --- /dev/null +++ b/examples/petrinet/operational_semantics/transition.od @@ -0,0 +1 @@ +tr:RAM_PNTransition \ No newline at end of file diff --git a/examples/petrinet/runner.py b/examples/petrinet/runner.py index b2d0c51..df8692e 100644 --- a/examples/petrinet/runner.py +++ b/examples/petrinet/runner.py @@ -1,3 +1,4 @@ +from examples.schedule.RuleExecuter import RuleExecuter from state.devstate import DevState from api.od import ODAPI from concrete_syntax.textual_od.renderer import render_od @@ -9,6 +10,10 @@ from transformation.ramify import ramify from examples.semantics.operational import simulator from examples.petrinet.renderer import show_petri_net +from examples.schedule.ScheduledActionGenerator import * +from examples.schedule.RuleExecuter import * + + if __name__ == "__main__": import os @@ -46,19 +51,24 @@ if __name__ == "__main__": mm_rt_ramified, ["fire_transition"]) # only 1 rule :( - matcher_rewriter = RuleMatcherRewriter(state, mm_rt, mm_rt_ramified) - action_generator = ActionGenerator(matcher_rewriter, rules) + # matcher_rewriter = RuleMatcherRewriter(state, mm_rt, mm_rt_ramified) + # action_generator = ActionGenerator(matcher_rewriter, rules) + + matcher_rewriter2 = RuleExecuter(state, mm_rt, mm_rt_ramified) + action_generator = ScheduleActionGenerator(matcher_rewriter2, f"models/schedule.od") def render_callback(od): show_petri_net(od) return render_od(state, od.m, od.mm) - sim = simulator.Simulator( + action_generator.generate_dot() + + sim = simulator.MinimalSimulator( action_generator=action_generator, decision_maker=simulator.InteractiveDecisionMaker(auto_proceed=False), # decision_maker=simulator.RandomDecisionMaker(seed=0), - renderer=render_callback, + termination_condition=action_generator.termination_condition, # renderer=lambda od: render_od(state, od.m, od.mm), ) - sim.run(ODAPI(state, m_rt_initial, mm_rt)) + sim.run(ODAPI(state, m_rt_initial, mm_rt)) \ No newline at end of file From bad9e8e32af1b07d548651885380aa0913eefa3c Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 12:24:51 +0200 Subject: [PATCH 06/10] removed unused variable --- transformation/rewriter.py | 1 - transformation/rule.py | 1 - 2 files changed, 2 deletions(-) diff --git a/transformation/rewriter.py b/transformation/rewriter.py index 100073f..9c29252 100644 --- a/transformation/rewriter.py +++ b/transformation/rewriter.py @@ -22,7 +22,6 @@ class TryAgainNextRound(Exception): # Rewrite is performed in-place (modifying `host_m`) def rewrite(state, - lhs_m: UUID, # LHS-pattern rhs_m: UUID, # RHS-pattern pattern_mm: UUID, # meta-model of both patterns (typically the RAMified host_mm) lhs_match: dict, # a match, morphism, from lhs_m to host_m (mapping pattern name -> host name), typically found by the 'match_od'-function. diff --git a/transformation/rule.py b/transformation/rule.py index 81436ad..7db576e 100644 --- a/transformation/rule.py +++ b/transformation/rule.py @@ -117,7 +117,6 @@ class RuleMatcherRewriter: try: rhs_match = rewrite(self.state, - lhs_m=lhs, rhs_m=rhs, pattern_mm=self.mm_ramified, lhs_match=lhs_match, From 5e5865d0d56485eb186ab0ae8af674946e19c3b0 Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 12:25:44 +0200 Subject: [PATCH 07/10] base_case of len == 0 added (same as Interactive decisionMaker) --- util/simulator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/util/simulator.py b/util/simulator.py index cdbe6a6..c967bbd 100644 --- a/util/simulator.py +++ b/util/simulator.py @@ -27,6 +27,8 @@ class RandomDecisionMaker(DecisionMaker): def __call__(self, actions): arr = [action for descr, action in actions] + if len(arr) == 0: + return i = math.floor(self.r.random()*len(arr)) return arr[i] @@ -91,7 +93,7 @@ class MinimalSimulator: self._print("Start simulation") self._print(f"Decision maker: {self.decision_maker}") step_counter = 0 - while True: + while step_counter < 10: termination_reason = self.termination_condition(model) if termination_reason != None: self._print(f"Termination condition satisfied.\nReason: {termination_reason}.") From 756b3f30da1f163aa32b5ab00b05a7fd99e8f496 Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 12:27:28 +0200 Subject: [PATCH 08/10] get_slots and is_instance added to readonly api + is_instance implementation --- api/cd.py | 5 ++++- api/od.py | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/api/cd.py b/api/cd.py index d18f7b0..16168d6 100644 --- a/api/cd.py +++ b/api/cd.py @@ -53,7 +53,7 @@ class CDAPI: return self.bottom.read_outgoing_elements(self.m, type_name)[0] def is_direct_subtype(self, super_type_name: str, sub_type_name: str): - return sub_type_name in self.direct_sub_types[super_type] + return sub_type_name in self.direct_sub_types[super_type_name] def is_direct_supertype(self, sub_type_name: str, super_type_name: str): return super_type_name in self.direct_super_types[sub_type_name] @@ -83,3 +83,6 @@ class CDAPI: result = self.find_attribute_type(supertype, attr_name) if result != None: return result + + def get_type(self, type_name: str): + return next(k for k, v in self.type_model_names.items() if v == type_name) diff --git a/api/od.py b/api/od.py index c23160d..b176abf 100644 --- a/api/od.py +++ b/api/od.py @@ -143,7 +143,7 @@ class ODAPI: typ = self.cdapi.get_type(type_name) types = set(typ) if not include_subtypes else self.cdapi.transitive_sub_types[type_name] for type_of_obj in self.bottom.read_outgoing_elements(obj, "Morphism"): - if type_of_obj in types: + if self.get_name(type_of_obj) in types: return True return False @@ -262,6 +262,7 @@ def bind_api_readonly(odapi): 'get_target': odapi.get_target, 'get_source': odapi.get_source, 'get_slot': odapi.get_slot, + 'get_slots': odapi.get_slots, 'get_slot_value': odapi.get_slot_value, 'get_slot_value_default': odapi.get_slot_value_default, 'get_all_instances': odapi.get_all_instances, @@ -270,6 +271,7 @@ def bind_api_readonly(odapi): 'get_outgoing': odapi.get_outgoing, 'get_incoming': odapi.get_incoming, 'has_slot': odapi.has_slot, + 'is_instance': odapi.is_instance, } return funcs From 8ee9fba4ea45fed8eb6c03d77722c62cc20187a9 Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 12:33:38 +0200 Subject: [PATCH 09/10] petrinet example fixed --- examples/petrinet/models/schedule.od | 8 ++------ examples/schedule/ScheduledActionGenerator.py | 4 ++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/examples/petrinet/models/schedule.od b/examples/petrinet/models/schedule.od index 3dce69d..1584a7c 100644 --- a/examples/petrinet/models/schedule.od +++ b/examples/petrinet/models/schedule.od @@ -8,13 +8,9 @@ transitions:Match{ d:Data_modify { - rename = ' + modify_dict = ' { - "tr": null - }'; - delete = ' - { - "tr": null + "tr": "t" }'; } diff --git a/examples/schedule/ScheduledActionGenerator.py b/examples/schedule/ScheduledActionGenerator.py index 34b1b96..0f91121 100644 --- a/examples/schedule/ScheduledActionGenerator.py +++ b/examples/schedule/ScheduledActionGenerator.py @@ -8,17 +8,17 @@ from concrete_syntax.textual_od import parser as parser_od from concrete_syntax.textual_cd import parser as parser_cd from api.od import ODAPI from bootstrap.scd import bootstrap_scd -from examples.petrinet.schedule import Schedule from examples.schedule.generator import schedule_generator from examples.schedule.schedule_lib import End, NullNode from framework.conformance import Conformance, render_conformance_check_result from state.devstate import DevState + class ScheduleActionGenerator: def __init__(self, rule_executer, schedulefile:str): self.rule_executer = rule_executer self.rule_dict = {} - self.schedule: Schedule + self.schedule: "Schedule" self.state = DevState() From 04a17f6ac8f17415f28a4008aed6c549c12b63d7 Mon Sep 17 00:00:00 2001 From: robbe Date: Thu, 24 Apr 2025 13:07:32 +0200 Subject: [PATCH 10/10] has_slot(obj) now works on instance ipv class. Useful for optional field --- api/od.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/od.py b/api/od.py index b176abf..bd0e2b7 100644 --- a/api/od.py +++ b/api/od.py @@ -151,10 +151,9 @@ class ODAPI: self.bottom.delete_element(obj) self.__recompute_mappings() - # Does the class of the object have the given attribute? + # Does the the object have the given attribute? def has_slot(self, obj: UUID, attr_name: str): - class_name = self.get_name(self.get_type(obj)) - return self.od.get_attr_link_name(class_name, attr_name) != None + return self.od.get_slot_link(obj, attr_name) != None def get_slots(self, obj: UUID) -> list[str]: return [attr_name for attr_name, _ in self.od.get_slots(obj)]