conformance.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. from services.bottom.V0 import Bottom
  2. from services.primitives.actioncode_type import ActionCode
  3. from uuid import UUID
  4. from state.base import State
  5. from typing import Dict, Tuple, Set, Any, List
  6. from pprint import pprint
  7. import traceback
  8. from concrete_syntax.common import indent
  9. from util.eval import exec_then_eval
  10. from api.cd import CDAPI
  11. from api.od import ODAPI
  12. import functools
  13. def render_conformance_check_result(error_list):
  14. if len(error_list) == 0:
  15. return "CONFORM"
  16. else:
  17. joined = ''.join(('\n ▸ ' + err for err in error_list))
  18. return f"NOT CONFORM, {len(error_list)} errors:{joined}"
  19. class Conformance:
  20. # Parameter 'constraint_check_subtypes': whether to check local type-level constraints also on subtypes.
  21. def __init__(self, state: State, model: UUID, type_model: UUID, constraint_check_subtypes=True):
  22. self.state = state
  23. self.bottom = Bottom(state)
  24. self.model = model
  25. self.type_model = type_model
  26. self.constraint_check_subtypes = constraint_check_subtypes
  27. # MCL
  28. type_model_id = state.read_dict(state.read_root(), "SCD")
  29. self.scd_model = UUID(state.read_value(type_model_id))
  30. # Helpers
  31. self.cdapi = CDAPI(state, type_model)
  32. self.odapi = ODAPI(state, model, type_model)
  33. self.type_odapi = ODAPI(state, type_model, self.scd_model)
  34. # Pre-computed:
  35. self.abstract_types: List[str] = []
  36. self.multiplicities: Dict[str, Tuple] = {}
  37. self.source_multiplicities: Dict[str, Tuple] = {}
  38. self.target_multiplicities: Dict[str, Tuple] = {}
  39. # ?
  40. self.structures = {}
  41. self.candidates = {}
  42. def check_nominal(self, *, log=False):
  43. """
  44. Perform a nominal conformance check
  45. Args:
  46. log: boolean indicating whether to log errors
  47. Returns:
  48. Boolean indicating whether the check has passed
  49. """
  50. errors = []
  51. errors += self.check_typing()
  52. errors += self.check_link_typing()
  53. errors += self.check_multiplicities()
  54. errors += self.check_constraints()
  55. return errors
  56. # def check_structural(self, *, build_morphisms=True, log=False):
  57. # """
  58. # Perform a structural conformance check
  59. # Args:
  60. # build_morphisms: boolean indicating whether to create morpishm links
  61. # log: boolean indicating whether to log errors
  62. # Returns:
  63. # Boolean indicating whether the check has passed
  64. # """
  65. # try:
  66. # self.precompute_structures()
  67. # self.match_structures()
  68. # if build_morphisms:
  69. # self.build_morphisms()
  70. # self.check_nominal(log=log)
  71. # return True
  72. # except RuntimeError as e:
  73. # if log:
  74. # print(e)
  75. # return False
  76. def precompute_multiplicities(self):
  77. """
  78. Creates an internal representation of type multiplicities that is
  79. more easily queryable that the state graph
  80. """
  81. for clss_name, clss in self.type_odapi.get_all_instances("Class"):
  82. abstract = self.type_odapi.get_slot_value_default(clss, "abstract", default=False)
  83. if abstract:
  84. self.abstract_types.append(clss_name)
  85. lc = self.type_odapi.get_slot_value_default(clss, "lower_cardinality", default=0)
  86. uc = self.type_odapi.get_slot_value_default(clss, "upper_cardinality", default=float('inf'))
  87. if lc or uc:
  88. self.multiplicities[clss_name] = (lc, uc)
  89. for assoc_name, assoc in self.type_odapi.get_all_instances("Association"):
  90. # multiplicities for associations
  91. slc = self.type_odapi.get_slot_value_default(assoc, "source_lower_cardinality", default=0)
  92. suc = self.type_odapi.get_slot_value_default(assoc, "source_upper_cardinality", default=float('inf'))
  93. if slc or suc:
  94. self.source_multiplicities[assoc_name] = (slc, suc)
  95. tlc = self.type_odapi.get_slot_value_default(assoc, "target_lower_cardinality", default=0)
  96. tuc = self.type_odapi.get_slot_value_default(assoc, "target_upper_cardinality", default=float('inf'))
  97. if tlc or tuc:
  98. self.target_multiplicities[assoc_name] = (tlc, tuc)
  99. for attr_name, attr in self.type_odapi.get_all_instances("AttributeLink"):
  100. # optional for attribute links
  101. opt = self.type_odapi.get_slot_value(attr, "optional")
  102. if opt != None:
  103. self.source_multiplicities[attr_name] = (0, float('inf'))
  104. self.target_multiplicities[attr_name] = (0 if opt else 1, 1)
  105. def check_typing(self):
  106. """
  107. for each element of model check whether a morphism
  108. link exists to some element of type_model
  109. """
  110. errors = []
  111. # Recursively do a conformance check for each ModelRef
  112. for ref_name, ref in self.type_odapi.get_all_instances("ModelRef"):
  113. sub_mm = UUID(self.bottom.read_value(ref))
  114. for ref_inst_name, ref_inst in self.odapi.get_all_instances(ref_name):
  115. sub_m = UUID(self.bottom.read_value(ref_inst))
  116. nested_errors = Conformance(self.state, sub_m, sub_mm).check_nominal()
  117. errors += [f"In ModelRef ({m_name}):" + err for err in nested_errors]
  118. return errors
  119. def check_link_typing(self):
  120. """
  121. for each link, check whether its source and target are of a valid type
  122. """
  123. errors = []
  124. for tm_name, tm_element in self.type_odapi.get_all_instances("Association") + self.type_odapi.get_all_instances("AttributeLink"):
  125. for m_name, m_element in self.odapi.get_all_instances(tm_name):
  126. m_source = self.bottom.read_edge_source(m_element)
  127. m_target = self.bottom.read_edge_target(m_element)
  128. if m_source == None or m_target == None:
  129. # element is not a link
  130. continue
  131. # tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  132. tm_source = self.bottom.read_edge_source(tm_element)
  133. tm_target = self.bottom.read_edge_target(tm_element)
  134. # check if source is typed correctly
  135. # source_name = self.odapi.m_obj_to_name[m_source]
  136. source_type_actual = self.odapi.get_type_name(m_source)
  137. source_type_expected = self.odapi.mm_obj_to_name[tm_source]
  138. if not self.cdapi.is_subtype(super_type_name=source_type_expected, sub_type_name=source_type_actual):
  139. errors.append(f"Invalid source type '{source_type_actual}' for link '{m_name}:{tm_name}'")
  140. # check if target is typed correctly
  141. # target_name = self.odapi.m_obj_to_name[m_target]
  142. target_type_actual = self.odapi.get_type_name(m_target)
  143. target_type_expected = self.odapi.mm_obj_to_name[tm_target]
  144. if not self.cdapi.is_subtype(super_type_name=source_type_expected, sub_type_name=source_type_actual):
  145. errors.append(f"Invalid target type '{target_type_actual}' for link '{m_name}:{tm_name}'")
  146. return errors
  147. def check_multiplicities(self):
  148. """
  149. Check whether multiplicities for all types are respected
  150. """
  151. self.precompute_multiplicities()
  152. errors = []
  153. for class_name, clss in self.type_odapi.get_all_instances("Class"):
  154. # for type_name in self.odapi.mm_obj_to_name.values():
  155. # abstract classes
  156. if class_name in self.abstract_types:
  157. count = len(self.odapi.get_all_instances(class_name, include_subtypes=False))
  158. if count > 0:
  159. errors.append(f"Invalid instantiation of abstract class: '{class_name}'")
  160. # class multiplicities
  161. if class_name in self.multiplicities:
  162. lc, uc = self.multiplicities[class_name]
  163. count = len(self.odapi.get_all_instances(class_name, include_subtypes=True))
  164. if count < lc or count > uc:
  165. errors.append(f"Cardinality of type exceeds valid multiplicity range: '{class_name}' ({count})")
  166. for assoc_name, assoc in self.type_odapi.get_all_instances("Association") + self.type_odapi.get_all_instances("AttributeLink"):
  167. # association/attribute source multiplicities
  168. if assoc_name in self.source_multiplicities:
  169. # type is an association
  170. assoc, = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
  171. tgt_type_obj = self.bottom.read_edge_target(assoc)
  172. tgt_type_name = self.odapi.mm_obj_to_name[tgt_type_obj]
  173. lc, uc = self.source_multiplicities[assoc_name]
  174. for obj_name, obj in self.odapi.get_all_instances(tgt_type_name, include_subtypes=True):
  175. # obj's type has this incoming association -> now we will count the number of links typed by it
  176. count = len(self.odapi.get_incoming(obj, assoc_name, include_subtypes=True))
  177. if count < lc or count > uc:
  178. errors.append(f"Source cardinality of type '{assoc_name}' ({count}) out of bounds ({lc}..{uc}) in '{obj_name}'.")
  179. # association/attribute target multiplicities
  180. if assoc_name in self.target_multiplicities:
  181. # type is an association
  182. type_obj, = self.bottom.read_outgoing_elements(self.type_model, assoc_name)
  183. src_type_obj = self.bottom.read_edge_source(type_obj)
  184. src_type_name = self.odapi.mm_obj_to_name[src_type_obj]
  185. lc, uc = self.target_multiplicities[assoc_name]
  186. for obj_name, obj in self.odapi.get_all_instances(src_type_name, include_subtypes=True):
  187. # obj's type has this outgoing association -> now we will count the number of links typed by it
  188. count = len(self.odapi.get_outgoing(obj, assoc_name, include_subtypes=True))
  189. if count < lc or count > uc:
  190. errors.append(f"Target cardinality of type '{assoc_name}' ({count}) out of bounds ({lc}..{uc}) in '{obj_name}'.")
  191. return errors
  192. def evaluate_constraint(self, code, **kwargs):
  193. """
  194. Evaluate constraint code (Python code)
  195. """
  196. funcs = {
  197. 'read_value': self.state.read_value,
  198. 'get': self.odapi.get,
  199. 'get_value': self.odapi.get_value,
  200. 'get_target': self.odapi.get_target,
  201. 'get_source': self.odapi.get_source,
  202. 'get_slot': self.odapi.get_slot,
  203. 'get_slot_value': self.odapi.get_slot_value,
  204. 'get_all_instances': self.odapi.get_all_instances,
  205. 'get_name': self.odapi.get_name,
  206. 'get_type_name': self.odapi.get_type_name,
  207. 'get_outgoing': self.odapi.get_outgoing,
  208. 'get_incoming': self.odapi.get_incoming,
  209. 'has_slot': self.odapi.has_slot,
  210. }
  211. # print("evaluating constraint ...", code)
  212. loc = {**kwargs, }
  213. result = exec_then_eval(
  214. code,
  215. {'__builtins__': {'isinstance': isinstance, 'print': print,
  216. 'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple, 'len': len, 'set': set, 'dict': dict},
  217. **funcs
  218. }, # globals
  219. loc # locals
  220. )
  221. return result
  222. def check_constraints(self):
  223. """
  224. Check whether all constraints defined for a model are respected
  225. """
  226. errors = []
  227. def get_code(tm_name):
  228. constraints = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}.constraint")
  229. if len(constraints) == 1:
  230. constraint = constraints[0]
  231. code = ActionCode(UUID(self.bottom.read_value(constraint)), self.bottom.state).read()
  232. return code
  233. def check_result(result, description):
  234. if result == None:
  235. return # OK
  236. if isinstance(result, str):
  237. errors.append(f"{description} not satisfied. Reason: {result}")
  238. elif isinstance(result, bool):
  239. if not result:
  240. errors.append(f"{description} not satisfied.")
  241. elif isinstance(result, list):
  242. if len(result) > 0:
  243. reasons = indent('\n'.join(result), 4)
  244. errors.append(f"{description} not satisfied. Reasons:\n{reasons}")
  245. else:
  246. raise Exception(f"{description} evaluation result should be boolean or string! Instead got {result}")
  247. # local constraints
  248. for type_name in self.bottom.read_keys(self.type_model):
  249. code = get_code(type_name)
  250. if code != None:
  251. instances = self.odapi.get_all_instances(type_name, include_subtypes=self.constraint_check_subtypes)
  252. for obj_name, obj_id in instances:
  253. description = f"Local constraint of \"{type_name}\" in \"{obj_name}\""
  254. # print(description)
  255. try:
  256. result = self.evaluate_constraint(code, this=obj_id) # may raise
  257. check_result(result, description)
  258. except:
  259. errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
  260. # global constraints
  261. glob_constraints = []
  262. # find global constraints...
  263. glob_constraint_type, = self.bottom.read_outgoing_elements(self.scd_model, "GlobalConstraint")
  264. for tm_name in self.bottom.read_keys(self.type_model):
  265. tm_node, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  266. # print(key, node)
  267. for type_of_node in self.bottom.read_outgoing_elements(tm_node, "Morphism"):
  268. if type_of_node == glob_constraint_type:
  269. # node is GlobalConstraint
  270. glob_constraints.append(tm_name)
  271. # evaluate them (each constraint once)
  272. for tm_name in glob_constraints:
  273. code = get_code(tm_name)
  274. if code != None:
  275. description = f"Global constraint \"{tm_name}\""
  276. try:
  277. result = self.evaluate_constraint(code, model=self.model)
  278. except:
  279. errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
  280. check_result(result, description)
  281. return errors
  282. def precompute_structures(self):
  283. """
  284. Make an internal representation of type structures such that comparing type structures is easier
  285. """
  286. scd_elements = self.bottom.read_outgoing_elements(self.scd_model)
  287. # collect types
  288. class_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class")
  289. association_element, = self.bottom.read_outgoing_elements(self.scd_model, "Association")
  290. for tm_element, tm_name in self.odapi.mm_obj_to_name.items():
  291. # retrieve elements that tm_element is a morphism of
  292. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  293. morphism, = [m for m in morphisms if m in scd_elements]
  294. # check if tm_element is a morphism of AttributeLink
  295. if class_element == morphism or association_element == morphism:
  296. self.structures[tm_name] = set()
  297. # collect type structures
  298. # retrieve AttributeLink to check whether element is a morphism of AttributeLink
  299. attr_link_element, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink")
  300. for tm_element, tm_name in self.odapi.mm_obj_to_name.items():
  301. # retrieve elements that tm_element is a morphism of
  302. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  303. morphism, = [m for m in morphisms if m in scd_elements]
  304. # check if tm_element is a morphism of AttributeLink
  305. if attr_link_element == morphism:
  306. # retrieve attributes of attribute link, i.e. 'name' and 'optional'
  307. attrs = self.bottom.read_outgoing_elements(tm_element)
  308. name_model_node, = filter(lambda x: self.odapi.m_obj_to_name.get(x, "").endswith(".name"), attrs)
  309. opt_model_node, = filter(lambda x: self.odapi.m_obj_to_name.get(x, "").endswith(".optional"), attrs)
  310. # get attr name value
  311. name_model = UUID(self.bottom.read_value(name_model_node))
  312. name_node, = self.bottom.read_outgoing_elements(name_model)
  313. name = self.bottom.read_value(name_node)
  314. # get attr opt value
  315. opt_model = UUID(self.bottom.read_value(opt_model_node))
  316. opt_node, = self.bottom.read_outgoing_elements(opt_model)
  317. opt = self.bottom.read_value(opt_node)
  318. # get attr type name
  319. source_type_node = self.bottom.read_edge_source(tm_element)
  320. source_type_name = self.odapi.mm_obj_to_name[source_type_node]
  321. target_type_node = self.bottom.read_edge_target(tm_element)
  322. target_type_name = self.odapi.mm_obj_to_name[target_type_node]
  323. # add attribute to the structure of its source type
  324. # attribute is stored as a (name, optional, type) triple
  325. self.structures.setdefault(source_type_name, set()).add((name, opt, target_type_name))
  326. # extend structures of sub types with attrs of super types
  327. for super_type, sub_types in self.odapi.transitive_sub_types.items():
  328. # JE: I made an untested change here! Can't test because structural conformance checking is broken.
  329. # for super_type, sub_types in self.sub_types.items():
  330. for sub_type in sub_types:
  331. if sub_type != super_type:
  332. self.structures.setdefault(sub_type, set()).update(self.structures[super_type])
  333. # filter out abstract types, as they cannot be instantiated
  334. # retrieve Class_abstract to check whether element is a morphism of Class_abstract
  335. class_abs_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class_abstract")
  336. for tm_element, tm_name in self.odapi.mm_obj_to_name.items():
  337. # retrieve elements that tm_element is a morphism of
  338. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  339. morphism, = [m for m in morphisms if m in scd_elements]
  340. # check if tm_element is a morphism of Class_abstract
  341. if class_abs_element == morphism:
  342. # retrieve 'abstract' attribute value
  343. target_node = self.bottom.read_edge_target(tm_element)
  344. abst_model = UUID(self.bottom.read_value(target_node))
  345. abst_node, = self.bottom.read_outgoing_elements(abst_model)
  346. is_abstract = self.bottom.read_value(abst_node)
  347. # retrieve type name
  348. source_node = self.bottom.read_edge_source(tm_element)
  349. type_name = self.odapi.mm_obj_to_name[source_node]
  350. if is_abstract:
  351. self.structures.pop(type_name)
  352. def match_structures(self):
  353. """
  354. Try to match the structure of each element in the instance model to some element in the type model
  355. """
  356. ref_element, = self.bottom.read_outgoing_elements(self.scd_model, "ModelRef")
  357. # matching
  358. for m_element, m_name in self.odapi.m_obj_to_name.items():
  359. is_edge = self.bottom.read_edge_source(m_element) != None
  360. print('element:', m_element, 'name:', m_name, 'is_edge', is_edge)
  361. for type_name, structure in self.structures.items():
  362. tm_element, = self.bottom.read_outgoing_elements(self.type_model, type_name)
  363. type_is_edge = self.bottom.read_edge_source(tm_element) != None
  364. if is_edge == type_is_edge:
  365. print(' type_name:', type_name, 'type_is_edge:', type_is_edge, "structure:", structure)
  366. mismatch = False
  367. matched = 0
  368. for name, optional, attr_type in structure:
  369. print(' name:', name, "optional:", optional, "attr_type:", attr_type)
  370. try:
  371. attr, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{name}")
  372. attr_tm, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
  373. # if attribute is a modelref, we need to check whether it
  374. # linguistically conforms to the specified type
  375. # if its an internally defined attribute, this will be checked by constraints
  376. morphisms = self.bottom.read_outgoing_elements(attr_tm, "Morphism")
  377. attr_conforms = True
  378. if ref_element in morphisms:
  379. # check conformance of reference model
  380. type_model_uuid = UUID(self.bottom.read_value(attr_tm))
  381. model_uuid = UUID(self.bottom.read_value(attr))
  382. attr_conforms = Conformance(self.state, model_uuid, type_model_uuid)\
  383. .check_nominal()
  384. else:
  385. # eval constraints
  386. code = self.read_attribute(attr_tm, "constraint")
  387. if code != None:
  388. attr_conforms = self.evaluate_constraint(code, this=attr)
  389. if attr_conforms:
  390. matched += 1
  391. print(" attr_conforms -> matched:", matched)
  392. except ValueError as e:
  393. # attr not found or failed parsing UUID
  394. if optional:
  395. print(" skipping:", e)
  396. continue
  397. else:
  398. # did not match mandatory attribute
  399. print(" breaking:", e)
  400. mismatch = True
  401. break
  402. print(' matched:', matched, 'len(structure):', len(structure))
  403. # if matched == len(structure):
  404. if not mismatch:
  405. print(' add to candidates:', m_name, type_name)
  406. self.candidates.setdefault(m_name, set()).add(type_name)
  407. # filter out candidates for links based on source and target types
  408. for m_element, m_name in self.odapi.m_obj_to_name.items():
  409. is_edge = self.bottom.read_edge_source(m_element) != None
  410. if is_edge and m_name in self.candidates:
  411. m_source = self.bottom.read_edge_source(m_element)
  412. m_target = self.bottom.read_edge_target(m_element)
  413. print(self.candidates)
  414. source_candidates = self.candidates[self.odapi.m_obj_to_name[m_source]]
  415. target_candidates = self.candidates[self.odapi.m_obj_to_name[m_target]]
  416. remove = set()
  417. for candidate_name in self.candidates[m_name]:
  418. candidate_element, = self.bottom.read_outgoing_elements(self.type_model, candidate_name)
  419. candidate_source = self.odapi.mm_obj_to_name[self.bottom.read_edge_source(candidate_element)]
  420. if candidate_source not in source_candidates:
  421. if len(source_candidates.intersection(set(self.odapi.transitive_sub_types[candidate_source]))) == 0:
  422. # if len(source_candidates.intersection(set(self.sub_types[candidate_source]))) == 0:
  423. remove.add(candidate_name)
  424. candidate_target = self.odapi.mm_obj_to_name[self.bottom.read_edge_target(candidate_element)]
  425. if candidate_target not in target_candidates:
  426. if len(target_candidates.intersection(set(self.odapi.transitive_sub_types[candidate_target]))) == 0:
  427. # if len(target_candidates.intersection(set(self.sub_types[candidate_target]))) == 0:
  428. remove.add(candidate_name)
  429. self.candidates[m_name] = self.candidates[m_name].difference(remove)
  430. def build_morphisms(self):
  431. """
  432. Build the morphisms between an instance and a type model that structurally match
  433. """
  434. if not all([len(c) == 1 for c in self.candidates.values()]):
  435. raise RuntimeError("Cannot build incomplete or ambiguous morphism.")
  436. mapping = {k: v.pop() for k, v in self.candidates.items()}
  437. for m_name, tm_name in mapping.items():
  438. # morphism to class/assoc
  439. m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
  440. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  441. self.bottom.create_edge(m_element, tm_element, "Morphism")
  442. # morphism for attributes and attribute links
  443. structure = self.structures[tm_name]
  444. for attr_name, _, attr_type in structure:
  445. try:
  446. # attribute node
  447. attr_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{attr_name}")
  448. attr_type_element, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
  449. self.bottom.create_edge(attr_element, attr_type_element, "Morphism")
  450. # attribute link
  451. attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}_{attr_name}")
  452. attr_link_type_element, = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}_{attr_name}")
  453. self.bottom.create_edge(attr_link_element, attr_link_type_element, "Morphism")
  454. except ValueError:
  455. pass
  456. if __name__ == '__main__':
  457. from state.devstate import DevState as State
  458. s = State()
  459. from bootstrap.scd import bootstrap_scd
  460. scd = bootstrap_scd(s)
  461. from bootstrap.pn import bootstrap_pn
  462. ltm_pn = bootstrap_pn(s, "PN")
  463. ltm_pn_lola = bootstrap_pn(s, "PNlola")
  464. from services.pn import PN
  465. my_pn = s.create_node()
  466. PNserv = PN(my_pn, s)
  467. PNserv.create_place("p1", 5)
  468. PNserv.create_place("p2", 0)
  469. PNserv.create_transition("t1")
  470. PNserv.create_p2t("p1", "t1", 1)
  471. PNserv.create_t2p("t1", "p2", 1)
  472. cf = Conformance(s, my_pn, ltm_pn_lola)
  473. # cf = Conformance(s, scd, ltm_pn, scd)
  474. cf.precompute_structures()
  475. cf.match_structures()
  476. cf.build_morphisms()
  477. print(cf.check_nominal())