conformance.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504
  1. from services.bottom.V0 import Bottom
  2. from uuid import UUID
  3. from state.base import State
  4. from typing import Dict, Tuple, Set, Any, List
  5. from pprint import pprint
  6. class Conformance:
  7. def __init__(self, state: State, model: UUID, type_model: UUID):
  8. self.state = state
  9. self.bottom = Bottom(state)
  10. type_model_id = state.read_dict(state.read_root(), "SCD")
  11. self.scd_model = UUID(state.read_value(type_model_id))
  12. self.model = model
  13. self.type_model = type_model
  14. self.type_mapping: Dict[str, str] = {}
  15. self.model_names = {
  16. # map model elements to their names to prevent iterating too much
  17. self.bottom.read_outgoing_elements(self.model, e)[0]: e
  18. for e in self.bottom.read_keys(self.model)
  19. }
  20. self.type_model_names = {
  21. # map type model elements to their names to prevent iterating too much
  22. self.bottom.read_outgoing_elements(self.type_model, e)[0]: e
  23. for e in self.bottom.read_keys(self.type_model)
  24. }
  25. self.sub_types: Dict[str, Set[str]] = {
  26. k: set() for k in self.bottom.read_keys(self.type_model)
  27. }
  28. self.primitive_values: Dict[UUID, Any] = {}
  29. self.abstract_types: List[str] = []
  30. self.multiplicities: Dict[str, Tuple] = {}
  31. self.source_multiplicities: Dict[str, Tuple] = {}
  32. self.target_multiplicities: Dict[str, Tuple] = {}
  33. self.structures = {}
  34. self.matches = {}
  35. self.candidates = {}
  36. def check_nominal(self, *, log=False):
  37. try:
  38. self.check_typing()
  39. self.check_link_typing()
  40. self.check_multiplicities()
  41. self.check_constraints()
  42. return True
  43. except RuntimeError as e:
  44. if log:
  45. print(e)
  46. return False
  47. def check_structural(self, *, build_morphisms=True, log=False):
  48. try:
  49. self.precompute_structures()
  50. self.match_structures()
  51. if build_morphisms:
  52. self.build_morphisms()
  53. self.check_nominal(log=log)
  54. return True
  55. except RuntimeError as e:
  56. if log:
  57. print(e)
  58. return False
  59. def read_attribute(self, element: UUID, attr_name: str):
  60. if element in self.type_model_names:
  61. # type model element
  62. element_name = self.type_model_names[element]
  63. model = self.type_model
  64. else:
  65. # model element
  66. element_name = self.model_names[element]
  67. model = self.model
  68. try:
  69. attr_elem, = self.bottom.read_outgoing_elements(model, f"{element_name}.{attr_name}")
  70. return self.primitive_values.get(attr_elem, self.bottom.read_value(attr_elem))
  71. except ValueError:
  72. return None
  73. def precompute_sub_types(self):
  74. inh_element, = self.bottom.read_outgoing_elements(self.scd_model, "Inheritance")
  75. inh_links = []
  76. for tm_element, tm_name in self.type_model_names.items():
  77. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  78. if inh_element in morphisms:
  79. inh_links.append(tm_element)
  80. for link in inh_links:
  81. tm_source = self.bottom.read_edge_source(link)
  82. tm_target = self.bottom.read_edge_target(link)
  83. parent_name = self.type_model_names[tm_target]
  84. child_name = self.type_model_names[tm_source]
  85. self.sub_types[parent_name].add(child_name)
  86. stop = False
  87. while not stop:
  88. stop = True
  89. for child_name, child_children in self.sub_types.items():
  90. for parent_name, parent_children in self.sub_types.items():
  91. if child_name in parent_children:
  92. original_size = len(parent_children)
  93. parent_children.update(child_children)
  94. if len(parent_children) != original_size:
  95. stop = False
  96. def deref_primitive_values(self):
  97. ref_element, = self.bottom.read_outgoing_elements(self.scd_model, "ModelRef")
  98. string_element, = self.bottom.read_outgoing_elements(self.scd_model, "String")
  99. boolean_element, = self.bottom.read_outgoing_elements(self.scd_model, "Boolean")
  100. integer_element, = self.bottom.read_outgoing_elements(self.scd_model, "Integer")
  101. t_deref = []
  102. t_refs = []
  103. for tm_element, tm_name in self.type_model_names.items():
  104. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  105. if ref_element in morphisms:
  106. t_refs.append(self.type_model_names[tm_element])
  107. elif string_element in morphisms:
  108. t_deref.append(tm_element)
  109. elif boolean_element in morphisms:
  110. t_deref.append(tm_element)
  111. elif integer_element in morphisms:
  112. t_deref.append(tm_element)
  113. for elem in t_deref:
  114. primitive_model = UUID(self.bottom.read_value(elem))
  115. primitive_value_node, = self.bottom.read_outgoing_elements(primitive_model)
  116. primitive_value = self.bottom.read_value(primitive_value_node)
  117. self.primitive_values[elem] = primitive_value
  118. for m_name, tm_name in self.type_mapping.items():
  119. if tm_name in t_refs:
  120. # dereference
  121. m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
  122. primitive_model = UUID(self.bottom.read_value(m_element))
  123. try:
  124. primitive_value_node, = self.bottom.read_outgoing_elements(primitive_model)
  125. primitive_value = self.bottom.read_value(primitive_value_node)
  126. self.primitive_values[m_element] = primitive_value
  127. except ValueError:
  128. pass # multiple elements in model indicate that we're not dealing with a primitive
  129. def precompute_multiplicities(self):
  130. for tm_element, tm_name in self.type_model_names.items():
  131. # class abstract flags and multiplicities
  132. abstract = self.read_attribute(tm_element, "abstract")
  133. lc = self.read_attribute(tm_element, "lower_cardinality")
  134. uc = self.read_attribute(tm_element, "upper_cardinality")
  135. if abstract:
  136. self.abstract_types.append(tm_name)
  137. if lc or uc:
  138. mult = (
  139. lc if lc is not None else float("-inf"),
  140. uc if uc is not None else float("inf")
  141. )
  142. self.multiplicities[tm_name] = mult
  143. # multiplicities for associations
  144. slc = self.read_attribute(tm_element, "source_lower_cardinality")
  145. suc = self.read_attribute(tm_element, "source_upper_cardinality")
  146. if slc or suc:
  147. mult = (
  148. slc if slc is not None else float("-inf"),
  149. suc if suc is not None else float("inf")
  150. )
  151. self.source_multiplicities[tm_name] = mult
  152. tlc = self.read_attribute(tm_element, "target_lower_cardinality")
  153. tuc = self.read_attribute(tm_element, "target_upper_cardinality")
  154. if tlc or tuc:
  155. mult = (
  156. tlc if tlc is not None else float("-inf"),
  157. tuc if tuc is not None else float("inf")
  158. )
  159. self.target_multiplicities[tm_name] = mult
  160. # optional for attribute links
  161. opt = self.read_attribute(tm_element, "optional")
  162. if opt is not None:
  163. self.source_multiplicities[tm_name] = (0 if opt else 1, 1)
  164. self.target_multiplicities[tm_name] = (0, 1)
  165. def get_type(self, element: UUID):
  166. morphisms = self.bottom.read_outgoing_elements(element, "Morphism")
  167. tm_element, = [m for m in morphisms if m in self.type_model_names.keys()]
  168. return tm_element
  169. def check_typing(self):
  170. """
  171. for each element of model check whether a morphism
  172. link exists to some element of type_model
  173. """
  174. ref_element, = self.bottom.read_outgoing_elements(self.scd_model, "ModelRef")
  175. model_names = self.bottom.read_keys(self.model)
  176. for m_name in model_names:
  177. m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
  178. try:
  179. tm_element = self.get_type(m_element)
  180. tm_name = self.type_model_names[tm_element]
  181. self.type_mapping[m_name] = tm_name
  182. if ref_element in self.bottom.read_outgoing_elements(tm_element, "Morphism"):
  183. sub_m = UUID(self.bottom.read_value(m_element))
  184. sub_tm = UUID(self.bottom.read_value(tm_element))
  185. if not Conformance(self.state, sub_m, sub_tm).check_nominal():
  186. raise RuntimeError(f"Incorrectly model reference: {m_name}")
  187. except ValueError:
  188. # no or too many morphism links found
  189. raise RuntimeError(f"Incorrectly typed element: {m_name}")
  190. return True
  191. def check_link_typing(self):
  192. self.precompute_sub_types()
  193. for m_name, tm_name in self.type_mapping.items():
  194. m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
  195. m_source = self.bottom.read_edge_source(m_element)
  196. m_target = self.bottom.read_edge_target(m_element)
  197. if m_source is None or m_target is None:
  198. # element is not a link
  199. continue
  200. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  201. tm_source = self.bottom.read_edge_source(tm_element)
  202. tm_target = self.bottom.read_edge_target(tm_element)
  203. # check if source is typed correctly
  204. source_name = self.model_names[m_source]
  205. source_type_actual = self.type_mapping[source_name]
  206. source_type_expected = self.type_model_names[tm_source]
  207. if source_type_actual != source_type_expected:
  208. if source_type_actual not in self.sub_types[source_type_expected]:
  209. raise RuntimeError(f"Invalid source type {source_type_actual} for element {m_name}")
  210. # check if target is typed correctly
  211. target_name = self.model_names[m_target]
  212. target_type_actual = self.type_mapping[target_name]
  213. target_type_expected = self.type_model_names[tm_target]
  214. if target_type_actual != target_type_expected:
  215. if target_type_actual not in self.sub_types[target_type_expected]:
  216. raise RuntimeError(f"Invalid target type {target_type_actual} for element {m_name}")
  217. return True
  218. def check_multiplicities(self):
  219. self.deref_primitive_values()
  220. self.precompute_multiplicities()
  221. for tm_name in self.type_model_names.values():
  222. # abstract classes
  223. if tm_name in self.abstract_types:
  224. type_count = list(self.type_mapping.values()).count(tm_name)
  225. if type_count > 0:
  226. raise RuntimeError(f"Invalid instantiation of abstract class: {tm_name}")
  227. # class multiplicities
  228. if tm_name in self.multiplicities:
  229. lc, uc = self.multiplicities[tm_name]
  230. type_count = list(self.type_mapping.values()).count(tm_name)
  231. for sub_type in self.sub_types[tm_name]:
  232. type_count += list(self.type_mapping.values()).count(sub_type)
  233. if type_count < lc or type_count > uc:
  234. raise RuntimeError(f"Cardinality of type exceeds valid multiplicity range: {tm_name} ({type_count})")
  235. # association source multiplicities
  236. if tm_name in self.source_multiplicities:
  237. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  238. tm_source_element = self.bottom.read_edge_source(tm_element)
  239. tm_source_name = self.type_model_names[tm_source_element]
  240. lc, uc = self.source_multiplicities[tm_name]
  241. for i, t in self.type_mapping.items():
  242. if t == tm_source_name or t in self.sub_types[tm_source_name]:
  243. count = 0
  244. i_element, = self.bottom.read_outgoing_elements(self.model, i)
  245. outgoing = self.bottom.read_outgoing_edges(i_element)
  246. for o in outgoing:
  247. try:
  248. if self.type_mapping[self.model_names[o]] == tm_name:
  249. count += 1
  250. except KeyError:
  251. pass # for elements not part of model, e.g. morphism links
  252. if count < lc or count > uc:
  253. raise RuntimeError(f"Source cardinality of type {tm_name} exceeds valid multiplicity range in {i}.")
  254. # association target multiplicities
  255. if tm_name in self.target_multiplicities:
  256. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  257. tm_target_element = self.bottom.read_edge_target(tm_element)
  258. tm_target_name = self.type_model_names[tm_target_element]
  259. lc, uc = self.target_multiplicities[tm_name]
  260. for i, t in self.type_mapping.items():
  261. if t == tm_target_name or t in self.sub_types[tm_target_name]:
  262. count = 0
  263. i_element, = self.bottom.read_outgoing_elements(self.model, i)
  264. outgoing = self.bottom.read_incoming_edges(i_element)
  265. for o in outgoing:
  266. try:
  267. if self.type_mapping[self.model_names[o]] == tm_name:
  268. count += 1
  269. except KeyError:
  270. pass # for elements not part of model, e.g. morphism links
  271. if count < lc or count > uc:
  272. print(f"Target cardinality of type {tm_name} exceeds valid multiplicity range in {i}.")
  273. return False
  274. return True
  275. def evaluate_constraint(self, code, **kwargs):
  276. funcs = {
  277. 'read_value': self.state.read_value
  278. }
  279. return eval(
  280. code,
  281. {'__builtins__': {'isinstance': isinstance, 'print': print,
  282. 'int': int, 'float': float, 'bool': bool, 'str': str, 'tuple': tuple}
  283. }, # globals
  284. {**kwargs, **funcs} # locals
  285. )
  286. def check_constraints(self):
  287. # local constraints
  288. for m_name, tm_name in self.type_mapping.items():
  289. if tm_name != "GlobalConstraint":
  290. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  291. code = self.read_attribute(tm_element, "constraint")
  292. if code is not None:
  293. morphisms = self.bottom.read_incoming_elements(tm_element, "Morphism")
  294. morphisms = [m for m in morphisms if m in self.model_names]
  295. for m_element in morphisms:
  296. if not self.evaluate_constraint(code, element=m_element):
  297. raise RuntimeError(f"Local constraint of {tm_name} not satisfied in {m_name}.")
  298. # global constraints
  299. for m_name, tm_name in self.type_mapping.items():
  300. if tm_name == "GlobalConstraint":
  301. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  302. code = self.read_attribute(tm_element, "constraint")
  303. if code is not None:
  304. if not self.evaluate_constraint(code, model=self.model):
  305. raise RuntimeError(f"Global constraint {tm_name} not satisfied.")
  306. return True
  307. def precompute_structures(self):
  308. self.precompute_sub_types()
  309. scd_elements = self.bottom.read_outgoing_elements(self.scd_model)
  310. # collect types
  311. class_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class")
  312. association_element, = self.bottom.read_outgoing_elements(self.scd_model, "Association")
  313. for tm_element, tm_name in self.type_model_names.items():
  314. # retrieve elements that tm_element is a morphism of
  315. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  316. morphism, = [m for m in morphisms if m in scd_elements]
  317. # check if tm_element is a morphism of AttributeLink
  318. if class_element == morphism or association_element == morphism:
  319. self.structures[tm_name] = set()
  320. # collect type structures
  321. # retrieve AttributeLink to check whether element is a morphism of AttributeLink
  322. attr_link_element, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink")
  323. for tm_element, tm_name in self.type_model_names.items():
  324. # retrieve elements that tm_element is a morphism of
  325. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  326. morphism, = [m for m in morphisms if m in scd_elements]
  327. # check if tm_element is a morphism of AttributeLink
  328. if attr_link_element == morphism:
  329. # retrieve attributes of attribute link, i.e. 'name' and 'optional'
  330. attrs = self.bottom.read_outgoing_elements(tm_element)
  331. name_model_node, = filter(lambda x: self.type_model_names.get(x, "").endswith(".name"), attrs)
  332. opt_model_node, = filter(lambda x: self.type_model_names.get(x, "").endswith(".optional"), attrs)
  333. # get attr name value
  334. name_model = UUID(self.bottom.read_value(name_model_node))
  335. name_node, = self.bottom.read_outgoing_elements(name_model)
  336. name = self.bottom.read_value(name_node)
  337. # get attr opt value
  338. opt_model = UUID(self.bottom.read_value(opt_model_node))
  339. opt_node, = self.bottom.read_outgoing_elements(opt_model)
  340. opt = self.bottom.read_value(opt_node)
  341. # get attr type name
  342. source_type_node = self.bottom.read_edge_source(tm_element)
  343. source_type_name = self.type_model_names[source_type_node]
  344. target_type_node = self.bottom.read_edge_target(tm_element)
  345. target_type_name = self.type_model_names[target_type_node]
  346. # add attribute to the structure of its source type
  347. # attribute is stored as a (name, optional, type) triple
  348. self.structures.setdefault(source_type_name, set()).add((name, opt, target_type_name))
  349. # extend structures of sub types with attrs of super types
  350. for super_type, sub_types in self.sub_types.items():
  351. for sub_type in sub_types:
  352. self.structures.setdefault(sub_type, set()).update(self.structures[super_type])
  353. # filter out abstract types, as they cannot be instantiated
  354. # retrieve Class_abstract to check whether element is a morphism of Class_abstract
  355. class_abs_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class_abstract")
  356. for tm_element, tm_name in self.type_model_names.items():
  357. # retrieve elements that tm_element is a morphism of
  358. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  359. morphism, = [m for m in morphisms if m in scd_elements]
  360. # check if tm_element is a morphism of Class_abstract
  361. if class_abs_element == morphism:
  362. # retrieve 'abstract' attribute value
  363. target_node = self.bottom.read_edge_target(tm_element)
  364. abst_model = UUID(self.bottom.read_value(target_node))
  365. abst_node, = self.bottom.read_outgoing_elements(abst_model)
  366. is_abstract = self.bottom.read_value(abst_node)
  367. # retrieve type name
  368. source_node = self.bottom.read_edge_source(tm_element)
  369. type_name = self.type_model_names[source_node]
  370. if is_abstract:
  371. self.structures.pop(type_name)
  372. def match_structures(self):
  373. ref_element, = self.bottom.read_outgoing_elements(self.scd_model, "ModelRef")
  374. # matching
  375. for m_element, m_name in self.model_names.items():
  376. is_edge = self.bottom.read_edge_source(m_element) is not None
  377. for type_name, structure in self.structures.items():
  378. tm_element, = self.bottom.read_outgoing_elements(self.type_model, type_name)
  379. type_is_edge = self.bottom.read_edge_source(tm_element) is not None
  380. if is_edge == type_is_edge:
  381. matched = 0
  382. for name, optional, attr_type in structure:
  383. try:
  384. attr, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{name}")
  385. attr_tm, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
  386. # if attribute is a modelref, we need to check whether it
  387. # linguistically conforms to the specified type
  388. # if its an internally defined attribute, this will be checked by constraints
  389. morphisms = self.bottom.read_outgoing_elements(attr_tm, "Morphism")
  390. attr_conforms = True
  391. if ref_element in morphisms:
  392. # check conformance of reference model
  393. type_model_uuid = UUID(self.bottom.read_value(attr_tm))
  394. model_uuid = UUID(self.bottom.read_value(attr))
  395. attr_conforms = Conformance(self.state, model_uuid, type_model_uuid)\
  396. .check_nominal()
  397. else:
  398. # eval constraints
  399. code = self.read_attribute(attr_tm, "constraint")
  400. if code is not None:
  401. attr_conforms = self.evaluate_constraint(code, element=attr)
  402. if attr_conforms:
  403. matched += 1
  404. except ValueError:
  405. # attr not found or failed parsing UUID
  406. if optional:
  407. continue
  408. else:
  409. break
  410. if matched == len(structure):
  411. self.candidates.setdefault(m_name, set()).add(type_name)
  412. # filter out candidates for links based on source and target types
  413. for m_element, m_name in self.model_names.items():
  414. is_edge = self.bottom.read_edge_source(m_element) is not None
  415. if is_edge and m_name in self.candidates:
  416. m_source = self.bottom.read_edge_source(m_element)
  417. m_target = self.bottom.read_edge_target(m_element)
  418. source_candidates = self.candidates[self.model_names[m_source]]
  419. target_candidates = self.candidates[self.model_names[m_target]]
  420. remove = set()
  421. for candidate_name in self.candidates[m_name]:
  422. candidate_element, = self.bottom.read_outgoing_elements(self.type_model, candidate_name)
  423. candidate_source = self.type_model_names[self.bottom.read_edge_source(candidate_element)]
  424. if candidate_source not in source_candidates:
  425. remove.add(candidate_name)
  426. candidate_target = self.type_model_names[self.bottom.read_edge_target(candidate_element)]
  427. if candidate_target not in target_candidates:
  428. remove.add(candidate_name)
  429. self.candidates[m_name] = self.candidates[m_name].difference(remove)
  430. def build_morphisms(self):
  431. if not all([len(c) == 1 for c in self.candidates.values()]):
  432. raise RuntimeError("Cannot build incomplete or ambiguous morphism.")
  433. mapping = {k: v.pop() for k, v in self.candidates.items()}
  434. for m_name, tm_name in mapping.items():
  435. # morphism to class/assoc
  436. m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
  437. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  438. self.bottom.create_edge(m_element, tm_element, "Morphism")
  439. # morphism for attributes and attribute links
  440. structure = self.structures[tm_name]
  441. for attr_name, _, attr_type in structure:
  442. try:
  443. # attribute node
  444. attr_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{attr_name}")
  445. attr_type_element, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
  446. self.bottom.create_edge(attr_element, attr_type_element, "Morphism")
  447. # attribute link
  448. attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{attr_name}_link")
  449. attr_link_type_element, = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}_{attr_name}")
  450. self.bottom.create_edge(attr_link_element, attr_link_type_element, "Morphism")
  451. except ValueError:
  452. pass
  453. if __name__ == '__main__':
  454. from state.devstate import DevState as State
  455. s = State()
  456. from bootstrap.scd import bootstrap_scd
  457. scd = bootstrap_scd(s)
  458. from bootstrap.pn import bootstrap_pn
  459. ltm_pn = bootstrap_pn(s, "PN")
  460. ltm_pn_lola = bootstrap_pn(s, "PNlola")
  461. from services.pn import PN
  462. my_pn = s.create_node()
  463. PNserv = PN(my_pn, s)
  464. PNserv.create_place("p1", 5)
  465. PNserv.create_place("p2", 0)
  466. PNserv.create_transition("t1")
  467. PNserv.create_p2t("p1", "t1", 1)
  468. PNserv.create_t2p("t1", "p2", 1)
  469. cf = Conformance(s, my_pn, ltm_pn_lola)
  470. # cf = Conformance(s, scd, ltm_pn, scd)
  471. cf.precompute_structures()
  472. cf.match_structures()
  473. cf.build_morphisms()
  474. print(cf.check_nominal())