chemicalml 0.2.0 → 0.3.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 (450) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +7 -1
  3. data/.rubocop.yml +23 -0
  4. data/CLAUDE.md +95 -40
  5. data/README.adoc +154 -26
  6. data/TODO.align/36-molecular-convention-coverage.md +46 -0
  7. data/TODO.align/37-compchem-convention-coverage.md +55 -0
  8. data/TODO.align/38-dictionary-convention-coverage.md +34 -0
  9. data/TODO.align/39-unit-dictionary-convention-coverage.md +33 -0
  10. data/TODO.align/40-unit-type-dictionary-convention-coverage.md +28 -0
  11. data/TODO.align/41-expand-canonical-model-layer.md +43 -0
  12. data/TODO.align/42-translator-wire-new-models.md +31 -0
  13. data/TODO.align/43-dictionary-model-no-to-h.md +39 -0
  14. data/TODO.align/44-schema3-only-and-schema24-module.md +44 -0
  15. data/TODO.align/45-documentation-and-final-verification.md +23 -0
  16. data/TODO.align/46-lattice-vector-content.md +24 -0
  17. data/TODO.align/47-molecule-crystal-spectra-property-lists.md +23 -0
  18. data/TODO.align/48-model-list-and-module-lists.md +21 -0
  19. data/TODO.align/49-more-chemistry-models.md +34 -0
  20. data/TODO.align/50-validation-report.md +29 -0
  21. data/TODO.align/51-convention-auto-detection.md +30 -0
  22. data/TODO.align/52-schema24-legacy-elements.md +28 -0
  23. data/TODO.align/53-round-trip-integration-specs.md +30 -0
  24. data/TODO.align/54-document-versioned-parser-shim.md +38 -0
  25. data/TODO.align/55-molecule-missing-attributes.md +20 -0
  26. data/TODO.align/56-geometry-models.md +24 -0
  27. data/TODO.align/57-compchem-models.md +26 -0
  28. data/TODO.align/58-remaining-models.md +24 -0
  29. data/TODO.align/59-wire-new-models-translator.md +18 -0
  30. data/TODO.align/60-docs-and-final-verification.md +19 -0
  31. data/TODO.align/61-parallel-array-attrs.md +54 -0
  32. data/TODO.align/62-per-element-attribute-gaps.md +49 -0
  33. data/TODO.align/63-universal-children-mixin.md +42 -0
  34. data/TODO.align/64-per-element-child-gaps.md +81 -0
  35. data/TODO.align/65-simple-unit-convention.md +40 -0
  36. data/TODO.align/66-schema24-legacy-children.md +32 -0
  37. data/TODO.align/67-final-verification.md +32 -0
  38. data/TODO.align/68-detection-coverage.md +33 -0
  39. data/TODO.align/69-json-yaml-roundtrip.md +27 -0
  40. data/TODO.align/70-iterative-walker.md +40 -0
  41. data/TODO.align/71-violation-value.md +25 -0
  42. data/TODO.align/72-close-child-gaps.md +66 -0
  43. data/TODO.align/73-schema24-parse-limitation.md +64 -0
  44. data/TODO.align/74-final-verification-round18.md +15 -0
  45. data/TODO.align/75-key-value-mappings.md +43 -0
  46. data/TODO.align/76-schema24-workaround.md +57 -0
  47. data/TODO.align/77-convention-coverage-matrix.md +31 -0
  48. data/TODO.align/78-fixture-audit.md +23 -0
  49. data/TODO.align/79-final-verification-round19.md +15 -0
  50. data/TODO.align/80-xsd-enum-constants.md +35 -0
  51. data/TODO.align/81-enum-validation-constraints.md +29 -0
  52. data/TODO.align/82-reference-resolver.md +33 -0
  53. data/TODO.align/83-toml-roundtrip.md +20 -0
  54. data/TODO.align/84-final-verification-round21.md +16 -0
  55. data/TODO.align/85-bond-distinct-atoms.md +25 -0
  56. data/TODO.align/86-references-should-resolve.md +20 -0
  57. data/TODO.align/87-cli-utility.md +26 -0
  58. data/TODO.align/88-diverse-fixtures.md +26 -0
  59. data/TODO.align/89-final-verification-round22.md +15 -0
  60. data/TODO.align/90-bond-stereo-completeness.md +31 -0
  61. data/TODO.align/91-atomparity-distinct.md +21 -0
  62. data/TODO.align/92-unit-dictionary-completeness.md +28 -0
  63. data/TODO.align/93-yard-docs.md +23 -0
  64. data/TODO.align/94-final-verification-round23.md +15 -0
  65. data/TODO.align/README.round17.md +28 -0
  66. data/TODO.align/README.round18.md +37 -0
  67. data/TODO.align/README.round19.md +40 -0
  68. data/TODO.align/README.round20.md +49 -0
  69. data/TODO.align/README.round21.md +35 -0
  70. data/TODO.align/README.round22.md +48 -0
  71. data/TODO.align/README.round23.md +46 -0
  72. data/TODO.align/README.round24.md +53 -0
  73. data/TODO.align/README.round25.md +66 -0
  74. data/TODO.align/README.round26.md +55 -0
  75. data/TODO.align/README.round27.md +51 -0
  76. data/TODO.align/README.round28.md +61 -0
  77. data/TODO.align/README.round29.md +64 -0
  78. data/TODO.align/README.round30.md +50 -0
  79. data/TODO.align/README.round31.md +54 -0
  80. data/TODO.align/README.round32.md +67 -0
  81. data/TODO.align/README.round33.md +62 -0
  82. data/TODO.align/README.round34.md +70 -0
  83. data/docs/constraints.md +161 -0
  84. data/docs/generate_constraint_docs.rb +44 -0
  85. data/exe/chemicalml +6 -0
  86. data/lib/chemicalml/cli/base.rb +68 -0
  87. data/lib/chemicalml/cli/command.rb +43 -0
  88. data/lib/chemicalml/cli/constraints_command.rb +27 -0
  89. data/lib/chemicalml/cli/conventions_command.rb +14 -0
  90. data/lib/chemicalml/cli/dictionaries_command.rb +14 -0
  91. data/lib/chemicalml/cli/elements_command.rb +17 -0
  92. data/lib/chemicalml/cli/enums_command.rb +17 -0
  93. data/lib/chemicalml/cli/info_command.rb +77 -0
  94. data/lib/chemicalml/cli/inspect_command.rb +39 -0
  95. data/lib/chemicalml/cli/validate_command.rb +54 -0
  96. data/lib/chemicalml/cli.rb +43 -0
  97. data/lib/chemicalml/cml/base/abundance.rb +21 -5
  98. data/lib/chemicalml/cml/base/action.rb +44 -6
  99. data/lib/chemicalml/cml/base/action_list.rb +44 -8
  100. data/lib/chemicalml/cml/base/alternative.rb +37 -0
  101. data/lib/chemicalml/cml/base/amount.rb +15 -5
  102. data/lib/chemicalml/cml/base/angle.rb +34 -7
  103. data/lib/chemicalml/cml/base/annotation.rb +38 -0
  104. data/lib/chemicalml/cml/base/any_cml.rb +12 -5
  105. data/lib/chemicalml/cml/base/appinfo.rb +40 -0
  106. data/lib/chemicalml/cml/base/arg.rb +80 -0
  107. data/lib/chemicalml/cml/base/array.rb +51 -8
  108. data/lib/chemicalml/cml/base/array_list.rb +15 -5
  109. data/lib/chemicalml/cml/base/atom.rb +93 -19
  110. data/lib/chemicalml/cml/base/atom_array.rb +77 -2
  111. data/lib/chemicalml/cml/base/atom_parity.rb +18 -2
  112. data/lib/chemicalml/cml/base/atom_set.rb +15 -5
  113. data/lib/chemicalml/cml/base/atom_type.rb +27 -7
  114. data/lib/chemicalml/cml/base/atom_type_list.rb +23 -7
  115. data/lib/chemicalml/cml/base/atomic_basis_function.rb +35 -11
  116. data/lib/chemicalml/cml/base/band.rb +22 -7
  117. data/lib/chemicalml/cml/base/band_list.rb +15 -7
  118. data/lib/chemicalml/cml/base/basis_set.rb +26 -7
  119. data/lib/chemicalml/cml/base/bond.rb +39 -7
  120. data/lib/chemicalml/cml/base/bond_array.rb +41 -2
  121. data/lib/chemicalml/cml/base/bond_set.rb +15 -5
  122. data/lib/chemicalml/cml/base/bond_stereo.rb +25 -4
  123. data/lib/chemicalml/cml/base/bond_type.rb +24 -7
  124. data/lib/chemicalml/cml/base/bond_type_list.rb +23 -7
  125. data/lib/chemicalml/cml/base/cell_parameter.rb +30 -8
  126. data/lib/chemicalml/cml/base/cml_module.rb +36 -11
  127. data/lib/chemicalml/cml/base/common_children.rb +35 -0
  128. data/lib/chemicalml/cml/base/complex_object.rb +37 -0
  129. data/lib/chemicalml/cml/base/condition_list.rb +28 -8
  130. data/lib/chemicalml/cml/base/crystal.rb +27 -9
  131. data/lib/chemicalml/cml/base/definition.rb +12 -5
  132. data/lib/chemicalml/cml/base/description.rb +15 -5
  133. data/lib/chemicalml/cml/base/dictionary.rb +30 -7
  134. data/lib/chemicalml/cml/base/dictionary_entry.rb +67 -8
  135. data/lib/chemicalml/cml/base/dimension.rb +31 -7
  136. data/lib/chemicalml/cml/base/document.rb +18 -8
  137. data/lib/chemicalml/cml/base/documentation.rb +12 -5
  138. data/lib/chemicalml/cml/base/eigen.rb +33 -7
  139. data/lib/chemicalml/cml/base/electron.rb +29 -9
  140. data/lib/chemicalml/cml/base/enumeration.rb +41 -0
  141. data/lib/chemicalml/cml/base/expression.rb +47 -0
  142. data/lib/chemicalml/cml/base/float.rb +52 -0
  143. data/lib/chemicalml/cml/base/float_array.rb +55 -0
  144. data/lib/chemicalml/cml/base/formula.rb +31 -10
  145. data/lib/chemicalml/cml/base/fragment.rb +33 -9
  146. data/lib/chemicalml/cml/base/fragment_list.rb +26 -7
  147. data/lib/chemicalml/cml/base/gradient.rb +19 -6
  148. data/lib/chemicalml/cml/base/identifier.rb +22 -4
  149. data/lib/chemicalml/cml/base/integer.rb +52 -0
  150. data/lib/chemicalml/cml/base/integer_array.rb +55 -0
  151. data/lib/chemicalml/cml/base/isotope.rb +29 -8
  152. data/lib/chemicalml/cml/base/isotope_list.rb +18 -7
  153. data/lib/chemicalml/cml/base/join.rb +28 -6
  154. data/lib/chemicalml/cml/base/kpoint.rb +17 -6
  155. data/lib/chemicalml/cml/base/kpoint_list.rb +15 -7
  156. data/lib/chemicalml/cml/base/label.rb +15 -5
  157. data/lib/chemicalml/cml/base/lattice.rb +36 -9
  158. data/lib/chemicalml/cml/base/lattice_vector.rb +17 -6
  159. data/lib/chemicalml/cml/base/length.rb +31 -7
  160. data/lib/chemicalml/cml/base/line3.rb +21 -5
  161. data/lib/chemicalml/cml/base/link.rb +51 -10
  162. data/lib/chemicalml/cml/base/list.rb +23 -9
  163. data/lib/chemicalml/cml/base/map.rb +38 -5
  164. data/lib/chemicalml/cml/base/matrix.rb +38 -9
  165. data/lib/chemicalml/cml/base/mechanism.rb +20 -7
  166. data/lib/chemicalml/cml/base/mechanism_component.rb +14 -6
  167. data/lib/chemicalml/cml/base/metadata.rb +20 -6
  168. data/lib/chemicalml/cml/base/metadata_list.rb +30 -5
  169. data/lib/chemicalml/cml/base/molecule.rb +95 -21
  170. data/lib/chemicalml/cml/base/molecule_list.rb +27 -7
  171. data/lib/chemicalml/cml/base/name.rb +11 -3
  172. data/lib/chemicalml/cml/base/object.rb +20 -6
  173. data/lib/chemicalml/cml/base/observation.rb +18 -5
  174. data/lib/chemicalml/cml/base/operator.rb +40 -0
  175. data/lib/chemicalml/cml/base/parameter.rb +38 -8
  176. data/lib/chemicalml/cml/base/parameter_list.rb +30 -5
  177. data/lib/chemicalml/cml/base/particle.rb +23 -6
  178. data/lib/chemicalml/cml/base/peak.rb +86 -11
  179. data/lib/chemicalml/cml/base/peak_group.rb +81 -7
  180. data/lib/chemicalml/cml/base/peak_list.rb +25 -8
  181. data/lib/chemicalml/cml/base/peak_structure.rb +47 -6
  182. data/lib/chemicalml/cml/base/plane3.rb +15 -5
  183. data/lib/chemicalml/cml/base/point3.rb +15 -5
  184. data/lib/chemicalml/cml/base/potential.rb +17 -6
  185. data/lib/chemicalml/cml/base/potential_form.rb +31 -6
  186. data/lib/chemicalml/cml/base/potential_list.rb +15 -7
  187. data/lib/chemicalml/cml/base/product.rb +53 -2
  188. data/lib/chemicalml/cml/base/product_list.rb +36 -2
  189. data/lib/chemicalml/cml/base/property.rb +32 -8
  190. data/lib/chemicalml/cml/base/property_list.rb +26 -5
  191. data/lib/chemicalml/cml/base/reactant.rb +53 -2
  192. data/lib/chemicalml/cml/base/reactant_list.rb +36 -2
  193. data/lib/chemicalml/cml/base/reaction.rb +82 -12
  194. data/lib/chemicalml/cml/base/reaction_list.rb +29 -2
  195. data/lib/chemicalml/cml/base/reaction_scheme.rb +48 -8
  196. data/lib/chemicalml/cml/base/reaction_step.rb +33 -9
  197. data/lib/chemicalml/cml/base/reaction_step_list.rb +34 -7
  198. data/lib/chemicalml/cml/base/reactive_centre.rb +31 -6
  199. data/lib/chemicalml/cml/base/region.rb +24 -5
  200. data/lib/chemicalml/cml/base/related_entry.rb +34 -0
  201. data/lib/chemicalml/cml/base/sample.rb +32 -5
  202. data/lib/chemicalml/cml/base/scalar.rb +41 -6
  203. data/lib/chemicalml/cml/base/spectator.rb +26 -5
  204. data/lib/chemicalml/cml/base/spectator_list.rb +20 -7
  205. data/lib/chemicalml/cml/base/spectrum.rb +63 -12
  206. data/lib/chemicalml/cml/base/spectrum_data.rb +20 -8
  207. data/lib/chemicalml/cml/base/spectrum_list.rb +30 -7
  208. data/lib/chemicalml/cml/base/sphere3.rb +15 -5
  209. data/lib/chemicalml/cml/base/stmml.rb +12 -5
  210. data/lib/chemicalml/cml/base/string.rb +40 -0
  211. data/lib/chemicalml/cml/base/string_array.rb +52 -0
  212. data/lib/chemicalml/cml/base/substance.rb +42 -8
  213. data/lib/chemicalml/cml/base/substance_list.rb +32 -7
  214. data/lib/chemicalml/cml/base/symmetry.rb +33 -7
  215. data/lib/chemicalml/cml/base/system.rb +23 -8
  216. data/lib/chemicalml/cml/base/table.rb +43 -8
  217. data/lib/chemicalml/cml/base/table_cell.rb +12 -5
  218. data/lib/chemicalml/cml/base/table_content.rb +20 -8
  219. data/lib/chemicalml/cml/base/table_header.rb +15 -7
  220. data/lib/chemicalml/cml/base/table_header_cell.rb +27 -5
  221. data/lib/chemicalml/cml/base/table_row.rb +15 -7
  222. data/lib/chemicalml/cml/base/table_row_list.rb +15 -7
  223. data/lib/chemicalml/cml/base/tcell.rb +43 -0
  224. data/lib/chemicalml/cml/base/torsion.rb +34 -7
  225. data/lib/chemicalml/cml/base/transform3.rb +12 -5
  226. data/lib/chemicalml/cml/base/transition_state.rb +23 -5
  227. data/lib/chemicalml/cml/base/trow.rb +48 -0
  228. data/lib/chemicalml/cml/base/unit.rb +45 -10
  229. data/lib/chemicalml/cml/base/unit_list.rb +40 -6
  230. data/lib/chemicalml/cml/base/unit_type.rb +32 -5
  231. data/lib/chemicalml/cml/base/unit_type_list.rb +34 -6
  232. data/lib/chemicalml/cml/base/vector3.rb +15 -5
  233. data/lib/chemicalml/cml/base/xaxis.rb +33 -7
  234. data/lib/chemicalml/cml/base/yaxis.rb +33 -7
  235. data/lib/chemicalml/cml/base/z_matrix.rb +26 -5
  236. data/lib/chemicalml/cml/base.rb +139 -121
  237. data/lib/chemicalml/cml/canonical_comparison.rb +54 -0
  238. data/lib/chemicalml/cml/elements.rb +168 -124
  239. data/lib/chemicalml/cml/enums.rb +55 -0
  240. data/lib/chemicalml/cml/namespace.rb +2 -3
  241. data/lib/chemicalml/cml/patterns.rb +33 -0
  242. data/lib/chemicalml/cml/reference_resolver.rb +137 -0
  243. data/lib/chemicalml/cml/role/alternative.rb +10 -0
  244. data/lib/chemicalml/cml/role/annotation.rb +10 -0
  245. data/lib/chemicalml/cml/role/appinfo.rb +10 -0
  246. data/lib/chemicalml/cml/role/arg.rb +10 -0
  247. data/lib/chemicalml/cml/role/cml_module.rb +2 -3
  248. data/lib/chemicalml/cml/role/complex_object.rb +10 -0
  249. data/lib/chemicalml/cml/role/enumeration.rb +10 -0
  250. data/lib/chemicalml/cml/role/expression.rb +10 -0
  251. data/lib/chemicalml/cml/role/float.rb +10 -0
  252. data/lib/chemicalml/cml/role/float_array.rb +10 -0
  253. data/lib/chemicalml/cml/role/integer.rb +10 -0
  254. data/lib/chemicalml/cml/role/integer_array.rb +10 -0
  255. data/lib/chemicalml/cml/role/operator.rb +10 -0
  256. data/lib/chemicalml/cml/role/related_entry.rb +10 -0
  257. data/lib/chemicalml/cml/role/string.rb +10 -0
  258. data/lib/chemicalml/cml/role/string_array.rb +10 -0
  259. data/lib/chemicalml/cml/role/tcell.rb +10 -0
  260. data/lib/chemicalml/cml/role/trow.rb +10 -0
  261. data/lib/chemicalml/cml/role.rb +138 -121
  262. data/lib/chemicalml/cml/schema24/alternative.rb +14 -0
  263. data/lib/chemicalml/cml/schema24/annotation.rb +15 -0
  264. data/lib/chemicalml/cml/schema24/appinfo.rb +15 -0
  265. data/lib/chemicalml/cml/schema24/arg.rb +14 -0
  266. data/lib/chemicalml/cml/schema24/cml_module.rb +18 -0
  267. data/lib/chemicalml/cml/schema24/complex_object.rb +14 -0
  268. data/lib/chemicalml/cml/schema24/configuration.rb +15 -2
  269. data/lib/chemicalml/cml/schema24/enumeration.rb +16 -0
  270. data/lib/chemicalml/cml/schema24/expression.rb +14 -0
  271. data/lib/chemicalml/cml/schema24/float.rb +14 -0
  272. data/lib/chemicalml/cml/schema24/float_array.rb +14 -0
  273. data/lib/chemicalml/cml/schema24/integer.rb +14 -0
  274. data/lib/chemicalml/cml/schema24/integer_array.rb +14 -0
  275. data/lib/chemicalml/cml/schema24/operator.rb +14 -0
  276. data/lib/chemicalml/cml/schema24/related_entry.rb +14 -0
  277. data/lib/chemicalml/cml/schema24/string.rb +14 -0
  278. data/lib/chemicalml/cml/schema24/string_array.rb +14 -0
  279. data/lib/chemicalml/cml/schema24/tcell.rb +14 -0
  280. data/lib/chemicalml/cml/schema24/trow.rb +14 -0
  281. data/lib/chemicalml/cml/schema24.rb +154 -126
  282. data/lib/chemicalml/cml/schema3.rb +123 -123
  283. data/lib/chemicalml/cml/visitable.rb +77 -3
  284. data/lib/chemicalml/cml/wire_class_registry.rb +3 -3
  285. data/lib/chemicalml/cml.rb +164 -132
  286. data/lib/chemicalml/context_configuration.rb +12 -4
  287. data/lib/chemicalml/convention/base.rb +17 -3
  288. data/lib/chemicalml/convention/cascade/constraints/reaction_scheme_must_have_content.rb +33 -0
  289. data/lib/chemicalml/convention/cascade/constraints/reaction_step_list_must_contain_steps.rb +31 -0
  290. data/lib/chemicalml/convention/cascade/constraints/reaction_step_must_have_reaction_or_lists.rb +33 -0
  291. data/lib/chemicalml/convention/cascade/constraints/reactive_centre_atom_refs_should_be_present.rb +34 -0
  292. data/lib/chemicalml/convention/cascade/constraints.rb +18 -0
  293. data/lib/chemicalml/convention/cascade.rb +43 -0
  294. data/lib/chemicalml/convention/compchem/constraints/array_rules.rb +44 -0
  295. data/lib/chemicalml/convention/compchem/constraints/calculation_requires_finalization.rb +28 -0
  296. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +5 -4
  297. data/lib/chemicalml/convention/compchem/constraints/environment_at_most_one_property_list.rb +26 -0
  298. data/lib/chemicalml/convention/compchem/constraints/environment_must_not_contain_parameter.rb +28 -0
  299. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_molecule.rb +26 -0
  300. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_property_list.rb +26 -0
  301. data/lib/chemicalml/convention/compchem/constraints/finalization_must_have_content.rb +44 -0
  302. data/lib/chemicalml/convention/compchem/constraints/finalization_must_not_contain_parameter.rb +27 -0
  303. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_molecule.rb +26 -0
  304. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_parameter_list.rb +26 -0
  305. data/lib/chemicalml/convention/compchem/constraints/initialization_must_have_content.rb +54 -0
  306. data/lib/chemicalml/convention/compchem/constraints/initialization_must_not_contain_property.rb +28 -0
  307. data/lib/chemicalml/convention/compchem/constraints/job_list_module_must_have_id.rb +25 -0
  308. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_environment.rb +25 -0
  309. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_finalization.rb +25 -0
  310. data/lib/chemicalml/convention/compchem/constraints/job_module_must_have_id.rb +25 -0
  311. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +7 -9
  312. data/lib/chemicalml/convention/compchem/constraints/matrix_rules.rb +48 -0
  313. data/lib/chemicalml/convention/compchem/constraints/module_predicates.rb +50 -0
  314. data/lib/chemicalml/convention/compchem/constraints/scalar_units.rb +42 -0
  315. data/lib/chemicalml/convention/compchem/constraints.rb +40 -2
  316. data/lib/chemicalml/convention/compchem.rb +20 -2
  317. data/lib/chemicalml/convention/constraint.rb +68 -24
  318. data/lib/chemicalml/convention/coordinator.rb +90 -0
  319. data/lib/chemicalml/convention/detection.rb +39 -0
  320. data/lib/chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace.rb +29 -0
  321. data/lib/chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash.rb +32 -0
  322. data/lib/chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern.rb +33 -0
  323. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +4 -4
  324. data/lib/chemicalml/convention/dictionary/constraints/entry_must_contain_definition.rb +29 -0
  325. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +6 -6
  326. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_unit_type.rb +31 -0
  327. data/lib/chemicalml/convention/dictionary/constraints/entry_units_co_constraints.rb +44 -0
  328. data/lib/chemicalml/convention/dictionary/constraints.rb +14 -2
  329. data/lib/chemicalml/convention/dictionary.rb +8 -2
  330. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula.rb +41 -0
  331. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +18 -9
  332. data/lib/chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired.rb +6 -6
  333. data/lib/chemicalml/convention/molecular/constraints/atom_element_type_should_be_in_periodic_table.rb +39 -0
  334. data/lib/chemicalml/convention/molecular/constraints/atom_id_must_match_pattern.rb +7 -6
  335. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +3 -2
  336. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_element_type.rb +4 -4
  337. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_id.rb +5 -4
  338. data/lib/chemicalml/convention/molecular/constraints/atom_parity_atom_refs4_should_be_distinct.rb +44 -0
  339. data/lib/chemicalml/convention/molecular/constraints/atom_parity_should_include_parent_atom.rb +65 -0
  340. data/lib/chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule.rb +37 -0
  341. data/lib/chemicalml/convention/molecular/constraints/bond_atom_refs2_should_be_distinct.rb +44 -0
  342. data/lib/chemicalml/convention/molecular/constraints/bond_id_should_match_pattern.rb +38 -0
  343. data/lib/chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule.rb +44 -0
  344. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2.rb +4 -4
  345. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_order.rb +5 -4
  346. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +4 -3
  347. data/lib/chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref.rb +34 -0
  348. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_be_in_enum.rb +39 -0
  349. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric.rb +3 -3
  350. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_atom_refs4_should_be_distinct.rb +43 -0
  351. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4.rb +45 -0
  352. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref.rb +33 -0
  353. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_should_be_in_enum.rb +38 -0
  354. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2.rb +45 -0
  355. data/lib/chemicalml/convention/molecular/constraints/dict_ref_should_resolve.rb +59 -0
  356. data/lib/chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children.rb +33 -0
  357. data/lib/chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children.rb +32 -0
  358. data/lib/chemicalml/convention/molecular/constraints/molecule_chirality_should_be_in_enum.rb +38 -0
  359. data/lib/chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level.rb +35 -0
  360. data/lib/chemicalml/convention/molecular/constraints/molecule_id_should_match_pattern.rb +38 -0
  361. data/lib/chemicalml/convention/molecular/constraints/molecule_must_have_id.rb +4 -4
  362. data/lib/chemicalml/convention/molecular/constraints/property_must_have_dict_ref.rb +4 -4
  363. data/lib/chemicalml/convention/molecular/constraints/property_scalar_data_type_matches_dictionary.rb +58 -0
  364. data/lib/chemicalml/convention/molecular/constraints/references_should_resolve.rb +37 -0
  365. data/lib/chemicalml/convention/molecular/constraints/scalar_must_have_data_type.rb +4 -4
  366. data/lib/chemicalml/convention/molecular/constraints.rb +59 -13
  367. data/lib/chemicalml/convention/molecular.rb +25 -2
  368. data/lib/chemicalml/convention/registry.rb +95 -10
  369. data/lib/chemicalml/convention/simple_unit/constraints/root_must_be_unit_list.rb +24 -0
  370. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_power.rb +32 -0
  371. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_symbol.rb +31 -0
  372. data/lib/chemicalml/convention/simple_unit/constraints.rb +16 -0
  373. data/lib/chemicalml/convention/simple_unit.rb +37 -0
  374. data/lib/chemicalml/convention/spectroscopy/constraints/peak_list_must_contain_peaks.rb +31 -0
  375. data/lib/chemicalml/convention/spectroscopy/constraints/peak_should_have_values.rb +34 -0
  376. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_content.rb +31 -0
  377. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_convention.rb +32 -0
  378. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_format.rb +31 -0
  379. data/lib/chemicalml/convention/spectroscopy/constraints.rb +20 -0
  380. data/lib/chemicalml/convention/spectroscopy.rb +41 -0
  381. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_contain_at_least_one_unit.rb +29 -0
  382. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_have_namespace.rb +36 -0
  383. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_contain_definition.rb +28 -0
  384. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_id.rb +28 -0
  385. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_multiplier_or_constant_to_si.rb +34 -0
  386. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_parent_si.rb +32 -0
  387. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +4 -4
  388. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_title.rb +32 -0
  389. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_parent_si_should_resolve.rb +45 -0
  390. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_unit_type_should_resolve.rb +46 -0
  391. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +19 -1
  392. data/lib/chemicalml/convention/unit_dictionary.rb +11 -2
  393. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_id_must_match_pattern.rb +31 -0
  394. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_contain_at_least_one_unit_type.rb +28 -0
  395. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_have_namespace.rb +35 -0
  396. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_contain_definition.rb +28 -0
  397. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +6 -6
  398. data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +9 -1
  399. data/lib/chemicalml/convention/unit_type_dictionary.rb +6 -2
  400. data/lib/chemicalml/convention/validation_report.rb +90 -0
  401. data/lib/chemicalml/convention/violation.rb +9 -3
  402. data/lib/chemicalml/convention.rb +49 -11
  403. data/lib/chemicalml/dictionary/entry.rb +6 -6
  404. data/lib/chemicalml/dictionary/enum.rb +1 -1
  405. data/lib/chemicalml/dictionary/link.rb +3 -3
  406. data/lib/chemicalml/dictionary/loader.rb +19 -19
  407. data/lib/chemicalml/dictionary/model.rb +5 -5
  408. data/lib/chemicalml/dictionary/registry.rb +9 -9
  409. data/lib/chemicalml/dictionary.rb +13 -9
  410. data/lib/chemicalml/logger.rb +47 -0
  411. data/lib/chemicalml/schema/definition.rb +2 -2
  412. data/lib/chemicalml/schema.rb +7 -7
  413. data/lib/chemicalml/version.rb +1 -1
  414. data/lib/chemicalml/versioned_parser.rb +28 -10
  415. data/lib/chemicalml.rb +79 -17
  416. data/lib/tasks/reference_fetch.rake +30 -31
  417. data/lib/tasks/scrape_fixtures.rake +23 -24
  418. metadata +235 -35
  419. data/lib/chemicalml/cml/child_mappings.rb +0 -168
  420. data/lib/chemicalml/cml/translator/value_translations.rb +0 -270
  421. data/lib/chemicalml/cml/translator.rb +0 -484
  422. data/lib/chemicalml/model/atom.rb +0 -64
  423. data/lib/chemicalml/model/atom_parity.rb +0 -21
  424. data/lib/chemicalml/model/bond.rb +0 -39
  425. data/lib/chemicalml/model/bond_stereo.rb +0 -29
  426. data/lib/chemicalml/model/cml_array.rb +0 -27
  427. data/lib/chemicalml/model/cml_module.rb +0 -45
  428. data/lib/chemicalml/model/document.rb +0 -29
  429. data/lib/chemicalml/model/formula.rb +0 -34
  430. data/lib/chemicalml/model/identifier.rb +0 -21
  431. data/lib/chemicalml/model/label.rb +0 -21
  432. data/lib/chemicalml/model/matrix.rb +0 -29
  433. data/lib/chemicalml/model/metadata.rb +0 -23
  434. data/lib/chemicalml/model/metadata_list.rb +0 -25
  435. data/lib/chemicalml/model/molecule.rb +0 -45
  436. data/lib/chemicalml/model/name.rb +0 -21
  437. data/lib/chemicalml/model/node.rb +0 -45
  438. data/lib/chemicalml/model/parameter.rb +0 -27
  439. data/lib/chemicalml/model/parameter_list.rb +0 -25
  440. data/lib/chemicalml/model/product.rb +0 -21
  441. data/lib/chemicalml/model/product_list.rb +0 -21
  442. data/lib/chemicalml/model/property.rb +0 -28
  443. data/lib/chemicalml/model/property_list.rb +0 -25
  444. data/lib/chemicalml/model/reactant.rb +0 -21
  445. data/lib/chemicalml/model/reactant_list.rb +0 -21
  446. data/lib/chemicalml/model/reaction.rb +0 -41
  447. data/lib/chemicalml/model/reaction_list.rb +0 -23
  448. data/lib/chemicalml/model/scalar.rb +0 -25
  449. data/lib/chemicalml/model/substance.rb +0 -25
  450. data/lib/chemicalml/model.rb +0 -40
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `<atomArray>` MUST be a child of
8
+ # `<molecule>` or `<formula>`. Any other parent is invalid.
9
+ # Checked from the parent's perspective during the walk —
10
+ # when visiting a non-molecule/non-formula node, look for
11
+ # misplaced atomArray children.
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 —"
14
+ def check_node(node, path)
15
+ return [] if allowed_parent?(node)
16
+
17
+ misplaced = node.wire_children.select { |c| atom_array?(c) }
18
+ misplaced.map do |child|
19
+ violation(
20
+ path: (path + [describe(node), describe(child)]).join('/'),
21
+ message: 'atomArray must be a child of molecule or formula, ' \
22
+ "but appears under #{node.element_name.inspect}"
23
+ )
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def allowed_parent?(node)
30
+ node.is_a?(Chemicalml::Cml::Role::Molecule) ||
31
+ node.is_a?(Chemicalml::Cml::Role::Formula)
32
+ end
33
+
34
+ def atom_array?(node)
35
+ node.is_a?(Chemicalml::Cml::Role::AtomArray)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -4,24 +4,33 @@ 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).'
12
+ applies_to Chemicalml::Cml::Role::Molecule
10
13
  def check_node(node, path)
11
- return [] unless molecule?(node)
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
- [violation(path: path.empty? ? "molecule" : path.join("/"),
18
- message: "atomArray must contain at least one atom")]
19
+ [violation(path: path.empty? ? 'molecule' : path.join('/'),
20
+ message: 'atomArray must contain at least one atom')]
19
21
  end
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,23 +5,23 @@ 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.'
9
+ applies_to Chemicalml::Cml::Role::Atom
8
10
  def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
10
-
11
11
  violations = []
12
12
  has_x2 = !node.x2.to_s.empty?
13
13
  has_y2 = !node.y2.to_s.empty?
14
14
  if has_x2 != has_y2
15
- violations << violation(path: path.empty? ? "atom" : path.join("/"),
16
- message: "x2 and y2 must both be present or both absent")
15
+ violations << violation(path: path.empty? ? 'atom' : path.join('/'),
16
+ message: 'x2 and y2 must both be present or both absent')
17
17
  end
18
18
 
19
19
  has_x3 = !node.x3.to_s.empty?
20
20
  has_y3 = !node.y3.to_s.empty?
21
21
  has_z3 = !node.z3.to_s.empty?
22
22
  if [has_x3, has_y3, has_z3].any? && ![has_x3, has_y3, has_z3].all?
23
- violations << violation(path: path.empty? ? "atom" : path.join("/"),
24
- message: "x3, y3, and z3 must all be present together")
23
+ violations << violation(path: path.empty? ? 'atom' : path.join('/'),
24
+ message: 'x3, y3, and z3 must all be present together')
25
25
  end
26
26
  violations
27
27
  end
@@ -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,19 +5,20 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class AtomIdMustMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
8
- ID_PATTERN = /\A[A-Za-z][A-Za-z0-9._\-]*\z/.freeze
8
+ self.description = "An <atom>'s id attribute SHOULD start with a letter and contain only letters, digits, dot, hyphen, or underscore."
9
+ applies_to Chemicalml::Cml::Role::Atom
10
+ ID_PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/
9
11
 
10
12
  def check_node(node, path)
11
- return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
12
-
13
13
  id = node.id.to_s
14
14
  return [] if id.empty?
15
15
  return [] if id.match?(ID_PATTERN)
16
16
 
17
- [violation(path: path.empty? ? "atom" : path.join("/"),
17
+ [violation(path: path.empty? ? 'atom' : path.join('/'),
18
18
  message: "atom id #{id.inspect} must start with a letter and " \
19
- "contain only letters, digits, dot, hyphen, or underscore",
20
- severity: :warning)]
19
+ 'contain only letters, digits, dot, hyphen, or underscore',
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|
@@ -16,7 +17,7 @@ module Chemicalml
16
17
  duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
17
18
  duplicates.each do |dup|
18
19
  violations << violation(
19
- path: path.empty? ? "molecule[#{node.id}]" : path.join("/"),
20
+ path: path.empty? ? "molecule[#{node.id}]" : path.join('/'),
20
21
  message: "duplicate atom id #{dup.inspect} within molecule #{node.id.inspect}"
21
22
  )
22
23
  end
@@ -33,7 +34,7 @@ module Chemicalml
33
34
  def atom_ids_within(molecule)
34
35
  return [] unless molecule.atom_array
35
36
 
36
- (molecule.atom_array.atoms || []).map { |a| a.id }.compact
37
+ (molecule.atom_array.atoms || []).map(&:id).compact
37
38
  end
38
39
  end
39
40
  end
@@ -5,13 +5,13 @@ 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.'
9
+ applies_to Chemicalml::Cml::Role::Atom
8
10
  def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
10
-
11
11
  return [] unless node.element_type.to_s.empty?
12
12
 
13
- [violation(path: path.empty? ? "atom" : path.join("/"),
14
- message: "atom must have an elementType attribute")]
13
+ [violation(path: path.empty? ? 'atom' : path.join('/'),
14
+ message: 'atom must have an elementType attribute')]
15
15
  end
16
16
  end
17
17
  end
@@ -5,13 +5,14 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class AtomMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
8
- def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
8
+ applies_to Chemicalml::Cml::Role::Atom
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
- [violation(path: path.empty? ? "atom" : path.join("/"),
14
- message: "atom must have an id attribute")]
14
+ [violation(path: path.empty? ? 'atom' : path.join('/'),
15
+ message: 'atom must have an id attribute')]
15
16
  end
16
17
  end
17
18
  end
@@ -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
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `<bondArray>` MUST be a child of
8
+ # `<molecule>`. Any other parent is invalid.
9
+ class BondArrayMustBeChildOfMolecule < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: `<bondArray>` MUST be a child of `<molecule>`. Any other parent is invalid.'
11
+ def check_node(node, path)
12
+ return [] if molecule?(node)
13
+
14
+ misplaced = node.wire_children.select { |c| bond_array?(c) }
15
+ misplaced.map do |child|
16
+ violation(
17
+ path: (path + [describe(node), describe(child)]).join('/'),
18
+ message: 'bondArray must be a child of molecule, ' \
19
+ "but appears under #{node.element_name.inspect}"
20
+ )
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def molecule?(node)
27
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
28
+ end
29
+
30
+ def bond_array?(node)
31
+ node.is_a?(Chemicalml::Cml::Role::BondArray)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -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
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: a `<bond>` `id` MUST be unique within
8
+ # the eldest containing molecule. Mirrors the existing
9
+ # `AtomIdsUniqueWithinMolecule` constraint for bonds.
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.'
12
+ def check(document)
13
+ violations = []
14
+ walk_nodes(document) do |node, path|
15
+ next unless molecule?(node)
16
+
17
+ ids = bond_ids_within(node)
18
+ duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
19
+ duplicates.each do |dup|
20
+ violations << violation(
21
+ path: (path + [describe(node)]).join('/'),
22
+ message: "duplicate bond id #{dup.inspect} within molecule #{node.id.inspect}"
23
+ )
24
+ end
25
+ end
26
+ violations
27
+ end
28
+
29
+ private
30
+
31
+ def molecule?(node)
32
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
33
+ end
34
+
35
+ def bond_ids_within(molecule)
36
+ return [] unless molecule.bond_array
37
+
38
+ (molecule.bond_array.bonds || []).map(&:id).compact
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -5,13 +5,13 @@ 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.'
9
+ applies_to Chemicalml::Cml::Role::Bond
8
10
  def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Bond)
10
-
11
11
  return [] unless node.atom_refs2.to_s.strip.empty?
12
12
 
13
- [violation(path: path.empty? ? "bond" : path.join("/"),
14
- message: "bond must have an atomRefs2 attribute")]
13
+ [violation(path: path.empty? ? 'bond' : path.join('/'),
14
+ message: 'bond must have an atomRefs2 attribute')]
15
15
  end
16
16
  end
17
17
  end
@@ -5,13 +5,14 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class BondMustHaveOrder < Chemicalml::Convention::Constraint::NodeConstraint
8
- def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Bond)
8
+ applies_to Chemicalml::Cml::Role::Bond
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
- [violation(path: path.empty? ? "bond" : path.join("/"),
14
- message: "bond must have an order attribute")]
14
+ [violation(path: path.empty? ? 'bond' : path.join('/'),
15
+ message: 'bond must have an order attribute')]
15
16
  end
16
17
  end
17
18
  end
@@ -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|
@@ -18,7 +19,7 @@ module Chemicalml
18
19
  next if missing.empty?
19
20
 
20
21
  violations << violation(
21
- path: path.empty? ? "molecule[#{node.id}]" : path.join("/"),
22
+ path: path.empty? ? "molecule[#{node.id}]" : path.join('/'),
22
23
  message: "bond #{bond.id.inspect} references atoms not in the same molecule: #{missing.inspect}"
23
24
  )
24
25
  end
@@ -35,7 +36,7 @@ module Chemicalml
35
36
  def atom_ids_within(molecule)
36
37
  return [] unless molecule.atom_array
37
38
 
38
- (molecule.atom_array.atoms || []).map { |a| a.id }
39
+ (molecule.atom_array.atoms || []).map(&:id)
39
40
  end
40
41
 
41
42
  def bonds_within(molecule)
@@ -45,7 +46,7 @@ module Chemicalml
45
46
  end
46
47
 
47
48
  def atom_refs_of(bond)
48
- bond.atom_refs2.to_s.split(" ")
49
+ bond.atom_refs2.to_s.split
49
50
  end
50
51
  end
51
52
  end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `<bond>` with `order="other"` MUST
8
+ # have a `dictRef` pointing to the dictionary that defines
9
+ # the order semantics. The warning-level "should not be
10
+ # numeric" rule is separate; this is the hard error.
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'
13
+ applies_to Chemicalml::Cml::Role::Bond
14
+ def check_node(node, path)
15
+ return [] unless node.order.to_s.downcase == 'other'
16
+ return [] unless node.dict_ref.to_s.empty?
17
+
18
+ [violation(
19
+ path: (path + [describe(node)]).join('/'),
20
+ message: "bond order 'other' must have a dictRef " \
21
+ 'identifying the dictionary entry that defines it'
22
+ )]
23
+ end
24
+
25
+ private
26
+
27
+ def bond?(node)
28
+ node.is_a?(Chemicalml::Cml::Role::Bond)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end