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
@@ -8,9 +8,11 @@ module Chemicalml
8
8
  # element. The schema allows it as a string; this constraint
9
9
  # enforces presence.
10
10
  class EntryMustContainDefinition < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'An `<entry>` MUST contain a single `definition` child element. '
11
12
  applies_to Chemicalml::Cml::Role::DictionaryEntry
12
13
  def check_node(node, path)
13
- return [] unless node.definition.to_s.strip.empty?
14
+ definition = node.definition
15
+ return [] if definition_contained?(definition)
14
16
 
15
17
  [violation(path: path.join('/'),
16
18
  message: "entry #{node.id.inspect} must contain a single definition child")]
@@ -18,6 +20,18 @@ module Chemicalml
18
20
 
19
21
  private
20
22
 
23
+ # CML allows the definition content as plain text or as
24
+ # rich text (<xhtml:p> paragraphs); the wire class types it
25
+ # structured, but a plain string remains valid at
26
+ # construction.
27
+ def definition_contained?(definition)
28
+ return false if definition.nil?
29
+ return !definition.to_s.strip.empty? if definition.is_a?(String)
30
+
31
+ !(definition.content.to_s.strip.empty? &&
32
+ Array(definition.paragraphs).empty?)
33
+ end
34
+
21
35
  def entry?(node)
22
36
  node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
23
37
  end
@@ -7,9 +7,9 @@ module Chemicalml
7
7
  # Every entry MUST have an `id` and a `term` attribute per the
8
8
  # dictionary convention.
9
9
  class EntryMustHaveIdAndTerm < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Every entry MUST have an `id` and a `term` attribute per the dictionary convention.'
10
11
  applies_to Chemicalml::Cml::Role::DictionaryEntry
11
12
  def check_node(node, path)
12
-
13
13
  violations = []
14
14
  if node.id.to_s.empty?
15
15
  violations << violation(path: path.join('/'),
@@ -9,6 +9,7 @@ module Chemicalml
9
9
  # of a program) should reference `none` in the standard CML
10
10
  # unitType dictionary.
11
11
  class EntryMustHaveUnitType < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = 'An `<entry>` MUST have a `unitType` attribute. Entries describing concepts that should not have units (e.g. name of a program) should reference `none` in the standard CML'
12
13
  applies_to Chemicalml::Cml::Role::DictionaryEntry
13
14
  def check_node(node, path)
14
15
  return [] unless node.unit_type.to_s.empty?
@@ -10,11 +10,11 @@ module Chemicalml
10
10
  # - If `unitType` is `none`, `units` MUST be present and point
11
11
  # to `http://www.xml-cml.org/unit/si#none`.
12
12
  class EntryUnitsCoConstraints < Chemicalml::Convention::Constraint::NodeConstraint
13
+ self.description = 'Co-constraint between `unitType` and `units`: - If `unitType` is `unknown`, `units` MUST NOT be present. - If `unitType` is `none`, `units` MUST be present and point'
13
14
  applies_to Chemicalml::Cml::Role::DictionaryEntry
14
15
  NONE_UNITS = 'http://www.xml-cml.org/unit/si#none'
15
16
 
16
17
  def check_node(node, path)
17
-
18
18
  unit_type = node.unit_type.to_s
19
19
  units = node.units.to_s
20
20
  violations = []
@@ -5,21 +5,21 @@ module Chemicalml
5
5
  module Dictionary
6
6
  module Constraints
7
7
  autoload :EntryMustHaveIdAndTerm,
8
- "chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term"
8
+ 'chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term'
9
9
  autoload :EntryIdsUniqueWithinDictionary,
10
- "chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary"
10
+ 'chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary'
11
11
  autoload :DictionaryMustHaveNamespace,
12
- "chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace"
12
+ 'chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace'
13
13
  autoload :DictionaryNamespaceShouldEndWithSlashOrHash,
14
- "chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash"
14
+ 'chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash'
15
15
  autoload :EntryMustContainDefinition,
16
- "chemicalml/convention/dictionary/constraints/entry_must_contain_definition"
16
+ 'chemicalml/convention/dictionary/constraints/entry_must_contain_definition'
17
17
  autoload :EntryIdMustMatchPattern,
18
- "chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern"
18
+ 'chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern'
19
19
  autoload :EntryMustHaveUnitType,
20
- "chemicalml/convention/dictionary/constraints/entry_must_have_unit_type"
20
+ 'chemicalml/convention/dictionary/constraints/entry_must_have_unit_type'
21
21
  autoload :EntryUnitsCoConstraints,
22
- "chemicalml/convention/dictionary/constraints/entry_units_co_constraints"
22
+ 'chemicalml/convention/dictionary/constraints/entry_units_co_constraints'
23
23
  end
24
24
  end
25
25
  end
@@ -8,9 +8,9 @@ module Chemicalml
8
8
  module Dictionary
9
9
  extend Base
10
10
 
11
- autoload :Constraints, "chemicalml/convention/dictionary/constraints"
11
+ autoload :Constraints, 'chemicalml/convention/dictionary/constraints'
12
12
 
13
- QNAME = "convention:dictionary".freeze
13
+ QNAME = 'convention:dictionary'
14
14
  NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}dictionary".freeze
15
15
 
16
16
  def self.qname
@@ -10,6 +10,7 @@ module Chemicalml
10
10
  # when visiting a non-molecule/non-formula node, look for
11
11
  # misplaced atomArray children.
12
12
  class AtomArrayMustBeChildOfMoleculeOrFormula < Chemicalml::Convention::Constraint::NodeConstraint
13
+ self.description = "Molecular convention: `<atomArray>` MUST be a child of `<molecule>` or `<formula>`. Any other parent is invalid. Checked from the parent's perspective during the walk —"
13
14
  def check_node(node, path)
14
15
  return [] if allowed_parent?(node)
15
16
 
@@ -4,15 +4,17 @@ module Chemicalml
4
4
  module Convention
5
5
  module Molecular
6
6
  module Constraints
7
- # `atomArray` MUST contain at least one `atom` child per the
8
- # molecular convention.
7
+ # `atomArray` MUST contain at least one atom. The atoms can
8
+ # be in the child form (`<atom>` elements) or the parallel-array
9
+ # form (`atomID` attribute with whitespace-separated ids).
9
10
  class AtomArrayMustContainAtoms < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = '`atomArray` MUST contain at least one atom. The atoms can be in the child form (`<atom>` elements) or the parallel-array form (`atomID` attribute with whitespace-separated ids).'
10
12
  applies_to Chemicalml::Cml::Role::Molecule
11
13
  def check_node(node, path)
12
14
  return [] unless node.atom_array
13
15
 
14
- atoms = node.atom_array.atoms || []
15
- return [] unless atoms.empty?
16
+ aa = node.atom_array
17
+ return [] if has_child_atoms?(aa) || has_parallel_array_atoms?(aa)
16
18
 
17
19
  [violation(path: path.empty? ? 'molecule' : path.join('/'),
18
20
  message: 'atomArray must contain at least one atom')]
@@ -20,8 +22,15 @@ module Chemicalml
20
22
 
21
23
  private
22
24
 
23
- def molecule?(node)
24
- node.is_a?(Chemicalml::Cml::Role::Molecule)
25
+ def has_child_atoms?(atom_array)
26
+ !(atom_array.atoms || []).empty?
27
+ end
28
+
29
+ # Parallel-array form is identified by a non-empty atomIDArray
30
+ # attribute — the XSD marks it as mandatory when atoms are
31
+ # encoded as parallel arrays.
32
+ def has_parallel_array_atoms?(atom_array)
33
+ !atom_array.atom_id_array.to_s.strip.empty?
25
34
  end
26
35
  end
27
36
  end
@@ -5,9 +5,9 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class AtomCoordinatesMustBePaired < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = '<atom> 2D coordinates (x2, y2) must appear together; 3D coordinates (x3, y3, z3) must appear together.'
8
9
  applies_to Chemicalml::Cml::Role::Atom
9
10
  def check_node(node, path)
10
-
11
11
  violations = []
12
12
  has_x2 = !node.x2.to_s.empty?
13
13
  has_y2 = !node.y2.to_s.empty?
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # An `<atom>`'s `elementType` attribute SHOULD be one of the
8
+ # XSD elementTypeType enum values (the periodic table plus
9
+ # the special "Du" dummy and "R" group placeholder). Warns
10
+ # on typos like "Carb" or "X".
11
+ class AtomElementTypeShouldBeInPeriodicTable < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = "An `<atom>`'s `elementType` attribute SHOULD be one of the XSD elementTypeType enum values (the periodic table plus the special \"Du\" dummy and \"R\" group placeholder). Warns"
13
+ include Chemicalml::Cml::Enums
14
+
15
+ applies_to Chemicalml::Cml::Role::Atom
16
+
17
+ def check_node(node, _path)
18
+ et = node.element_type.to_s.strip
19
+ return [] if et.empty?
20
+ return [] if ELEMENT_TYPE_VALUES.include?(et)
21
+
22
+ [violation(path: yield_path(node),
23
+ message: "atom #{node.id.inspect} elementType #{et.inspect} should be one of " \
24
+ "the periodic table symbols (e.g. C, H, O, Fe) — got #{et.inspect}",
25
+ severity: :warning,
26
+ value: et)]
27
+ end
28
+
29
+ private
30
+
31
+ def yield_path(node)
32
+ id = node.node_id
33
+ id ? "atom[#{id}]" : 'atom'
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,11 +5,11 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class AtomIdMustMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = "An <atom>'s id attribute SHOULD start with a letter and contain only letters, digits, dot, hyphen, or underscore."
8
9
  applies_to Chemicalml::Cml::Role::Atom
9
10
  ID_PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/
10
11
 
11
12
  def check_node(node, path)
12
-
13
13
  id = node.id.to_s
14
14
  return [] if id.empty?
15
15
  return [] if id.match?(ID_PATTERN)
@@ -17,7 +17,8 @@ module Chemicalml
17
17
  [violation(path: path.empty? ? 'atom' : path.join('/'),
18
18
  message: "atom id #{id.inspect} must start with a letter and " \
19
19
  'contain only letters, digits, dot, hyphen, or underscore',
20
- severity: :warning)]
20
+ severity: :warning,
21
+ value: id)]
21
22
  end
22
23
  end
23
24
  end
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # Atom ids MUST be unique within the eldest containing molecule
8
8
  # per the molecular convention.
9
9
  class AtomIdsUniqueWithinMolecule < Chemicalml::Convention::Constraint::DocumentConstraint
10
+ self.description = 'Atom ids MUST be unique within the eldest containing molecule per the molecular convention.'
10
11
  def check(document)
11
12
  violations = []
12
13
  walk_nodes(document) do |node, path|
@@ -5,9 +5,9 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class AtomMustHaveElementType < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'An <atom> inside an atomArray in a molecule MUST have an elementType attribute.'
8
9
  applies_to Chemicalml::Cml::Role::Atom
9
10
  def check_node(node, path)
10
-
11
11
  return [] unless node.element_type.to_s.empty?
12
12
 
13
13
  [violation(path: path.empty? ? 'atom' : path.join('/'),
@@ -6,8 +6,9 @@ module Chemicalml
6
6
  module Constraints
7
7
  class AtomMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
8
8
  applies_to Chemicalml::Cml::Role::Atom
9
- def check_node(node, path)
9
+ self.description = 'An <atom> inside an atomArray in a molecule MUST have an id attribute.'
10
10
 
11
+ def check_node(node, path)
11
12
  return [] unless node.id.to_s.empty?
12
13
 
13
14
  [violation(path: path.empty? ? 'atom' : path.join('/'),
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # An `<atomParity>` element's `atomRefs4` attribute SHOULD
8
+ # reference four distinct atom ids around a chiral center.
9
+ # Duplicate ids make the parity descriptor meaningless.
10
+ # Warning severity.
11
+ class AtomParityAtomRefs4ShouldBeDistinct < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = "An `<atomParity>` element's `atomRefs4` attribute SHOULD reference four distinct atom ids around a chiral center. Duplicate ids make the parity descriptor meaningless."
13
+ applies_to Chemicalml::Cml::Role::AtomParity
14
+
15
+ def check_node(node, _path)
16
+ ids = parse_refs(node.atom_refs4)
17
+ return [] if ids.size < 2
18
+ return [] if ids.uniq.size == ids.size
19
+
20
+ duplicates = ids.group_by { |i| i }.select { |_, v| v.size > 1 }.keys
21
+ [violation(path: yield_path(node),
22
+ message: "atomParity #{node.id.inspect} atomRefs4 should reference four distinct atoms; " \
23
+ "duplicate ids: #{duplicates.inspect}",
24
+ severity: :warning,
25
+ value: node.atom_refs4)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "atomParity[#{id}]" : 'atomParity'
33
+ end
34
+
35
+ def parse_refs(value)
36
+ return [] if value.nil?
37
+
38
+ value.to_s.split(/\s+/).reject(&:empty?)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,65 @@
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 `<atomParity>` elements
8
+ # whose parent `<atom>`'s id does not appear in atomRefs4.
9
+ # The CML convention requires the parent atom to be one of
10
+ # the four references (otherwise the parity is meaningless).
11
+ #
12
+ # DocumentConstraint because it needs to walk the tree and
13
+ # track parent-child relationships.
14
+ class AtomParityShouldIncludeParentAtom < Chemicalml::Convention::Constraint::DocumentConstraint
15
+ self.description = "Walks the document and warns on `<atomParity>` elements whose parent `<atom>`'s id does not appear in atomRefs4. The CML convention requires the parent atom to be one of"
16
+ def check(document)
17
+ violations = []
18
+ visit_with_parent(document, nil) do |node, parent|
19
+ next unless atom_parity?(node)
20
+ next unless atom?(parent)
21
+
22
+ refs = parse_refs(node.atom_refs4)
23
+ next if refs.empty?
24
+ next if refs.include?(parent.id)
25
+
26
+ violations << violation(
27
+ path: "atom[#{parent.id}]/atomParity",
28
+ message: "atomParity atomRefs4 #{refs.inspect} should include the parent atom id " \
29
+ "#{parent.id.inspect}",
30
+ severity: :warning,
31
+ value: node.atom_refs4
32
+ )
33
+ end
34
+ violations
35
+ end
36
+
37
+ private
38
+
39
+ def visit_with_parent(node, parent, &block)
40
+ return unless node.is_a?(Lutaml::Model::Serializable)
41
+
42
+ block.call(node, parent)
43
+ return unless node.is_a?(Chemicalml::Cml::Visitable)
44
+
45
+ node.wire_children.each { |c| visit_with_parent(c, node, &block) }
46
+ end
47
+
48
+ def atom_parity?(node)
49
+ node.is_a?(Chemicalml::Cml::Role::AtomParity)
50
+ end
51
+
52
+ def atom?(node)
53
+ node.is_a?(Chemicalml::Cml::Role::Atom)
54
+ end
55
+
56
+ def parse_refs(value)
57
+ return [] if value.nil?
58
+
59
+ value.to_s.split(/\s+/).reject(&:empty?)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # Molecular convention: `<bondArray>` MUST be a child of
8
8
  # `<molecule>`. Any other parent is invalid.
9
9
  class BondArrayMustBeChildOfMolecule < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: `<bondArray>` MUST be a child of `<molecule>`. Any other parent is invalid.'
10
11
  def check_node(node, path)
11
12
  return [] if molecule?(node)
12
13
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<bond>`'s `atomRefs2` SHOULD reference two distinct atom
8
+ # ids. A self-bond (`a1 a1`) is chemically meaningless.
9
+ # Warning severity — there are edge cases in non-classical
10
+ # chemistry (e.g. agostic interactions).
11
+ class BondAtomRefs2ShouldBeDistinct < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = "A `<bond>`'s `atomRefs2` SHOULD reference two distinct atom ids. A self-bond (`a1 a1`) is chemically meaningless. Warning severity — there are edge cases in non-classical"
13
+ applies_to Chemicalml::Cml::Role::Bond
14
+
15
+ def check_node(node, _path)
16
+ ids = parse_refs(node.atom_refs2)
17
+ return [] if ids.size < 2
18
+ return [] if ids.uniq.size == ids.size
19
+
20
+ duplicates = ids.group_by { |i| i }.select { |_, v| v.size > 1 }.keys
21
+ [violation(path: yield_path(node),
22
+ message: "bond #{node.id.inspect} atomRefs2 should reference two distinct atoms; " \
23
+ "duplicate ids: #{duplicates.inspect}",
24
+ severity: :warning,
25
+ value: node.atom_refs2)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "bond[#{id}]" : 'bond'
33
+ end
34
+
35
+ def parse_refs(value)
36
+ return [] if value.nil?
37
+
38
+ value.to_s.split(/\s+/).reject(&:empty?)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<bond>`'s `id` attribute SHOULD match the XSD idType
8
+ # pattern (letter start, alphanumeric body with dots/dashes).
9
+ # Warning severity.
10
+ class BondIdShouldMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<bond>`'s `id` attribute SHOULD match the XSD idType pattern (letter start, alphanumeric body with dots/dashes). Warning severity."
12
+ include Chemicalml::Cml::Patterns
13
+
14
+ applies_to Chemicalml::Cml::Role::Bond
15
+
16
+ def check_node(node, _path)
17
+ id = node.id.to_s
18
+ return [] if id.empty?
19
+ return [] if id.match?(/\A#{ID_PATTERN.source}\z/)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "bond id #{id.inspect} should match the idType pattern " \
23
+ '(letter start, alphanumeric body with . and -)',
24
+ severity: :warning,
25
+ value: id)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "bond[#{id}]" : 'bond'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -8,6 +8,7 @@ module Chemicalml
8
8
  # the eldest containing molecule. Mirrors the existing
9
9
  # `AtomIdsUniqueWithinMolecule` constraint for bonds.
10
10
  class BondIdsUniqueWithinMolecule < Chemicalml::Convention::Constraint::DocumentConstraint
11
+ self.description = 'Molecular convention: a `<bond>` `id` MUST be unique within the eldest containing molecule. Mirrors the existing `AtomIdsUniqueWithinMolecule` constraint for bonds.'
11
12
  def check(document)
12
13
  violations = []
13
14
  walk_nodes(document) do |node, path|
@@ -5,9 +5,9 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class BondMustHaveAtomRefs2 < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <bond> MUST have an atomRefs2 attribute referencing two distinct atoms in the same molecule.'
8
9
  applies_to Chemicalml::Cml::Role::Bond
9
10
  def check_node(node, path)
10
-
11
11
  return [] unless node.atom_refs2.to_s.strip.empty?
12
12
 
13
13
  [violation(path: path.empty? ? 'bond' : path.join('/'),
@@ -6,8 +6,9 @@ module Chemicalml
6
6
  module Constraints
7
7
  class BondMustHaveOrder < Chemicalml::Convention::Constraint::NodeConstraint
8
8
  applies_to Chemicalml::Cml::Role::Bond
9
- def check_node(node, path)
9
+ self.description = 'A <bond> MUST have an order attribute (S/D/T/A recommended).'
10
10
 
11
+ def check_node(node, path)
11
12
  return [] unless node.order.to_s.strip.empty?
12
13
 
13
14
  [violation(path: path.empty? ? 'bond' : path.join('/'),
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # Bonds MUST reference atoms within the same parent molecule
8
8
  # per the molecular convention.
9
9
  class BondMustReferenceAtomsInSameMolecule < Chemicalml::Convention::Constraint::DocumentConstraint
10
+ self.description = 'Bonds MUST reference atoms within the same parent molecule per the molecular convention.'
10
11
  def check(document)
11
12
  violations = []
12
13
  walk_nodes(document) do |node, path|
@@ -9,6 +9,7 @@ module Chemicalml
9
9
  # the order semantics. The warning-level "should not be
10
10
  # numeric" rule is separate; this is the hard error.
11
11
  class BondOrderOtherMustHaveDictRef < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = 'Molecular convention: `<bond>` with `order="other"` MUST have a `dictRef` pointing to the dictionary that defines the order semantics. The warning-level "should not be'
12
13
  applies_to Chemicalml::Cml::Role::Bond
13
14
  def check_node(node, path)
14
15
  return [] unless node.order.to_s.downcase == 'other'
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<bond>`'s `order` attribute SHOULD be one of the XSD
8
+ # orderType enum values: S/1/D/2/T/3/A/unknown/other.
9
+ # Warning severity — CML permits extension values via
10
+ # convention-specific dicts.
11
+ class BondOrderShouldBeInEnum < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = "A `<bond>`'s `order` attribute SHOULD be one of the XSD orderType enum values: S/1/D/2/T/3/A/unknown/other. Warning severity — CML permits extension values via"
13
+ include Chemicalml::Cml::Enums
14
+
15
+ applies_to Chemicalml::Cml::Role::Bond
16
+
17
+ def check_node(node, _path)
18
+ order = node.order.to_s
19
+ return [] if order.empty?
20
+ return [] if ORDER_VALUES.include?(order)
21
+
22
+ [violation(path: yield_path(node),
23
+ message: "bond #{node.id.inspect} order #{order.inspect} should be one of " \
24
+ "#{ORDER_VALUES.to_a.sort.inspect}",
25
+ severity: :warning,
26
+ value: order)]
27
+ end
28
+
29
+ private
30
+
31
+ def yield_path(node)
32
+ id = node.node_id
33
+ id ? "bond[#{id}]" : 'bond'
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -5,11 +5,11 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class BondOrderShouldNotBeNumeric < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = "A <bond>'s order attribute SHOULD be one of S/D/T/Q/A or 'other' (numeric values are not recommended)."
8
9
  applies_to Chemicalml::Cml::Role::Bond
9
10
  VALID_ORDERS = %w[S D T Q A other].freeze
10
11
 
11
12
  def check_node(node, path)
12
-
13
13
  order = node.order.to_s
14
14
  return [] if order.empty?
15
15
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<bondStereo>` with `atomRefs4` (used by C/T stereo)
8
+ # SHOULD reference four distinct atom ids. Duplicate ids make
9
+ # the stereo descriptor meaningless. Warning severity.
10
+ class BondStereoAtomRefs4ShouldBeDistinct < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'A `<bondStereo>` with `atomRefs4` (used by C/T stereo) SHOULD reference four distinct atom ids. Duplicate ids make the stereo descriptor meaningless. Warning severity.'
12
+ applies_to Chemicalml::Cml::Role::BondStereo
13
+
14
+ def check_node(node, _path)
15
+ ids = parse_refs(node.atom_refs4)
16
+ return [] if ids.size < 2
17
+ return [] if ids.uniq.size == ids.size
18
+
19
+ duplicates = ids.group_by { |i| i }.select { |_, v| v.size > 1 }.keys
20
+ [violation(path: yield_path(node),
21
+ message: "bondStereo #{node.id.inspect} atomRefs4 should reference four distinct atoms; " \
22
+ "duplicate ids: #{duplicates.inspect}",
23
+ severity: :warning,
24
+ value: node.atom_refs4)]
25
+ end
26
+
27
+ private
28
+
29
+ def yield_path(node)
30
+ id = node.node_id
31
+ id ? "bondStereo[#{id}]" : 'bondStereo'
32
+ end
33
+
34
+ def parse_refs(value)
35
+ return [] if value.nil?
36
+
37
+ value.to_s.split(/\s+/).reject(&:empty?)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # Molecular convention: `<bondStereo>` value C (cis) or T
8
8
  # (trans) MUST have `atomRefs4` and MUST NOT have `atomRefs2`.
9
9
  class BondStereoCisTransMustHaveAtomRefs4 < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: `<bondStereo>` value C (cis) or T (trans) MUST have `atomRefs4` and MUST NOT have `atomRefs2`.'
10
11
  applies_to Chemicalml::Cml::Role::BondStereo
11
12
  CIS_TRANS_VALUES = %w[C T].freeze
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  value = node.content.to_s.upcase
16
16
  return [] unless CIS_TRANS_VALUES.include?(value)
17
17
 
@@ -8,6 +8,7 @@ module Chemicalml
8
8
  # MUST have a `dictRef` pointing to the convention that
9
9
  # defines the stereo semantics.
10
10
  class BondStereoOtherMustHaveDictRef < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'Molecular convention: `<bondStereo>` with value `other` MUST have a `dictRef` pointing to the convention that defines the stereo semantics.'
11
12
  applies_to Chemicalml::Cml::Role::BondStereo
12
13
  def check_node(node, path)
13
14
  return [] unless node.content.to_s.downcase == 'other'