From ae5eaedb4bec1cbd7efbc034432e01aa7f3873a6 Mon Sep 17 00:00:00 2001 From: Joeri Exelmans Date: Tue, 10 Sep 2024 13:18:14 +0200 Subject: [PATCH] RAMification + pattern matching: put typing information straight into the Vertices, as a Python attribute (don't put it in separate Vertices+Edges). --- experiments/exp_scd.py | 28 +++- pattern_matching/benchmark.py | 2 +- pattern_matching/matcher.py | 15 +- pattern_matching/mvs_adapter.py | 234 +++++++++++++++++++++------- services/od.py | 27 +++- services/primitives/integer_type.py | 5 + services/scd.py | 10 +- transformation/ramify.py | 92 ++++++----- 8 files changed, 284 insertions(+), 129 deletions(-) diff --git a/experiments/exp_scd.py b/experiments/exp_scd.py index 6e40345..65a8b71 100644 --- a/experiments/exp_scd.py +++ b/experiments/exp_scd.py @@ -109,6 +109,7 @@ def main(): od.create_link("A2B", "a2", "b") od.create_slot("size", "a", od.create_integer_value("a.size", 42)) + od.create_slot("size", "a2", od.create_integer_value("a2.size", 50)) print("checking conformance....") conf2 = Conformance(state, inst_id, model_id) @@ -119,28 +120,41 @@ def main(): pattern_id = state.create_node() pattern = OD(ramified_MM_id, pattern_id, state) - pattern.create_object("a1", "A") - pattern.create_slot("size", "a1", pattern.create_string_value("a1.size", 'v < 100')) + pattern.create_object("pattern_a", "LHS_A") + # pattern.create_slot("constraint", "a1", pattern.create_string_value("a1.constraint", 'read_int(get_slot("LHS_size")) > 50')) + pattern.create_slot("LHS_size", "pattern_a", pattern.create_string_value("pattern_a.LHS_size", 'v < 99')) # pattern.create_object("a2", "A") # pattern.create_slot("size", "a2", pattern.create_string_value("a2.size", '99')) - pattern.create_object("b1", "B") - # pattern.create_link("A2B", "a1", "b1") + pattern.create_object("pattern_b", "LHS_B") + + pattern.create_link("LHS_A2B", "pattern_a", "pattern_b") + conf3 = Conformance(state, pattern_id, ramified_MM_id) print("conforms?", conf3.check_nominal(log=True)) - host = mvs_adapter.model_to_graph(state, inst_id) - guest = mvs_adapter.model_to_graph(state, pattern_id) + host = mvs_adapter.model_to_graph(state, inst_id, model_id) + guest = mvs_adapter.model_to_graph(state, pattern_id, ramified_MM_id) + print("HOST:") print(host.vtxs) print(host.edges) + print("GUEST:") + print(guest.vtxs) + print(guest.edges) + print("matching...") - matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state))) + matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), od)) prev = None for m in matcher.match(): print("\nMATCH:\n", m) + name_to_matched = {} + for guest_vtx, host_vtx in m.mapping_vtxs.items(): + if isinstance(guest_vtx, mvs_adapter.NamedNode) and isinstance(host_vtx, mvs_adapter.NamedNode): + name_to_matched[guest_vtx.name] = host_vtx.name + print(name_to_matched) input() print("DONE") diff --git a/pattern_matching/benchmark.py b/pattern_matching/benchmark.py index 7f16795..3f841dc 100644 --- a/pattern_matching/benchmark.py +++ b/pattern_matching/benchmark.py @@ -29,7 +29,7 @@ def run_benchmark(jhost, jguest, shost, sguest, expected=None): # benchmark Joeri m = j.MatcherVF2(host, guest, - lambda g_val, h_val: g_val == h_val) # all vertices can be matched + lambda g_vtx, h_vtx: g_vtx.value == h_vtx.value) # all vertices can be matched iterations = 50 print(" Patience (joeri)...") for n in range(iterations): diff --git a/pattern_matching/matcher.py b/pattern_matching/matcher.py index 873b76a..9c1a966 100644 --- a/pattern_matching/matcher.py +++ b/pattern_matching/matcher.py @@ -57,9 +57,9 @@ class Edge: def __repr__(self): if self.label != None: - return f"E({self.src}--{self.label}->{self.tgt})" + return f"({self.src}--{self.label}->{self.tgt})" else: - return f"E({self.src}->{self.tgt})" + return f"({self.src}->{self.tgt})" class MatcherState: def __init__(self): @@ -133,12 +133,9 @@ class MatcherVF2: self.guest = guest self.compare_fn = compare_fn - with Timer("find_connected_components - host"): - self.host_vtx_to_component, self.host_component_to_vtxs = find_connected_components(host) with Timer("find_connected_components - guest"): self.guest_vtx_to_component, self.guest_component_to_vtxs = find_connected_components(guest) - print("number of host connected components:", len(self.host_component_to_vtxs)) print("number of guest connected components:", len(self.guest_component_to_vtxs)) def match(self): @@ -201,9 +198,9 @@ class MatcherVF2: if h_candidate_edge in state.r_mapping_edges: print_debug(" skip, host edge already matched") continue # skip already matched host edge - h_candidate_vtx = read_edge(h_candidate_edge, direction) print_debug('grow edge', g_candidate_edge, ':', h_candidate_edge, id(g_candidate_edge), id(h_candidate_edge)) new_state = state.grow_edge(h_candidate_edge, g_candidate_edge) + h_candidate_vtx = read_edge(h_candidate_edge, direction) yield from attempt_match_vtxs( new_state, g_candidate_vtx, @@ -231,7 +228,7 @@ class MatcherVF2: if g_indegree > h_indegree: print_debug(" nope, indegree") return - if not self.compare_fn(g_candidate_vtx.value, h_candidate_vtx.value): + if not self.compare_fn(g_candidate_vtx, h_candidate_vtx): print_debug(" nope, bad compare") return new_state = state.grow_vtx( @@ -288,7 +285,7 @@ if __name__ == "__main__": # Edge(guest.vtxs[1], guest.vtxs[0]), ] - m = MatcherVF2(host, guest, lambda g_val, h_val: eval(g_val, {}, {'v':h_val})) + m = MatcherVF2(host, guest, lambda g_vtx, h_vtx: eval(g_vtx.value, {}, {'v':h_vtx.value})) import time durations = 0 iterations = 1 @@ -332,7 +329,7 @@ if __name__ == "__main__": Edge(guest.vtxs[1], guest.vtxs[0]), ] - m = MatcherVF2(host, guest, lambda g_val, h_val: g_val == h_val) + m = MatcherVF2(host, guest, lambda g_vtx, h_vtx: g_vtx.value == h_vtx.value) import time durations = 0 iterations = 1 diff --git a/pattern_matching/mvs_adapter.py b/pattern_matching/mvs_adapter.py index 3b0c0d7..6a16a8f 100644 --- a/pattern_matching/mvs_adapter.py +++ b/pattern_matching/mvs_adapter.py @@ -2,63 +2,117 @@ from state.base import State from uuid import UUID from services.bottom.V0 import Bottom from services.scd import SCD +from services.od import OD from pattern_matching.matcher import Graph, Edge, Vertex import itertools import re +import functools from util.timer import Timer +from services.primitives.integer_type import Integer + class _is_edge: def __repr__(self): return "EDGE" + def to_json(self): + return "EDGE" # just a unique symbol that is only equal to itself IS_EDGE = _is_edge() +class _is_modelref: + def __repr__(self): + return "REF" + def to_json(self): + return "REF" +IS_MODELREF = _is_modelref() + class IS_TYPE: def __init__(self, type): # mvs-node of the type self.type = type - def __repr__(self): return f"TYPE({str(self.type)[-4:]})" - # def __eq__(self, other): - # if not isinstance(other, IS_TYPE): - # return False - # return other.type == self.type +class NamedNode(Vertex): + def __init__(self, value, name): + super().__init__(value) + # the name of the node in the context of the model + # the matcher by default ignores this value + self.name = name - # def __hash__(self): - # return self.type.__hash__() +# MVS-nodes become vertices +class MVSNode(NamedNode): + def __init__(self, value, node_id, name): + super().__init__(value, name) + # useful for debugging + self.node_id = node_id + def __repr__(self): + if self.value == None: + return f"N({self.name})" + if isinstance(self.value, str): + return f"N({self.name}=\"{self.value}\")" + return f"N({self.name}={self.value})" + # if isinstance(self.value, str): + # return f"N({self.name}=\"{self.value}\",{str(self.node_id)[-4:]})" + # return f"N({self.name}={self.value},{str(self.node_id)[-4:]})" +# MVS-edges become vertices. +class MVSEdge(NamedNode): + def __init__(self, node_id, name): + super().__init__(IS_EDGE, name) + # useful for debugging + self.node_id = node_id + def __repr__(self): + return f"E({self.name})" + # return f"E({self.name}{str(self.node_id)[-4:]})" +# dirty way of detecting whether a node is a ModelRef 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-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z]-[0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z][0-9a-z]") - # Converts an object/class diagram in MVS state to the pattern matcher graph type # ModelRefs are flattened -def model_to_graph(state: State, model: UUID): +def model_to_graph(state: State, model: UUID, metamodel: UUID): with Timer("model_to_graph"): + od = OD(model, metamodel, state) + scd = SCD(model, state) + scd_mm = SCD(metamodel, state) + bottom = Bottom(state) graph = Graph() mvs_edges = [] modelrefs = {} - def extract_modelref(el): - value = bottom.read_value(el) - # If the value of the el is a ModelRef (only way to detect this is to match a regex - not very clean), then extract it. We'll create a link to the referred model later. + # constraints = {} + + def to_vtx(el, name): + # print("name:", name) if bottom.is_edge(el): + # if filter_constraint: + # try: + # supposed_obj = bottom.read_edge_source(el) + # slot_node = od.get_slot(supposed_obj, "constraint") + # if el == slot_node: + # # `el` is the constraint-slot + # constraints[supposed_obj] = el + # return + # except: + # pass mvs_edges.append(el) - return IS_EDGE + return MVSEdge(el, name) + # If the value of the el is a ModelRef (only way to detect this is to match a regex - not very clean), then extract it. We'll create a link to the referred model later. + value = bottom.read_value(el) if isinstance(value, str): if UUID_REGEX.match(value) != None: # side-effect modelrefs[el] = UUID(value) - return None - return value + return MVSNode(IS_MODELREF, el, name) + return MVSNode(value, el, name) # MVS-Nodes become vertices - uuid_to_vtx = { node: Vertex(value=extract_modelref(node)) for node in bottom.read_outgoing_elements(model) } + uuid_to_vtx = { node: to_vtx(node, key) for key in bottom.read_keys(model) for node in bottom.read_outgoing_elements(model, key) } + uuid_to_vtx = { key: val for key,val in uuid_to_vtx.items() if val != None } graph.vtxs = [ vtx for vtx in uuid_to_vtx.values() ] # For every MSV-Edge, two edges are created (for src and tgt) @@ -72,14 +126,18 @@ def model_to_graph(state: State, model: UUID): mvs_tgt = bottom.read_edge_target(mvs_edge) if mvs_tgt in uuid_to_vtx: graph.edges.append(Edge( - src=uuid_to_vtx[mvs_tgt], - tgt=uuid_to_vtx[mvs_edge], + src=uuid_to_vtx[mvs_edge], + tgt=uuid_to_vtx[mvs_tgt], label="tgt")) for node, ref in modelrefs.items(): + # Get MM of ref'ed model + type_node, = bottom.read_outgoing_elements(node, "Morphism") + print("modelref type node:", type_node) + # Recursively convert ref'ed model to graph - ref_model = model_to_graph(state, ref) + ref_model = model_to_graph(state, ref, type_node) # Flatten and create link to ref'ed model graph.vtxs += ref_model.vtxs @@ -91,44 +149,61 @@ def model_to_graph(state: State, model: UUID): def add_types(node): type_node, = bottom.read_outgoing_elements(node, "Morphism") - print('node', node, 'has type', type_node) + + # Put the type straigt into the Vertex-object + uuid_to_vtx[node].typ = type_node + + # We used to put the types in separate nodes, but we no longer do this: + + # print('node', node, 'has type', type_node) # We create a Vertex storing the type - type_vertex = Vertex(value=IS_TYPE(type_node)) - graph.vtxs.append(type_vertex) - type_edge = Edge( - src=uuid_to_vtx[node], - tgt=type_vertex, - label="type") - print(type_edge) - graph.edges.append(type_edge) + # type_vertex = Vertex(value=IS_TYPE(type_node)) + # graph.vtxs.append(type_vertex) + # type_edge = Edge( + # src=uuid_to_vtx[node], + # tgt=type_vertex, + # label="type") + # # print(type_edge) + # graph.edges.append(type_edge) - # Add typing information of classes, attributes, and associations - scd = SCD(model, state) - for name,node in scd.get_classes().items(): - add_types(node) - for attr_name,attr_node in scd.get_attributes(name): - add_types(attr_node) - for _,node in scd.get_associations().items(): - add_types(node) + # Add typing information for: + # - classes + # - attributes + # - associations + for class_name, class_node in scd_mm.get_classes().items(): + 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) + 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) + 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) return graph # 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: - def __init__(self, bottom): + def __init__(self, bottom, host_od): self.bottom = bottom + self.host_od = host_od type_model_id = bottom.state.read_dict(bottom.state.read_root(), "SCD") self.scd_model = UUID(bottom.state.read_value(type_model_id)) def is_subtype_of(self, supposed_subtype: UUID, supposed_supertype: UUID): - inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance") - if supposed_subtype == supposed_supertype: # reflexive: return True + inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance") + for outgoing in self.bottom.read_outgoing_edges(supposed_subtype): if inheritance_node in self.bottom.read_outgoing_elements(outgoing, "Morphism"): # 'outgoing' is an inheritance link @@ -139,32 +214,71 @@ class RAMCompare: return False - def __call__(self, g_val, h_val): - if g_val == None: - return h_val == None + def has_subtype(self, g_vtx_type, h_vtx_type): + g_vtx_original_types = self.bottom.read_outgoing_elements(g_vtx_type, "RAMifies") + for typ in g_vtx_original_types: + # print(g_vtx, "is ramified") + result = self.is_subtype_of(h_vtx_type, g_vtx_original_types[0]) + if result: + return True + else: + # print(g_vtx, "is not ramified") + return False + + + # Memoizing the result of comparison gives a huge performance boost! + # Especially `is_subtype_of` is very slow, and will be performed many times over on the same pair of nodes during the matching process. + @functools.cache + def __call__(self, g_vtx, h_vtx): + # First check if the types match (if we have type-information) + if hasattr(g_vtx, 'typ'): + if not hasattr(h_vtx, 'typ'): + return False + return self.has_subtype(g_vtx.typ, h_vtx.typ) + + # Then, match by value + + if g_vtx.value == None: + return h_vtx.value == None # mvs-edges (which are converted to vertices) only match with mvs-edges - if g_val == IS_EDGE: - return h_val == IS_EDGE + if g_vtx.value == IS_EDGE: + return h_vtx.value == IS_EDGE - if h_val == IS_EDGE: + if h_vtx.value == IS_EDGE: return False - # types only match with their supertypes - # we assume that 'RAMifies'-traceability links have been created between guest and host types - # we need these links, because the guest types are different types (RAMified) - if isinstance(g_val, IS_TYPE): - if not isinstance(h_val, IS_TYPE): - return False - g_val_original_types = self.bottom.read_outgoing_elements(g_val.type, "RAMifies") - if len(g_val_original_types) > 0: - result = self.is_subtype_of(h_val.type, g_val_original_types[0]) - return result - else: - return False + if g_vtx.value == IS_MODELREF: + return h_vtx.value == IS_MODELREF - if isinstance(h_val, IS_TYPE): + if h_vtx.value == IS_MODELREF: return False - # print(g_val, h_val) - return eval(g_val, {}, {'v': h_val}) + # # types only match with their supertypes + # # we assume that 'RAMifies'-traceability links have been created between guest and host types + # # we need these links, because the guest types are different types (RAMified) + # if isinstance(g_vtx.value, IS_TYPE): + # if not isinstance(h_vtx.value, IS_TYPE): + # return False + # return self.has_subtype(g_vtx.value.type, h_vtx.value.type) + + # if isinstance(h_vtx.value, IS_TYPE): + # return False + + # print(g_vtx.value, h_vtx.value) + def get_slot(h_vtx, slot_name: str): + slot_node = self.host_od.get_slot(h_vtx.node_id, slot_name) + return slot_node + + def read_int(slot: UUID): + i = Integer(slot, self.bottom.state) + return i.read() + + try: + return eval(g_vtx.value, {}, { + 'v': h_vtx.value, + 'get_slot': functools.partial(get_slot, h_vtx), + 'read_int': read_int, + }) + except Exception as e: + return False diff --git a/services/od.py b/services/od.py index 39abe3b..16965a4 100644 --- a/services/od.py +++ b/services/od.py @@ -4,6 +4,9 @@ from services.bottom.V0 import Bottom from services.primitives.integer_type import Integer from services.primitives.string_type import String +def get_attr_link_name(class_name: str, attr_name: str): + return f"{class_name}_{attr_name}" + # Object Diagrams service class OD: @@ -47,6 +50,9 @@ class OD: def get_class_of_object(self, object_name: str): object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object + return self._get_class_of_object(object_node) + + def _get_class_of_object(self, object_node: UUID): type_el, = self.bottom.read_outgoing_elements(object_node, "Morphism") for key in self.bottom.read_keys(self.type_model): type_el2, = self.bottom.read_outgoing_elements(self.type_model, key) @@ -55,10 +61,22 @@ class OD: def create_slot(self, attr_name: str, object_name: str, target_name: str): class_name = self.get_class_of_object(object_name) - attr_link_name = f"{class_name}_{attr_name}" + attr_link_name = get_attr_link_name(class_name, attr_name) # An attribute-link is indistinguishable from an ordinary link: return self.create_link(attr_link_name, object_name, target_name) + def get_slot(self, object_node: UUID, attr_name: str): + # I really don't like how complex and inefficient it is to read an attribute of an object... + class_name = self._get_class_of_object(object_node) + attr_link_name = get_attr_link_name(class_name, attr_name) + print(attr_link_name) + type_edge, = self.bottom.read_outgoing_elements(self.type_model, attr_link_name) + for outgoing_edge in self.bottom.read_outgoing_edges(object_node): + if type_edge in self.bottom.read_outgoing_elements(outgoing_edge, "Morphism"): + slot_ref = self.bottom.read_edge_target(outgoing_edge) + slot_node = UUID(self.bottom.read_value(slot_ref)) + return slot_node + def create_integer_value(self, name: str, value: int): from services.primitives.integer_type import Integer int_node = self.bottom.create_node() @@ -84,16 +102,15 @@ class OD: # create element + morphism links element_node = self.bottom.create_node(str(model)) # create element node self.bottom.create_edge(self.model, element_node, name) # attach to model - scd_node, = self.bottom.read_outgoing_elements(self.type_model, type_name) # retrieve type - self.bottom.create_edge(element_node, scd_node, "Morphism") # create morphism link + type_node, = self.bottom.read_outgoing_elements(self.type_model, type_name) # retrieve type + self.bottom.create_edge(element_node, type_node, "Morphism") # create morphism link def create_link(self, assoc_name: str, src_obj_name: str, tgt_obj_name: str): - print(tgt_obj_name) src_obj_node, = self.bottom.read_outgoing_elements(self.model, src_obj_name) tgt_obj_node, = self.bottom.read_outgoing_elements(self.model, tgt_obj_name) - link_edge = self.bottom.create_edge(src_obj_node, tgt_obj_node); + link_edge = self.bottom.create_edge(src_obj_node, tgt_obj_node) # generate a unique name for the link i = 0; diff --git a/services/primitives/integer_type.py b/services/primitives/integer_type.py index 44864bb..c4bfde5 100644 --- a/services/primitives/integer_type.py +++ b/services/primitives/integer_type.py @@ -11,6 +11,7 @@ class Integer: self.type_model = UUID(self.bottom.read_value(type_model_id_node)) def create(self, value: int): + # delete existing integer, if there is one if "string" in self.bottom.read_keys(self.model): instance, = self.bottom.read_outgoing_elements(self.model, "integer") self.bottom.delete_element(instance) @@ -18,3 +19,7 @@ class Integer: self.bottom.create_edge(self.model, _instance, "integer") _type, = self.bottom.read_outgoing_elements(self.type_model, "Integer") self.bottom.create_edge(_instance, _type, "Morphism") + + def read(self): + instance, = self.bottom.read_outgoing_elements(self.model, "integer") + return self.bottom.read_value(instance) \ No newline at end of file diff --git a/services/scd.py b/services/scd.py index 67da3a5..b8688f2 100644 --- a/services/scd.py +++ b/services/scd.py @@ -330,10 +330,12 @@ class SCD: attr_link_node, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink") class_node, = self.bottom.read_outgoing_elements(self.model, class_name) name_to_attr = {} - for edge in self.bottom.read_outgoing_edges(class_node): - edge_types = self.bottom.read_outgoing_elements(edge, "Morphism") - if attr_link_node in edge_types: - name_to_attr[key] = edge + for name in self.bottom.read_keys(class_node): + edges = self.bottom.read_outgoing_edges(class_node, name) + for edge in edges: + edge_types = self.bottom.read_outgoing_elements(edge, "Morphism") + if attr_link_node in edge_types: + name_to_attr[name] = edge return name_to_attr def delete_element(self, name: str): diff --git a/transformation/ramify.py b/transformation/ramify.py index 5f83f13..90b2290 100644 --- a/transformation/ramify.py +++ b/transformation/ramify.py @@ -5,39 +5,39 @@ from services.scd import SCD from framework.conformance import Conformance -def ramify(state: State, model: UUID) -> UUID: +def ramify(state: State, model: UUID, prefix = "LHS_") -> UUID: - def print_tree(root, max_depth, depth=0): - print(" "*depth, "root=", root, "value=", state.read_value(root)) - src,tgt = state.read_edge(root) - if src != None: - print(" "*depth, "src...") - print_tree(src, max_depth, depth+1) - if tgt != None: - print(" "*depth, "tgt...") - print_tree(tgt, max_depth, depth+1) - for edge in state.read_outgoing(root): - for edge_label in state.read_outgoing(edge): - [_,tgt] = state.read_edge(edge_label) - label = state.read_value(tgt) - print(" "*depth, " key:", label) - [_, tgt] = state.read_edge(edge) - value = state.read_value(tgt) - if value != None: - print(" "*depth, " ->", tgt, " (value:", value, ")") - else: - print(" "*depth, " ->", tgt) - if depth < max_depth: - if isinstance(value, str) and len(value) == 36: - i = None - try: - i = UUID(value) - except ValueError as e: - # print("invalid UUID:", value) - pass - if i != None: - print_tree(i, max_depth, depth+1) - print_tree(tgt, max_depth, depth+1) + # def print_tree(root, max_depth, depth=0): + # print(" "*depth, "root=", root, "value=", state.read_value(root)) + # src,tgt = state.read_edge(root) + # if src != None: + # print(" "*depth, "src...") + # print_tree(src, max_depth, depth+1) + # if tgt != None: + # print(" "*depth, "tgt...") + # print_tree(tgt, max_depth, depth+1) + # for edge in state.read_outgoing(root): + # for edge_label in state.read_outgoing(edge): + # [_,tgt] = state.read_edge(edge_label) + # label = state.read_value(tgt) + # print(" "*depth, " key:", label) + # [_, tgt] = state.read_edge(edge) + # value = state.read_value(tgt) + # if value != None: + # print(" "*depth, " ->", tgt, " (value:", value, ")") + # else: + # print(" "*depth, " ->", tgt) + # if depth < max_depth: + # if isinstance(value, str) and len(value) == 36: + # i = None + # try: + # i = UUID(value) + # except ValueError as e: + # # print("invalid UUID:", value) + # pass + # if i != None: + # print_tree(i, max_depth, depth+1) + # print_tree(tgt, max_depth, depth+1) bottom = Bottom(state) @@ -117,20 +117,24 @@ def ramify(state: State, model: UUID) -> UUID: # - max-card: same as original upper_card = find_cardinality(class_node, class_upper_card_node) print('creating class', class_name, "with card 0 ..", upper_card) - ramified_class = ramified_scd.create_class(class_name, abstract=None, max_c=upper_card) + ramified_class = ramified_scd.create_class(prefix+class_name, abstract=None, max_c=upper_card) # traceability link bottom.create_edge(ramified_class, class_node, "RAMifies") + # We don't add a 'label' attribute (as described in literature on RAMification) + # Instead, the names of the objects (which only exist in the scope of the object diagram 'model', and are not visible to the matcher) are used as labels + + # Optional constraint on the object + # ramified_scd._create_attribute_link(prefix+class_name, string_modelref, "constraint", optional=True) + for (attr_name, attr_edge) in get_attributes(class_node): print(' creating attribute', attr_name, "with type String") # Every attribute becomes 'string' type # The string will be a Python expression - ramified_attr_link = ramified_scd._create_attribute_link(class_name, string_modelref, attr_name, optional=False) + ramified_attr_link = ramified_scd._create_attribute_link(prefix+class_name, string_modelref, prefix+attr_name, optional=True) # traceability link bottom.create_edge(ramified_attr_link, attr_edge, "RAMifies") - - associations = scd.get_associations() for assoc_name, assoc_node in associations.items(): # For every association in our original model, create an association: @@ -143,23 +147,25 @@ def ramify(state: State, model: UUID) -> UUID: src = scd.get_class_name(bottom.read_edge_source(assoc_node)) tgt = scd.get_class_name(bottom.read_edge_target(assoc_node)) print('creating assoc', src, "->", tgt, ", name =", assoc_name, ", src card = 0 ..", src_upper_card, "and tgt card = 0 ..", tgt_upper_card) - ramified_assoc = ramified_scd.create_association(assoc_name, src, tgt, + ramified_assoc = ramified_scd.create_association( + prefix+assoc_name, prefix+src, prefix+tgt, src_max_c=src_upper_card, tgt_max_c=tgt_upper_card) # traceability link bottom.create_edge(ramified_assoc, assoc_node, "RAMifies") - for inh_name, inh_node in scd.get_inheritances().items(): # Re-create inheritance links like in our original model: src = scd.get_class_name(bottom.read_edge_source(inh_node)) tgt = scd.get_class_name(bottom.read_edge_target(inh_node)) - print('creating inheritance link', src, '->', tgt) - ramified_inh_link = ramified_scd.create_inheritance(src, tgt) + print('creating inheritance link', prefix+src, '->', prefix+tgt) + ramified_inh_link = ramified_scd.create_inheritance(prefix+src, prefix+tgt) - - # The RAMified meta-model should also conform to 'SCD': + # Double-check: The RAMified meta-model should also conform to 'SCD': conf = Conformance(state, ramified, scd_metamodel) - print("conforms?", conf.check_nominal(log=True)) + if not conf.check_nominal(log=True): + raise Exception("Unexpected error: RAMified MM does not conform to SCD MM") + else: + print("RAMification successful.") return ramified \ No newline at end of file