diff --git a/examples/geraniums/runner.py b/examples/geraniums/runner.py index fdaaa68..cd72db6 100644 --- a/examples/geraniums/runner.py +++ b/examples/geraniums/runner.py @@ -34,7 +34,7 @@ if __name__ == "__main__": print(render_conformance_check_result(conf_err)) mm_ramified = ramify(state, mm) - action_generator = RuleSchedular(state, mm, mm_ramified, verbose=True, directory="examples/geraniums", eval_context=eval_context) + action_generator = RuleScheduler(state, mm, mm_ramified, verbose=True, directory="examples/geraniums", eval_context=eval_context) od = ODAPI(state, m, mm) render_geraniums_dot(od, f"{THIS_DIR}/geraniums.dot") diff --git a/examples/petrinet/models/schedules/petrinet3.drawio b/examples/petrinet/models/schedules/petrinet3.drawio new file mode 100644 index 0000000..4e701fe --- /dev/null +++ b/examples/petrinet/models/schedules/petrinet3.drawio @@ -0,0 +1,915 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/petrinet/runner.py b/examples/petrinet/runner.py index 6df572a..6e99a96 100644 --- a/examples/petrinet/runner.py +++ b/examples/petrinet/runner.py @@ -41,13 +41,13 @@ if __name__ == "__main__": - action_generator = RuleSchedular(state, mm_rt, mm_rt_ramified, verbose=True, directory="models") + scheduler = RuleScheduler(state, mm_rt, mm_rt_ramified, verbose=True, directory="models") # if action_generator.load_schedule(f"petrinet.od"): # if action_generator.load_schedule("schedules/combinatory.drawio"): if action_generator.load_schedule("schedules/petrinet3.drawio"): - action_generator.generate_dot("../dot.dot") - code, message = action_generator.run(ODAPI(state, m_rt_initial, mm_rt)) + scheduler.generate_dot("../dot.dot") + code, message = scheduler.run(ODAPI(state, m_rt_initial, mm_rt)) print(f"{code}: {message}") diff --git a/transformation/schedule/Tests/Test_meta_model.py b/transformation/schedule/Tests/Test_meta_model.py index 47392c1..a0ef942 100644 --- a/transformation/schedule/Tests/Test_meta_model.py +++ b/transformation/schedule/Tests/Test_meta_model.py @@ -7,12 +7,9 @@ sys.path.insert( 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../")) ) -from icecream import ic - from api.od import ODAPI from bootstrap.scd import bootstrap_scd -from examples.schedule import rule_schedular -from examples.schedule.rule_schedular import ScheduleActionGenerator +from transformation.schedule.rule_scheduler import RuleScheduler from state.devstate import DevState from transformation.ramify import ramify from util import loader @@ -37,7 +34,7 @@ class Test_Meta_Model(unittest.TestCase): def setUp(self): self.model = ODAPI(*self.model_param) self.out = io.StringIO() - self.generator = ScheduleActionGenerator( + self.generator = RuleScheduler( *self.generator_param, directory=self.dir + "/models", verbose=True, @@ -50,9 +47,7 @@ class Test_Meta_Model(unittest.TestCase): try: self.generator.load_schedule(f"schedule/{file}") errors = self.out.getvalue().split("\u25b8")[1:] - ic(errors) if len(errors) != len(expected_substr_err.keys()): - ic("len total errors") assert len(errors) == len(expected_substr_err.keys()) for err in errors: error_lines = err.strip().split("\n") @@ -62,13 +57,9 @@ class Test_Meta_Model(unittest.TestCase): key = key_pattern break else: - ic("no matching key") - ic(line) assert False expected = expected_substr_err[key] if (len(error_lines) - 1) != len(expected): - ic("len substr errors") - ic(line) assert (len(error_lines) - 1) == len(expected) it = error_lines.__iter__() it.__next__() @@ -77,15 +68,11 @@ class Test_Meta_Model(unittest.TestCase): all(exp in err_line for exp in line_exp) for line_exp in expected ): - ic("wrong substr error") - ic(line) - ic(error_lines) assert False expected_substr_err.pop(key) except AssertionError: raise except Exception as e: - ic(e) assert False def test_no_start(self): @@ -101,12 +88,15 @@ class Test_Meta_Model(unittest.TestCase): self._test_conformance("multiple_end.od", {("End", "Cardinality"): []}) def test_connections_start(self): + # try to load the following schedule. + # The schedules contains happy day nodes and faulty nodes. + # Use the error messages to select error location and further validate the multiple reasons of failure. self._test_conformance( "connections_start.od", { - ("Start", "start"): [ - ["input exec", "foo_in", "exist"], - ["output exec", "out", "multiple"], + ("Start", "start"): [ # locate failure (contains these two substrings), make sure other do not fully overlap -> flakey test + ["input exec", "foo_in", "exist"], # 4 total reasons, a reason contains these three substrings + ["output exec", "out", "multiple"], # a reason will match to exactly one subnstring list ["output exec", "foo_out", "exist"], ["input data", "in", "exist"], ] @@ -180,32 +170,52 @@ class Test_Meta_Model(unittest.TestCase): ) def test_connections_modify(self): + #TODO: + # see test_connections_merge self._test_conformance( "connections_modify.od", { + ("Invalid source", "Conn_exec"): [], + ("Invalid target", "Conn_exec"): [], ("Modify", "m_foo"): [ - ["input exec", "in", "exist"], - ["input exec", "in", "exist"], - ["output exec", "out", "exist"], ["input data", "foo_in", "exist"], ["output data", "foo_out", "exist"], ["input data", "in", "multiple"], + ], + ("Modify", "m_exec"): [ + ["input exec", "in", "exist"], + ["input exec", "in", "exist"], + ["output exec", "out", "exist"], ] }, ) def test_connections_merge(self): + #TODO: + # mm: + # association Conn_exec [0..*] Exec -> Exec [0..*] { + # ...; + # } + # m: + # Conn_exec ( Data -> Exec) {...;} -> Invalid source type 'Merge' for link '__Conn_exec_3:Conn_exec' (1) + # -> Invalid target type 'End' for link '__Conn_exec_3:Conn_exec' (2) + # Conn_exec ( Exec -> Data) {...;} -> No error at all, inconsistent and unexpected behaviour (3) + # different combinations behave unexpected + self._test_conformance( "connections_merge.od", { + ("Invalid source", "Conn_exec"): [], # (1), expected + ("Invalid target", "Conn_exec"): [], # (2), invalid error, should not be shown ("Merge", "m_foo"): [ - ["input exec", "in", "exist"], + ["input data", "foo_in", "exist"], + ["input data", "in2", "multiple"], + ["output data", "foo_out", "exist"], + ], + ("Merge", "m_exec"): [ # (3), checked in Merge itself ["input exec", "in", "exist"], ["output exec", "out", "exist"], - ["input data", "foo_in", "exist"], - ["output data", "foo_out", "exist"], - ["input data", "in2", "multiple"], - ] + ], }, ) @@ -274,7 +284,7 @@ class Test_Meta_Model(unittest.TestCase): ["Unexpected type", "ports_exec_out", "str"], ["Unexpected type", "ports_data_out", "str"], ], - ("Start", '"int"'): [ + ("Start", '"int"'): [ # included " to avoid flakey test ["Unexpected type", "ports_exec_out", "int"], ["Unexpected type", "ports_data_out", "int"], ], @@ -380,13 +390,16 @@ class Test_Meta_Model(unittest.TestCase): ["Unexpected type", "ports_data_out", "NoneType"], ["Unexpected type", "ports_data_in", "NoneType"], ], - ("Action", '"invalid"'): [ + ('"Action"', '"invalid"'): [ ["Invalid python", "ports_exec_out"], ["Invalid python", "ports_exec_in"], ["Invalid python", "ports_data_out"], ["Invalid python", "ports_data_in"], ], - ("Action_action", "invalid_action"): [], + ('"Action_action"', '"invalid_action"'): [ + ["Invalid python code"], + ["line"], + ], ("Action", "subtype"): [ ["Unexpected type", "ports_exec_out", "list"], ["Unexpected type", "ports_exec_in", "list"], diff --git a/transformation/schedule/Tests/Test_xmlparser.py b/transformation/schedule/Tests/Test_xmlparser.py index 0530ee7..b3b8a08 100644 --- a/transformation/schedule/Tests/Test_xmlparser.py +++ b/transformation/schedule/Tests/Test_xmlparser.py @@ -1,16 +1,14 @@ -import io import os import unittest -from transformation.schedule import rule_scheduler -from transformation.schedule.rule_scheduler import RuleSchedular +from transformation.schedule.rule_scheduler import RuleScheduler from state.devstate import DevState class MyTestCase(unittest.TestCase): def setUp(self): state = DevState() - self.generator = RuleSchedular(state, "", "") + self.generator = RuleScheduler(state, "", "") def test_empty(self): try: diff --git a/transformation/schedule/Tests/models/schedule/connections_merge.od b/transformation/schedule/Tests/models/schedule/connections_merge.od index b564525..8144496 100644 --- a/transformation/schedule/Tests/models/schedule/connections_merge.od +++ b/transformation/schedule/Tests/models/schedule/connections_merge.od @@ -12,6 +12,10 @@ m3:Match{ file="rules/transition.od"; } +m_exec:Merge { + ports_data_in = `["in1", "in2"]`; +} + m_foo:Merge { ports_data_in = `["in1", "in2"]`; } @@ -28,10 +32,8 @@ end:End { :Conn_exec (m -> m2) {from="fail";to="in";} :Conn_exec (m -> m3) {from="success";to="in";} -:Conn_exec (m2 -> m_foo) {from="success";to="in";} -:Conn_exec (m2 -> m_foo) {from="fail";to="in";} - -:Conn_exec (m_foo -> end) {from="out";to="in";} +:Conn_exec (m2 -> m_exec) {from="success";to="in";} +:Conn_exec (m_exec -> end) {from="out";to="in";} :Conn_data (start -> m_foo) {from="1";to="in1";} :Conn_data (start -> m_foo) {from="1";to="in2";} diff --git a/transformation/schedule/Tests/models/schedule/connections_modify.od b/transformation/schedule/Tests/models/schedule/connections_modify.od index f3eebdc..9027d0c 100644 --- a/transformation/schedule/Tests/models/schedule/connections_modify.od +++ b/transformation/schedule/Tests/models/schedule/connections_modify.od @@ -12,6 +12,7 @@ m3:Match{ file="rules/transition.od"; } +m_exec:Modify m_foo:Modify m_void:Modify @@ -25,10 +26,10 @@ end:End { :Conn_exec (m -> m2) {from="fail";to="in";} :Conn_exec (m -> m3) {from="success";to="in";} -:Conn_exec (m2 -> m_foo) {from="success";to="in";} -:Conn_exec (m2 -> m_foo) {from="fail";to="in";} +:Conn_exec (m2 -> m_exec) {from="success";to="in";} +:Conn_exec (m2 -> m_exec) {from="fail";to="in";} -:Conn_exec (m_foo -> end) {from="out";to="in";} +:Conn_exec (m_exec -> end) {from="out";to="in";} :Conn_data (start -> mo) {from="1";to="in";} :Conn_data (mo -> m2) {from="out";to="in";} diff --git a/transformation/schedule/doc/images/example_1.png b/transformation/schedule/doc/images/example_1.png new file mode 100644 index 0000000..8ea0451 Binary files /dev/null and b/transformation/schedule/doc/images/example_1.png differ diff --git a/transformation/schedule/doc/images/example_2.png b/transformation/schedule/doc/images/example_2.png new file mode 100644 index 0000000..40994fd Binary files /dev/null and b/transformation/schedule/doc/images/example_2.png differ diff --git a/transformation/schedule/doc/images/example_3.png b/transformation/schedule/doc/images/example_3.png new file mode 100644 index 0000000..d3092bb Binary files /dev/null and b/transformation/schedule/doc/images/example_3.png differ diff --git a/transformation/schedule/doc/images/geraniums-main.png b/transformation/schedule/doc/images/geraniums-main.png new file mode 100644 index 0000000..42c7174 Binary files /dev/null and b/transformation/schedule/doc/images/geraniums-main.png differ diff --git a/transformation/schedule/doc/images/geraniums-repot_flowers.png b/transformation/schedule/doc/images/geraniums-repot_flowers.png new file mode 100644 index 0000000..4a89de0 Binary files /dev/null and b/transformation/schedule/doc/images/geraniums-repot_flowers.png differ diff --git a/transformation/schedule/doc/schedule.md b/transformation/schedule/doc/schedule.md new file mode 100644 index 0000000..39837f9 --- /dev/null +++ b/transformation/schedule/doc/schedule.md @@ -0,0 +1,251 @@ +# Schedule Module + +This module is used to define and execute model transformations using a schedule in the muMLE framework. +The development of this module is port of a research project of Robbe Teughels with Joeri Exelmans and Hans Vangheluwe. + +## Module Structure + +The entire module is wrapped in single interface [schedule.py](../rule_scheduler.py) responsible for loading, executing and other optional functionalities, such as generating dot files. +Loading modules (.py and .drawio) requires compilation. All these transformations are grouped together in [generator.py](../generator.py). +The interactions with the muMLE framework uses the custom interface: [rule_executor.py](../rule_executor.py). This reduces the dependency between the module and the framework. + +Schedules are compiled to python files. These files have a fixed interface defined in [schedule.pyi](../schedule.pyi). +This interface includes functionalities that will setup the schedule structure and link patterns or other schedules from the module interface with the nodes. +The compiled files do not include any functional implementation to reduce their size and compile time. They are linked to a libary [schedule_lib](../schedule_lib) including an implementation for each node type. +This means that nodes can be treated as a black box by the schedule. This architecture allowing easier testing of the library as generation is fully independent of the core implementation. + +The implementation of a given node is similar in the inheritance compared to the original meta-model to increasing traceability between the original instance and the compiled instance. + +## Usage + +### Running Module + +```python + +from state.devstate import DevState +from bootstrap.scd import bootstrap_scd +from util import loader +from transformation.ramify import ramify +from api.od import ODAPI +from transformation.schedule.rule_scheduler import RuleScheduler + +state = DevState() +scd_mmm = bootstrap_scd(state) + +# load model and meta-model +metamodel_cs = open('your_metamodel.od', 'r', encoding="utf-8").read() +model_cs = open('your_model.od', 'r', encoding="utf-8").read() + +# Parse them +metamodel = loader.parse_and_check(state, metamodel_cs, scd_mmm, "your_metamodel") +model = loader.parse_and_check(state, model_cs, metamodel, "Example model") + +# Ramified model +metamodel_ramified = ramify(state, metamodel) + +# scheduler +scheduler = RuleScheduler(state, metamodel, metamodel_ramified) + +# load schedule +scheduler.load_schedule("your_schedule.od") +# scheduler.load_schedule("your_schedule.py") # compiled version (without conformance checking) +# scheduler.load_schedule("your_schedule.drawio") # main page will be executed + +# execute model transformation +api = ODAPI(state, model, metamodel) +scheduler.run(api) +``` + +#### Simple example schedules (.od format) + +A schedule is executed from start to end or NullNode (reachable only from unconnected exec-gates). +Given the following basic schedule (ARule without NAC), the first match of the pre-condition_pattern is used to rewrite the host graph. +This schedule expect at least one match as the `fail' exec-gate of the match is not connected. +Zero matches leads to a NullState, resulting in early termination. + +```markdown +start:Start +end:End + +# match once +m:Match{ + file = "your_pre-condition_pattern.od"; + n = 1; +} + +# rewrite +r:Rewrite{ + file = "your_post-condition_pattern.od"; +} + +:Conn_exec (start -> m) {from="out"; to="in";} +:Conn_exec (m -> r) {from="success"; to="in";} +:Conn_exec (r -> end) {from="out"; to="in";} + +:Conn_data (m -> r) {from="out"; to="in";} +``` +![schedule_1](images/example_1.png) + +With some small adjustments, all matches can be rewritten (FRule without NAC) + +```markdown +start:Start +end:End + +# match all +m:Match{ + file = "your_pre-condition_pattern.od"; + # n = +INF (if missing: all matches) +} + +l:Loop + +# rewrite +r:Rewrite{ + file = "your_post-condition_pattern.od"; +} + +:Conn_exec (start -> m) {from="out"; to="in";} +:Conn_exec (m -> l) {from="success"; to="in";} +:Conn_exec (l -> r) {from="it"; to="in";} +:Conn_exec (r -> l) {from="out"; to="in";} +:Conn_exec (l -> end) {from="out"; to="in";} + +:Conn_data (m -> l) {from="out"; to="in";} +:Conn_data (l -> r) {from="out"; to="in";} +``` +![schedule_2](images/example_2.png) + +Adding a NAC to this example: adding a match using the previous match and expecting it to fail. (FRule with NAC) + +```markdown +start:Start +end:End + +# match all +m:Match{ + file = "your_pre-condition_pattern.od"; + # n = +INF (if missing: all matches) +} + +l:Loop + +# NAC +n:Match{ + file = "your_NAC_pre-condition_pattern.od"; + n = 1; # one fail is enough +} + +# rewrite +r:Rewrite{ + file = "your_post-condition_pattern.od"; +} + +:Conn_exec (start -> m) {from="out"; to="in";} +:Conn_exec (m -> l) {from="success"; to="in";} +:Conn_exec (l -> n) {from="it"; to="in";} +:Conn_exec (n -> r) {from="fail"; to="in";} +:Conn_exec (r -> l) {from="out"; to="in";} +:Conn_exec (l -> end) {from="out"; to="in";} + +:Conn_data (m -> l) {from="out"; to="in";} +:Conn_data (l -> n) {from="out"; to="in";} +:Conn_data (l -> r) {from="out"; to="in";} +``` +![schedule_3](images/example_3.png) + +## Node Types + +### Start +This node indicates the start of a schedule. +It signature (additional ports) can be used to insert match sets or alternative exec-paths, increasing reusability. + +[Start](schedule_lib/start.md) + +### End +Counterpart to Start node. Reaching this node result in successful termination of the schedule. +It signature (additional ports) can be used to extract match sets or alternative exec-paths, increasing reusability. + +[End](schedule_lib/end.md) + +### Match +Matches a pre-condition pattern on the host-graph. A primitive defined in T-Core + +[Match](schedule_lib/match.md) + +### Rewrite +Rewrite the host-graph using a post-condition pattern. A primitive defined in T-Core + +[Rewrite](schedule_lib/rewrite.md) + +### Modify +Modifies the match set. This allows patterns to name elements to their linking. +This node modifies or deletes elements to be usable as pivot in another pattern with different names. +An example usage can be found in [examples/geraniums](../../../examples/geraniums). + +In the following schedule, a cracked filed was matched and no longer needed. +The Modify node deletes this, allowing for the flowering flower match node to use a pattern without this element, reducing the size and making it more general. + ![geraniums_main](images/geraniums-main.png) + +[Modify](schedule_lib/modify.md) + +### Merge +Combines multiple matches. +Allowing patterns to be split into different parts or reuse a specific part with another match without recalculating. +An example usage can be found in [examples/geraniums](../../../examples/geraniums). + +In the following sub-schedule, a new pot and the flower with old pot and their connection, is combined to move the flower in a rewrite. +Replanting multiple flowers into one new pot would require markers, making the matching harder in order to combine these elements without the use of this node. + +![geraniums_repot_flowers](images/geraniums-repot_flowers.png) + +[Merge](schedule_lib/merge.md) + +### Store +Combines matches (set) into a new match set. +Use the exec port to insert the data on the associated data-port to the set. + +The direct usage of this node is limited but invaluable for libraries. +An example usage is petrinet-execution with user interface. +This requires a list of all transitions that can fire. +Matching "all transitions" followed by a loop to check the NAC leaves single matches. +This nodes allows these matches to be recombined into a set that can be used to choose a transition from. + +[Store](schedule_lib/store.md) + +### Loop +Iterate over a given match set. +Nodes such as Match or Rewrite uses a single match as a pivot. +Executing these nodes over all the element is possible with this node. +See the examples in [Modify](#Modify) or [Merge](#Merge) for an example view. + +[Loop](schedule_lib/loop.md) + +### Print +Print the input data. This is mainly used as a debugging/testing tool to validate intermediate information or state. + +[Print](schedule_lib/print.md) + +### Action +This node allows for code to be injected into the schedule. +This node can be used for general purpuse and even recreate all other nodes (except start and end). +Not all functionalities can be described using the current nodes. For petrinets, an example can be to generate a visual overview of the petrinet-system. + +[Action.md](schedule_lib/action.md) + +## file formats + +### .od +This is the original textual file format used by the framework. The main advantage of this format is the integration with the framework that allows conformance checking of the scheduling language. +Therefore, all other formats are converted to this type for conformance checking before being compiled. + +### .py +All schedules are compiled to python after conformance checking. Allowing this format provides the benefit to load schedules without expensive compilation or conformance checking, reducing computational cost. +This format is recommended in the deployment of applications where the schedule will not change. +It is not advisable to implement schedules directly in this format as conformance checking guarantees proper working of the schedule module. + +### .drawio +A visual format for the drawio application. +The library includes a drawio [library](../schedule_lib/Schedule_lib.xml) that includes a representation with additional fields for easy integration with the application. +The main advantage of this format is the usage of pages that allows sub-schedules be easily created and organised within one schedule. (layers are not allowed) + diff --git a/transformation/schedule/doc/schedule_lib/end.md b/transformation/schedule/doc/schedule_lib/end.md new file mode 100644 index 0000000..9805841 --- /dev/null +++ b/transformation/schedule/doc/schedule_lib/end.md @@ -0,0 +1 @@ +# Under construction \ No newline at end of file diff --git a/transformation/schedule/schedule_lib/README.md b/transformation/schedule/doc/schedule_lib/node.md similarity index 100% rename from transformation/schedule/schedule_lib/README.md rename to transformation/schedule/doc/schedule_lib/node.md diff --git a/transformation/schedule/doc/schedule_lib/start.md b/transformation/schedule/doc/schedule_lib/start.md new file mode 100644 index 0000000..9805841 --- /dev/null +++ b/transformation/schedule/doc/schedule_lib/start.md @@ -0,0 +1 @@ +# Under construction \ No newline at end of file diff --git a/transformation/schedule/models/scheduling_MM.od b/transformation/schedule/models/scheduling_MM.od index edae0b9..73f5131 100644 --- a/transformation/schedule/models/scheduling_MM.od +++ b/transformation/schedule/models/scheduling_MM.od @@ -7,12 +7,11 @@ association Conn_exec [0..*] Exec -> Exec [0..*] { abstract class Data association Conn_data [0..*] Data -> Data [0..*] { - Integer from; - Integer to; + String from; + String to; } -abstract class Node (Exec, Data) -class Start [1..1] (Node) { +class Start [1..1] (Exec, Data) { optional ActionCode ports_exec_out; optional ActionCode ports_data_out; ``` @@ -28,7 +27,7 @@ class Start [1..1] (Node) { err ```; } -class End [1..1] (Node) { +class End [1..1] (Exec, Data) { optional ActionCode ports_exec_in; optional ActionCode ports_data_in; ``` @@ -45,7 +44,7 @@ class End [1..1] (Node) { ```; } -abstract class Rule (Node) +abstract class Rule (Exec, Data) { String file; } @@ -75,7 +74,7 @@ class Rewrite (Rule) ```; } -class Action (Node) +class Action (Exec, Data) { optional ActionCode ports_exec_in; optional ActionCode ports_exec_out; @@ -100,7 +99,7 @@ class Action (Node) } -class Modify (Node) +class Modify (Data) { optional ActionCode rename; optional ActionCode delete; @@ -122,7 +121,7 @@ class Modify (Node) ```; } -class Merge (Node) +class Merge (Data) { ActionCode ports_data_in; ``` @@ -138,7 +137,7 @@ class Merge (Node) ```; } -class Store (Node) +class Store (Exec, Data) { ActionCode ports; ``` @@ -154,7 +153,7 @@ class Store (Node) ```; } -class Schedule (Node) +class Schedule (Exec, Data) { String file; ``` @@ -167,7 +166,7 @@ class Schedule (Node) ```; } -class Loop(Node) +class Loop(Exec, Data) { ``` check_all_connections(this, [ @@ -179,7 +178,7 @@ class Loop(Node) ```; } -class Print(Node) +class Print(Exec, Data) { optional Boolean event; optional String label; diff --git a/transformation/schedule/rule_scheduler.py b/transformation/schedule/rule_scheduler.py index 474a6ab..2b2e133 100644 --- a/transformation/schedule/rule_scheduler.py +++ b/transformation/schedule/rule_scheduler.py @@ -34,7 +34,7 @@ if TYPE_CHECKING: from transformation.schedule.schedule import Schedule -class RuleSchedular: +class RuleScheduler: __slots__ = ( "rule_executor", "schedule_main", diff --git a/transformation/schedule/schedule.pyi b/transformation/schedule/schedule.pyi index 0e6547f..0edc014 100644 --- a/transformation/schedule/schedule.pyi +++ b/transformation/schedule/schedule.pyi @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING from transformation.schedule.schedule_lib import * if TYPE_CHECKING: from transformation.schedule.rule_executor import RuleExecutor - from rule_scheduler import RuleSchedular + from rule_scheduler import RuleScheduler class Schedule: __slots__ = { @@ -14,5 +14,5 @@ class Schedule: @staticmethod def get_matchers(): ... - def init_schedule(self, schedular: RuleSchedular, rule_executor: RuleExecutor, matchers): ... + def init_schedule(self, scheduler: RuleScheduler, rule_executor: RuleExecutor, matchers): ... def generate_dot(self, *args, **kwargs): ... \ No newline at end of file diff --git a/transformation/schedule/schedule_lib/exec_node.py b/transformation/schedule/schedule_lib/exec_node.py index c46125f..ea1cc8b 100644 --- a/transformation/schedule/schedule_lib/exec_node.py +++ b/transformation/schedule/schedule_lib/exec_node.py @@ -1,5 +1,9 @@ from abc import abstractmethod +from typing import override +from jinja2 import Template + from api.od import ODAPI +from .funcs import generate_dot_edge from .node import Node @@ -33,3 +37,25 @@ class ExecNode(Node): @abstractmethod def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: return None + + @override + def generate_dot( + self, nodes: list[str], edges: list[str], visited: set[int], template: Template + ) -> None: + for out_port, edge in self.next_node.items(): + template.render() + generate_dot_edge( + self, + edge[0], + edges, + template, + kwargs={ + "prefix": "e", + "from_gate": out_port, + "to_gate": edge[1], + "color": "darkblue", + }, + ) + + for edge in self.next_node.values(): + edge[0].generate_dot(nodes, edges, visited, template) diff --git a/transformation/schedule/schedule_lib/match.py b/transformation/schedule/schedule_lib/match.py index f3da3f2..e0b097f 100644 --- a/transformation/schedule/schedule_lib/match.py +++ b/transformation/schedule/schedule_lib/match.py @@ -52,7 +52,7 @@ class Match(ExecNode, DataNode): nodes, template, **{ - "label": f"match_{self.n}\n{self.label}", + "label": f"match\n{self.label}\nn = {self.n}", "ports_exec": ( self.get_exec_input_gates(), self.get_exec_output_gates(), diff --git a/transformation/schedule/schedule_lib/rewrite.py b/transformation/schedule/schedule_lib/rewrite.py index ba2be83..2196d1d 100644 --- a/transformation/schedule/schedule_lib/rewrite.py +++ b/transformation/schedule/schedule_lib/rewrite.py @@ -41,7 +41,7 @@ class Rewrite(ExecNode, DataNode): nodes, template, **{ - "label": "rewrite", + "label": f"rewrite\n{self.label}", "ports_exec": ( self.get_exec_input_gates(), self.get_exec_output_gates(), diff --git a/transformation/schedule/schedule_lib/sub_schedule.py b/transformation/schedule/schedule_lib/sub_schedule.py index 7b97e43..048658c 100644 --- a/transformation/schedule/schedule_lib/sub_schedule.py +++ b/transformation/schedule/schedule_lib/sub_schedule.py @@ -8,7 +8,7 @@ from .exec_node import ExecNode from .funcs import not_visited, generate_dot_node, IdGenerator if TYPE_CHECKING: - from ..rule_scheduler import RuleSchedular + from ..rule_scheduler import RuleScheduler class ScheduleState: @@ -16,9 +16,9 @@ class ScheduleState: self.end_gate: str = "" class SubSchedule(ExecNode, DataNode): - def __init__(self, schedular: "RuleSchedular", file: str) -> None: - self.schedule = schedular._load_schedule(file, _main=False) - self.schedular = schedular + def __init__(self, scheduler: "RuleScheduler", file: str) -> None: + self.schedule = scheduler._load_schedule(file, _main=False) + self.scheduler = scheduler super().__init__() self.state: dict[int, ScheduleState] = {} @@ -58,7 +58,7 @@ class SubSchedule(ExecNode, DataNode): @override def execute(self, port: str, exec_id: int, od: ODAPI) -> tuple[int, any] | None: - runstatus, result = self.schedular._runner( + runstatus, result = self.scheduler._runner( od, self.schedule, port, diff --git a/transformation/schedule/templates/schedule_dot.j2 b/transformation/schedule/templates/schedule_dot.j2 index 7937884..ca715dc 100644 --- a/transformation/schedule/templates/schedule_dot.j2 +++ b/transformation/schedule/templates/schedule_dot.j2 @@ -11,9 +11,14 @@ digraph G { {% endfor %} } -{% macro Node(label, id, ports_exec=[], ports_data=[]) %} +{% macro Node(label, id, ports_exec=[], ports_data=[], debug = False) %} subgraph cluster_{{ id }} { - label = "{{ id }}__{{ label }}"; + label = " + {%- if debug %} + {{ id }}_ + {%- endif -%} + {{ label }}" + style = rounded; input_{{ id }} [ shape=rect; @@ -54,7 +59,7 @@ output_{{ from_id }}:{{ prefix }}_{{ from_gate }} -> input_{{ to_id }}:{{ prefix {% endif %} {% else %} - X +   {% endif %} > {%- endmacro %} \ No newline at end of file diff --git a/transformation/schedule/templates/schedule_template.j2 b/transformation/schedule/templates/schedule_template.j2 index 6075b61..e696681 100644 --- a/transformation/schedule/templates/schedule_template.j2 +++ b/transformation/schedule/templates/schedule_template.j2 @@ -31,7 +31,7 @@ {%- endmacro %} {% macro Schedule(name, file) %} -{{ name }} = SubSchedule(schedular, "{{ file }}") +{{ name }} = SubSchedule(scheduler, "{{ file }}") {%- endmacro %} {% macro Loop(name) %} diff --git a/transformation/schedule/templates/schedule_template_wrap.j2 b/transformation/schedule/templates/schedule_template_wrap.j2 index 59bb425..d1e8dfc 100644 --- a/transformation/schedule/templates/schedule_template_wrap.j2 +++ b/transformation/schedule/templates/schedule_template_wrap.j2 @@ -16,7 +16,7 @@ class Schedule: {% endfor %} ] - def init_schedule(self, schedular, rule_executer, matchers): + def init_schedule(self, scheduler, rule_executer, matchers): {% for block in blocks_start_end%} {{ block }} {% endfor %}