Conformance checker: use CDAPI for subtype checking

This commit is contained in:
Joeri Exelmans 2024-11-05 14:17:09 +01:00
parent 361591f971
commit 31e0f8195f

View file

@ -51,9 +51,6 @@ class Conformance:
self.bottom.read_outgoing_elements(self.type_model, e)[0] self.bottom.read_outgoing_elements(self.type_model, e)[0]
: e for e in self.bottom.read_keys(self.type_model) : e for e in self.bottom.read_keys(self.type_model)
} }
self.sub_types: Dict[str, Set[str]] = {
k: set() for k in self.bottom.read_keys(self.type_model)
}
self.primitive_values: Dict[UUID, Any] = {} self.primitive_values: Dict[UUID, Any] = {}
self.abstract_types: List[str] = [] self.abstract_types: List[str] = []
self.multiplicities: Dict[str, Tuple] = {} self.multiplicities: Dict[str, Tuple] = {}
@ -131,40 +128,6 @@ class Conformance:
except ValueError: except ValueError:
return None return None
def precompute_sub_types(self):
"""
Creates an internal representation of sub-type hierarchies that is
more easily queryable that the state graph
"""
# collect inheritance link instances
inh_element, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
inh_links = []
for tm_element, tm_name in self.type_model_names.items():
morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
if inh_element in morphisms:
# we have an instance of an inheritance link
inh_links.append(tm_element)
# for each inheritance link we add the parent and child to the sub types map
for link in inh_links:
tm_source = self.bottom.read_edge_source(link)
tm_target = self.bottom.read_edge_target(link)
parent_name = self.type_model_names[tm_target]
child_name = self.type_model_names[tm_source]
self.sub_types[parent_name].add(child_name)
# iteratively expand the sub type hierarchies in the sub types map
stop = False
while not stop:
stop = True
for child_name, child_children in self.sub_types.items():
for parent_name, parent_children in self.sub_types.items():
if child_name in parent_children:
original_size = len(parent_children)
parent_children.update(child_children)
if len(parent_children) != original_size:
stop = False
def deref_primitive_values(self): def deref_primitive_values(self):
""" """
Prefetch the values stored in referenced primitive type models Prefetch the values stored in referenced primitive type models
@ -286,7 +249,6 @@ class Conformance:
for each link, check whether its source and target are of a valid type for each link, check whether its source and target are of a valid type
""" """
errors = [] errors = []
self.precompute_sub_types()
for m_name, tm_name in self.type_mapping.items(): for m_name, tm_name in self.type_mapping.items():
m_element, = self.bottom.read_outgoing_elements(self.model, m_name) m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
m_source = self.bottom.read_edge_source(m_element) m_source = self.bottom.read_edge_source(m_element)
@ -321,17 +283,17 @@ class Conformance:
for type_name in self.type_model_names.values(): for type_name in self.type_model_names.values():
# abstract classes # abstract classes
if type_name in self.abstract_types: if type_name in self.abstract_types:
type_count = list(self.type_mapping.values()).count(type_name) count = list(self.type_mapping.values()).count(type_name)
if type_count > 0: if count > 0:
errors.append(f"Invalid instantiation of abstract class: '{type_name}'") errors.append(f"Invalid instantiation of abstract class: '{type_name}'")
# class multiplicities # class multiplicities
if type_name in self.multiplicities: if type_name in self.multiplicities:
lc, uc = self.multiplicities[type_name] lc, uc = self.multiplicities[type_name]
type_count = list(self.type_mapping.values()).count(type_name) count = 0
for sub_type in self.sub_types[type_name]: for sub_type in self.cdapi.transitive_sub_types[type_name]:
type_count += list(self.type_mapping.values()).count(sub_type) count += list(self.type_mapping.values()).count(sub_type)
if type_count < lc or type_count > uc: if count < lc or count > uc:
errors.append(f"Cardinality of type exceeds valid multiplicity range: '{type_name}' ({type_count})") errors.append(f"Cardinality of type exceeds valid multiplicity range: '{type_name}' ({count})")
# association/attribute source multiplicities # association/attribute source multiplicities
if type_name in self.source_multiplicities: if type_name in self.source_multiplicities:
@ -341,7 +303,7 @@ class Conformance:
tgt_type_name = self.type_model_names[tgt_type_obj] tgt_type_name = self.type_model_names[tgt_type_obj]
lc, uc = self.source_multiplicities[type_name] lc, uc = self.source_multiplicities[type_name]
for obj_name, obj_type_name in self.type_mapping.items(): for obj_name, obj_type_name in self.type_mapping.items():
if obj_type_name == tgt_type_name or obj_type_name in self.sub_types[tgt_type_name]: if self.cdapi.is_subtype(super_type_name=tgt_type_name, sub_type_name=obj_type_name):
# obj's type has this incoming association -> now we will count the number of links typed by it # obj's type has this incoming association -> now we will count the number of links typed by it
count = 0 count = 0
obj, = self.bottom.read_outgoing_elements(self.model, obj_name) obj, = self.bottom.read_outgoing_elements(self.model, obj_name)
@ -349,7 +311,7 @@ class Conformance:
for i in incoming: for i in incoming:
try: try:
type_of_incoming_link = self.type_mapping[self.model_names[i]] type_of_incoming_link = self.type_mapping[self.model_names[i]]
if type_of_incoming_link == type_name or type_of_incoming_link in self.sub_types[type_name]: if self.cdapi.is_subtype(super_type_name=type_name, sub_type_name=type_of_incoming_link):
count += 1 count += 1
except KeyError: except KeyError:
pass # for elements not part of model, e.g. morphism links pass # for elements not part of model, e.g. morphism links
@ -364,7 +326,7 @@ class Conformance:
src_type_name = self.type_model_names[src_type_obj] src_type_name = self.type_model_names[src_type_obj]
lc, uc = self.target_multiplicities[type_name] lc, uc = self.target_multiplicities[type_name]
for obj_name, obj_type_name in self.type_mapping.items(): for obj_name, obj_type_name in self.type_mapping.items():
if obj_type_name == src_type_name or obj_type_name in self.sub_types[src_type_name]: if self.cdapi.is_subtype(super_type_name=src_type_name, sub_type_name=obj_type_name):
# obj's type has this outgoing association -> now we will count the number of links typed by it # obj's type has this outgoing association -> now we will count the number of links typed by it
count = 0 count = 0
obj, = self.bottom.read_outgoing_elements(self.model, obj_name) obj, = self.bottom.read_outgoing_elements(self.model, obj_name)
@ -372,7 +334,7 @@ class Conformance:
for o in outgoing: for o in outgoing:
try: try:
type_of_outgoing_link = self.type_mapping[self.model_names[o]] type_of_outgoing_link = self.type_mapping[self.model_names[o]]
if type_of_outgoing_link == type_name or type_of_outgoing_link in self.sub_types[type_name]: if self.cdapi.is_subtype(super_type_name=type_name, sub_type_name=type_of_outgoing_link):
count += 1 count += 1
except KeyError: except KeyError:
pass # for elements not part of model, e.g. morphism links pass # for elements not part of model, e.g. morphism links
@ -481,7 +443,6 @@ class Conformance:
""" """
Make an internal representation of type structures such that comparing type structures is easier Make an internal representation of type structures such that comparing type structures is easier
""" """
self.precompute_sub_types()
scd_elements = self.bottom.read_outgoing_elements(self.scd_model) scd_elements = self.bottom.read_outgoing_elements(self.scd_model)
# collect types # collect types
class_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class") class_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class")
@ -523,8 +484,11 @@ class Conformance:
# attribute is stored as a (name, optional, type) triple # attribute is stored as a (name, optional, type) triple
self.structures.setdefault(source_type_name, set()).add((name, opt, target_type_name)) self.structures.setdefault(source_type_name, set()).add((name, opt, target_type_name))
# extend structures of sub types with attrs of super types # extend structures of sub types with attrs of super types
for super_type, sub_types in self.sub_types.items(): for super_type, sub_types in self.odapi.transitive_sub_types.items():
# JE: I made an untested change here! Can't test because structural conformance checking is broken.
# for super_type, sub_types in self.sub_types.items():
for sub_type in sub_types: for sub_type in sub_types:
if sub_type != super_type:
self.structures.setdefault(sub_type, set()).update(self.structures[super_type]) self.structures.setdefault(sub_type, set()).update(self.structures[super_type])
# filter out abstract types, as they cannot be instantiated # filter out abstract types, as they cannot be instantiated
# retrieve Class_abstract to check whether element is a morphism of Class_abstract # retrieve Class_abstract to check whether element is a morphism of Class_abstract
@ -616,11 +580,13 @@ class Conformance:
candidate_element, = self.bottom.read_outgoing_elements(self.type_model, candidate_name) candidate_element, = self.bottom.read_outgoing_elements(self.type_model, candidate_name)
candidate_source = self.type_model_names[self.bottom.read_edge_source(candidate_element)] candidate_source = self.type_model_names[self.bottom.read_edge_source(candidate_element)]
if candidate_source not in source_candidates: if candidate_source not in source_candidates:
if len(source_candidates.intersection(set(self.sub_types[candidate_source]))) == 0: if len(source_candidates.intersection(set(self.odapi.transitive_sub_types[candidate_source]))) == 0:
# if len(source_candidates.intersection(set(self.sub_types[candidate_source]))) == 0:
remove.add(candidate_name) remove.add(candidate_name)
candidate_target = self.type_model_names[self.bottom.read_edge_target(candidate_element)] candidate_target = self.type_model_names[self.bottom.read_edge_target(candidate_element)]
if candidate_target not in target_candidates: if candidate_target not in target_candidates:
if len(target_candidates.intersection(set(self.sub_types[candidate_target]))) == 0: if len(target_candidates.intersection(set(self.odapi.transitive_sub_types[candidate_target]))) == 0:
# if len(target_candidates.intersection(set(self.sub_types[candidate_target]))) == 0:
remove.add(candidate_name) remove.add(candidate_name)
self.candidates[m_name] = self.candidates[m_name].difference(remove) self.candidates[m_name] = self.candidates[m_name].difference(remove)