conformance.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  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, bind_api_readonly
  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 ({ref_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 check_constraints(self):
  193. """
  194. Check whether all constraints defined for a model are respected
  195. """
  196. errors = []
  197. def get_code(tm_name):
  198. constraints = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}.constraint")
  199. if len(constraints) == 1:
  200. constraint = constraints[0]
  201. code = ActionCode(UUID(self.bottom.read_value(constraint)), self.bottom.state).read()
  202. return code
  203. def check_result(result, description):
  204. if result == None:
  205. return # OK
  206. if isinstance(result, str):
  207. errors.append(f"{description} not satisfied. Reason: {result}")
  208. elif isinstance(result, bool):
  209. if not result:
  210. errors.append(f"{description} not satisfied.")
  211. elif isinstance(result, list):
  212. if len(result) > 0:
  213. reasons = indent('\n'.join(result), 4)
  214. errors.append(f"{description} not satisfied. Reasons:\n{reasons}")
  215. else:
  216. raise Exception(f"{description} evaluation result should be boolean or string! Instead got {result}")
  217. # local constraints
  218. for type_name in self.bottom.read_keys(self.type_model):
  219. code = get_code(type_name)
  220. if code != None:
  221. instances = self.odapi.get_all_instances(type_name, include_subtypes=self.constraint_check_subtypes)
  222. for obj_name, obj_id in instances:
  223. description = f"Local constraint of \"{type_name}\" in \"{obj_name}\""
  224. # print(description)
  225. try:
  226. result = exec_then_eval(code, _globals=bind_api_readonly(self.odapi), _locals={'this': obj_id}) # may raise
  227. check_result(result, description)
  228. except:
  229. errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
  230. # global constraints
  231. glob_constraints = []
  232. # find global constraints...
  233. glob_constraint_type, = self.bottom.read_outgoing_elements(self.scd_model, "GlobalConstraint")
  234. for tm_name in self.bottom.read_keys(self.type_model):
  235. tm_node, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  236. # print(key, node)
  237. for type_of_node in self.bottom.read_outgoing_elements(tm_node, "Morphism"):
  238. if type_of_node == glob_constraint_type:
  239. # node is GlobalConstraint
  240. glob_constraints.append(tm_name)
  241. # evaluate them (each constraint once)
  242. for tm_name in glob_constraints:
  243. code = get_code(tm_name)
  244. if code != None:
  245. description = f"Global constraint \"{tm_name}\""
  246. try:
  247. result = exec_then_eval(code, _globals=bind_api_readonly(self.odapi)) # may raise
  248. check_result(result, description)
  249. except:
  250. errors.append(f"Runtime error during evaluation of {description}:\n{indent(traceback.format_exc(), 6)}")
  251. return errors
  252. def precompute_structures(self):
  253. """
  254. Make an internal representation of type structures such that comparing type structures is easier
  255. """
  256. scd_elements = self.bottom.read_outgoing_elements(self.scd_model)
  257. # collect types
  258. class_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class")
  259. association_element, = self.bottom.read_outgoing_elements(self.scd_model, "Association")
  260. for tm_element, tm_name in self.odapi.mm_obj_to_name.items():
  261. # retrieve elements that tm_element is a morphism of
  262. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  263. morphism, = [m for m in morphisms if m in scd_elements]
  264. # check if tm_element is a morphism of AttributeLink
  265. if class_element == morphism or association_element == morphism:
  266. self.structures[tm_name] = set()
  267. # collect type structures
  268. # retrieve AttributeLink to check whether element is a morphism of AttributeLink
  269. attr_link_element, = self.bottom.read_outgoing_elements(self.scd_model, "AttributeLink")
  270. for tm_element, tm_name in self.odapi.mm_obj_to_name.items():
  271. # retrieve elements that tm_element is a morphism of
  272. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  273. morphism, = [m for m in morphisms if m in scd_elements]
  274. # check if tm_element is a morphism of AttributeLink
  275. if attr_link_element == morphism:
  276. # retrieve attributes of attribute link, i.e. 'name' and 'optional'
  277. attrs = self.bottom.read_outgoing_elements(tm_element)
  278. name_model_node, = filter(lambda x: self.odapi.m_obj_to_name.get(x, "").endswith(".name"), attrs)
  279. opt_model_node, = filter(lambda x: self.odapi.m_obj_to_name.get(x, "").endswith(".optional"), attrs)
  280. # get attr name value
  281. name_model = UUID(self.bottom.read_value(name_model_node))
  282. name_node, = self.bottom.read_outgoing_elements(name_model)
  283. name = self.bottom.read_value(name_node)
  284. # get attr opt value
  285. opt_model = UUID(self.bottom.read_value(opt_model_node))
  286. opt_node, = self.bottom.read_outgoing_elements(opt_model)
  287. opt = self.bottom.read_value(opt_node)
  288. # get attr type name
  289. source_type_node = self.bottom.read_edge_source(tm_element)
  290. source_type_name = self.odapi.mm_obj_to_name[source_type_node]
  291. target_type_node = self.bottom.read_edge_target(tm_element)
  292. target_type_name = self.odapi.mm_obj_to_name[target_type_node]
  293. # add attribute to the structure of its source type
  294. # attribute is stored as a (name, optional, type) triple
  295. self.structures.setdefault(source_type_name, set()).add((name, opt, target_type_name))
  296. # extend structures of sub types with attrs of super types
  297. for super_type, sub_types in self.odapi.transitive_sub_types.items():
  298. # JE: I made an untested change here! Can't test because structural conformance checking is broken.
  299. # for super_type, sub_types in self.sub_types.items():
  300. for sub_type in sub_types:
  301. if sub_type != super_type:
  302. self.structures.setdefault(sub_type, set()).update(self.structures[super_type])
  303. # filter out abstract types, as they cannot be instantiated
  304. # retrieve Class_abstract to check whether element is a morphism of Class_abstract
  305. class_abs_element, = self.bottom.read_outgoing_elements(self.scd_model, "Class_abstract")
  306. for tm_element, tm_name in self.odapi.mm_obj_to_name.items():
  307. # retrieve elements that tm_element is a morphism of
  308. morphisms = self.bottom.read_outgoing_elements(tm_element, "Morphism")
  309. morphism, = [m for m in morphisms if m in scd_elements]
  310. # check if tm_element is a morphism of Class_abstract
  311. if class_abs_element == morphism:
  312. # retrieve 'abstract' attribute value
  313. target_node = self.bottom.read_edge_target(tm_element)
  314. abst_model = UUID(self.bottom.read_value(target_node))
  315. abst_node, = self.bottom.read_outgoing_elements(abst_model)
  316. is_abstract = self.bottom.read_value(abst_node)
  317. # retrieve type name
  318. source_node = self.bottom.read_edge_source(tm_element)
  319. type_name = self.odapi.mm_obj_to_name[source_node]
  320. if is_abstract:
  321. self.structures.pop(type_name)
  322. def match_structures(self):
  323. """
  324. Try to match the structure of each element in the instance model to some element in the type model
  325. """
  326. ref_element, = self.bottom.read_outgoing_elements(self.scd_model, "ModelRef")
  327. # matching
  328. for m_element, m_name in self.odapi.m_obj_to_name.items():
  329. is_edge = self.bottom.read_edge_source(m_element) != None
  330. print('element:', m_element, 'name:', m_name, 'is_edge', is_edge)
  331. for type_name, structure in self.structures.items():
  332. tm_element, = self.bottom.read_outgoing_elements(self.type_model, type_name)
  333. type_is_edge = self.bottom.read_edge_source(tm_element) != None
  334. if is_edge == type_is_edge:
  335. print(' type_name:', type_name, 'type_is_edge:', type_is_edge, "structure:", structure)
  336. mismatch = False
  337. matched = 0
  338. for name, optional, attr_type in structure:
  339. print(' name:', name, "optional:", optional, "attr_type:", attr_type)
  340. try:
  341. attr, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{name}")
  342. attr_tm, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
  343. # if attribute is a modelref, we need to check whether it
  344. # linguistically conforms to the specified type
  345. # if its an internally defined attribute, this will be checked by constraints
  346. morphisms = self.bottom.read_outgoing_elements(attr_tm, "Morphism")
  347. attr_conforms = True
  348. if ref_element in morphisms:
  349. # check conformance of reference model
  350. type_model_uuid = UUID(self.bottom.read_value(attr_tm))
  351. model_uuid = UUID(self.bottom.read_value(attr))
  352. attr_conforms = Conformance(self.state, model_uuid, type_model_uuid)\
  353. .check_nominal()
  354. else:
  355. # eval constraints
  356. code = self.read_attribute(attr_tm, "constraint")
  357. if code != None:
  358. attr_conforms = self.evaluate_constraint(code, this=attr)
  359. if attr_conforms:
  360. matched += 1
  361. print(" attr_conforms -> matched:", matched)
  362. except ValueError as e:
  363. # attr not found or failed parsing UUID
  364. if optional:
  365. print(" skipping:", e)
  366. continue
  367. else:
  368. # did not match mandatory attribute
  369. print(" breaking:", e)
  370. mismatch = True
  371. break
  372. print(' matched:', matched, 'len(structure):', len(structure))
  373. # if matched == len(structure):
  374. if not mismatch:
  375. print(' add to candidates:', m_name, type_name)
  376. self.candidates.setdefault(m_name, set()).add(type_name)
  377. # filter out candidates for links based on source and target types
  378. for m_element, m_name in self.odapi.m_obj_to_name.items():
  379. is_edge = self.bottom.read_edge_source(m_element) != None
  380. if is_edge and m_name in self.candidates:
  381. m_source = self.bottom.read_edge_source(m_element)
  382. m_target = self.bottom.read_edge_target(m_element)
  383. print(self.candidates)
  384. source_candidates = self.candidates[self.odapi.m_obj_to_name[m_source]]
  385. target_candidates = self.candidates[self.odapi.m_obj_to_name[m_target]]
  386. remove = set()
  387. for candidate_name in self.candidates[m_name]:
  388. candidate_element, = self.bottom.read_outgoing_elements(self.type_model, candidate_name)
  389. candidate_source = self.odapi.mm_obj_to_name[self.bottom.read_edge_source(candidate_element)]
  390. if candidate_source not in source_candidates:
  391. if len(source_candidates.intersection(set(self.odapi.transitive_sub_types[candidate_source]))) == 0:
  392. # if len(source_candidates.intersection(set(self.sub_types[candidate_source]))) == 0:
  393. remove.add(candidate_name)
  394. candidate_target = self.odapi.mm_obj_to_name[self.bottom.read_edge_target(candidate_element)]
  395. if candidate_target not in target_candidates:
  396. if len(target_candidates.intersection(set(self.odapi.transitive_sub_types[candidate_target]))) == 0:
  397. # if len(target_candidates.intersection(set(self.sub_types[candidate_target]))) == 0:
  398. remove.add(candidate_name)
  399. self.candidates[m_name] = self.candidates[m_name].difference(remove)
  400. def build_morphisms(self):
  401. """
  402. Build the morphisms between an instance and a type model that structurally match
  403. """
  404. if not all([len(c) == 1 for c in self.candidates.values()]):
  405. raise RuntimeError("Cannot build incomplete or ambiguous morphism.")
  406. mapping = {k: v.pop() for k, v in self.candidates.items()}
  407. for m_name, tm_name in mapping.items():
  408. # morphism to class/assoc
  409. m_element, = self.bottom.read_outgoing_elements(self.model, m_name)
  410. tm_element, = self.bottom.read_outgoing_elements(self.type_model, tm_name)
  411. self.bottom.create_edge(m_element, tm_element, "Morphism")
  412. # morphism for attributes and attribute links
  413. structure = self.structures[tm_name]
  414. for attr_name, _, attr_type in structure:
  415. try:
  416. # attribute node
  417. attr_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}.{attr_name}")
  418. attr_type_element, = self.bottom.read_outgoing_elements(self.type_model, attr_type)
  419. self.bottom.create_edge(attr_element, attr_type_element, "Morphism")
  420. # attribute link
  421. attr_link_element, = self.bottom.read_outgoing_elements(self.model, f"{m_name}_{attr_name}")
  422. attr_link_type_element, = self.bottom.read_outgoing_elements(self.type_model, f"{tm_name}_{attr_name}")
  423. self.bottom.create_edge(attr_link_element, attr_link_type_element, "Morphism")
  424. except ValueError:
  425. pass
  426. if __name__ == '__main__':
  427. from state.devstate import DevState as State
  428. s = State()
  429. from bootstrap.scd import bootstrap_scd
  430. scd = bootstrap_scd(s)
  431. from bootstrap.pn import bootstrap_pn
  432. ltm_pn = bootstrap_pn(s, "PN")
  433. ltm_pn_lola = bootstrap_pn(s, "PNlola")
  434. from services.pn import PN
  435. my_pn = s.create_node()
  436. PNserv = PN(my_pn, s)
  437. PNserv.create_place("p1", 5)
  438. PNserv.create_place("p2", 0)
  439. PNserv.create_transition("t1")
  440. PNserv.create_p2t("p1", "t1", 1)
  441. PNserv.create_t2p("t1", "p2", 1)
  442. cf = Conformance(s, my_pn, ltm_pn_lola)
  443. # cf = Conformance(s, scd, ltm_pn, scd)
  444. cf.precompute_structures()
  445. cf.match_structures()
  446. cf.build_morphisms()
  447. print(cf.check_nominal())