CBD model is computing Fibonacci numbers! :)
This commit is contained in:
parent
9c68b288c1
commit
80cba4b9f8
27 changed files with 429 additions and 269 deletions
|
|
@ -76,7 +76,8 @@ UUID_REGEX = re.compile(r"[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-
|
|||
|
||||
# Converts an object diagram in MVS state to the pattern matcher graph type
|
||||
# ModelRefs are flattened
|
||||
def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
||||
def model_to_graph(state: State, model: UUID, metamodel: UUID,
|
||||
_filter=lambda node: True, prefix=""):
|
||||
# with Timer("model_to_graph"):
|
||||
od = services_od.OD(model, metamodel, state)
|
||||
scd = SCD(model, state)
|
||||
|
|
@ -121,7 +122,7 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
return node
|
||||
|
||||
# Objects and Links become vertices
|
||||
uuid_to_vtx = { node: to_vtx(node, prefix+key) for key in bottom.read_keys(model) for node in bottom.read_outgoing_elements(model, key) }
|
||||
uuid_to_vtx = { node: to_vtx(node, prefix+key) for key in bottom.read_keys(model) for node in bottom.read_outgoing_elements(model, key) if _filter(node) }
|
||||
graph.vtxs = [ vtx for vtx in uuid_to_vtx.values() ]
|
||||
|
||||
# For every Link, two edges are created (for src and tgt)
|
||||
|
|
@ -183,7 +184,6 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
# # print(type_edge)
|
||||
# graph.edges.append(type_edge)
|
||||
|
||||
|
||||
# Add typing information for:
|
||||
# - classes
|
||||
# - attributes
|
||||
|
|
@ -192,25 +192,31 @@ def model_to_graph(state: State, model: UUID, metamodel: UUID, prefix=""):
|
|||
objects = scd.get_typed_by(class_node)
|
||||
# print("typed by:", class_name, objects)
|
||||
for obj_name, obj_node in objects.items():
|
||||
add_types(obj_node)
|
||||
if _filter(obj_node):
|
||||
add_types(obj_node)
|
||||
for attr_name, attr_node in scd_mm.get_attributes(class_name).items():
|
||||
attrs = scd.get_typed_by(attr_node)
|
||||
for slot_name, slot_node in attrs.items():
|
||||
add_types(slot_node)
|
||||
if _filter(slot_node):
|
||||
add_types(slot_node)
|
||||
for assoc_name, assoc_node in scd_mm.get_associations().items():
|
||||
objects = scd.get_typed_by(assoc_node)
|
||||
# print("typed by:", assoc_name, objects)
|
||||
for link_name, link_node in objects.items():
|
||||
add_types(link_node)
|
||||
if _filter(link_node):
|
||||
add_types(link_node)
|
||||
|
||||
return names, graph
|
||||
|
||||
|
||||
def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
||||
bottom = Bottom(state)
|
||||
|
||||
# compute subtype relations and such:
|
||||
cdapi = CDAPI(state, host_mm)
|
||||
odapi = ODAPI(state, host_m, host_mm)
|
||||
pattern_odapi = ODAPI(state, pattern_m, pattern_mm)
|
||||
pattern_mm_odapi = ODAPI(state, pattern_mm, cdapi.mm)
|
||||
|
||||
# Function object for pattern matching. Decides whether to match host and guest vertices, where guest is a RAMified instance (e.g., the attributes are all strings with Python expressions), and the host is an instance (=object diagram) of the original model (=class diagram)
|
||||
class RAMCompare:
|
||||
|
|
@ -221,6 +227,9 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
|||
type_model_id = bottom.state.read_dict(bottom.state.read_root(), "SCD")
|
||||
self.scd_model = UUID(bottom.state.read_value(type_model_id))
|
||||
|
||||
# constraints need to be checked at the very end, after a complete match is established, because constraint code may refer to matched elements by their name
|
||||
self.conditions_to_check = []
|
||||
|
||||
def match_types(self, g_vtx_type, h_vtx_type):
|
||||
# types only match with their supertypes
|
||||
# we assume that 'RAMifies'-traceability links have been created between guest and host types
|
||||
|
|
@ -256,26 +265,10 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
|||
return False
|
||||
|
||||
python_code = services_od.read_primitive_value(self.bottom, g_vtx.node_id, pattern_mm)[0]
|
||||
return exec_then_eval(python_code,
|
||||
_globals=bind_api_readonly(odapi),
|
||||
_locals={'this': h_vtx.node_id})
|
||||
|
||||
# nested_matches = [m for m in match_od(state, h_ref_m, h_ref_mm, g_ref_m, g_ref_mm)]
|
||||
self.conditions_to_check.append((python_code, h_vtx.node_id))
|
||||
|
||||
|
||||
# print('begin recurse')
|
||||
# g_ref_m, g_ref_mm = g_vtx.modelref
|
||||
# h_ref_m, h_ref_mm = h_vtx.modelref
|
||||
# print('nested_matches:', nested_matches)
|
||||
# if len(nested_matches) == 0:
|
||||
# return False
|
||||
# elif len(nested_matches) == 1:
|
||||
# return True
|
||||
# else:
|
||||
# raise Exception("We have a problem: there is more than 1 match in the nested models.")
|
||||
# print('end recurse')
|
||||
|
||||
# Then, match by value
|
||||
return True # do be determined later, if it's actually a match
|
||||
|
||||
if g_vtx.value == None:
|
||||
return h_vtx.value == None
|
||||
|
|
@ -293,20 +286,26 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
|||
if h_vtx.value == IS_MODELREF:
|
||||
return False
|
||||
|
||||
# python_code = g_vtx.value
|
||||
# try:
|
||||
# return exec_then_eval(python_code,
|
||||
# _globals=bind_api_readonly(odapi),
|
||||
# _locals={'this': h_vtx.node_id})
|
||||
# except Exception as e:
|
||||
# print(e)
|
||||
# return False
|
||||
return True
|
||||
|
||||
# Convert to format understood by matching algorithm
|
||||
h_names, host = model_to_graph(state, host_m, host_mm)
|
||||
g_names, guest = model_to_graph(state, pattern_m, pattern_mm)
|
||||
|
||||
# Only match matchable pattern elements
|
||||
# E.g., the 'condition'-attribute that is added to every class, cannot be matched with anything
|
||||
def is_matchable(pattern_el):
|
||||
pattern_el_name = pattern_odapi.get_name(pattern_el)
|
||||
if pattern_odapi.get_type_name(pattern_el) == "GlobalCondition":
|
||||
return False
|
||||
# Super-cheap and unreliable way of filtering out the 'condition'-attribute, added to every class:
|
||||
return not (pattern_el_name.endswith("condition")
|
||||
# as an extra safety measure, if the user defined her own 'condition' attribute, RAMification turned this into 'RAM_condition', and we can detect this
|
||||
# of course this breaks if the class name already ended with 'RAM', but let's hope that never happens
|
||||
# also, we are assuming the default "RAM_" prefix is used, but the user can change this...
|
||||
and not pattern_el_name.endswith("RAM_condition"))
|
||||
|
||||
g_names, guest = model_to_graph(state, pattern_m, pattern_mm,
|
||||
_filter=is_matchable)
|
||||
|
||||
graph_pivot = {
|
||||
g_names[guest_name] : h_names[host_name]
|
||||
|
|
@ -314,7 +313,46 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
|||
if guest_name in g_names
|
||||
}
|
||||
|
||||
matcher = MatcherVF2(host, guest, RAMCompare(Bottom(state), services_od.OD(host_mm, host_m, state)))
|
||||
obj_conditions = []
|
||||
for class_name, class_node in pattern_mm_odapi.get_all_instances("Class"):
|
||||
for obj_name, obj_node in pattern_odapi.get_all_instances(class_name):
|
||||
python_code = pattern_odapi.get_slot_value_default(obj_node, "condition", 'True')
|
||||
if class_name == "GlobalCondition":
|
||||
obj_conditions.append((python_code, None))
|
||||
else:
|
||||
obj_conditions.append((python_code, obj_name))
|
||||
|
||||
|
||||
def check_conditions(name_mapping):
|
||||
def check(python_code: str, loc):
|
||||
return exec_then_eval(python_code,
|
||||
_globals={
|
||||
**bind_api_readonly(odapi),
|
||||
'matched': lambda name: bottom.read_outgoing_elements(host_m, name_mapping[name])[0],
|
||||
},
|
||||
_locals=loc)
|
||||
|
||||
# Attribute conditions
|
||||
for python_code, host_node in compare.conditions_to_check:
|
||||
if not check(python_code, {'this': host_node}):
|
||||
return False
|
||||
|
||||
for python_code, pattern_el_name in obj_conditions:
|
||||
if pattern_el_name == None:
|
||||
# GlobalCondition
|
||||
if not check(python_code, {}):
|
||||
return False
|
||||
else:
|
||||
# object-lvl condition
|
||||
host_el_name = name_mapping[pattern_el_name]
|
||||
host_node = odapi.get(host_el_name)
|
||||
if not check(python_code, {'this': host_node}):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
compare = RAMCompare(bottom, services_od.OD(host_mm, host_m, state))
|
||||
matcher = MatcherVF2(host, guest, compare)
|
||||
for m in matcher.match(graph_pivot):
|
||||
# print("\nMATCH:\n", m)
|
||||
# Convert mapping
|
||||
|
|
@ -322,4 +360,8 @@ def match_od(state, host_m, host_mm, pattern_m, pattern_mm, pivot={}):
|
|||
for guest_vtx, host_vtx in m.mapping_vtxs.items():
|
||||
if isinstance(guest_vtx, NamedNode) and isinstance(host_vtx, NamedNode):
|
||||
name_mapping[guest_vtx.name] = host_vtx.name
|
||||
|
||||
if not check_conditions(name_mapping):
|
||||
continue # not a match after all...
|
||||
|
||||
yield name_mapping
|
||||
|
|
|
|||
|
|
@ -54,8 +54,13 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# create traceability link
|
||||
bottom.create_edge(ramified_attr_link, attr_edge, RAMIFIES_LABEL)
|
||||
|
||||
# Additional constraint that can be specified
|
||||
ramified_scd._create_attribute_link(prefix+class_name, actioncode_modelref, "condition", optional=True)
|
||||
|
||||
already_ramified.add(class_name)
|
||||
|
||||
glob_cond = ramified_scd.create_class("GlobalCondition", abstract=None)
|
||||
ramified_scd._create_attribute_link("GlobalCondition", actioncode_modelref, "condition", optional=False)
|
||||
|
||||
assocs_to_ramify = m_scd.get_associations()
|
||||
|
||||
|
|
@ -90,6 +95,9 @@ def ramify(state: State, model: UUID, prefix = "RAM_") -> UUID:
|
|||
# create traceability link
|
||||
bottom.create_edge(ramified_assoc, assoc_node, RAMIFIES_LABEL)
|
||||
|
||||
# Additional constraint that can be specified
|
||||
ramified_scd._create_attribute_link(prefix+assoc_name, actioncode_modelref, "condition", optional=True)
|
||||
|
||||
already_ramified.add(assoc_name)
|
||||
|
||||
assocs_to_ramify = ramify_later
|
||||
|
|
|
|||
|
|
@ -11,25 +11,34 @@ from services import od
|
|||
from services.primitives.string_type import String
|
||||
from services.primitives.actioncode_type import ActionCode
|
||||
from services.primitives.integer_type import Integer
|
||||
from util.eval import exec_then_eval
|
||||
from util.eval import exec_then_eval, simply_exec
|
||||
|
||||
def process_rule(state, lhs: UUID, rhs: UUID):
|
||||
def preprocess_rule(state, lhs: UUID, rhs: UUID, name_mapping):
|
||||
bottom = Bottom(state)
|
||||
|
||||
to_delete = { name for name in bottom.read_keys(lhs) if name not in bottom.read_keys(rhs) }
|
||||
to_create = { name for name in bottom.read_keys(rhs) if name not in bottom.read_keys(lhs) }
|
||||
common = { name for name in bottom.read_keys(lhs) if name in bottom.read_keys(rhs) }
|
||||
to_delete = { name for name in bottom.read_keys(lhs) if name not in bottom.read_keys(rhs) and name in name_mapping }
|
||||
to_create = { name for name in bottom.read_keys(rhs) if name not in bottom.read_keys(lhs)
|
||||
# extremely dirty:
|
||||
and "GlobalCondition" not in name }
|
||||
common = { name for name in bottom.read_keys(lhs) if name in bottom.read_keys(rhs) and name in name_mapping }
|
||||
|
||||
# print("to_delete:", to_delete)
|
||||
# print("to_create:", to_create)
|
||||
print("to_delete:", to_delete)
|
||||
print("to_create:", to_create)
|
||||
|
||||
return to_delete, to_create, common
|
||||
|
||||
# Rewrite is performed in-place (modifying `host_m`)
|
||||
# Also updates the `mapping` in-place, to become RHS -> host
|
||||
def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dict, host_m: UUID, mm: UUID):
|
||||
def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dict, host_m: UUID, host_mm: UUID):
|
||||
bottom = Bottom(state)
|
||||
|
||||
orig_name_mapping = dict(name_mapping)
|
||||
|
||||
# function that can be called from within RHS action code
|
||||
def matched_callback(pattern_name: str):
|
||||
host_name = orig_name_mapping[pattern_name]
|
||||
return bottom.read_outgoing_elements(host_m, host_name)[0]
|
||||
|
||||
scd_metamodel_id = state.read_dict(state.read_root(), "SCD")
|
||||
scd_metamodel = UUID(state.read_value(scd_metamodel_id))
|
||||
|
||||
|
|
@ -38,12 +47,12 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
assoc_type = od.get_scd_mm_assoc_node(bottom)
|
||||
modelref_type = od.get_scd_mm_modelref_node(bottom)
|
||||
|
||||
m_od = od.OD(mm, host_m, bottom.state)
|
||||
m_od = od.OD(host_mm, host_m, bottom.state)
|
||||
rhs_od = od.OD(pattern_mm, rhs_m, bottom.state)
|
||||
|
||||
to_delete, to_create, common = process_rule(state, lhs_m, rhs_m)
|
||||
to_delete, to_create, common = preprocess_rule(state, lhs_m, rhs_m, orig_name_mapping)
|
||||
|
||||
odapi = ODAPI(state, host_m, mm)
|
||||
odapi = ODAPI(state, host_m, host_mm)
|
||||
|
||||
# Perform deletions
|
||||
for pattern_name_to_delete in to_delete:
|
||||
|
|
@ -89,7 +98,7 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
# print(' -> postpone (is link)')
|
||||
edges_to_create.append((pattern_name_to_create, rhs_el_to_create, original_type, 'link', model_el_name_to_create))
|
||||
else:
|
||||
original_type_name = od.get_object_name(bottom, mm, original_type)
|
||||
original_type_name = od.get_object_name(bottom, host_mm, original_type)
|
||||
print(" -> warning: don't know about", original_type_name)
|
||||
else:
|
||||
# print(" -> no original (un-RAMified) type")
|
||||
|
|
@ -100,7 +109,11 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
# Assume the string is a Python expression to evaluate
|
||||
python_expr = ActionCode(UUID(bottom.read_value(rhs_el_to_create)), bottom.state).read()
|
||||
|
||||
result = exec_then_eval(python_expr, _globals=bind_api(odapi))
|
||||
result = exec_then_eval(python_expr, _globals={
|
||||
**bind_api(odapi),
|
||||
'matched': matched_callback,
|
||||
})
|
||||
|
||||
# Write the result into the host model.
|
||||
# This will be the *value* of an attribute. The attribute-link (connecting an object to the attribute) will be created as an edge later.
|
||||
if isinstance(result, int):
|
||||
|
|
@ -109,7 +122,8 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
m_od.create_string_value(model_el_name_to_create, result)
|
||||
name_mapping[pattern_name_to_create] = model_el_name_to_create
|
||||
else:
|
||||
raise Exception(f"RHS element '{pattern_name_to_create}' needs to be created in host, but has no un-RAMified type, and I don't know what to do with it. It's type is '{type_name}'")
|
||||
msg = f"RHS element '{pattern_name_to_create}' needs to be created in host, but has no un-RAMified type, and I don't know what to do with it. It's type is '{type_name}'"
|
||||
raise Exception(msg)
|
||||
|
||||
# print("create edges....")
|
||||
for pattern_name_to_create, rhs_el_to_create, original_type, original_type_name, model_el_name_to_create in edges_to_create:
|
||||
|
|
@ -130,7 +144,7 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
tgt = bottom.read_edge_target(rhs_el_to_create)
|
||||
tgt_name = od.get_object_name(bottom, rhs_m, tgt)
|
||||
obj_name = name_mapping[src_name] # name of object in host graph to create slot for
|
||||
attr_name = od.get_object_name(bottom, mm, original_type)
|
||||
attr_name = od.get_object_name(bottom, host_mm, original_type)
|
||||
m_od.create_link(model_el_name_to_create, attr_name, obj_name, name_mapping[tgt_name])
|
||||
|
||||
|
||||
|
|
@ -146,7 +160,7 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, assoc_type):
|
||||
print(' -> is association')
|
||||
# print(' -> is association')
|
||||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, attr_link_type):
|
||||
|
|
@ -154,12 +168,15 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
# nothing to do
|
||||
pass
|
||||
elif od.is_typed_by(bottom, host_type, modelref_type):
|
||||
print(' -> is modelref')
|
||||
old_value, _ = od.read_primitive_value(bottom, host_el, mm)
|
||||
# print(' -> is modelref')
|
||||
old_value, _ = od.read_primitive_value(bottom, host_el, host_mm)
|
||||
rhs_el, = bottom.read_outgoing_elements(rhs_m, pattern_el_name)
|
||||
python_expr, _ = od.read_primitive_value(bottom, rhs_el, pattern_mm)
|
||||
result = exec_then_eval(python_expr,
|
||||
_globals=bind_api(odapi),
|
||||
_globals={
|
||||
**bind_api(odapi),
|
||||
'matched': matched_callback,
|
||||
},
|
||||
_locals={'this': host_el})
|
||||
# print('eval result=', result)
|
||||
if isinstance(result, int):
|
||||
|
|
@ -172,3 +189,11 @@ def rewrite(state, lhs_m: UUID, rhs_m: UUID, pattern_mm: UUID, name_mapping: dic
|
|||
msg = f"Don't know what to do with element '{pattern_el_name}'->'{host_el_name}' of type ({host_type})"
|
||||
# print(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
rhs_odapi = ODAPI(state, rhs_m, pattern_mm)
|
||||
for cond_name, cond in rhs_odapi.get_all_instances("GlobalCondition"):
|
||||
python_code = rhs_odapi.get_slot_value(cond, "condition")
|
||||
simply_exec(python_code, _globals={
|
||||
**bind_api(odapi),
|
||||
'matched': matched_callback,
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue