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:
parent
700a4d103f
commit
ae5eaedb4b
8 changed files with 284 additions and 129 deletions
|
|
@ -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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Add table
Add a link
Reference in a new issue