Add 'simplified' version of the FTG+PM++ formalism with operational semantics
This commit is contained in:
parent
ced3edbd08
commit
d00b9c25db
16 changed files with 1135 additions and 0 deletions
47
examples/ftg_pm_pt/ftg_pm_pt.py
Normal file
47
examples/ftg_pm_pt/ftg_pm_pt.py
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import os
|
||||
|
||||
# Todo: remove src.backend.muMLE from the imports
|
||||
from state.devstate import DevState
|
||||
from bootstrap.scd import bootstrap_scd
|
||||
from concrete_syntax.textual_od.parser import parse_od
|
||||
from api.od import ODAPI
|
||||
from concrete_syntax.textual_od.renderer import render_od as od_renderer
|
||||
from concrete_syntax.plantuml import make_url as plant_url, renderer as plant_renderer
|
||||
from concrete_syntax.graphviz import make_url as graphviz_url, renderer as graphviz_renderer
|
||||
|
||||
class FtgPmPt:
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.state = DevState()
|
||||
self.scd_mmm = bootstrap_scd(self.state)
|
||||
self.meta_model = self.load_metamodel()
|
||||
self.model = None
|
||||
self.odapi = None
|
||||
self.name = name
|
||||
|
||||
@staticmethod
|
||||
def read_file(file_name):
|
||||
with open(os.path.join(os.path.dirname(__file__), file_name)) as file:
|
||||
return file.read()
|
||||
|
||||
def load_metamodel(self):
|
||||
mm_cs = self.read_file("pm/metamodels/mm_design.od")
|
||||
mm_rt_cs = mm_cs + self.read_file("pm/metamodels/mm_runtime.od")
|
||||
mm_total = mm_rt_cs + self.read_file("pt/metamodels/mm_design.od")
|
||||
return parse_od(self.state, m_text=mm_total, mm=self.scd_mmm)
|
||||
|
||||
def load_model(self, m_text: str | None = None):
|
||||
m_text = "" if not m_text else m_text
|
||||
self.model = parse_od(self.state, m_text=m_text, mm=self.meta_model)
|
||||
self.odapi = ODAPI(self.state, self.model, self.meta_model)
|
||||
|
||||
def render_od(self):
|
||||
return od_renderer(self.state, self.model, self.meta_model, hide_names=False)
|
||||
|
||||
def render_plantuml_object_diagram(self):
|
||||
print(plant_url.make_url(plant_renderer.render_package(
|
||||
self.name, plant_renderer.render_object_diagram(self.state, self.model, self.meta_model)))
|
||||
)
|
||||
|
||||
def render_graphviz_object_diagram(self):
|
||||
print(graphviz_url.make_url(graphviz_renderer.render_object_diagram(self.state, self.model, self.meta_model)))
|
||||
68
examples/ftg_pm_pt/help_functions.py
Normal file
68
examples/ftg_pm_pt/help_functions.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import copy
|
||||
import pickle
|
||||
|
||||
from api.od import ODAPI
|
||||
|
||||
from examples.ftg_pm_pt.helpers.composite_activity import execute_composite_workflow
|
||||
|
||||
def serialize(obj):
|
||||
return pickle.dumps(obj)
|
||||
|
||||
|
||||
def deserialize(obj):
|
||||
return pickle.loads(obj)
|
||||
|
||||
|
||||
def create_activity_links(od: ODAPI, activity, prev_element, ctrl_port, end_trace=None,
|
||||
relation_type="pt_IsFollowedBy"):
|
||||
od.create_link(None, "pt_RelatesTo", activity, ctrl_port)
|
||||
od.create_link(None, relation_type, prev_element, activity)
|
||||
if end_trace:
|
||||
od.create_link(None, "pt_IsFollowedBy", activity, end_trace)
|
||||
|
||||
|
||||
def extract_input_data(od: ODAPI, activity):
|
||||
input_data = {}
|
||||
for has_data_in in od.get_outgoing(activity, "pm_HasDataIn"):
|
||||
data_port = od.get_target(has_data_in)
|
||||
artefact_state = od.get_source(od.get_incoming(od.get_source(od.get_incoming(data_port, "pm_DataFlowOut")[0]), "pm_Of")[0])
|
||||
input_data[od.get_name(data_port)] = deserialize(od.get_slot_value(artefact_state, "data"))
|
||||
return input_data
|
||||
|
||||
|
||||
def execute_activity(od: ODAPI, globs, activity, input_data):
|
||||
inp = copy.deepcopy(input_data) # Necessary, otherwise the function changes the values inside the dictionary -> need the original values for process trace
|
||||
func = globs[od.get_slot_value(activity, "func")]
|
||||
return func(inp) if func.__code__.co_argcount > 0 else func()
|
||||
|
||||
|
||||
def handle_artefact(od: ODAPI, activity, artefact_type, relation_type, data_port=None, data=None,
|
||||
direction="DataFlowIn"):
|
||||
artefact = od.create_object(None, "pt_Artefact")
|
||||
if 'pt_Consumes' == relation_type:
|
||||
od.create_link(None, relation_type, artefact, activity)
|
||||
else:
|
||||
od.create_link(None, relation_type, activity, artefact)
|
||||
if data_port:
|
||||
flow_direction = od.get_incoming if relation_type == 'pt_Consumes' else od.get_outgoing
|
||||
ass_side = od.get_source if relation_type == 'pt_Consumes' else od.get_target
|
||||
pm_artefact = ass_side(flow_direction(data_port, f"pm_{direction}")[0])
|
||||
prev_artefact = find_previous_artefact(od, od.get_incoming(pm_artefact, "pt_BelongsTo"))
|
||||
if prev_artefact:
|
||||
od.create_link(None, "pt_PrevVersion", artefact, prev_artefact)
|
||||
od.create_link(None, "pt_BelongsTo", artefact, pm_artefact)
|
||||
if data is not None:
|
||||
artefact_state = od.get_source(od.get_incoming(pm_artefact, "pm_Of")[0])
|
||||
od.set_slot_value(artefact_state, "data", serialize(data))
|
||||
od.set_slot_value(artefact, "data", serialize(data))
|
||||
|
||||
|
||||
def find_previous_artefact(od: ODAPI, linked_artefacts):
|
||||
return next((od.get_source(link) for link in linked_artefacts if
|
||||
not od.get_incoming(od.get_source(link), "pt_PrevVersion")), None)
|
||||
|
||||
|
||||
def update_control_states(od: ODAPI, activity, ctrl_out):
|
||||
for has_ctrl_in in od.get_outgoing(activity, "pm_HasCtrlIn"):
|
||||
od.set_slot_value(od.get_source(od.get_incoming(od.get_target(has_ctrl_in), "pm_Of")[0]), "active", False)
|
||||
od.set_slot_value(od.get_source(od.get_incoming(ctrl_out, "pm_Of")[0]), "active", True)
|
||||
272
examples/ftg_pm_pt/helpers/composite_activity.py
Normal file
272
examples/ftg_pm_pt/helpers/composite_activity.py
Normal file
|
|
@ -0,0 +1,272 @@
|
|||
from uuid import UUID
|
||||
|
||||
from api.od import ODAPI
|
||||
from examples.ftg_pm_pt.ftg_pm_pt import FtgPmPt
|
||||
from examples.ftg_pm_pt.runner import FtgPmPtRunner
|
||||
|
||||
|
||||
def find_previous_artefact(od: ODAPI, linked_artefacts):
|
||||
return next((od.get_source(link) for link in linked_artefacts if
|
||||
not od.get_incoming(od.get_source(link), "pt_PrevVersion")), None)
|
||||
|
||||
|
||||
def create_activity_links(od: ODAPI, activity, prev_element, ctrl_port, end_trace=None,
|
||||
relation_type="pt_IsFollowedBy"):
|
||||
od.create_link(None, "pt_RelatesTo", activity, ctrl_port)
|
||||
od.create_link(None, relation_type, prev_element, activity)
|
||||
if end_trace:
|
||||
od.create_link(None, "pt_IsFollowedBy", activity, end_trace)
|
||||
|
||||
|
||||
def get_workflow_path(od: ODAPI, activity: UUID):
|
||||
return od.get_slot_value(activity, "subworkflow_path")
|
||||
|
||||
|
||||
def get_workflow(workflow_path: str):
|
||||
with open(workflow_path, "r") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
############################
|
||||
|
||||
def get_runtime_state(od: ODAPI, design_obj: UUID):
|
||||
states = od.get_incoming(design_obj, "pm_Of")
|
||||
if len(states) == 0:
|
||||
print(f"Design object '{od.get_name(design_obj)}' has no runtime state.")
|
||||
return None
|
||||
return od.get_source(states[0])
|
||||
|
||||
|
||||
def get_source_incoming(od: ODAPI, obj: UUID, link_name: str):
|
||||
links = od.get_incoming(obj, link_name)
|
||||
if len(links) == 0:
|
||||
print(f"Object '{od.get_name(obj)} has no incoming links of type '{link_name}'.")
|
||||
return None
|
||||
return od.get_source(links[0])
|
||||
|
||||
|
||||
def get_target_outgoing(od: ODAPI, obj: UUID, link_name: str):
|
||||
links = od.get_outgoing(obj, link_name)
|
||||
if len(links) == 0:
|
||||
print(f"Object '{od.get_name(obj)} has no outgoing links of type '{link_name}'.")
|
||||
return None
|
||||
return od.get_target(links[0])
|
||||
|
||||
|
||||
def set_control_port_value(od: ODAPI, port: UUID, value: bool):
|
||||
state = get_runtime_state(od, port)
|
||||
od.set_slot_value(state, "active", value)
|
||||
|
||||
|
||||
def set_artefact_data(od: ODAPI, artefact: UUID, value: bytes):
|
||||
state = artefact
|
||||
# Only the proces model of the artefact contains a runtime state
|
||||
if od.get_type_name(state) == "pm_Artefact":
|
||||
state = get_runtime_state(od, artefact)
|
||||
od.set_slot_value(state, "data", value)
|
||||
|
||||
|
||||
def get_artefact_data(od: ODAPI, artefact):
|
||||
state = artefact
|
||||
# Only the proces model of the artefact contains a runtime state
|
||||
if od.get_type_name(state) == "pm_Artefact":
|
||||
state = get_runtime_state(od, artefact)
|
||||
return od.get_slot_value(state, "data")
|
||||
|
||||
|
||||
############################
|
||||
|
||||
def set_workflow_control_source(workflow_model: FtgPmPt, ctrl_port_name: str, composite_linkage: dict):
|
||||
od = workflow_model.odapi
|
||||
source_port_name = composite_linkage[ctrl_port_name]
|
||||
source_port = od.get(source_port_name)
|
||||
set_control_port_value(od, source_port, True)
|
||||
|
||||
|
||||
def set_workflow_artefacts(act_od: ODAPI, activity: UUID, workflow_model: FtgPmPt, composite_linkage: dict):
|
||||
for data_port in [act_od.get_target(data_in) for data_in in act_od.get_outgoing(activity, "pm_HasDataIn")]:
|
||||
# Get the data source port of the inner workflow
|
||||
data_port_name = act_od.get_name(data_port)
|
||||
source_port_name = composite_linkage[data_port_name]
|
||||
source_port = workflow_model.odapi.get(source_port_name)
|
||||
|
||||
# Get the artefact that is linked to the data port of the activity
|
||||
act_artefact = get_source_incoming(act_od, data_port, "pm_DataFlowOut")
|
||||
# Get the data of the artefact
|
||||
artefact_data = get_artefact_data(act_od, act_artefact)
|
||||
|
||||
# Get the artefact that is linked to the data port of the inner workflow
|
||||
workflow_artefact = get_target_outgoing(workflow_model.odapi, source_port, "pm_DataFlowIn")
|
||||
set_artefact_data(workflow_model.odapi, workflow_artefact, artefact_data)
|
||||
|
||||
|
||||
def get_activity_port_from_inner_port(composite_linkage: dict, port_name: str):
|
||||
for act_port_name, work_port_name in composite_linkage.items():
|
||||
if work_port_name == port_name:
|
||||
return act_port_name
|
||||
|
||||
|
||||
def execute_composite_workflow(od: ODAPI, activity: UUID, ctrl_port: UUID, composite_linkage: dict,
|
||||
packages: dict | None, matched=None):
|
||||
activity_name = od.get_slot_value(activity, "name")
|
||||
|
||||
# First get the path of the object diagram file that contains the inner workflow of the activity
|
||||
workflow_path = get_workflow_path(od, activity)
|
||||
|
||||
# Read the object diagram file
|
||||
workflow = get_workflow(workflow_path)
|
||||
|
||||
# Create an FtgPmPt object
|
||||
workflow_model = FtgPmPt(activity_name)
|
||||
|
||||
# Load the workflow to the object
|
||||
workflow_model.load_model(workflow)
|
||||
|
||||
# Set the correct control source port of the workflow to active
|
||||
set_workflow_control_source(workflow_model, od.get_name(ctrl_port), composite_linkage[activity_name])
|
||||
|
||||
# If a data port is linked, set the data of the artefact
|
||||
set_workflow_artefacts(od, activity, workflow_model, composite_linkage[activity_name])
|
||||
|
||||
# Create an FtgPmPtRunner object with the FtgPmPt object
|
||||
workflow_runner = FtgPmPtRunner(workflow_model)
|
||||
|
||||
# Set the packages if present
|
||||
workflow_runner.set_packages(packages, is_path=False)
|
||||
|
||||
# Run the FtgPmPtRunner (is a subprocess necessary? This makes it more complicated because now we have direct access to the object)
|
||||
workflow_runner.run()
|
||||
|
||||
# Contains all the ports of the inner workflow -> map back to the activity ports, and so we can set the correct
|
||||
# Control ports to active and also set the data artefacts correctly
|
||||
ports = extract_inner_workflow(workflow_model.odapi)
|
||||
start_act = None
|
||||
end_act = None
|
||||
for port in [port for port in ports if port]:
|
||||
port_name = workflow_model.odapi.get_name(port)
|
||||
activity_port_name = get_activity_port_from_inner_port(composite_linkage[activity_name], port_name)
|
||||
activity_port = od.get(activity_port_name)
|
||||
match workflow_model.odapi.get_type_name(port):
|
||||
case "pm_CtrlSource":
|
||||
start_act = handle_control_source(od, activity_port, matched("prev_trace_element"))
|
||||
case "pm_CtrlSink":
|
||||
end_act = handle_control_sink(od, activity_port, start_act, matched("end_trace"))
|
||||
case "pm_DataSource":
|
||||
handle_data_source(od, activity_port, start_act)
|
||||
case "pm_DataSink":
|
||||
handle_data_sink(od, workflow_model.odapi, activity_port, port, end_act)
|
||||
|
||||
|
||||
def handle_control_source(od: ODAPI, port, prev_trace_elem):
|
||||
set_control_port_value(od, port, False)
|
||||
start_activity = od.create_object(None, "pt_StartActivity")
|
||||
create_activity_links(od, start_activity, prev_trace_elem, port)
|
||||
return start_activity
|
||||
|
||||
|
||||
def handle_control_sink(od: ODAPI, port, start_act, end_trace):
|
||||
set_control_port_value(od, port, True)
|
||||
end_activity = od.create_object(None, "pt_EndActivity")
|
||||
create_activity_links(od, end_activity, start_act, port, end_trace)
|
||||
return end_activity
|
||||
|
||||
|
||||
def handle_data_source(od: ODAPI, port, start_activity):
|
||||
pt_artefact = od.create_object(None, "pt_Artefact")
|
||||
od.create_link(None, "pt_Consumes", pt_artefact, start_activity)
|
||||
|
||||
pm_artefact = get_source_incoming(od, port, "pm_DataFlowOut")
|
||||
pm_artefact_data = get_artefact_data(od, pm_artefact)
|
||||
set_artefact_data(od, pt_artefact, pm_artefact_data)
|
||||
prev_pt_artefact = find_previous_artefact(od, od.get_incoming(pm_artefact, "pt_BelongsTo"))
|
||||
if prev_pt_artefact:
|
||||
od.create_link(None, "pt_PrevVersion", pt_artefact, prev_pt_artefact)
|
||||
od.create_link(None, "pt_BelongsTo", pt_artefact, pm_artefact)
|
||||
|
||||
|
||||
def handle_data_sink(act_od: ODAPI, work_od: ODAPI, act_port, work_port, end_activity):
|
||||
pt_artefact = act_od.create_object(None, "pt_Artefact")
|
||||
act_od.create_link(None, "pt_Produces", end_activity, pt_artefact)
|
||||
|
||||
work_artefact = get_source_incoming(work_od, work_port, "pm_DataFlowOut")
|
||||
work_artefact_data = get_artefact_data(work_od, work_artefact)
|
||||
|
||||
act_artefact = get_target_outgoing(act_od, act_port, "pm_DataFlowIn")
|
||||
|
||||
set_artefact_data(act_od, act_artefact, work_artefact_data)
|
||||
set_artefact_data(act_od, pt_artefact, work_artefact_data)
|
||||
|
||||
prev_pt_artefact = find_previous_artefact(act_od, act_od.get_incoming(act_artefact, "pt_BelongsTo"))
|
||||
if prev_pt_artefact:
|
||||
act_od.create_link(None, "pt_PrevVersion", pt_artefact, prev_pt_artefact)
|
||||
act_od.create_link(None, "pt_BelongsTo", pt_artefact, act_artefact)
|
||||
|
||||
|
||||
def extract_inner_workflow(workflow: ODAPI):
|
||||
# Get the model, this should be only one
|
||||
name, model = workflow.get_all_instances("pm_Model")[0]
|
||||
|
||||
# Get the start of the process trace
|
||||
start_trace = get_source_incoming(workflow, model, "pt_Starts")
|
||||
# Get the end of the process trace
|
||||
end_trace = get_source_incoming(workflow, model, "pt_Ends")
|
||||
|
||||
# Get the first started activity
|
||||
first_activity = get_target_outgoing(workflow, start_trace, "pt_IsFollowedBy")
|
||||
# Get the last ended activity
|
||||
end_activity = get_source_incoming(workflow, end_trace, "pt_IsFollowedBy")
|
||||
|
||||
# Get the control port that started the activity
|
||||
act_ctrl_in = get_target_outgoing(workflow, first_activity, "pt_RelatesTo")
|
||||
# Get the control port that is activated when the activity is executed
|
||||
act_ctrl_out = get_target_outgoing(workflow, end_activity, "pt_RelatesTo")
|
||||
|
||||
# Get the control source of the workflow
|
||||
ports = []
|
||||
for port in workflow.get_incoming(act_ctrl_in, "pm_CtrlFlow"):
|
||||
source = workflow.get_source(port)
|
||||
if workflow.get_type_name(source) == "pm_CtrlSource":
|
||||
# Only one port can activate an activity
|
||||
ports.append(source)
|
||||
break
|
||||
|
||||
# Get the control sink of the workflow
|
||||
for port in workflow.get_outgoing(act_ctrl_out, "pm_CtrlFlow"):
|
||||
sink = workflow.get_target(port)
|
||||
if workflow.get_type_name(sink) == "pm_CtrlSink":
|
||||
# Only one port can be set to active one an activity is ended
|
||||
ports.append(sink)
|
||||
break
|
||||
|
||||
# Get the data port that the activity consumes (if used)
|
||||
consumed_links = workflow.get_incoming(first_activity, "pt_Consumes")
|
||||
if len(consumed_links) > 0:
|
||||
pt_artefact = None
|
||||
for link in consumed_links:
|
||||
pt_artefact = workflow.get_source(link)
|
||||
# Check if it is the first artefact -> contains no previous version
|
||||
if len(workflow.get_outgoing(pt_artefact, "pt_PrevVersion")) == 0:
|
||||
break
|
||||
|
||||
pm_artefact = get_target_outgoing(workflow, pt_artefact, "pt_BelongsTo")
|
||||
# Find the data source port
|
||||
for link in workflow.get_incoming(pm_artefact, "pm_DataFlowIn"):
|
||||
source = workflow.get_source(link)
|
||||
if workflow.get_type_name(source) == "pm_DataSource":
|
||||
# An activity can only use one artefact as input
|
||||
ports.append(source)
|
||||
break
|
||||
|
||||
# Get all data ports that are connected to an artefact that is produced by an activity in the workflow,
|
||||
# where the artefact is also part of main workflow
|
||||
for port_name, data_sink in workflow.get_all_instances("pm_DataSink"):
|
||||
pm_art = get_source_incoming(workflow, data_sink, "pm_DataFlowOut")
|
||||
# If the pm_artefact is linked to a proces trace artefact that is produced, we can add to port
|
||||
links = workflow.get_incoming(pm_art, "pt_BelongsTo")
|
||||
if not len(links):
|
||||
continue
|
||||
# A data sink port linkage will only be added to the proces trace when an activity is ended and so an artefact
|
||||
# is produced, meaning that if a belongsTo link exists, a proces trace artefact is linked to this data port
|
||||
ports.append(data_sink)
|
||||
|
||||
return ports
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
# Match the model
|
||||
model:RAM_pm_Model
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
model:RAM_pm_Model
|
||||
|
||||
# Check if the model isn't already connected to a process trace
|
||||
start_trace:RAM_pt_StartTrace
|
||||
:RAM_pt_Starts (start_trace -> model)
|
||||
end_trace:RAM_pt_EndTrace
|
||||
:RAM_pt_Ends (end_trace -> model)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
# Keep the left hand side
|
||||
model:RAM_pm_Model
|
||||
|
||||
# Connect a process trace to it
|
||||
start_trace:RAM_pt_StartTrace
|
||||
starts:RAM_pt_Starts (start_trace -> model)
|
||||
|
||||
end_trace:RAM_pt_EndTrace
|
||||
ends:RAM_pt_Ends (end_trace -> model)
|
||||
|
||||
# Connect the start with the end
|
||||
:RAM_pt_IsFollowedBy (start_trace -> end_trace)
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# When a control port is active and is connected to an activity, we want to execute the activity
|
||||
# But, if the activity has input_and (input_or = False). It only can be activated if all its inputs are active
|
||||
|
||||
|
||||
# Match the model
|
||||
model:RAM_pm_Model
|
||||
|
||||
# Match the a python automated activity
|
||||
py_activity:RAM_pm_PythonAutomatedActivity {
|
||||
# Check if all connected ports are active in case of input_and
|
||||
condition = ```
|
||||
all_active = True
|
||||
|
||||
# Check for or / and
|
||||
if not get_slot_value(this, "input_or"):
|
||||
# Get all the ctrl in ports
|
||||
for has_ctrl_in in get_outgoing(this, "pm_HasCtrlIn"):
|
||||
c_in_state = get_source(get_incoming(get_target(has_ctrl_in), "pm_Of")[0])
|
||||
# Check if the port is active or not
|
||||
if not get_slot_value(c_in_state, "active"):
|
||||
all_active = False
|
||||
break
|
||||
|
||||
all_active
|
||||
```;
|
||||
} model_to_activity:RAM_pm_Owns (model -> py_activity)
|
||||
|
||||
|
||||
# Match a control activity in port that is active
|
||||
ctrl_in:RAM_pm_CtrlActivityIn
|
||||
|
||||
ctrl_in_state:RAM_pm_CtrlPortState {
|
||||
RAM_active = `get_value(this)`;
|
||||
}
|
||||
|
||||
state_to_port:RAM_pm_Of (ctrl_in_state -> ctrl_in)
|
||||
|
||||
# Match the activity link to the port
|
||||
activity_to_port:RAM_pm_HasCtrlIn (py_activity -> ctrl_in)
|
||||
|
||||
# Match the end of the trace
|
||||
end_trace:RAM_pt_EndTrace
|
||||
ends:RAM_pt_Ends (end_trace -> model)
|
||||
|
||||
# Match the previous trace element before the end trace
|
||||
prev_trace_element:RAM_pt_Event
|
||||
|
||||
followed_by:RAM_pt_IsFollowedBy (prev_trace_element -> end_trace)
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
model:RAM_pm_Model
|
||||
|
||||
py_activity:RAM_pm_PythonAutomatedActivity {
|
||||
|
||||
condition = ```
|
||||
start_activity = create_object(None, "pt_StartActivity")
|
||||
create_activity_links(odapi, start_activity, matched("prev_trace_element"), matched("ctrl_in"))
|
||||
input_data = extract_input_data(odapi, this)
|
||||
result = execute_activity(odapi, globals()["packages"], this, input_data)
|
||||
if len(result) == 3:
|
||||
status_code, output_data, input_used = result
|
||||
else:
|
||||
status_code, output_data, input_used = *result, None
|
||||
if input_used:
|
||||
handle_artefact(odapi, start_activity, "pt_Artefact", "pt_Consumes", get(input_used), input_data[input_used], direction="DataFlowOut")
|
||||
end_activity = create_object(None, "pt_EndActivity")
|
||||
ctrl_out = get(status_code)
|
||||
create_activity_links(odapi, end_activity, start_activity, ctrl_out, end_trace=matched("end_trace"))
|
||||
if output_data:
|
||||
port, data = output_data
|
||||
handle_artefact(odapi, end_activity, "pt_Artefact", "pt_Produces", get(port), data, direction="DataFlowIn")
|
||||
update_control_states(odapi, this, ctrl_out)
|
||||
```;
|
||||
}
|
||||
|
||||
model_to_activity:RAM_pm_Owns
|
||||
|
||||
ctrl_in:RAM_pm_CtrlActivityIn
|
||||
|
||||
ctrl_in_state:RAM_pm_CtrlPortState {
|
||||
RAM_active = `False`;
|
||||
}
|
||||
|
||||
state_to_port:RAM_pm_Of (ctrl_in_state -> ctrl_in)
|
||||
|
||||
activity_to_port:RAM_pm_HasCtrlIn (py_activity -> ctrl_in)
|
||||
|
||||
end_trace:RAM_pt_EndTrace
|
||||
ends:RAM_pt_Ends (end_trace -> model)
|
||||
|
||||
prev_trace_element:RAM_pt_Event
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# When a control port is active and is connected to an activity, we want to execute the activity. If it is a composite one, we execute the inner workflow of it
|
||||
# But, if the activity has input_and (input_or = False). It only can be activated if all its inputs are active
|
||||
|
||||
|
||||
# Match the model
|
||||
model:RAM_pm_Model
|
||||
|
||||
# Match the a python automated activity
|
||||
activity:RAM_pm_Activity {
|
||||
|
||||
RAM_composite = `True`;
|
||||
|
||||
} model_to_activity:RAM_pm_Owns (model -> activity)
|
||||
|
||||
|
||||
# Match a control activity in port that is active
|
||||
ctrl_in:RAM_pm_CtrlActivityIn
|
||||
|
||||
ctrl_in_state:RAM_pm_CtrlPortState {
|
||||
RAM_active = `get_value(this)`;
|
||||
}
|
||||
|
||||
state_to_port:RAM_pm_Of (ctrl_in_state -> ctrl_in)
|
||||
|
||||
# Match the activity link to the port
|
||||
activity_to_port:RAM_pm_HasCtrlIn (activity -> ctrl_in)
|
||||
|
||||
# Match the end of the trace
|
||||
end_trace:RAM_pt_EndTrace
|
||||
ends:RAM_pt_Ends (end_trace -> model)
|
||||
|
||||
# Match the previous trace element before the end trace
|
||||
prev_trace_element:RAM_pt_Event
|
||||
|
||||
followed_by:RAM_pt_IsFollowedBy (prev_trace_element -> end_trace)
|
||||
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
model:RAM_pm_Model
|
||||
|
||||
activity:RAM_pm_Activity {
|
||||
|
||||
RAM_composite = `True`;
|
||||
|
||||
condition = ```
|
||||
# Execute inner workflow
|
||||
execute_composite_workflow(odapi, this, matched("ctrl_in"), globals()["composite_linkage"], globals()["packages"], matched)
|
||||
```;
|
||||
}
|
||||
|
||||
model_to_activity:RAM_pm_Owns
|
||||
|
||||
ctrl_in:RAM_pm_CtrlActivityIn
|
||||
|
||||
ctrl_in_state:RAM_pm_CtrlPortState {
|
||||
RAM_active = `False`;
|
||||
}
|
||||
|
||||
state_to_port:RAM_pm_Of (ctrl_in_state -> ctrl_in)
|
||||
|
||||
activity_to_port:RAM_pm_HasCtrlIn (activity -> ctrl_in)
|
||||
|
||||
end_trace:RAM_pt_EndTrace
|
||||
ends:RAM_pt_Ends (end_trace -> model)
|
||||
|
||||
prev_trace_element:RAM_pt_Event
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Match an active control output port
|
||||
out_state:RAM_pm_CtrlPortState {
|
||||
RAM_active = `get_value(this)`;
|
||||
}
|
||||
|
||||
out:RAM_pm_CtrlOut
|
||||
|
||||
state_to_out:RAM_pm_Of (out_state -> out)
|
||||
|
||||
# Match an inactive control input port
|
||||
in_state:RAM_pm_CtrlPortState {
|
||||
RAM_active = `not get_value(this)`;
|
||||
}
|
||||
|
||||
in:RAM_pm_CtrlIn
|
||||
|
||||
state_to_in:RAM_pm_Of (in_state -> in)
|
||||
|
||||
# Match the connection between those two ports
|
||||
flow:RAM_pm_CtrlFlow (out -> in)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
# Copy the left hand side
|
||||
|
||||
out_state:RAM_pm_CtrlPortState {
|
||||
# Only set the output port to inactive if all connected input ports are set to active
|
||||
RAM_active = ```
|
||||
set_to_active = False
|
||||
|
||||
output_port = matched("out")
|
||||
outgoing_flows = get_outgoing(output_port, "pm_CtrlFlow")
|
||||
|
||||
# for each flow: pm_CtrlFlow -> pm_CtrlIn <- pm_Of <- pm_CtrlPortState == state
|
||||
all_input_port_states = [get_source(get_incoming(get_target(flow), "pm_Of")[0]) for flow in outgoing_flows]
|
||||
input_port_state = matched("in_state")
|
||||
|
||||
for state in all_input_port_states:
|
||||
is_active = get_slot_value(state, "active")
|
||||
|
||||
# If the state is not active and it is not the input port state we have matched and planned to set active
|
||||
# Then we can't yet set this output port state to active
|
||||
if not is_active and state != input_port_state:
|
||||
set_to_active = True
|
||||
break
|
||||
|
||||
# Set the attribute to the assigned value
|
||||
set_to_active
|
||||
```;
|
||||
}
|
||||
|
||||
out:RAM_pm_CtrlOut
|
||||
|
||||
state_to_out:RAM_pm_Of (out_state -> out)
|
||||
|
||||
in_state:RAM_pm_CtrlPortState {
|
||||
# Set the input port active
|
||||
RAM_active = `True`;
|
||||
}
|
||||
|
||||
in:RAM_pm_CtrlIn
|
||||
|
||||
state_to_in:RAM_pm_Of (in_state -> in)
|
||||
|
||||
flow:RAM_pm_CtrlFlow (out -> in)
|
||||
200
examples/ftg_pm_pt/pm/metamodels/mm_design.od
Normal file
200
examples/ftg_pm_pt/pm/metamodels/mm_design.od
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
##################################################
|
||||
|
||||
pm_Model:Class
|
||||
|
||||
##################################################
|
||||
|
||||
pm_Stateful:Class
|
||||
|
||||
##################################################
|
||||
|
||||
pm_ModelElement:Class {
|
||||
abstract = True;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pm_Activity:Class
|
||||
:Inheritance (pm_Activity -> pm_ModelElement)
|
||||
|
||||
pm_Activity_name:AttributeLink (pm_Activity -> String) {
|
||||
name = "name";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
pm_Activity_composite:AttributeLink (pm_Activity -> Boolean) {
|
||||
name = "composite";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
pm_Activity_subworkflow_path:AttributeLink (pm_Activity -> String) {
|
||||
name = "subworkflow_path";
|
||||
optional = True;
|
||||
}
|
||||
|
||||
|
||||
pm_AutomatedActivity:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pm_AutomatedActivity -> pm_Activity)
|
||||
|
||||
pm_AutomatedActivity_input_or:AttributeLink (pm_AutomatedActivity -> Boolean) {
|
||||
name = "input_or";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
pm_PythonAutomatedActivity:Class
|
||||
:Inheritance (pm_PythonAutomatedActivity -> pm_AutomatedActivity)
|
||||
|
||||
pm_PythonAutomatedActivity_func:AttributeLink (pm_PythonAutomatedActivity -> ActionCode) {
|
||||
name = "func";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pm_Artefact:Class
|
||||
:Inheritance (pm_Artefact -> pm_ModelElement)
|
||||
:Inheritance (pm_Artefact -> pm_Stateful)
|
||||
|
||||
##################################################
|
||||
|
||||
pm_CtrlPort:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pm_CtrlPort -> pm_Stateful)
|
||||
|
||||
pm_CtrlIn:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pm_CtrlIn -> pm_CtrlPort)
|
||||
|
||||
pm_CtrlSink:Class {
|
||||
# 1) A control sink port must have at least one incoming control flow
|
||||
# 2) A control sink port can't have any control flow output
|
||||
constraint = ```
|
||||
has_incoming = len(get_incoming(this, "pm_CtrlFlow")) > 0
|
||||
no_outgoing = len(get_outgoing(this, "pm_CtrlFlow")) == 0
|
||||
|
||||
# Return constraint
|
||||
has_incoming and no_outgoing
|
||||
```;
|
||||
} :Inheritance (pm_CtrlSink -> pm_CtrlIn)
|
||||
|
||||
pm_CtrlActivityIn:Class {
|
||||
# 1) Must have at least one incoming control flow
|
||||
constraint = ```
|
||||
has_incoming = len(get_incoming(this, "pm_CtrlFlow")) > 0
|
||||
# Return constraint
|
||||
has_incoming
|
||||
```;
|
||||
} :Inheritance (pm_CtrlActivityIn -> pm_CtrlIn)
|
||||
|
||||
pm_CtrlOut:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pm_CtrlOut -> pm_CtrlPort)
|
||||
|
||||
pm_CtrlSource:Class {
|
||||
# 1) A control source port can't have any control flow inputs
|
||||
# 2) A control source port must have at least one outgoing control flow
|
||||
constraint = ```
|
||||
no_incoming = len(get_incoming(this, "pm_CtrlFlow")) == 0
|
||||
has_outgoing = len(get_outgoing(this, "pm_CtrlFlow")) > 0
|
||||
|
||||
# Return constraint
|
||||
no_incoming and has_outgoing
|
||||
```;
|
||||
} :Inheritance (pm_CtrlSource -> pm_CtrlOut)
|
||||
|
||||
pm_CtrlActivityOut:Class {
|
||||
# 1) Must have at least one outgoing control flow
|
||||
constraint = ```
|
||||
has_outgoing = len(get_outgoing(this, "pm_CtrlFlow")) > 0
|
||||
|
||||
# Return constraint
|
||||
has_outgoing
|
||||
```;
|
||||
} :Inheritance (pm_CtrlActivityOut -> pm_CtrlOut)
|
||||
|
||||
##################################################
|
||||
|
||||
pm_DataPort:Class {
|
||||
abstract = True;
|
||||
}
|
||||
|
||||
pm_DataIn:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pm_DataIn -> pm_DataPort)
|
||||
|
||||
pm_DataSink:Class
|
||||
:Inheritance (pm_DataSink -> pm_DataIn)
|
||||
|
||||
pm_DataActivityIn:Class
|
||||
:Inheritance (pm_DataActivityIn -> pm_DataIn)
|
||||
|
||||
pm_DataOut:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pm_DataOut -> pm_DataPort)
|
||||
|
||||
pm_DataSource:Class
|
||||
:Inheritance (pm_DataSource -> pm_DataOut)
|
||||
|
||||
pm_DataActivityOut:Class
|
||||
:Inheritance (pm_DataActivityOut -> pm_DataOut)
|
||||
|
||||
##################################################
|
||||
##################################################
|
||||
|
||||
pm_Owns:Association (pm_Model -> pm_ModelElement) {
|
||||
source_lower_cardinality = 1;
|
||||
source_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pm_CtrlFlow:Association (pm_CtrlPort -> pm_CtrlPort)
|
||||
|
||||
##################################################
|
||||
|
||||
pm_HasCtrlIn:Association (pm_Activity -> pm_CtrlIn) {
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
}
|
||||
|
||||
pm_HasCtrlOut:Association (pm_Activity -> pm_CtrlOut) {
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
}
|
||||
|
||||
pm_HasDataIn:Association (pm_Activity -> pm_DataIn) {
|
||||
source_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
pm_HasDataOut:Association (pm_Activity -> pm_DataOut) {
|
||||
source_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pm_DataFlowIn:Association (pm_DataOut -> pm_Artefact) {
|
||||
source_lower_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
}
|
||||
|
||||
pm_DataFlowOut:Association (pm_Artefact -> pm_DataIn) {
|
||||
source_lower_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
##################################################
|
||||
|
||||
has_source_and_sink:GlobalConstraint {
|
||||
# There should be at least one source and sink control port
|
||||
constraint = ```
|
||||
contains_source = len(get_all_instances("pm_CtrlSource")) > 0
|
||||
contains_sink = len(get_all_instances("pm_CtrlSink")) > 0
|
||||
|
||||
# return constraint
|
||||
contains_source and contains_sink
|
||||
```;
|
||||
}
|
||||
|
||||
##################################################
|
||||
38
examples/ftg_pm_pt/pm/metamodels/mm_runtime.od
Normal file
38
examples/ftg_pm_pt/pm/metamodels/mm_runtime.od
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
##################################################
|
||||
|
||||
pm_State:Class {
|
||||
abstract = True;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pm_ArtefactState:Class
|
||||
:Inheritance (pm_ArtefactState -> pm_State)
|
||||
|
||||
pm_ArtefactState_data:AttributeLink (pm_ArtefactState -> Bytes) {
|
||||
name = "data";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pm_CtrlPortState:Class
|
||||
:Inheritance (pm_CtrlPortState -> pm_State)
|
||||
|
||||
pm_CtrlPortState_active:AttributeLink (pm_CtrlPortState -> Boolean) {
|
||||
name = "active";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
##################################################
|
||||
##################################################
|
||||
|
||||
pm_Of:Association (pm_State -> pm_Stateful) {
|
||||
# one-to-one
|
||||
source_lower_cardinality = 1;
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
109
examples/ftg_pm_pt/pt/metamodels/mm_design.od
Normal file
109
examples/ftg_pm_pt/pt/metamodels/mm_design.od
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
##################################################
|
||||
|
||||
pt_Event:Class {
|
||||
abstract = True;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pt_Activity:Class {
|
||||
abstract = True;
|
||||
} :Inheritance (pt_Activity -> pt_Event)
|
||||
|
||||
pt_StartActivity:Class {
|
||||
# A start activity can only be related to a control in port
|
||||
constraint = ```
|
||||
correct_related = True
|
||||
|
||||
port = get_target(get_outgoing(this, "pt_RelatesTo")[0])
|
||||
correct_related = port in [uid for _, uid in get_all_instances("pm_CtrlIn")]
|
||||
correct_related
|
||||
```;
|
||||
|
||||
} :Inheritance (pt_StartActivity -> pt_Activity)
|
||||
|
||||
pt_EndActivity:Class {
|
||||
# A end activity can only be related to a control out port
|
||||
constraint = ```
|
||||
correct_related = True
|
||||
|
||||
port = get_target(get_outgoing(this, "pt_RelatesTo")[0])
|
||||
correct_related = port in [uid for _, uid in get_all_instances("pm_CtrlOut")]
|
||||
|
||||
correct_related
|
||||
```;
|
||||
|
||||
} :Inheritance (pt_EndActivity -> pt_Activity)
|
||||
|
||||
##################################################
|
||||
|
||||
pt_StartTrace:Class
|
||||
:Inheritance (pt_StartTrace -> pt_Event)
|
||||
|
||||
pt_EndTrace:Class
|
||||
:Inheritance (pt_EndTrace -> pt_Event)
|
||||
|
||||
##################################################
|
||||
|
||||
pt_Artefact:Class
|
||||
:Inheritance (pt_Artefact -> pt_Event)
|
||||
|
||||
pt_Artefact_data:AttributeLink (pt_Artefact -> Bytes) {
|
||||
name = "data";
|
||||
optional = False;
|
||||
}
|
||||
|
||||
##################################################
|
||||
##################################################
|
||||
|
||||
pt_IsFollowedBy:Association (pt_Event -> pt_Event) {
|
||||
source_upper_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pt_RelatesTo:Association (pt_Activity -> pm_CtrlPort) {
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
pt_Consumes:Association (pt_Artefact -> pt_StartActivity) {
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
pt_Produces:Association (pt_EndActivity -> pt_Artefact) {
|
||||
source_lower_cardinality = 1;
|
||||
source_upper_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
|
||||
pt_Starts:Association (pt_StartTrace -> pm_Model) {
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
pt_Ends:Association (pt_EndTrace -> pm_Model) {
|
||||
source_upper_cardinality = 1;
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
##################################################
|
||||
|
||||
pt_PrevVersion:Association (pt_Artefact -> pt_Artefact) {
|
||||
source_upper_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
pt_BelongsTo:Association (pt_Artefact -> pm_Artefact) {
|
||||
target_lower_cardinality = 1;
|
||||
target_upper_cardinality = 1;
|
||||
}
|
||||
|
||||
##################################################
|
||||
162
examples/ftg_pm_pt/runner.py
Normal file
162
examples/ftg_pm_pt/runner.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
import re
|
||||
|
||||
from state.devstate import DevState
|
||||
from bootstrap.scd import bootstrap_scd
|
||||
from util import loader
|
||||
from transformation.rule import RuleMatcherRewriter
|
||||
from transformation.ramify import ramify
|
||||
from concrete_syntax.graphviz import renderer as graphviz
|
||||
from concrete_syntax.graphviz.make_url import make_url
|
||||
from concrete_syntax.plantuml import renderer as plantuml
|
||||
from concrete_syntax.plantuml.make_url import make_url as plant_make_url
|
||||
from api.od import ODAPI
|
||||
import os
|
||||
from os import listdir
|
||||
from os.path import isfile, join
|
||||
import importlib.util
|
||||
from util.module_to_dict import module_to_dict
|
||||
from examples.ftg_pm_pt import help_functions
|
||||
|
||||
from examples.ftg_pm_pt.ftg_pm_pt import FtgPmPt
|
||||
|
||||
|
||||
|
||||
class FtgPmPtRunner:
|
||||
|
||||
def __init__(self, model: FtgPmPt, composite_linkage: dict | None = None):
|
||||
self.model = model
|
||||
self.ram_mm = ramify(self.model.state, self.model.meta_model)
|
||||
self.rules = self.load_rules()
|
||||
self.packages = None
|
||||
self.composite_linkage = composite_linkage
|
||||
|
||||
def load_rules(self):
|
||||
return loader.load_rules(
|
||||
self.model.state,
|
||||
lambda rule_name, kind: os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
f"operational_semantics/r_{rule_name}_{kind}.od"
|
||||
),
|
||||
self.ram_mm,
|
||||
["connect_process_trace", "trigger_ctrl_flow", "exec_activity", "exec_composite_activity"]
|
||||
)
|
||||
|
||||
def set_packages(self, packages: str | dict, is_path: bool):
|
||||
if not is_path:
|
||||
self.packages = packages
|
||||
return
|
||||
|
||||
self.packages = self.parse_packages(packages)
|
||||
|
||||
def parse_packages(self, packages_path: str) -> dict:
|
||||
return self.collect_functions_from_packages(packages_path, packages_path)
|
||||
|
||||
def collect_functions_from_packages(self, base_path, current_path):
|
||||
functions_dict = {}
|
||||
|
||||
for entry in listdir(current_path):
|
||||
entry_path = join(current_path, entry)
|
||||
|
||||
if isfile(entry_path) and entry.endswith(".py"):
|
||||
module_name = self.convert_path_to_module_name(base_path, entry_path)
|
||||
module = self.load_module_from_file(entry_path)
|
||||
|
||||
for func_name, func in module_to_dict(module).items():
|
||||
functions_dict[f"{module_name}.{func_name}"] = func
|
||||
|
||||
elif not isfile(entry_path):
|
||||
nested_functions = self.collect_functions_from_packages(base_path, entry_path)
|
||||
functions_dict.update(nested_functions)
|
||||
|
||||
return functions_dict
|
||||
|
||||
@staticmethod
|
||||
def convert_path_to_module_name(base_path, file_path):
|
||||
return file_path.replace(base_path, "").replace(".py", "").replace("/", "")
|
||||
|
||||
@staticmethod
|
||||
def load_module_from_file(file_path):
|
||||
spec = importlib.util.spec_from_file_location("", file_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
def create_matcher(self):
|
||||
packages = module_to_dict(help_functions)
|
||||
|
||||
if self.packages:
|
||||
packages.update({ "packages": self.packages })
|
||||
|
||||
if self.composite_linkage:
|
||||
packages.update({ "composite_linkage": self.composite_linkage })
|
||||
|
||||
matcher_rewriter = RuleMatcherRewriter(
|
||||
self.model.state, self.model.meta_model, self.ram_mm, eval_context=packages
|
||||
)
|
||||
return matcher_rewriter
|
||||
|
||||
def visualize_model(self):
|
||||
print(make_url(graphviz.render_object_diagram(self.model.state, self.model.model, self.model.meta_model)))
|
||||
print(plant_make_url(plantuml.render_object_diagram(self.model.state, self.model.model, self.model.meta_model)))
|
||||
|
||||
@staticmethod
|
||||
def __extract_artefact_info(od, pt_art):
|
||||
"""Extract artefact metadata and data."""
|
||||
data = od.get_slot_value(pt_art, "data")
|
||||
pm_art = od.get_name(od.get_target(od.get_outgoing(pt_art, "pt_BelongsTo")[0]))
|
||||
has_prev_version = bool(od.get_outgoing(pt_art, "pt_PrevVersion"))
|
||||
is_last_version = not od.get_incoming(pt_art, "pt_PrevVersion")
|
||||
return {
|
||||
"Artefact Name": pm_art,
|
||||
"Data": data,
|
||||
"Has previous version": has_prev_version,
|
||||
"Is last version": is_last_version
|
||||
}
|
||||
|
||||
def __extract_inputs(self, od, event_node):
|
||||
"""Extract all consumed artefacts for an event."""
|
||||
return [
|
||||
self.__extract_artefact_info(od, od.get_source(consumes))
|
||||
for consumes in od.get_incoming(event_node, "pt_Consumes")
|
||||
]
|
||||
|
||||
def __extract_outputs(self, od, event_node):
|
||||
"""Extract all produced artefacts for an event."""
|
||||
return [
|
||||
self.__extract_artefact_info(od, od.get_target(produces))
|
||||
for produces in od.get_outgoing(event_node, "pt_Produces")
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def to_snake_case(experiment_type):
|
||||
# Finds uppercase letters that are not at the start of the string.
|
||||
# Example: AtomicExperiment -> atomic_experiment
|
||||
return re.sub(r'(?<!^)(?=[A-Z])', '_', experiment_type).lower()
|
||||
|
||||
def run(self, debug_flag: bool = False):
|
||||
matcher = self.create_matcher()
|
||||
|
||||
rule_performed = True
|
||||
while rule_performed:
|
||||
|
||||
# Loop over all the rules first in order priority
|
||||
for i, (rule_name, rule) in enumerate(self.rules.items()):
|
||||
rule_performed = False
|
||||
|
||||
result = matcher.exec_on_first_match(
|
||||
self.model.model, rule, rule_name, in_place=True
|
||||
)
|
||||
|
||||
# If the rule cannot be executed go to the next rule
|
||||
if not result:
|
||||
continue
|
||||
|
||||
rule_performed = True
|
||||
self.model.model, lhs_match, _ = result
|
||||
|
||||
if debug_flag:
|
||||
print(f"Match: {lhs_match}")
|
||||
self.visualize_model()
|
||||
|
||||
# If a rule is performed, break and start loping over the rules from the beginning
|
||||
break
|
||||
Loading…
Add table
Add a link
Reference in a new issue