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]
: 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.abstract_types: List[str] = []
self.multiplicities: Dict[str, Tuple] = {}
@ -131,40 +128,6 @@ class Conformance:
except ValueError:
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):
"""
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
"""
errors = []
self.precompute_sub_types()
for m_name, tm_name in self.type_mapping.items():
m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
m_source = self.bottom.read_edge_source(m_element)
@ -321,17 +283,17 @@ class Conformance:
for type_name in self.type_model_names.values():
# abstract classes
if type_name in self.abstract_types:
type_count = list(self.type_mapping.values()).count(type_name)
if type_count > 0:
count = list(self.type_mapping.values()).count(type_name)
if count > 0:
errors.append(f"Invalid instantiation of abstract class: '{type_name}'")
# class multiplicities
if type_name in self.multiplicities:
lc, uc = self.multiplicities[type_name]
type_count = list(self.type_mapping.values()).count(type_name)
for sub_type in self.sub_types[type_name]:
type_count += list(self.type_mapping.values()).count(sub_type)
if type_count < lc or type_count > uc:
errors.append(f"Cardinality of type exceeds valid multiplicity range: '{type_name}' ({type_count})")
count = 0
for sub_type in self.cdapi.transitive_sub_types[type_name]:
count += list(self.type_mapping.values()).count(sub_type)
if count < lc or count > uc:
errors.append(f"Cardinality of type exceeds valid multiplicity range: '{type_name}' ({count})")
# association/attribute 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]
lc, uc = self.source_multiplicities[type_name]
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
count = 0
obj, = self.bottom.read_outgoing_elements(self.model, obj_name)
@ -349,7 +311,7 @@ class Conformance:
for i in incoming:
try:
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
except KeyError:
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]
lc, uc = self.target_multiplicities[type_name]
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
count = 0
obj, = self.bottom.read_outgoing_elements(self.model, obj_name)
@ -372,7 +334,7 @@ class Conformance:
for o in outgoing:
try:
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
except KeyError:
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
"""
self.precompute_sub_types()
scd_elements = self.bottom.read_outgoing_elements(self.scd_model)
# collect types
class_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class")
@ -523,9 +484,12 @@ class Conformance:
# attribute is stored as a (name, optional, type) triple
self.structures.setdefault(source_type_name, set()).add((name, opt, target_type_name))
# 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:
self.structures.setdefault(sub_type, set()).update(self.structures[super_type])
if sub_type != super_type:
self.structures.setdefault(sub_type, set()).update(self.structures[super_type])
# filter out abstract types, as they cannot be instantiated
# retrieve Class_abstract to check whether element is a morphism of Class_abstract
class_abs_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class_abstract")
@ -616,11 +580,13 @@ class Conformance:
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)]
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)
candidate_target = self.type_model_names[self.bottom.read_edge_target(candidate_element)]
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)
self.candidates[m_name] = self.candidates[m_name].difference(remove)