RAMification + pattern matching: put typing information straight into the Vertices, as a Python attribute (don't put it in separate Vertices+Edges).

This commit is contained in:
Joeri Exelmans 2024-09-10 13:18:14 +02:00
parent 700a4d103f
commit ae5eaedb4b
8 changed files with 284 additions and 129 deletions

View file

@ -109,6 +109,7 @@ def main():
od.create_link("A2B", "a2", "b") od.create_link("A2B", "a2", "b")
od.create_slot("size", "a", od.create_integer_value("a.size", 42)) 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....") print("checking conformance....")
conf2 = Conformance(state, inst_id, model_id) conf2 = Conformance(state, inst_id, model_id)
@ -119,28 +120,41 @@ def main():
pattern_id = state.create_node() pattern_id = state.create_node()
pattern = OD(ramified_MM_id, pattern_id, state) pattern = OD(ramified_MM_id, pattern_id, state)
pattern.create_object("a1", "A") pattern.create_object("pattern_a", "LHS_A")
pattern.create_slot("size", "a1", pattern.create_string_value("a1.size", 'v < 100')) # 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_object("a2", "A")
# pattern.create_slot("size", "a2", pattern.create_string_value("a2.size", '99')) # pattern.create_slot("size", "a2", pattern.create_string_value("a2.size", '99'))
pattern.create_object("b1", "B") pattern.create_object("pattern_b", "LHS_B")
# pattern.create_link("A2B", "a1", "b1")
pattern.create_link("LHS_A2B", "pattern_a", "pattern_b")
conf3 = Conformance(state, pattern_id, ramified_MM_id) conf3 = Conformance(state, pattern_id, ramified_MM_id)
print("conforms?", conf3.check_nominal(log=True)) print("conforms?", conf3.check_nominal(log=True))
host = mvs_adapter.model_to_graph(state, inst_id) host = mvs_adapter.model_to_graph(state, inst_id, model_id)
guest = mvs_adapter.model_to_graph(state, pattern_id) guest = mvs_adapter.model_to_graph(state, pattern_id, ramified_MM_id)
print("HOST:")
print(host.vtxs) print(host.vtxs)
print(host.edges) print(host.edges)
print("GUEST:")
print(guest.vtxs)
print(guest.edges)
print("matching...") print("matching...")
matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state))) matcher = MatcherVF2(host, guest, mvs_adapter.RAMCompare(Bottom(state), od))
prev = None prev = None
for m in matcher.match(): for m in matcher.match():
print("\nMATCH:\n", m) 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() input()
print("DONE") print("DONE")

View file

@ -29,7 +29,7 @@ def run_benchmark(jhost, jguest, shost, sguest, expected=None):
# benchmark Joeri # benchmark Joeri
m = j.MatcherVF2(host, guest, 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 iterations = 50
print(" Patience (joeri)...") print(" Patience (joeri)...")
for n in range(iterations): for n in range(iterations):

View file

@ -57,9 +57,9 @@ class Edge:
def __repr__(self): def __repr__(self):
if self.label != None: if self.label != None:
return f"E({self.src}--{self.label}->{self.tgt})" return f"({self.src}--{self.label}->{self.tgt})"
else: else:
return f"E({self.src}->{self.tgt})" return f"({self.src}->{self.tgt})"
class MatcherState: class MatcherState:
def __init__(self): def __init__(self):
@ -133,12 +133,9 @@ class MatcherVF2:
self.guest = guest self.guest = guest
self.compare_fn = compare_fn 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"): with Timer("find_connected_components - guest"):
self.guest_vtx_to_component, self.guest_component_to_vtxs = 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)) print("number of guest connected components:", len(self.guest_component_to_vtxs))
def match(self): def match(self):
@ -201,9 +198,9 @@ class MatcherVF2:
if h_candidate_edge in state.r_mapping_edges: if h_candidate_edge in state.r_mapping_edges:
print_debug(" skip, host edge already matched") print_debug(" skip, host edge already matched")
continue # skip already matched host edge 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)) 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) 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( yield from attempt_match_vtxs(
new_state, new_state,
g_candidate_vtx, g_candidate_vtx,
@ -231,7 +228,7 @@ class MatcherVF2:
if g_indegree > h_indegree: if g_indegree > h_indegree:
print_debug(" nope, indegree") print_debug(" nope, indegree")
return 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") print_debug(" nope, bad compare")
return return
new_state = state.grow_vtx( new_state = state.grow_vtx(
@ -288,7 +285,7 @@ if __name__ == "__main__":
# Edge(guest.vtxs[1], guest.vtxs[0]), # 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 import time
durations = 0 durations = 0
iterations = 1 iterations = 1
@ -332,7 +329,7 @@ if __name__ == "__main__":
Edge(guest.vtxs[1], guest.vtxs[0]), 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 import time
durations = 0 durations = 0
iterations = 1 iterations = 1

View file

@ -2,63 +2,117 @@ from state.base import State
from uuid import UUID from uuid import UUID
from services.bottom.V0 import Bottom from services.bottom.V0 import Bottom
from services.scd import SCD from services.scd import SCD
from services.od import OD
from pattern_matching.matcher import Graph, Edge, Vertex from pattern_matching.matcher import Graph, Edge, Vertex
import itertools import itertools
import re import re
import functools
from util.timer import Timer from util.timer import Timer
from services.primitives.integer_type import Integer
class _is_edge: class _is_edge:
def __repr__(self): def __repr__(self):
return "EDGE" return "EDGE"
def to_json(self):
return "EDGE"
# just a unique symbol that is only equal to itself # just a unique symbol that is only equal to itself
IS_EDGE = _is_edge() 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: class IS_TYPE:
def __init__(self, type): def __init__(self, type):
# mvs-node of the type # mvs-node of the type
self.type = type self.type = type
def __repr__(self): def __repr__(self):
return f"TYPE({str(self.type)[-4:]})" return f"TYPE({str(self.type)[-4:]})"
# def __eq__(self, other): class NamedNode(Vertex):
# if not isinstance(other, IS_TYPE): def __init__(self, value, name):
# return False super().__init__(value)
# return other.type == self.type # the name of the node in the context of the model
# the matcher by default ignores this value
self.name = name
# def __hash__(self): # MVS-nodes become vertices
# return self.type.__hash__() 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]") 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 # Converts an object/class diagram in MVS state to the pattern matcher graph type
# ModelRefs are flattened # 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"): with Timer("model_to_graph"):
od = OD(model, metamodel, state)
scd = SCD(model, state)
scd_mm = SCD(metamodel, state)
bottom = Bottom(state) bottom = Bottom(state)
graph = Graph() graph = Graph()
mvs_edges = [] mvs_edges = []
modelrefs = {} modelrefs = {}
def extract_modelref(el): # constraints = {}
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. def to_vtx(el, name):
# print("name:", name)
if bottom.is_edge(el): 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) 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 isinstance(value, str):
if UUID_REGEX.match(value) != None: if UUID_REGEX.match(value) != None:
# side-effect # side-effect
modelrefs[el] = UUID(value) modelrefs[el] = UUID(value)
return None return MVSNode(IS_MODELREF, el, name)
return value return MVSNode(value, el, name)
# MVS-Nodes become vertices # 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() ] graph.vtxs = [ vtx for vtx in uuid_to_vtx.values() ]
# For every MSV-Edge, two edges are created (for src and tgt) # 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) mvs_tgt = bottom.read_edge_target(mvs_edge)
if mvs_tgt in uuid_to_vtx: if mvs_tgt in uuid_to_vtx:
graph.edges.append(Edge( graph.edges.append(Edge(
src=uuid_to_vtx[mvs_tgt], src=uuid_to_vtx[mvs_edge],
tgt=uuid_to_vtx[mvs_edge], tgt=uuid_to_vtx[mvs_tgt],
label="tgt")) label="tgt"))
for node, ref in modelrefs.items(): 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 # 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 # Flatten and create link to ref'ed model
graph.vtxs += ref_model.vtxs graph.vtxs += ref_model.vtxs
@ -91,44 +149,61 @@ def model_to_graph(state: State, model: UUID):
def add_types(node): def add_types(node):
type_node, = bottom.read_outgoing_elements(node, "Morphism") 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 # We create a Vertex storing the type
type_vertex = Vertex(value=IS_TYPE(type_node)) # type_vertex = Vertex(value=IS_TYPE(type_node))
graph.vtxs.append(type_vertex) # graph.vtxs.append(type_vertex)
type_edge = Edge( # type_edge = Edge(
src=uuid_to_vtx[node], # src=uuid_to_vtx[node],
tgt=type_vertex, # tgt=type_vertex,
label="type") # label="type")
print(type_edge) # # print(type_edge)
graph.edges.append(type_edge) # graph.edges.append(type_edge)
# Add typing information of classes, attributes, and associations # Add typing information for:
scd = SCD(model, state) # - classes
for name,node in scd.get_classes().items(): # - attributes
add_types(node) # - associations
for attr_name,attr_node in scd.get_attributes(name): for class_name, class_node in scd_mm.get_classes().items():
add_types(attr_node) objects = scd.get_typed_by(class_node)
for _,node in scd.get_associations().items(): # print("typed by:", class_name, objects)
add_types(node) 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 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) # 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: class RAMCompare:
def __init__(self, bottom): def __init__(self, bottom, host_od):
self.bottom = bottom self.bottom = bottom
self.host_od = host_od
type_model_id = bottom.state.read_dict(bottom.state.read_root(), "SCD") type_model_id = bottom.state.read_dict(bottom.state.read_root(), "SCD")
self.scd_model = UUID(bottom.state.read_value(type_model_id)) self.scd_model = UUID(bottom.state.read_value(type_model_id))
def is_subtype_of(self, supposed_subtype: UUID, supposed_supertype: UUID): 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: if supposed_subtype == supposed_supertype:
# reflexive: # reflexive:
return True return True
inheritance_node, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
for outgoing in self.bottom.read_outgoing_edges(supposed_subtype): for outgoing in self.bottom.read_outgoing_edges(supposed_subtype):
if inheritance_node in self.bottom.read_outgoing_elements(outgoing, "Morphism"): if inheritance_node in self.bottom.read_outgoing_elements(outgoing, "Morphism"):
# 'outgoing' is an inheritance link # 'outgoing' is an inheritance link
@ -139,32 +214,71 @@ class RAMCompare:
return False return False
def __call__(self, g_val, h_val): def has_subtype(self, g_vtx_type, h_vtx_type):
if g_val == None: g_vtx_original_types = self.bottom.read_outgoing_elements(g_vtx_type, "RAMifies")
return h_val == None 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 # mvs-edges (which are converted to vertices) only match with mvs-edges
if g_val == IS_EDGE: if g_vtx.value == IS_EDGE:
return h_val == IS_EDGE return h_vtx.value == IS_EDGE
if h_val == IS_EDGE: if h_vtx.value == IS_EDGE:
return False return False
# types only match with their supertypes if g_vtx.value == IS_MODELREF:
# we assume that 'RAMifies'-traceability links have been created between guest and host types return h_vtx.value == IS_MODELREF
# 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 isinstance(h_val, IS_TYPE): if h_vtx.value == IS_MODELREF:
return False return False
# print(g_val, h_val) # # types only match with their supertypes
return eval(g_val, {}, {'v': h_val}) # # 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

View file

@ -4,6 +4,9 @@ from services.bottom.V0 import Bottom
from services.primitives.integer_type import Integer from services.primitives.integer_type import Integer
from services.primitives.string_type import String 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 # Object Diagrams service
class OD: class OD:
@ -47,6 +50,9 @@ class OD:
def get_class_of_object(self, object_name: str): def get_class_of_object(self, object_name: str):
object_node, = self.bottom.read_outgoing_elements(self.model, object_name) # get the object 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") type_el, = self.bottom.read_outgoing_elements(object_node, "Morphism")
for key in self.bottom.read_keys(self.type_model): for key in self.bottom.read_keys(self.type_model):
type_el2, = self.bottom.read_outgoing_elements(self.type_model, key) 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): def create_slot(self, attr_name: str, object_name: str, target_name: str):
class_name = self.get_class_of_object(object_name) 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: # An attribute-link is indistinguishable from an ordinary link:
return self.create_link(attr_link_name, object_name, target_name) 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): def create_integer_value(self, name: str, value: int):
from services.primitives.integer_type import Integer from services.primitives.integer_type import Integer
int_node = self.bottom.create_node() int_node = self.bottom.create_node()
@ -84,16 +102,15 @@ class OD:
# create element + morphism links # create element + morphism links
element_node = self.bottom.create_node(str(model)) # create element node element_node = self.bottom.create_node(str(model)) # create element node
self.bottom.create_edge(self.model, element_node, name) # attach to model 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 type_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 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): 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) 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) 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 # generate a unique name for the link
i = 0; i = 0;

View file

@ -11,6 +11,7 @@ class Integer:
self.type_model = UUID(self.bottom.read_value(type_model_id_node)) self.type_model = UUID(self.bottom.read_value(type_model_id_node))
def create(self, value: int): def create(self, value: int):
# delete existing integer, if there is one
if "string" in self.bottom.read_keys(self.model): if "string" in self.bottom.read_keys(self.model):
instance, = self.bottom.read_outgoing_elements(self.model, "integer") instance, = self.bottom.read_outgoing_elements(self.model, "integer")
self.bottom.delete_element(instance) self.bottom.delete_element(instance)
@ -18,3 +19,7 @@ class Integer:
self.bottom.create_edge(self.model, _instance, "integer") self.bottom.create_edge(self.model, _instance, "integer")
_type, = self.bottom.read_outgoing_elements(self.type_model, "Integer") _type, = self.bottom.read_outgoing_elements(self.type_model, "Integer")
self.bottom.create_edge(_instance, _type, "Morphism") 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)

View file

@ -330,10 +330,12 @@ class SCD:
attr_link_node, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink") attr_link_node, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink")
class_node, = self.bottom.read_outgoing_elements(self.model, class_name) class_node, = self.bottom.read_outgoing_elements(self.model, class_name)
name_to_attr = {} name_to_attr = {}
for edge in self.bottom.read_outgoing_edges(class_node): for name in self.bottom.read_keys(class_node):
edge_types = self.bottom.read_outgoing_elements(edge, "Morphism") edges = self.bottom.read_outgoing_edges(class_node, name)
if attr_link_node in edge_types: for edge in edges:
name_to_attr[key] = edge 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 return name_to_attr
def delete_element(self, name: str): def delete_element(self, name: str):

View file

@ -5,39 +5,39 @@ from services.scd import SCD
from framework.conformance import Conformance 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): # def print_tree(root, max_depth, depth=0):
print(" "*depth, "root=", root, "value=", state.read_value(root)) # print(" "*depth, "root=", root, "value=", state.read_value(root))
src,tgt = state.read_edge(root) # src,tgt = state.read_edge(root)
if src != None: # if src != None:
print(" "*depth, "src...") # print(" "*depth, "src...")
print_tree(src, max_depth, depth+1) # print_tree(src, max_depth, depth+1)
if tgt != None: # if tgt != None:
print(" "*depth, "tgt...") # print(" "*depth, "tgt...")
print_tree(tgt, max_depth, depth+1) # print_tree(tgt, max_depth, depth+1)
for edge in state.read_outgoing(root): # for edge in state.read_outgoing(root):
for edge_label in state.read_outgoing(edge): # for edge_label in state.read_outgoing(edge):
[_,tgt] = state.read_edge(edge_label) # [_,tgt] = state.read_edge(edge_label)
label = state.read_value(tgt) # label = state.read_value(tgt)
print(" "*depth, " key:", label) # print(" "*depth, " key:", label)
[_, tgt] = state.read_edge(edge) # [_, tgt] = state.read_edge(edge)
value = state.read_value(tgt) # value = state.read_value(tgt)
if value != None: # if value != None:
print(" "*depth, " ->", tgt, " (value:", value, ")") # print(" "*depth, " ->", tgt, " (value:", value, ")")
else: # else:
print(" "*depth, " ->", tgt) # print(" "*depth, " ->", tgt)
if depth < max_depth: # if depth < max_depth:
if isinstance(value, str) and len(value) == 36: # if isinstance(value, str) and len(value) == 36:
i = None # i = None
try: # try:
i = UUID(value) # i = UUID(value)
except ValueError as e: # except ValueError as e:
# print("invalid UUID:", value) # # print("invalid UUID:", value)
pass # pass
if i != None: # if i != None:
print_tree(i, max_depth, depth+1) # print_tree(i, max_depth, depth+1)
print_tree(tgt, max_depth, depth+1) # print_tree(tgt, max_depth, depth+1)
bottom = Bottom(state) bottom = Bottom(state)
@ -117,20 +117,24 @@ def ramify(state: State, model: UUID) -> UUID:
# - max-card: same as original # - max-card: same as original
upper_card = find_cardinality(class_node, class_upper_card_node) upper_card = find_cardinality(class_node, class_upper_card_node)
print('creating class', class_name, "with card 0 ..", upper_card) 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 # traceability link
bottom.create_edge(ramified_class, class_node, "RAMifies") 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): for (attr_name, attr_edge) in get_attributes(class_node):
print(' creating attribute', attr_name, "with type String") print(' creating attribute', attr_name, "with type String")
# Every attribute becomes 'string' type # Every attribute becomes 'string' type
# The string will be a Python expression # 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 # traceability link
bottom.create_edge(ramified_attr_link, attr_edge, "RAMifies") bottom.create_edge(ramified_attr_link, attr_edge, "RAMifies")
associations = scd.get_associations() associations = scd.get_associations()
for assoc_name, assoc_node in associations.items(): for assoc_name, assoc_node in associations.items():
# For every association in our original model, create an association: # 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)) src = scd.get_class_name(bottom.read_edge_source(assoc_node))
tgt = scd.get_class_name(bottom.read_edge_target(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) 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, src_max_c=src_upper_card,
tgt_max_c=tgt_upper_card) tgt_max_c=tgt_upper_card)
# traceability link # traceability link
bottom.create_edge(ramified_assoc, assoc_node, "RAMifies") bottom.create_edge(ramified_assoc, assoc_node, "RAMifies")
for inh_name, inh_node in scd.get_inheritances().items(): for inh_name, inh_node in scd.get_inheritances().items():
# Re-create inheritance links like in our original model: # Re-create inheritance links like in our original model:
src = scd.get_class_name(bottom.read_edge_source(inh_node)) src = scd.get_class_name(bottom.read_edge_source(inh_node))
tgt = scd.get_class_name(bottom.read_edge_target(inh_node)) tgt = scd.get_class_name(bottom.read_edge_target(inh_node))
print('creating inheritance link', src, '->', tgt) print('creating inheritance link', prefix+src, '->', prefix+tgt)
ramified_inh_link = ramified_scd.create_inheritance(src, tgt) ramified_inh_link = ramified_scd.create_inheritance(prefix+src, prefix+tgt)
# Double-check: The RAMified meta-model should also conform to 'SCD':
# The RAMified meta-model should also conform to 'SCD':
conf = Conformance(state, ramified, scd_metamodel) 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 return ramified