Browse Source

PlantUML: render class cardinalities. Extend constraint checker API.

Joeri Exelmans 10 months ago
parent
commit
e70eae2286

+ 1 - 23
bootstrap/primitive.py

@@ -36,13 +36,9 @@ def bootstrap_type(type_name: str, scd_root: UUID, model_root: UUID, integer_typ
     return class_node
 
 def bootstrap_constraint(class_node, type_name: str, python_type: str, scd_root: UUID, model_root: UUID, actioncode_type: UUID, state: State):
-
-    # set constraint
-    # chicken-and-egg problem: we cannot create an action-code constraint because the action-code MM doesn't exist yet
-
     bottom = Bottom(state)
     constraint_model = bottom.create_node()
-    ActionCode(constraint_model, state).create(f"isinstance(read_value(element),{python_type})")
+    ActionCode(constraint_model, state).create(f"isinstance(read_value(this),{python_type})")
     constraint_node = bottom.create_node(str(constraint_model))
     bottom.create_edge(model_root, constraint_node, f"{type_name}.constraint")
     constraint_link = bottom.create_edge(class_node, constraint_node)
@@ -52,24 +48,6 @@ def bootstrap_constraint(class_node, type_name: str, python_type: str, scd_root:
     bottom.create_edge(constraint_node, scd_node, "Morphism")
     bottom.create_edge(constraint_link, scd_link, "Morphism")
     
-
-# def bootstrap_type_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
-
-
-# def bootstrap_boolean_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
-
-
-# def bootstrap_integer_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
-
-
-# def bootstrap_float_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
-
-
-# def bootstrap_string_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
-
-# def bootstrap_actioncode_type(scd_root: UUID, model_root: UUID, integer_type: UUID, actioncode_type: UUID, state: State):
-#     # we store action code as Python string:
-
 def bootstrap_primitive_types(scd_root, state, integer_type, boolean_type, float_type, string_type, type_type, actioncode_type):
     # Order is important: Integer must come first
     class_integer    = bootstrap_type("Integer",    scd_root, integer_type,    integer_type, state)

+ 11 - 2
concrete_syntax/plantuml/renderer.py

@@ -23,10 +23,19 @@ def render_class_diagram(state, model, prefix_ids=""):
         if slot != None:
             is_abstract, _ = od.read_primitive_value(bottom, slot, model_od.type_model)
 
+        lower_card, upper_card = model_scd.get_class_cardinalities(class_node)
+
+        if lower_card == None and upper_card == None:
+            card_spec = ""
+        else:
+            card_spec = f"{0 if lower_card == None else lower_card}..{"*" if upper_card == None else upper_card}"
+
         if is_abstract:
-            output += f"\nabstract class \"{name}\" as {make_id(class_node)}"
+            output += f"\nabstract class \"{name} {card_spec}\" as {make_id(class_node)}"
         else:
-            output += f"\nclass \"{name}\" as {make_id(class_node)}"
+            output += f"\nclass \"{name} {card_spec}\" as {make_id(class_node)}"
+
+
 
         # Render attributes
         output += " {"

+ 5 - 2
concrete_syntax/textual_od/parser.py

@@ -29,8 +29,11 @@ BOOL: "True" | "False"
 CODE: /`[^`]*`/
 INDENTED_CODE: /```[^`]*```/
 
-object: [IDENTIFIER] ":" IDENTIFIER [link_spec] ["{" slot* "}"]
+#        name (optional)      type        
+object: [IDENTIFIER]     ":"  IDENTIFIER [link_spec] ["{" slot* "}"]
+
 link_spec: "(" IDENTIFIER "->" IDENTIFIER ")"
+
 slot: IDENTIFIER "=" literal ";"
 """
 
@@ -78,7 +81,7 @@ def parse_od(state, cs_text, mm):
                 space_count += 1
             lines = token.split('\n')[1:-1]
             for line in lines:
-                if line[0:space_count] != ' '*space_count:
+                if len(line) >= space_count and line[0:space_count] != ' '*space_count:
                     raise Exception("wrong indentation of INDENTED_CODE")
             unindented_lines = [l[space_count:] for l in lines]
             return _Code('\n'.join(unindented_lines))

+ 1 - 10
experiments/exp_plantuml.py

@@ -1,16 +1,7 @@
 from state.devstate import DevState
 from bootstrap.scd import bootstrap_scd
-from uuid import UUID
 from services.scd import SCD
-from framework.conformance import Conformance
-from services.od import OD
-from transformation.ramify import ramify
-from transformation import rewriter
-from services.bottom.V0 import Bottom
-from services.primitives.integer_type import Integer
-from pattern_matching import mvs_adapter
-from pattern_matching.matcher import MatcherVF2
-from renderer import plantuml
+from concrete_syntax.plantuml import renderer as plantuml
 
 def main():
     state = DevState()

+ 139 - 0
experiments/exp_scd.plantuml

@@ -0,0 +1,139 @@
+package "DSL Meta-Model" {
+class "Bear" as 00000000_0000_0000_0000_00000000046d {
+}
+abstract class "Animal" as 00000000_0000_0000_0000_000000000474 {
+}
+class "Man" as 00000000_0000_0000_0000_000000000491 {
+  weight : Integer
+}
+
+00000000_0000_0000_0000_000000000474 <|-- 00000000_0000_0000_0000_000000000491
+00000000_0000_0000_0000_000000000474 <|-- 00000000_0000_0000_0000_00000000046d
+
+00000000_0000_0000_0000_000000000491 " " --> "1 .. *" 00000000_0000_0000_0000_000000000474 : afraidOf
+}
+package "Int Meta-Model" {
+class "Integer" as 00000000_0000_0000_0000_000000000094 {
+}
+
+
+}
+package "RAMified DSL Meta-Model" {
+class "RAM_Bear" as 00000000_0000_0000_0000_0000000005bb {
+}
+class "RAM_Animal" as 00000000_0000_0000_0000_0000000005c5 {
+}
+class "RAM_Man" as 00000000_0000_0000_0000_0000000005cf {
+  RAM_weight : ActionCode
+}
+
+00000000_0000_0000_0000_0000000005c5 <|-- 00000000_0000_0000_0000_0000000005cf
+00000000_0000_0000_0000_0000000005c5 <|-- 00000000_0000_0000_0000_0000000005bb
+
+00000000_0000_0000_0000_0000000005cf " " --> "0 .. *" 00000000_0000_0000_0000_0000000005c5 : RAM_afraidOf
+}
+package "RAMified Int Meta-Model" {
+class "RAM_Integer" as 00000000_0000_0000_0000_00000000064c {
+}
+
+
+}
+00000000_0000_0000_0000_0000000005bb ..> 00000000_0000_0000_0000_00000000046d #line:green;text:green : RAMifies
+00000000_0000_0000_0000_0000000005c5 ..> 00000000_0000_0000_0000_000000000474 #line:green;text:green : RAMifies
+00000000_0000_0000_0000_0000000005cf ..> 00000000_0000_0000_0000_000000000491 #line:green;text:green : RAMifies
+00000000_0000_0000_0000_0000000005cf::RAM_weight ..> 00000000_0000_0000_0000_000000000491::weight #line:green;text:green : RAMifies
+00000000_0000_0000_0000_00000000064c ..> 00000000_0000_0000_0000_000000000094 #line:green;text:green : RAMifies
+package "LHS" {
+map "scaryAnimal : RAM_Animal" as 00000000_0000_0000_0000_00000000068a {
+}
+map "man : RAM_Man" as 00000000_0000_0000_0000_00000000066d {
+RAM_weight => `v > 60`
+}
+
+00000000_0000_0000_0000_00000000066d -> 00000000_0000_0000_0000_00000000068a : :RAM_afraidOf
+}
+00000000_0000_0000_0000_00000000068a ..> 00000000_0000_0000_0000_0000000005c5 #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_00000000066d ..> 00000000_0000_0000_0000_0000000005cf #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_00000000066d::RAM_weight ..> 00000000_0000_0000_0000_0000000005cf::RAM_weight #line:blue;text:blue : instanceOf
+
+package "RHS" {
+map "man : RAM_Man" as 00000000_0000_0000_0000_000000000699 {
+RAM_weight => `v + 5`
+}
+map "bill : RAM_Man" as 00000000_0000_0000_0000_0000000006b6 {
+RAM_weight => `100`
+}
+
+00000000_0000_0000_0000_0000000006b6 -> 00000000_0000_0000_0000_000000000699 : :RAM_afraidOf
+}
+00000000_0000_0000_0000_000000000699 ..> 00000000_0000_0000_0000_0000000005cf #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000699::RAM_weight ..> 00000000_0000_0000_0000_0000000005cf::RAM_weight #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_0000000006b6 ..> 00000000_0000_0000_0000_0000000005cf #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_0000000006b6::RAM_weight ..> 00000000_0000_0000_0000_0000000005cf::RAM_weight #line:blue;text:blue : instanceOf
+
+package "Model (before rewrite)" {
+map "bear2 : Bear" as 00000000_0000_0000_0000_000000000597 {
+}
+map "bear1 : Bear" as 00000000_0000_0000_0000_000000000590 {
+}
+map "george : Man" as 00000000_0000_0000_0000_000000000573 {
+weight => 80
+}
+
+00000000_0000_0000_0000_000000000573 -> 00000000_0000_0000_0000_000000000590 : :afraidOf
+00000000_0000_0000_0000_000000000573 -> 00000000_0000_0000_0000_000000000597 : :afraidOf
+}
+00000000_0000_0000_0000_000000000597 ..> 00000000_0000_0000_0000_00000000046d #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000590 ..> 00000000_0000_0000_0000_00000000046d #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000573 ..> 00000000_0000_0000_0000_000000000491 #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000573::weight ..> 00000000_0000_0000_0000_000000000491::weight #line:blue;text:blue : instanceOf
+
+00000000_0000_0000_0000_00000000068a ..> 00000000_0000_0000_0000_000000000590 #line:red;line.dotted;text:red : matchedWith
+00000000_0000_0000_0000_00000000066d ..> 00000000_0000_0000_0000_000000000573 #line:red;line.dotted;text:red : matchedWith
+00000000_0000_0000_0000_00000000066d::RAM_weight ..> 00000000_0000_0000_0000_000000000573::weight #line:red;line.dotted;text:red : matchedWith
+package "Model (after rewrite 0)" {
+map "bear2 : Bear" as 00000000_0000_0000_0000_0000000006db {
+}
+map "george : Man" as 00000000_0000_0000_0000_0000000006e9 {
+weight => 85
+}
+map "bill0 : Man" as 00000000_0000_0000_0000_000000000723 {
+weight => 100
+}
+
+00000000_0000_0000_0000_000000000723 -> 00000000_0000_0000_0000_0000000006e9 : :afraidOf
+00000000_0000_0000_0000_0000000006e9 -> 00000000_0000_0000_0000_0000000006db : :afraidOf
+}
+00000000_0000_0000_0000_000000000699 ..> 00000000_0000_0000_0000_0000000006e9 #line:red;line.dotted;text:red : matchedWith
+00000000_0000_0000_0000_000000000699::RAM_weight ..> 00000000_0000_0000_0000_0000000006e9::weight #line:red;line.dotted;text:red : matchedWith
+00000000_0000_0000_0000_0000000006b6 ..> 00000000_0000_0000_0000_000000000723 #line:red;line.dotted;text:red : matchedWith
+00000000_0000_0000_0000_0000000006db ..> 00000000_0000_0000_0000_00000000046d #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_0000000006e9 ..> 00000000_0000_0000_0000_000000000491 #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_0000000006e9::weight ..> 00000000_0000_0000_0000_000000000491::weight #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000723 ..> 00000000_0000_0000_0000_000000000491 #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000723::weight ..> 00000000_0000_0000_0000_000000000491::weight #line:blue;text:blue : instanceOf
+
+00000000_0000_0000_0000_00000000068a ..> 00000000_0000_0000_0000_000000000597 #line:orange;line.dotted;text:orange : matchedWith
+00000000_0000_0000_0000_00000000066d ..> 00000000_0000_0000_0000_000000000573 #line:orange;line.dotted;text:orange : matchedWith
+00000000_0000_0000_0000_00000000066d::RAM_weight ..> 00000000_0000_0000_0000_000000000573::weight #line:orange;line.dotted;text:orange : matchedWith
+package "Model (after rewrite 1)" {
+map "bear1 : Bear" as 00000000_0000_0000_0000_000000000747 {
+}
+map "george : Man" as 00000000_0000_0000_0000_00000000074e {
+weight => 85
+}
+map "bill0 : Man" as 00000000_0000_0000_0000_000000000788 {
+weight => 100
+}
+
+00000000_0000_0000_0000_000000000788 -> 00000000_0000_0000_0000_00000000074e : :afraidOf
+00000000_0000_0000_0000_00000000074e -> 00000000_0000_0000_0000_000000000747 : :afraidOf
+}
+00000000_0000_0000_0000_000000000699 ..> 00000000_0000_0000_0000_00000000074e #line:orange;line.dotted;text:orange : matchedWith
+00000000_0000_0000_0000_000000000699::RAM_weight ..> 00000000_0000_0000_0000_00000000074e::weight #line:orange;line.dotted;text:orange : matchedWith
+00000000_0000_0000_0000_0000000006b6 ..> 00000000_0000_0000_0000_000000000788 #line:orange;line.dotted;text:orange : matchedWith
+00000000_0000_0000_0000_000000000747 ..> 00000000_0000_0000_0000_00000000046d #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_00000000074e ..> 00000000_0000_0000_0000_000000000491 #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_00000000074e::weight ..> 00000000_0000_0000_0000_000000000491::weight #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000788 ..> 00000000_0000_0000_0000_000000000491 #line:blue;text:blue : instanceOf
+00000000_0000_0000_0000_000000000788::weight ..> 00000000_0000_0000_0000_000000000491::weight #line:blue;text:blue : instanceOf

+ 58 - 100
experiments/exp_scd.py

@@ -15,8 +15,6 @@ from services.primitives.integer_type import Integer
 from concrete_syntax.plantuml import renderer as plantuml
 from concrete_syntax.textual_od import parser, renderer
 
-import sys
-
 def create_integer_node(state, i: int):
     node = state.create_node()
     integer_t = Integer(node, state)
@@ -27,119 +25,79 @@ def main():
     state = DevState()
     root = state.read_root() # id: 0
 
-    scd_mm_id = bootstrap_scd(state)
+    # Meta-meta-model: a class diagram that describes the language of class diagrams
+    scd_mmm_id = bootstrap_scd(state)
     int_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "Integer")))
     string_mm_id = UUID(state.read_value(state.read_dict(state.read_root(), "String")))
 
-    # conf = Conformance(state, scd_mm_id, scd_mm_id)
+    # conf = Conformance(state, scd_mmm_id, scd_mmm_id)
     # print("Conformance SCD_MM -> SCD_MM?", conf.check_nominal(log=True))
     # print("--------------------------------------")
-    # print(renderer.render_od(state, scd_mm_id, scd_mm_id, hide_names=True))
+    # print(renderer.render_od(state, scd_mmm_id, scd_mmm_id, hide_names=True))
     # print("--------------------------------------")
 
-    def create_dsl_mm_api():
-        # Create DSL MM with SCD API
-        dsl_mm_id = state.create_node()
-        dsl_mm_scd = SCD(dsl_mm_id, state)
-        dsl_mm_scd.create_class("Animal", abstract=True)
-        dsl_mm_scd.create_class("Man", min_c=1, max_c=2)
-        dsl_mm_scd.create_inheritance("Man", "Animal")
-        dsl_mm_scd.create_model_ref("Integer", int_mm_id)
-        dsl_mm_scd.create_attribute_link("Man", "Integer", "weight", optional=False)
-        dsl_mm_scd.create_class("Bear")
-        dsl_mm_scd.create_inheritance("Bear", "Animal")
-        dsl_mm_scd.create_association("afraidOf", "Man", "Animal",
-            # Every Man afraid of at least one Animal:
-            src_min_c=0,
-            src_max_c=None,
-            tgt_min_c=1,
-            tgt_max_c=None,
-        )
-        dsl_mm_scd.add_constraint("Man", "read_value(element) < 100")
-        return dsl_mm_id
-
-    def create_dsl_mm_parser():
-        # Create DSL MM with parser
-        dsl_mm_cs = """
-            # Integer:ModelRef
-            Bear:Class
-            Animal:Class {
-                abstract = True;
-            }
-            Man:Class {
-                lower_cardinality = 1;
-                upper_cardinality = 2;
-                constraint = `get_value(get_slot(element, "weight")) > 20`;
-            }
-            Man_weight:AttributeLink (Man -> Integer) {
-                name = "weight";
-                optional = False;
-                constraint = ```
-                    # this is the same constraint as above, but this time, part of the attributelink itself (and thus shorter)
-                    node = get_target(element)
-                    get_value(node) > 20
-                ```;
-            }
-            afraidOf:Association (Man -> Animal) {
-                target_lower_cardinality = 1;
-            }
-            :Inheritance (Man -> Animal)
-            :Inheritance (Bear -> Animal)
-
-            not_too_fat:GlobalConstraint {
-                constraint = ```
-                    # total weight of all men low enough
-                    total_weight = 0
-                    for man_name, man_id in get_all_instances("Man"):
-                        total_weight += get_value(get_slot(man_id, "weight"))
-                    total_weight < 85
-                ```;
-            }
-        """
-        dsl_mm_id = parser.parse_od(state, dsl_mm_cs, mm=scd_mm_id)
-        return dsl_mm_id
+    # Create DSL MM with parser
+    dsl_mm_cs = """
+        # Integer:ModelRef
+        Bear:Class
+        Animal:Class {
+            abstract = True;
+        }
+        Man:Class {
+            lower_cardinality = 1;
+            upper_cardinality = 2;
+            constraint = ```
+                get_value(get_slot(this, "weight")) > 20
+            ```;
+        }
+        Man_weight:AttributeLink (Man -> Integer) {
+            name = "weight";
+            optional = False;
+            constraint = ```
+                # this is the same constraint as above, but this time, part of the attributelink itself (and thus shorter)
+                tgt = get_target(this)
+                tgt_type = get_type_name(tgt)
+                get_value(tgt) > 20
+            ```;
+        }
+        afraidOf:Association (Man -> Animal) {
+            target_lower_cardinality = 1;
+        }
+        :Inheritance (Man -> Animal)
+        :Inheritance (Bear -> Animal)
+
+        not_too_fat:GlobalConstraint {
+            constraint = ```
+                # total weight of all men low enough
+                total_weight = 0
+                for man_name, man_id in get_all_instances("Man"):
+                    total_weight += get_value(get_slot(man_id, "weight"))
+                total_weight < 85
+            ```;
+        }
+    """
+    dsl_mm_id = parser.parse_od(state, dsl_mm_cs, mm=scd_mmm_id)
     
-    def create_dsl_m_api():
-        # Create DSL M with OD API
-        dsl_m_id = state.create_node()
-        dsl_m_od = OD(dsl_mm_id, dsl_m_id, state)
-        dsl_m_od.create_object("george", "Man")
-        dsl_m_od.create_slot("weight", "george",
-            dsl_m_od.create_integer_value("george.weight", 80))
-        dsl_m_od.create_object("bear1", "Bear")
-        dsl_m_od.create_object("bear2", "Bear")
-        dsl_m_od.create_link("georgeAfraidOfBear1", "afraidOf", "george", "bear1")
-        dsl_m_od.create_link("georgeAfraidOfBear2", "afraidOf", "george", "bear2")
-        return dsl_m_id
-
-    def create_dsl_m_parser():
-        # Create DSL M with parser
-        dsl_m_cs = """
-            george:Man {
-                weight = 80;
-            }
-            bear1:Bear
-            bear2:Bear
-            :afraidOf (george -> bear1)
-            :afraidOf (george -> bear2)
-        """
-        dsl_m_id = parser.parse_od(state, dsl_m_cs, mm=dsl_mm_id)
-        return dsl_m_id
-
-
-    # dsl_mm_id = create_dsl_mm_api()
-    dsl_mm_id = create_dsl_mm_parser()
+    # Create DSL M with parser
+    dsl_m_cs = """
+        george:Man {
+            weight = 80;
+        }
+        bear1:Bear
+        bear2:Bear
+        :afraidOf (george -> bear1)
+        :afraidOf (george -> bear2)
+    """
+    dsl_m_id = parser.parse_od(state, dsl_m_cs, mm=dsl_mm_id)
 
     # print("DSL MM:")
     # print("--------------------------------------")
-    # print(renderer.render_od(state, dsl_mm_id, scd_mm_id, hide_names=True))
+    # print(renderer.render_od(state, dsl_mm_id, scd_mmm_id, hide_names=True))
     # print("--------------------------------------")
 
-    conf = Conformance(state, dsl_mm_id, scd_mm_id)
+    conf = Conformance(state, dsl_mm_id, scd_mmm_id)
     print("Conformance DSL_MM -> SCD_MM?", conf.check_nominal(log=True))
 
-    # dsl_m_id = create_dsl_m_api()
-    dsl_m_id = create_dsl_m_parser()
     # print("DSL M:")
     # print("--------------------------------------")
     # print(renderer.render_od(state, dsl_m_id, dsl_mm_id, hide_names=True))

+ 23 - 5
framework/conformance.py

@@ -375,21 +375,33 @@ class Conformance:
             'read_value': self.state.read_value,
             'get_value': lambda el: od.read_primitive_value(self.bottom, el, self.type_model)[0],
             'get_target': lambda el: self.bottom.read_edge_target(el),
+            'get_source': lambda el: self.bottom.read_edge_source(el),
             'get_slot': od.OD(self.type_model, self.model, self.state).get_slot,
-            'get_all_instances': self.get_all_instances
+            'get_all_instances': self.get_all_instances,
+            'get_name': lambda el: [name for name in self.bottom.read_keys(self.model) if self.bottom.read_outgoing_elements(self.model, name)[0] == el][0],
+            'get_type_name': self.get_type_name,
+            'get_outgoing': self.get_outgoing,
+            'get_incoming': self.get_incoming,
         }
         # print("evaluating constraint ...", code)
-        loc = {**kwargs, **funcs}
+        loc = {**kwargs, }
         result = exec_then_eval(
             code,
             {'__builtins__': {'isinstance': isinstance, 'print': print,
-                              'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len}
+                              'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len, 'set': set, 'dict': dict},
+                **funcs
              },  # globals
              loc # locals
         )
         # print('result =', result)
         return result
 
+    def get_type_name(self, element: UUID):
+        type_node = self.bottom.read_outgoing_elements(element, "Morphism")[0]
+        for type_name in self.bottom.read_keys(self.type_model):
+            if self.bottom.read_outgoing_elements(self.type_model, type_name)[0] == type_node:
+                return type_name
+
     def get_all_instances(self, type_name: str, include_subtypes=True):
         result = [e_name for e_name, t_name in self.type_mapping.items() if t_name == type_name]
         if include_subtypes:
@@ -399,6 +411,12 @@ class Conformance:
         result_with_ids = [ (e_name, self.bottom.read_outgoing_elements(self.model, e_name)[0]) for e_name in result]
         return result_with_ids
 
+    def get_outgoing(self, element: UUID, assoc_or_attr_name: str):
+        return od.find_outgoing_typed_by(self.bottom, src=element, type_node=self.bottom.read_outgoing_elements(self.type_model, assoc_or_attr_name)[0])
+
+    def get_incoming(self, element: UUID, assoc_or_attr_name: str):
+        return od.find_incoming_typed_by(self.bottom, tgt=element, type_node=self.bottom.read_outgoing_elements(self.type_model, assoc_or_attr_name)[0])
+
     def check_constraints(self):
         """
         Check whether all constraints defined for a model are respected
@@ -426,7 +444,7 @@ class Conformance:
                 morphisms = self.bottom.read_incoming_elements(tm_element, "Morphism")
                 morphisms = [m for m in morphisms if m in self.model_names]
                 for m_element in morphisms:
-                    result = self.evaluate_constraint(code, element=m_element, type_name=tm_name)
+                    result = self.evaluate_constraint(code, this=m_element)
                     description = f"Local constraint of \"{tm_name}\" in \"{m_name}\""
                     check_result(result, description)
 
@@ -555,7 +573,7 @@ class Conformance:
                                 # eval constraints
                                 code = self.read_attribute(attr_tm, "constraint")
                                 if code != None:
-                                    attr_conforms = self.evaluate_constraint(code, element=attr)
+                                    attr_conforms = self.evaluate_constraint(code, this=attr)
                             if attr_conforms:
                                 matched += 1
                                 print("     attr_conforms -> matched:", matched)

+ 9 - 0
services/od.py

@@ -317,6 +317,15 @@ def find_outgoing_typed_by(bottom, src: UUID, type_node: UUID):
                 break
     return edges
 
+def find_incoming_typed_by(bottom, tgt: UUID, type_node: UUID):
+    edges = []
+    for incoming_edge in bottom.read_incoming_edges(tgt):
+        for typedBy in bottom.read_outgoing_elements(incoming_edge, "Morphism"):
+            if typedBy == type_node:
+                edges.append(incoming_edge)
+                break
+    return edges
+
 def navigate_modelref(bottom, node: UUID):
     uuid = bottom.read_value(node)
     return UUID(uuid)

+ 5 - 0
services/scd.py

@@ -355,6 +355,11 @@ class SCD:
                     name_to_attr[name] = edge
         return name_to_attr
 
+    def get_class_cardinalities(self, class_node):
+        lower_card = od.find_cardinality(self.bottom, class_node, od.get_scd_mm_class_lowercard_node(self.bottom))
+        upper_card = od.find_cardinality(self.bottom, class_node, od.get_scd_mm_class_uppercard_node(self.bottom))
+        return lower_card, upper_card
+
     def get_assoc_cardinalities(self, assoc_edge):
         src_lower_card = od.find_cardinality(self.bottom, assoc_edge, od.get_scd_mm_assoc_src_lowercard_node(self.bottom))
         src_upper_card = od.find_cardinality(self.bottom, assoc_edge, od.get_scd_mm_assoc_src_uppercard_node(self.bottom))