chemicalml 0.2.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (352) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +21 -14
  3. data/.rubocop.yml +23 -0
  4. data/CHANGELOG.md +32 -0
  5. data/CLAUDE.md +49 -1
  6. data/README.adoc +154 -26
  7. data/TODO.align/61-parallel-array-attrs.md +54 -0
  8. data/TODO.align/62-per-element-attribute-gaps.md +49 -0
  9. data/TODO.align/63-universal-children-mixin.md +42 -0
  10. data/TODO.align/64-per-element-child-gaps.md +81 -0
  11. data/TODO.align/65-simple-unit-convention.md +40 -0
  12. data/TODO.align/66-schema24-legacy-children.md +32 -0
  13. data/TODO.align/67-final-verification.md +32 -0
  14. data/TODO.align/68-detection-coverage.md +33 -0
  15. data/TODO.align/69-json-yaml-roundtrip.md +27 -0
  16. data/TODO.align/70-iterative-walker.md +40 -0
  17. data/TODO.align/71-violation-value.md +25 -0
  18. data/TODO.align/72-close-child-gaps.md +66 -0
  19. data/TODO.align/73-schema24-parse-limitation.md +64 -0
  20. data/TODO.align/74-final-verification-round18.md +15 -0
  21. data/TODO.align/75-key-value-mappings.md +43 -0
  22. data/TODO.align/76-schema24-workaround.md +57 -0
  23. data/TODO.align/77-convention-coverage-matrix.md +31 -0
  24. data/TODO.align/78-fixture-audit.md +23 -0
  25. data/TODO.align/79-final-verification-round19.md +15 -0
  26. data/TODO.align/80-xsd-enum-constants.md +35 -0
  27. data/TODO.align/81-enum-validation-constraints.md +29 -0
  28. data/TODO.align/82-reference-resolver.md +33 -0
  29. data/TODO.align/83-toml-roundtrip.md +20 -0
  30. data/TODO.align/84-final-verification-round21.md +16 -0
  31. data/TODO.align/85-bond-distinct-atoms.md +25 -0
  32. data/TODO.align/86-references-should-resolve.md +20 -0
  33. data/TODO.align/87-cli-utility.md +26 -0
  34. data/TODO.align/88-diverse-fixtures.md +26 -0
  35. data/TODO.align/89-final-verification-round22.md +15 -0
  36. data/TODO.align/90-bond-stereo-completeness.md +31 -0
  37. data/TODO.align/91-atomparity-distinct.md +21 -0
  38. data/TODO.align/92-unit-dictionary-completeness.md +28 -0
  39. data/TODO.align/93-yard-docs.md +23 -0
  40. data/TODO.align/94-final-verification-round23.md +15 -0
  41. data/TODO.align/README.round17.md +28 -0
  42. data/TODO.align/README.round18.md +37 -0
  43. data/TODO.align/README.round19.md +40 -0
  44. data/TODO.align/README.round20.md +49 -0
  45. data/TODO.align/README.round21.md +35 -0
  46. data/TODO.align/README.round22.md +48 -0
  47. data/TODO.align/README.round23.md +46 -0
  48. data/TODO.align/README.round24.md +53 -0
  49. data/TODO.align/README.round25.md +66 -0
  50. data/TODO.align/README.round26.md +55 -0
  51. data/TODO.align/README.round27.md +51 -0
  52. data/TODO.align/README.round28.md +61 -0
  53. data/TODO.align/README.round29.md +64 -0
  54. data/TODO.align/README.round30.md +50 -0
  55. data/TODO.align/README.round31.md +54 -0
  56. data/TODO.align/README.round32.md +67 -0
  57. data/TODO.align/README.round33.md +62 -0
  58. data/TODO.align/README.round34.md +70 -0
  59. data/chemicalml.gemspec +1 -1
  60. data/docs/constraints.md +161 -0
  61. data/docs/generate_constraint_docs.rb +44 -0
  62. data/exe/chemicalml +6 -0
  63. data/lib/chemicalml/cli/base.rb +68 -0
  64. data/lib/chemicalml/cli/command.rb +43 -0
  65. data/lib/chemicalml/cli/constraints_command.rb +27 -0
  66. data/lib/chemicalml/cli/conventions_command.rb +14 -0
  67. data/lib/chemicalml/cli/dictionaries_command.rb +14 -0
  68. data/lib/chemicalml/cli/elements_command.rb +17 -0
  69. data/lib/chemicalml/cli/enums_command.rb +17 -0
  70. data/lib/chemicalml/cli/info_command.rb +77 -0
  71. data/lib/chemicalml/cli/inspect_command.rb +39 -0
  72. data/lib/chemicalml/cli/validate_command.rb +54 -0
  73. data/lib/chemicalml/cli.rb +43 -0
  74. data/lib/chemicalml/cml/base/abundance.rb +18 -8
  75. data/lib/chemicalml/cml/base/action.rb +34 -16
  76. data/lib/chemicalml/cml/base/action_list.rb +35 -17
  77. data/lib/chemicalml/cml/base/alternative.rb +13 -4
  78. data/lib/chemicalml/cml/base/amount.rb +14 -6
  79. data/lib/chemicalml/cml/base/angle.rb +28 -13
  80. data/lib/chemicalml/cml/base/annotation.rb +14 -6
  81. data/lib/chemicalml/cml/base/any_cml.rb +12 -5
  82. data/lib/chemicalml/cml/base/appinfo.rb +14 -6
  83. data/lib/chemicalml/cml/base/arg.rb +47 -13
  84. data/lib/chemicalml/cml/base/array.rb +40 -19
  85. data/lib/chemicalml/cml/base/array_list.rb +14 -6
  86. data/lib/chemicalml/cml/base/atom.rb +84 -28
  87. data/lib/chemicalml/cml/base/atom_array.rb +60 -23
  88. data/lib/chemicalml/cml/base/atom_parity.rb +14 -6
  89. data/lib/chemicalml/cml/base/atom_set.rb +14 -6
  90. data/lib/chemicalml/cml/base/atom_type.rb +25 -9
  91. data/lib/chemicalml/cml/base/atom_type_list.rb +22 -8
  92. data/lib/chemicalml/cml/base/atomic_basis_function.rb +33 -13
  93. data/lib/chemicalml/cml/base/band.rb +21 -8
  94. data/lib/chemicalml/cml/base/band_list.rb +15 -7
  95. data/lib/chemicalml/cml/base/basis_set.rb +24 -9
  96. data/lib/chemicalml/cml/base/bond.rb +36 -10
  97. data/lib/chemicalml/cml/base/bond_array.rb +34 -11
  98. data/lib/chemicalml/cml/base/bond_set.rb +14 -6
  99. data/lib/chemicalml/cml/base/bond_stereo.rb +20 -9
  100. data/lib/chemicalml/cml/base/bond_type.rb +23 -8
  101. data/lib/chemicalml/cml/base/bond_type_list.rb +22 -8
  102. data/lib/chemicalml/cml/base/cell_parameter.rb +28 -10
  103. data/lib/chemicalml/cml/base/cml_module.rb +42 -11
  104. data/lib/chemicalml/cml/base/common_children.rb +35 -0
  105. data/lib/chemicalml/cml/base/complex_object.rb +12 -5
  106. data/lib/chemicalml/cml/base/condition_list.rb +26 -10
  107. data/lib/chemicalml/cml/base/crystal.rb +26 -10
  108. data/lib/chemicalml/cml/base/definition.rb +14 -5
  109. data/lib/chemicalml/cml/base/description.rb +16 -6
  110. data/lib/chemicalml/cml/base/dictionary.rb +28 -11
  111. data/lib/chemicalml/cml/base/dictionary_entry.rb +69 -10
  112. data/lib/chemicalml/cml/base/dimension.rb +26 -12
  113. data/lib/chemicalml/cml/base/document.rb +24 -8
  114. data/lib/chemicalml/cml/base/documentation.rb +12 -5
  115. data/lib/chemicalml/cml/base/eigen.rb +32 -8
  116. data/lib/chemicalml/cml/base/electron.rb +26 -12
  117. data/lib/chemicalml/cml/base/enumeration.rb +16 -7
  118. data/lib/chemicalml/cml/base/expression.rb +21 -6
  119. data/lib/chemicalml/cml/base/float.rb +22 -10
  120. data/lib/chemicalml/cml/base/float_array.rb +24 -11
  121. data/lib/chemicalml/cml/base/formula.rb +31 -10
  122. data/lib/chemicalml/cml/base/fragment.rb +30 -12
  123. data/lib/chemicalml/cml/base/fragment_list.rb +24 -9
  124. data/lib/chemicalml/cml/base/gradient.rb +19 -6
  125. data/lib/chemicalml/cml/base/identifier.rb +18 -8
  126. data/lib/chemicalml/cml/base/integer.rb +22 -10
  127. data/lib/chemicalml/cml/base/integer_array.rb +24 -11
  128. data/lib/chemicalml/cml/base/isotope.rb +28 -9
  129. data/lib/chemicalml/cml/base/isotope_list.rb +17 -8
  130. data/lib/chemicalml/cml/base/join.rb +25 -9
  131. data/lib/chemicalml/cml/base/kpoint.rb +16 -7
  132. data/lib/chemicalml/cml/base/kpoint_list.rb +15 -7
  133. data/lib/chemicalml/cml/base/label.rb +14 -6
  134. data/lib/chemicalml/cml/base/lattice.rb +35 -10
  135. data/lib/chemicalml/cml/base/lattice_vector.rb +16 -7
  136. data/lib/chemicalml/cml/base/length.rb +26 -12
  137. data/lib/chemicalml/cml/base/line3.rb +18 -8
  138. data/lib/chemicalml/cml/base/link.rb +41 -20
  139. data/lib/chemicalml/cml/base/list.rb +22 -10
  140. data/lib/chemicalml/cml/base/map.rb +32 -11
  141. data/lib/chemicalml/cml/base/matrix.rb +32 -15
  142. data/lib/chemicalml/cml/base/mechanism.rb +20 -7
  143. data/lib/chemicalml/cml/base/mechanism_component.rb +14 -6
  144. data/lib/chemicalml/cml/base/metadata.rb +18 -8
  145. data/lib/chemicalml/cml/base/metadata_list.rb +27 -8
  146. data/lib/chemicalml/cml/base/molecule.rb +92 -26
  147. data/lib/chemicalml/cml/base/molecule_list.rb +26 -8
  148. data/lib/chemicalml/cml/base/name.rb +10 -4
  149. data/lib/chemicalml/cml/base/object.rb +18 -8
  150. data/lib/chemicalml/cml/base/observation.rb +16 -7
  151. data/lib/chemicalml/cml/base/operator.rb +14 -6
  152. data/lib/chemicalml/cml/base/parameter.rb +33 -13
  153. data/lib/chemicalml/cml/base/parameter_list.rb +27 -8
  154. data/lib/chemicalml/cml/base/particle.rb +20 -9
  155. data/lib/chemicalml/cml/base/peak.rb +73 -26
  156. data/lib/chemicalml/cml/base/peak_group.rb +62 -26
  157. data/lib/chemicalml/cml/base/peak_list.rb +24 -9
  158. data/lib/chemicalml/cml/base/peak_structure.rb +40 -13
  159. data/lib/chemicalml/cml/base/plane3.rb +14 -6
  160. data/lib/chemicalml/cml/base/point3.rb +14 -6
  161. data/lib/chemicalml/cml/base/potential.rb +17 -6
  162. data/lib/chemicalml/cml/base/potential_form.rb +30 -7
  163. data/lib/chemicalml/cml/base/potential_list.rb +15 -7
  164. data/lib/chemicalml/cml/base/product.rb +45 -10
  165. data/lib/chemicalml/cml/base/product_list.rb +29 -9
  166. data/lib/chemicalml/cml/base/property.rb +29 -11
  167. data/lib/chemicalml/cml/base/property_list.rb +23 -8
  168. data/lib/chemicalml/cml/base/reactant.rb +45 -10
  169. data/lib/chemicalml/cml/base/reactant_list.rb +29 -9
  170. data/lib/chemicalml/cml/base/reaction.rb +74 -20
  171. data/lib/chemicalml/cml/base/reaction_list.rb +23 -8
  172. data/lib/chemicalml/cml/base/reaction_scheme.rb +43 -13
  173. data/lib/chemicalml/cml/base/reaction_step.rb +30 -12
  174. data/lib/chemicalml/cml/base/reaction_step_list.rb +30 -11
  175. data/lib/chemicalml/cml/base/reactive_centre.rb +31 -6
  176. data/lib/chemicalml/cml/base/region.rb +20 -9
  177. data/lib/chemicalml/cml/base/related_entry.rb +11 -3
  178. data/lib/chemicalml/cml/base/sample.rb +30 -7
  179. data/lib/chemicalml/cml/base/scalar.rb +32 -15
  180. data/lib/chemicalml/cml/base/spectator.rb +25 -6
  181. data/lib/chemicalml/cml/base/spectator_list.rb +20 -7
  182. data/lib/chemicalml/cml/base/spectrum.rb +57 -18
  183. data/lib/chemicalml/cml/base/spectrum_data.rb +19 -9
  184. data/lib/chemicalml/cml/base/spectrum_list.rb +28 -9
  185. data/lib/chemicalml/cml/base/sphere3.rb +14 -6
  186. data/lib/chemicalml/cml/base/stmml.rb +12 -5
  187. data/lib/chemicalml/cml/base/string.rb +14 -6
  188. data/lib/chemicalml/cml/base/string_array.rb +22 -10
  189. data/lib/chemicalml/cml/base/substance.rb +36 -14
  190. data/lib/chemicalml/cml/base/substance_list.rb +29 -10
  191. data/lib/chemicalml/cml/base/symmetry.rb +31 -9
  192. data/lib/chemicalml/cml/base/system.rb +21 -10
  193. data/lib/chemicalml/cml/base/table.rb +38 -13
  194. data/lib/chemicalml/cml/base/table_cell.rb +12 -5
  195. data/lib/chemicalml/cml/base/table_content.rb +19 -9
  196. data/lib/chemicalml/cml/base/table_header.rb +15 -7
  197. data/lib/chemicalml/cml/base/table_header_cell.rb +22 -10
  198. data/lib/chemicalml/cml/base/table_row.rb +15 -7
  199. data/lib/chemicalml/cml/base/table_row_list.rb +15 -7
  200. data/lib/chemicalml/cml/base/tcell.rb +16 -7
  201. data/lib/chemicalml/cml/base/torsion.rb +28 -13
  202. data/lib/chemicalml/cml/base/transform3.rb +12 -5
  203. data/lib/chemicalml/cml/base/transition_state.rb +23 -5
  204. data/lib/chemicalml/cml/base/trow.rb +22 -6
  205. data/lib/chemicalml/cml/base/unit.rb +39 -16
  206. data/lib/chemicalml/cml/base/unit_list.rb +34 -12
  207. data/lib/chemicalml/cml/base/unit_type.rb +30 -10
  208. data/lib/chemicalml/cml/base/unit_type_list.rb +29 -11
  209. data/lib/chemicalml/cml/base/vector3.rb +14 -6
  210. data/lib/chemicalml/cml/base/xaxis.rb +32 -10
  211. data/lib/chemicalml/cml/base/yaxis.rb +30 -10
  212. data/lib/chemicalml/cml/base/z_matrix.rb +26 -5
  213. data/lib/chemicalml/cml/base.rb +139 -138
  214. data/lib/chemicalml/cml/canonical_comparison.rb +54 -0
  215. data/lib/chemicalml/cml/elements.rb +145 -136
  216. data/lib/chemicalml/cml/enums.rb +55 -0
  217. data/lib/chemicalml/cml/namespace.rb +2 -3
  218. data/lib/chemicalml/cml/patterns.rb +33 -0
  219. data/lib/chemicalml/cml/reference_resolver.rb +137 -0
  220. data/lib/chemicalml/cml/role.rb +138 -138
  221. data/lib/chemicalml/cml/schema24/configuration.rb +11 -1
  222. data/lib/chemicalml/cml/schema24.rb +139 -139
  223. data/lib/chemicalml/cml/schema3.rb +123 -123
  224. data/lib/chemicalml/cml/visitable.rb +77 -3
  225. data/lib/chemicalml/cml/wire_class_registry.rb +3 -3
  226. data/lib/chemicalml/cml/xhtml/p.rb +21 -0
  227. data/lib/chemicalml/cml/xhtml.rb +20 -0
  228. data/lib/chemicalml/cml.rb +164 -129
  229. data/lib/chemicalml/context_configuration.rb +4 -2
  230. data/lib/chemicalml/convention/cascade/constraints/reaction_scheme_must_have_content.rb +33 -0
  231. data/lib/chemicalml/convention/cascade/constraints/reaction_step_list_must_contain_steps.rb +31 -0
  232. data/lib/chemicalml/convention/cascade/constraints/reaction_step_must_have_reaction_or_lists.rb +33 -0
  233. data/lib/chemicalml/convention/cascade/constraints/reactive_centre_atom_refs_should_be_present.rb +34 -0
  234. data/lib/chemicalml/convention/cascade/constraints.rb +18 -0
  235. data/lib/chemicalml/convention/cascade.rb +43 -0
  236. data/lib/chemicalml/convention/compchem/constraints/array_rules.rb +1 -1
  237. data/lib/chemicalml/convention/compchem/constraints/calculation_requires_finalization.rb +1 -1
  238. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +2 -1
  239. data/lib/chemicalml/convention/compchem/constraints/environment_at_most_one_property_list.rb +1 -1
  240. data/lib/chemicalml/convention/compchem/constraints/environment_must_not_contain_parameter.rb +1 -1
  241. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_molecule.rb +1 -1
  242. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_property_list.rb +1 -1
  243. data/lib/chemicalml/convention/compchem/constraints/finalization_must_have_content.rb +44 -0
  244. data/lib/chemicalml/convention/compchem/constraints/finalization_must_not_contain_parameter.rb +1 -1
  245. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_molecule.rb +1 -1
  246. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_parameter_list.rb +1 -1
  247. data/lib/chemicalml/convention/compchem/constraints/initialization_must_have_content.rb +54 -0
  248. data/lib/chemicalml/convention/compchem/constraints/initialization_must_not_contain_property.rb +1 -1
  249. data/lib/chemicalml/convention/compchem/constraints/job_list_module_must_have_id.rb +1 -0
  250. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_environment.rb +1 -1
  251. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_finalization.rb +1 -1
  252. data/lib/chemicalml/convention/compchem/constraints/job_module_must_have_id.rb +1 -0
  253. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +1 -0
  254. data/lib/chemicalml/convention/compchem/constraints/matrix_rules.rb +1 -1
  255. data/lib/chemicalml/convention/compchem/constraints/scalar_units.rb +1 -1
  256. data/lib/chemicalml/convention/compchem/constraints.rb +4 -0
  257. data/lib/chemicalml/convention/compchem.rb +4 -2
  258. data/lib/chemicalml/convention/constraint.rb +40 -9
  259. data/lib/chemicalml/convention/coordinator.rb +4 -3
  260. data/lib/chemicalml/convention/detection.rb +10 -4
  261. data/lib/chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace.rb +1 -0
  262. data/lib/chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash.rb +1 -1
  263. data/lib/chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern.rb +1 -1
  264. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +1 -1
  265. data/lib/chemicalml/convention/dictionary/constraints/entry_must_contain_definition.rb +15 -1
  266. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +1 -1
  267. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_unit_type.rb +1 -0
  268. data/lib/chemicalml/convention/dictionary/constraints/entry_units_co_constraints.rb +1 -1
  269. data/lib/chemicalml/convention/dictionary/constraints.rb +8 -8
  270. data/lib/chemicalml/convention/dictionary.rb +2 -2
  271. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula.rb +1 -0
  272. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +15 -6
  273. data/lib/chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired.rb +1 -1
  274. data/lib/chemicalml/convention/molecular/constraints/atom_element_type_should_be_in_periodic_table.rb +39 -0
  275. data/lib/chemicalml/convention/molecular/constraints/atom_id_must_match_pattern.rb +3 -2
  276. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +1 -0
  277. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_element_type.rb +1 -1
  278. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_id.rb +2 -1
  279. data/lib/chemicalml/convention/molecular/constraints/atom_parity_atom_refs4_should_be_distinct.rb +44 -0
  280. data/lib/chemicalml/convention/molecular/constraints/atom_parity_should_include_parent_atom.rb +65 -0
  281. data/lib/chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule.rb +1 -0
  282. data/lib/chemicalml/convention/molecular/constraints/bond_atom_refs2_should_be_distinct.rb +44 -0
  283. data/lib/chemicalml/convention/molecular/constraints/bond_id_should_match_pattern.rb +38 -0
  284. data/lib/chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule.rb +1 -0
  285. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2.rb +1 -1
  286. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_order.rb +2 -1
  287. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +1 -0
  288. data/lib/chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref.rb +1 -0
  289. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_be_in_enum.rb +39 -0
  290. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric.rb +1 -1
  291. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_atom_refs4_should_be_distinct.rb +43 -0
  292. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4.rb +1 -1
  293. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref.rb +1 -0
  294. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_should_be_in_enum.rb +38 -0
  295. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2.rb +1 -1
  296. data/lib/chemicalml/convention/molecular/constraints/dict_ref_should_resolve.rb +59 -0
  297. data/lib/chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children.rb +1 -0
  298. data/lib/chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children.rb +1 -0
  299. data/lib/chemicalml/convention/molecular/constraints/molecule_chirality_should_be_in_enum.rb +38 -0
  300. data/lib/chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level.rb +1 -0
  301. data/lib/chemicalml/convention/molecular/constraints/molecule_id_should_match_pattern.rb +38 -0
  302. data/lib/chemicalml/convention/molecular/constraints/molecule_must_have_id.rb +1 -1
  303. data/lib/chemicalml/convention/molecular/constraints/property_must_have_dict_ref.rb +1 -1
  304. data/lib/chemicalml/convention/molecular/constraints/property_scalar_data_type_matches_dictionary.rb +58 -0
  305. data/lib/chemicalml/convention/molecular/constraints/references_should_resolve.rb +37 -0
  306. data/lib/chemicalml/convention/molecular/constraints/scalar_must_have_data_type.rb +1 -1
  307. data/lib/chemicalml/convention/molecular/constraints.rb +49 -23
  308. data/lib/chemicalml/convention/molecular.rb +15 -2
  309. data/lib/chemicalml/convention/registry.rb +79 -11
  310. data/lib/chemicalml/convention/simple_unit/constraints/root_must_be_unit_list.rb +24 -0
  311. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_power.rb +32 -0
  312. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_symbol.rb +31 -0
  313. data/lib/chemicalml/convention/simple_unit/constraints.rb +16 -0
  314. data/lib/chemicalml/convention/simple_unit.rb +37 -0
  315. data/lib/chemicalml/convention/spectroscopy/constraints/peak_list_must_contain_peaks.rb +31 -0
  316. data/lib/chemicalml/convention/spectroscopy/constraints/peak_should_have_values.rb +34 -0
  317. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_content.rb +31 -0
  318. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_convention.rb +32 -0
  319. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_format.rb +31 -0
  320. data/lib/chemicalml/convention/spectroscopy/constraints.rb +20 -0
  321. data/lib/chemicalml/convention/spectroscopy.rb +41 -0
  322. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_contain_at_least_one_unit.rb +1 -0
  323. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_have_namespace.rb +1 -1
  324. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_contain_definition.rb +1 -0
  325. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_id.rb +1 -0
  326. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_multiplier_or_constant_to_si.rb +34 -0
  327. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_parent_si.rb +32 -0
  328. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +1 -1
  329. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_title.rb +32 -0
  330. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_parent_si_should_resolve.rb +45 -0
  331. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_unit_type_should_resolve.rb +46 -0
  332. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +10 -0
  333. data/lib/chemicalml/convention/unit_dictionary.rb +7 -2
  334. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_id_must_match_pattern.rb +1 -1
  335. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_contain_at_least_one_unit_type.rb +1 -0
  336. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_have_namespace.rb +1 -1
  337. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_contain_definition.rb +1 -0
  338. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +1 -1
  339. data/lib/chemicalml/convention/unit_type_dictionary.rb +2 -2
  340. data/lib/chemicalml/convention/validation_report.rb +30 -3
  341. data/lib/chemicalml/convention/violation.rb +9 -3
  342. data/lib/chemicalml/convention.rb +41 -14
  343. data/lib/chemicalml/dictionary.rb +13 -9
  344. data/lib/chemicalml/logger.rb +47 -0
  345. data/lib/chemicalml/schema/definition.rb +2 -2
  346. data/lib/chemicalml/schema.rb +7 -7
  347. data/lib/chemicalml/version.rb +1 -1
  348. data/lib/chemicalml/versioned_parser.rb +10 -10
  349. data/lib/chemicalml.rb +78 -15
  350. data/lib/tasks/reference_fetch.rake +30 -31
  351. data/lib/tasks/scrape_fixtures.rake +23 -24
  352. metadata +118 -5
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<bondStereo>`'s `value` attribute SHOULD be one of the
8
+ # XSD stereoType enum values: C/T/W/H/undefined/other.
9
+ # Warning severity.
10
+ class BondStereoShouldBeInEnum < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<bondStereo>`'s `value` attribute SHOULD be one of the XSD stereoType enum values: C/T/W/H/undefined/other. Warning severity."
12
+ include Chemicalml::Cml::Enums
13
+
14
+ applies_to Chemicalml::Cml::Role::BondStereo
15
+
16
+ def check_node(node, _path)
17
+ value = node.content.to_s
18
+ return [] if value.empty?
19
+ return [] if STEREO_VALUES.include?(value)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "bondStereo #{node.id.inspect} value #{value.inspect} should be one of " \
23
+ "#{STEREO_VALUES.to_a.sort.inspect}",
24
+ severity: :warning,
25
+ value: value)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "bondStereo[#{id}]" : 'bondStereo'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # Molecular convention: `<bondStereo>` value W (wedge) or H
8
8
  # (hatch) MUST have `atomRefs2` and MUST NOT have `atomRefs4`.
9
9
  class BondStereoWedgeHashMustHaveAtomRefs2 < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: `<bondStereo>` value W (wedge) or H (hatch) MUST have `atomRefs2` and MUST NOT have `atomRefs4`.'
10
11
  applies_to Chemicalml::Cml::Role::BondStereo
11
12
  WEDGE_HASH_VALUES = %w[W H].freeze
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  value = node.content.to_s.upcase
16
16
  return [] unless WEDGE_HASH_VALUES.include?(value)
17
17
 
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Walks the document and warns on any element whose `dictRef`
8
+ # attribute cannot be resolved against the built-in
9
+ # dictionaries. Catches typos like `dictRef="cml:bpingpoint"`.
10
+ #
11
+ # DocumentConstraint — needs the whole tree, but the resolver
12
+ # works per-node so it's just a wrapper around the per-node
13
+ # check.
14
+ class DictRefShouldResolve < Chemicalml::Convention::Constraint::NodeConstraint
15
+ self.description = 'Walks the document and warns on any element whose `dictRef` attribute cannot be resolved against the built-in dictionaries. Catches typos like `dictRef="cml:bpingpoint"`.'
16
+ applies_to Chemicalml::Cml::Role::Atom,
17
+ Chemicalml::Cml::Role::Bond,
18
+ Chemicalml::Cml::Role::Molecule,
19
+ Chemicalml::Cml::Role::Property,
20
+ Chemicalml::Cml::Role::Scalar,
21
+ Chemicalml::Cml::Role::Array,
22
+ Chemicalml::Cml::Role::Matrix,
23
+ Chemicalml::Cml::Role::Name,
24
+ Chemicalml::Cml::Role::Label
25
+
26
+ def check_node(node, path)
27
+ dict_ref = node.dict_ref.to_s.strip
28
+ return [] if dict_ref.empty?
29
+ return [] if resolves?(dict_ref)
30
+
31
+ [violation(path: path_fingerprint(node, path),
32
+ message: "#{node.element_name} #{node_id_or_blank(node).inspect} " \
33
+ "dictRef #{dict_ref.inspect} does not resolve against any built-in dictionary",
34
+ severity: :warning,
35
+ value: dict_ref)]
36
+ end
37
+
38
+ private
39
+
40
+ def resolves?(dict_ref)
41
+ !Chemicalml::Dictionary::Registry.lookup(dict_ref).nil?
42
+ rescue StandardError
43
+ false
44
+ end
45
+
46
+ def path_fingerprint(node, path)
47
+ base = path.empty? ? node.element_name : path.join('/')
48
+ id = node.node_id
49
+ id ? "#{base}[#{id}]" : base
50
+ end
51
+
52
+ def node_id_or_blank(node)
53
+ node.node_id || ''
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -8,6 +8,7 @@ module Chemicalml
8
8
  # OR child `<molecule>` elements, but not both. The two are
9
9
  # mutually exclusive ways of describing composition.
10
10
  class MoleculeAtomArrayMutuallyExclusiveWithChildren < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'Molecular convention: a `<molecule>` MAY hold an `atomArray` OR child `<molecule>` elements, but not both. The two are mutually exclusive ways of describing composition.'
11
12
  applies_to Chemicalml::Cml::Role::Molecule
12
13
  def check_node(node, path)
13
14
  return [] if node.atom_array.nil?
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # Molecular convention: a `<molecule>` MAY hold a `bondArray`
8
8
  # OR child `<molecule>` elements, but not both.
9
9
  class MoleculeBondArrayMutuallyExclusiveWithChildren < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: a `<molecule>` MAY hold a `bondArray` OR child `<molecule>` elements, but not both.'
10
11
  applies_to Chemicalml::Cml::Role::Molecule
11
12
  def check_node(node, path)
12
13
  return [] if node.bond_array.nil?
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<molecule>`'s `chirality` attribute SHOULD be one of the
8
+ # XSD chiralityType enum values:
9
+ # enantiomer/racemate/unknown/other. Warning severity.
10
+ class MoleculeChiralityShouldBeInEnum < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<molecule>`'s `chirality` attribute SHOULD be one of the XSD chiralityType enum values: enantiomer/racemate/unknown/other. Warning severity."
12
+ include Chemicalml::Cml::Enums
13
+
14
+ applies_to Chemicalml::Cml::Role::Molecule
15
+
16
+ def check_node(node, _path)
17
+ chirality = node.chirality.to_s
18
+ return [] if chirality.empty?
19
+ return [] if CHIRALITY_VALUES.include?(chirality)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "molecule #{node.id.inspect} chirality #{chirality.inspect} should be one of " \
23
+ "#{CHIRALITY_VALUES.to_a.sort.inspect}",
24
+ severity: :warning,
25
+ value: chirality)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "molecule[#{id}]" : 'molecule'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -8,6 +8,7 @@ module Chemicalml
8
8
  # molecules (direct children of `<cml>`). It is REQUIRED on
9
9
  # molecules nested inside another `<molecule>`.
10
10
  class MoleculeCountMustNotAppearOnTopLevel < Chemicalml::Convention::Constraint::DocumentConstraint
11
+ self.description = 'Molecular convention: `count` MUST NOT appear on top-level molecules (direct children of `<cml>`). It is REQUIRED on molecules nested inside another `<molecule>`.'
11
12
  def check(document)
12
13
  violations = []
13
14
  top_level_molecules(document).each do |mol, path|
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<molecule>`'s `id` attribute SHOULD match the XSD
8
+ # moleculeIDType pattern (letter/underscore start, alphanumeric
9
+ # body, optional namespace prefix). Warning severity.
10
+ class MoleculeIdShouldMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<molecule>`'s `id` attribute SHOULD match the XSD moleculeIDType pattern (letter/underscore start, alphanumeric body, optional namespace prefix). Warning severity."
12
+ include Chemicalml::Cml::Patterns
13
+
14
+ applies_to Chemicalml::Cml::Role::Molecule
15
+
16
+ def check_node(node, _path)
17
+ id = node.id.to_s
18
+ return [] if id.empty?
19
+ return [] if id.match?(/\A#{MOLECULE_ID_PATTERN.source}\z/)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "molecule id #{id.inspect} should match the moleculeIDType pattern " \
23
+ '(letter start, alphanumeric body, optional prefix)',
24
+ severity: :warning,
25
+ value: id)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "molecule[#{id}]" : 'molecule'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -5,9 +5,9 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class MoleculeMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <molecule> in a molecular document MUST have an id attribute unique within document scope.'
8
9
  applies_to Chemicalml::Cml::Role::Molecule
9
10
  def check_node(node, path)
10
-
11
11
  return [] unless node.id.to_s.empty?
12
12
 
13
13
  [violation(path: path.empty? ? 'molecule' : path.join('/'),
@@ -5,9 +5,9 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class PropertyMustHaveDictRef < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <property> MUST have a dictRef attribute that identifies the property type via a dictionary entry.'
8
9
  applies_to Chemicalml::Cml::Role::Property
9
10
  def check_node(node, path)
10
-
11
11
  return [] unless node.dict_ref.to_s.empty?
12
12
 
13
13
  [violation(path: path.empty? ? 'property' : path.join('/'),
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<property>` with a `dictRef` whose dictionary entry declares
8
+ # a `dataType` SHOULD have a child `<scalar>` whose `dataType`
9
+ # attribute matches. Catches inconsistencies like a property
10
+ # declaring `dictRef="cml:bp"` (boiling point, xsd:float) but
11
+ # carrying a `<scalar dataType="xsd:string">`.
12
+ #
13
+ # Warning severity — some dictionaries allow multiple dataTypes.
14
+ class PropertyScalarDataTypeMatchesDictionary < Chemicalml::Convention::Constraint::NodeConstraint
15
+ self.description = 'A `<property>` with a `dictRef` whose dictionary entry declares a `dataType` SHOULD have a child `<scalar>` whose `dataType` attribute matches. Catches inconsistencies like a property'
16
+ applies_to Chemicalml::Cml::Role::Property
17
+
18
+ def check_node(node, _path)
19
+ dict_ref = node.dict_ref.to_s
20
+ return [] if dict_ref.empty?
21
+
22
+ entry = lookup_entry(dict_ref)
23
+ return [] unless entry
24
+
25
+ expected_type = entry.data_type.to_s
26
+ return [] if expected_type.empty?
27
+
28
+ scalar = node.scalar
29
+ return [] unless scalar
30
+
31
+ actual_type = scalar.data_type.to_s
32
+ return [] if actual_type.empty?
33
+ return [] if actual_type == expected_type
34
+
35
+ [violation(path: yield_path(node),
36
+ message: "property #{node.id.inspect} dictRef #{dict_ref.inspect} " \
37
+ "expects dataType #{expected_type.inspect} but scalar has #{actual_type.inspect}",
38
+ severity: :warning,
39
+ value: { expected: expected_type, actual: actual_type }.freeze)]
40
+ end
41
+
42
+ private
43
+
44
+ def lookup_entry(dict_ref)
45
+ Chemicalml::Dictionary::Registry.lookup(dict_ref)
46
+ rescue StandardError
47
+ nil
48
+ end
49
+
50
+ def yield_path(node)
51
+ id = node.node_id
52
+ id ? "property[#{id}]" : 'property'
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Walks the document via `Cml::ReferenceResolver` and reports
8
+ # every bond whose `atomRefs2` references atoms that don't
9
+ # exist in the parent molecule. Warning severity — there are
10
+ # edge cases (inter-molecular bonds in transition states).
11
+ #
12
+ # DocumentConstraint because it needs the whole tree to
13
+ # resolve references.
14
+ class ReferencesShouldResolve < Chemicalml::Convention::Constraint::DocumentConstraint
15
+ self.description = "Walks the document via `Cml::ReferenceResolver` and reports every bond whose `atomRefs2` references atoms that don't exist in the parent molecule. Warning severity — there are"
16
+ def check(document)
17
+ resolver = Chemicalml::Cml::ReferenceResolver.new(document)
18
+ resolver.unresolved_refs.map do |entry|
19
+ violation(path: describe(entry[:node]),
20
+ message: "bond #{entry[:node].id.inspect} #{entry[:attr]} references " \
21
+ "missing atoms: #{entry[:missing].inspect}",
22
+ severity: :warning,
23
+ value: entry[:missing])
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def describe(node)
30
+ id = node.node_id
31
+ id ? "#{node.element_name}[#{id}]" : node.element_name
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -5,9 +5,9 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class ScalarMustHaveDataType < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <scalar> child of a <property> MUST have a dataType attribute (e.g. xsd:float, xsd:integer).'
8
9
  applies_to Chemicalml::Cml::Role::Scalar
9
10
  def check_node(node, path)
10
-
11
11
  return [] unless node.data_type.to_s.empty?
12
12
 
13
13
  [violation(path: path.empty? ? 'scalar' : path.join('/'),
@@ -5,51 +5,77 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  autoload :AtomArrayMustContainAtoms,
8
- "chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms"
8
+ 'chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms'
9
9
  autoload :AtomIdsUniqueWithinMolecule,
10
- "chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule"
10
+ 'chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule'
11
11
  autoload :BondMustReferenceAtomsInSameMolecule,
12
- "chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule"
12
+ 'chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule'
13
13
  autoload :AtomMustHaveId,
14
- "chemicalml/convention/molecular/constraints/atom_must_have_id"
14
+ 'chemicalml/convention/molecular/constraints/atom_must_have_id'
15
15
  autoload :AtomMustHaveElementType,
16
- "chemicalml/convention/molecular/constraints/atom_must_have_element_type"
16
+ 'chemicalml/convention/molecular/constraints/atom_must_have_element_type'
17
17
  autoload :BondMustHaveAtomRefs2,
18
- "chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2"
18
+ 'chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2'
19
19
  autoload :BondMustHaveOrder,
20
- "chemicalml/convention/molecular/constraints/bond_must_have_order"
20
+ 'chemicalml/convention/molecular/constraints/bond_must_have_order'
21
21
  autoload :MoleculeMustHaveId,
22
- "chemicalml/convention/molecular/constraints/molecule_must_have_id"
22
+ 'chemicalml/convention/molecular/constraints/molecule_must_have_id'
23
23
  autoload :AtomCoordinatesMustBePaired,
24
- "chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired"
24
+ 'chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired'
25
25
  autoload :PropertyMustHaveDictRef,
26
- "chemicalml/convention/molecular/constraints/property_must_have_dict_ref"
26
+ 'chemicalml/convention/molecular/constraints/property_must_have_dict_ref'
27
27
  autoload :ScalarMustHaveDataType,
28
- "chemicalml/convention/molecular/constraints/scalar_must_have_data_type"
28
+ 'chemicalml/convention/molecular/constraints/scalar_must_have_data_type'
29
29
  autoload :BondOrderShouldNotBeNumeric,
30
- "chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric"
30
+ 'chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric'
31
31
  autoload :AtomIdMustMatchPattern,
32
- "chemicalml/convention/molecular/constraints/atom_id_must_match_pattern"
32
+ 'chemicalml/convention/molecular/constraints/atom_id_must_match_pattern'
33
33
  autoload :MoleculeCountMustNotAppearOnTopLevel,
34
- "chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level"
34
+ 'chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level'
35
35
  autoload :MoleculeAtomArrayMutuallyExclusiveWithChildren,
36
- "chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children"
36
+ 'chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children'
37
37
  autoload :MoleculeBondArrayMutuallyExclusiveWithChildren,
38
- "chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children"
38
+ 'chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children'
39
39
  autoload :BondStereoWedgeHashMustHaveAtomRefs2,
40
- "chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2"
40
+ 'chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2'
41
41
  autoload :BondStereoCisTransMustHaveAtomRefs4,
42
- "chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4"
42
+ 'chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4'
43
43
  autoload :BondStereoOtherMustHaveDictRef,
44
- "chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref"
44
+ 'chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref'
45
45
  autoload :BondIdsUniqueWithinMolecule,
46
- "chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule"
46
+ 'chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule'
47
47
  autoload :BondOrderOtherMustHaveDictRef,
48
- "chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref"
48
+ 'chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref'
49
49
  autoload :AtomArrayMustBeChildOfMoleculeOrFormula,
50
- "chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula"
50
+ 'chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula'
51
51
  autoload :BondArrayMustBeChildOfMolecule,
52
- "chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule"
52
+ 'chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule'
53
+ autoload :BondOrderShouldBeInEnum,
54
+ 'chemicalml/convention/molecular/constraints/bond_order_should_be_in_enum'
55
+ autoload :BondStereoShouldBeInEnum,
56
+ 'chemicalml/convention/molecular/constraints/bond_stereo_should_be_in_enum'
57
+ autoload :MoleculeChiralityShouldBeInEnum,
58
+ 'chemicalml/convention/molecular/constraints/molecule_chirality_should_be_in_enum'
59
+ autoload :BondAtomRefs2ShouldBeDistinct,
60
+ 'chemicalml/convention/molecular/constraints/bond_atom_refs2_should_be_distinct'
61
+ autoload :ReferencesShouldResolve,
62
+ 'chemicalml/convention/molecular/constraints/references_should_resolve'
63
+ autoload :BondStereoAtomRefs4ShouldBeDistinct,
64
+ 'chemicalml/convention/molecular/constraints/bond_stereo_atom_refs4_should_be_distinct'
65
+ autoload :AtomParityAtomRefs4ShouldBeDistinct,
66
+ 'chemicalml/convention/molecular/constraints/atom_parity_atom_refs4_should_be_distinct'
67
+ autoload :AtomElementTypeShouldBeInPeriodicTable,
68
+ 'chemicalml/convention/molecular/constraints/atom_element_type_should_be_in_periodic_table'
69
+ autoload :DictRefShouldResolve,
70
+ 'chemicalml/convention/molecular/constraints/dict_ref_should_resolve'
71
+ autoload :MoleculeIdShouldMatchPattern,
72
+ 'chemicalml/convention/molecular/constraints/molecule_id_should_match_pattern'
73
+ autoload :BondIdShouldMatchPattern,
74
+ 'chemicalml/convention/molecular/constraints/bond_id_should_match_pattern'
75
+ autoload :AtomParityShouldIncludeParentAtom,
76
+ 'chemicalml/convention/molecular/constraints/atom_parity_should_include_parent_atom'
77
+ autoload :PropertyScalarDataTypeMatchesDictionary,
78
+ 'chemicalml/convention/molecular/constraints/property_scalar_data_type_matches_dictionary'
53
79
  end
54
80
  end
55
81
  end
@@ -8,9 +8,9 @@ module Chemicalml
8
8
  module Molecular
9
9
  extend Base
10
10
 
11
- autoload :Constraints, "chemicalml/convention/molecular/constraints"
11
+ autoload :Constraints, 'chemicalml/convention/molecular/constraints'
12
12
 
13
- QNAME = "convention:molecular".freeze
13
+ QNAME = 'convention:molecular'
14
14
  NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}molecular".freeze
15
15
 
16
16
  def self.qname
@@ -44,6 +44,19 @@ module Chemicalml
44
44
  register Constraints::BondOrderOtherMustHaveDictRef
45
45
  register Constraints::AtomArrayMustBeChildOfMoleculeOrFormula
46
46
  register Constraints::BondArrayMustBeChildOfMolecule
47
+ register Constraints::BondOrderShouldBeInEnum
48
+ register Constraints::BondStereoShouldBeInEnum
49
+ register Constraints::MoleculeChiralityShouldBeInEnum
50
+ register Constraints::BondAtomRefs2ShouldBeDistinct
51
+ register Constraints::ReferencesShouldResolve
52
+ register Constraints::BondStereoAtomRefs4ShouldBeDistinct
53
+ register Constraints::AtomParityAtomRefs4ShouldBeDistinct
54
+ register Constraints::AtomElementTypeShouldBeInPeriodicTable
55
+ register Constraints::DictRefShouldResolve
56
+ register Constraints::MoleculeIdShouldMatchPattern
57
+ register Constraints::BondIdShouldMatchPattern
58
+ register Constraints::AtomParityShouldIncludeParentAtom
59
+ register Constraints::PropertyScalarDataTypeMatchesDictionary
47
60
  end
48
61
  end
49
62
  end
@@ -35,7 +35,7 @@ module Chemicalml
35
35
  # declares no convention or an unknown one.
36
36
  def self.detect_and_validate(document)
37
37
  qname = Convention::Detection.convention_of(document)
38
- raise ArgumentError, "document declares no convention attribute" unless qname
38
+ raise ArgumentError, 'document declares no convention attribute' unless qname
39
39
 
40
40
  validate_report(document, qname: qname)
41
41
  end
@@ -44,21 +44,89 @@ module Chemicalml
44
44
  load_cache.keys.sort
45
45
  end
46
46
 
47
+ # Iterate every registered convention. Yields the convention
48
+ # module. Enumerable-style: returns an Enumerator if no block.
49
+ #
50
+ # @yield [Module] each convention module.
51
+ # @return [Array<Module>, Enumerator] the conventions if a
52
+ # block is given; otherwise an Enumerator.
53
+ def self.each(&)
54
+ return to_enum(:each) unless block_given?
55
+
56
+ load_cache.values.sort_by(&:qname).each(&)
57
+ end
58
+
59
+ # Iterate every constraint across every convention. Yields
60
+ # (convention, constraint_class) pairs. Useful for documentation
61
+ # generation and code introspection.
62
+ #
63
+ # @yieldparam convention [Module] the convention module.
64
+ # @yieldparam constraint [Class] the constraint class.
65
+ # @return [Enumerator, Integer] count if block given; otherwise
66
+ # an Enumerator.
67
+ def self.each_constraint(&)
68
+ return to_enum(:each_constraint) unless block_given?
69
+
70
+ count = 0
71
+ each do |conv|
72
+ conv.constraints.each do |constraint|
73
+ yield(conv, constraint)
74
+ count += 1
75
+ end
76
+ end
77
+ count
78
+ end
79
+
80
+ # Total count of registered constraints across all conventions.
81
+ def self.total_constraint_count
82
+ each.sum(&:constraint_count)
83
+ end
84
+
85
+ # True if a document with the given Role could carry a
86
+ # convention attribute that Detection would recognise.
87
+ #
88
+ # @param role [Module] a Role::* module.
89
+ # @return [Boolean]
90
+ def self.convention_root?(role)
91
+ Detection::CONVENTION_ROOTS.any? { |r| role <= r }
92
+ rescue StandardError
93
+ Detection::CONVENTION_ROOTS.include?(role)
94
+ end
95
+
96
+ # Register a custom convention at runtime. The convention module
97
+ # must extend `Chemicalml::Convention::Base` and implement
98
+ # `qname` and `namespace_uri`.
99
+ #
100
+ # @param mod [Module] the convention module (must `extend Base`).
101
+ # @return [Module] the registered module.
102
+ # @raise [ArgumentError] if `mod.qname` is nil/empty.
103
+ def self.register_custom(mod)
104
+ qname = mod.qname
105
+ raise ArgumentError, 'convention module must return a non-empty qname' if qname.nil? || qname.to_s.empty?
106
+
107
+ # Force load_cache to populate (acquires/releases mutex internally),
108
+ # then mutate directly without re-entering the mutex.
109
+ cache = load_cache
110
+ cache[qname.to_s] = mod
111
+ mod
112
+ end
113
+
47
114
  def self.reset!
48
- @mutex.synchronize { @cache = nil }
115
+ @mutex.synchronize { @load_cache = nil }
49
116
  end
50
117
 
51
118
  def self.load_cache
52
119
  @mutex.synchronize do
53
- @cache ||= begin
54
- {
55
- Molecular.qname => Molecular,
56
- Compchem.qname => Compchem,
57
- Dictionary.qname => Dictionary,
58
- UnitDictionary.qname => UnitDictionary,
59
- UnitTypeDictionary.qname => UnitTypeDictionary
60
- }
61
- end
120
+ @load_cache ||= {
121
+ Molecular.qname => Molecular,
122
+ Compchem.qname => Compchem,
123
+ Dictionary.qname => Dictionary,
124
+ UnitDictionary.qname => UnitDictionary,
125
+ UnitTypeDictionary.qname => UnitTypeDictionary,
126
+ Spectroscopy.qname => Spectroscopy,
127
+ Cascade.qname => Cascade,
128
+ SimpleUnit.qname => SimpleUnit
129
+ }
62
130
  end
63
131
  end
64
132
  private_class_method :load_cache
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module SimpleUnit
6
+ module Constraints
7
+ # The root of a simpleUnit document MUST be a `<unitList>`
8
+ # declaring `convention="convention:simpleUnit"`. Any other
9
+ # root shape is rejected.
10
+ class RootMustBeUnitList < Chemicalml::Convention::Constraint::DocumentConstraint
11
+ self.description = 'The root of a simpleUnit document MUST be a `<unitList>` declaring `convention="convention:simpleUnit"`. Any other root shape is rejected.'
12
+ def check(document)
13
+ return [] if document.is_a?(Chemicalml::Cml::Role::UnitList) &&
14
+ document.convention == 'convention:simpleUnit'
15
+
16
+ [violation(path: '/',
17
+ message: 'simpleUnit root must be <unitList> declaring ' \
18
+ 'convention="convention:simpleUnit"')]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module SimpleUnit
6
+ module Constraints
7
+ # A `<unit>` under simpleUnit MUST declare a `power` attribute
8
+ # (integer). Without a power the unit's exponent is undefined.
9
+ class UnitMustHavePower < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = "A `<unit>` under simpleUnit MUST declare a `power` attribute (integer). Without a power the unit's exponent is undefined."
11
+ applies_to Chemicalml::Cml::Role::Unit
12
+
13
+ def check_node(node, _path)
14
+ power = node.power.to_s.strip
15
+ return [] unless power.empty?
16
+
17
+ [violation(path: yield_path(node),
18
+ message: "unit #{node.id.inspect} must declare a power attribute",
19
+ value: node.power)]
20
+ end
21
+
22
+ private
23
+
24
+ def yield_path(node)
25
+ id = node.node_id
26
+ id ? "unit[#{id}]" : 'unit'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end