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,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,17 +5,17 @@ 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)."
9
+ applies_to Chemicalml::Cml::Role::Bond
8
10
  VALID_ORDERS = %w[S D T Q A other].freeze
9
11
 
10
12
  def check_node(node, path)
11
- return [] unless node.is_a?(Chemicalml::Cml::Role::Bond)
12
-
13
13
  order = node.order.to_s
14
14
  return [] if order.empty?
15
15
 
16
16
  return [] if VALID_ORDERS.include?(order) || node.dict_ref.to_s.size.positive?
17
17
 
18
- [violation(path: path.empty? ? "bond" : path.join("/"),
18
+ [violation(path: path.empty? ? 'bond' : path.join('/'),
19
19
  message: "bond order #{order.inspect} is not recommended " \
20
20
  "(use S/D/T/Q/A or 'other' with dictRef)",
21
21
  severity: :warning)]
@@ -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
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `<bondStereo>` value C (cis) or T
8
+ # (trans) MUST have `atomRefs4` and MUST NOT have `atomRefs2`.
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`.'
11
+ applies_to Chemicalml::Cml::Role::BondStereo
12
+ CIS_TRANS_VALUES = %w[C T].freeze
13
+
14
+ def check_node(node, path)
15
+ value = node.content.to_s.upcase
16
+ return [] unless CIS_TRANS_VALUES.include?(value)
17
+
18
+ violations = []
19
+ if node.atom_refs4.to_s.strip.empty?
20
+ violations << violation(
21
+ path: (path + [describe(node)]).join('/'),
22
+ message: "bondStereo value #{value.inspect} must have atomRefs4 " \
23
+ '(four atom ids; two must match the parent bond)'
24
+ )
25
+ end
26
+ unless node.atom_refs2.to_s.strip.empty?
27
+ violations << violation(
28
+ path: (path + [describe(node)]).join('/'),
29
+ message: "bondStereo value #{value.inspect} must not have atomRefs2 " \
30
+ '(only W/H wedge/hatch values use atomRefs2)'
31
+ )
32
+ end
33
+ violations
34
+ end
35
+
36
+ private
37
+
38
+ def bond_stereo?(node)
39
+ node.is_a?(Chemicalml::Cml::Role::BondStereo)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `<bondStereo>` with value `other`
8
+ # MUST have a `dictRef` pointing to the convention that
9
+ # defines the stereo semantics.
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.'
12
+ applies_to Chemicalml::Cml::Role::BondStereo
13
+ def check_node(node, path)
14
+ return [] unless node.content.to_s.downcase == 'other'
15
+ return [] unless node.dict_ref.to_s.empty?
16
+
17
+ [violation(
18
+ path: (path + [describe(node)]).join('/'),
19
+ message: "bondStereo value 'other' must have a dictRef " \
20
+ 'identifying the convention that defines it'
21
+ )]
22
+ end
23
+
24
+ private
25
+
26
+ def bond_stereo?(node)
27
+ node.is_a?(Chemicalml::Cml::Role::BondStereo)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ 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 `<bondStereo>`'s `value` attribute SHOULD be one of the
8
+ # XSD stereoType enum values: C/T/W/H/undefined/other.
9
+ # Warning severity.
10
+ class BondStereoShouldBeInEnum < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<bondStereo>`'s `value` attribute SHOULD be one of the XSD stereoType enum values: C/T/W/H/undefined/other. Warning severity."
12
+ include Chemicalml::Cml::Enums
13
+
14
+ applies_to Chemicalml::Cml::Role::BondStereo
15
+
16
+ def check_node(node, _path)
17
+ value = node.content.to_s
18
+ return [] if value.empty?
19
+ return [] if STEREO_VALUES.include?(value)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "bondStereo #{node.id.inspect} value #{value.inspect} should be one of " \
23
+ "#{STEREO_VALUES.to_a.sort.inspect}",
24
+ severity: :warning,
25
+ value: value)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "bondStereo[#{id}]" : 'bondStereo'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `<bondStereo>` value W (wedge) or H
8
+ # (hatch) MUST have `atomRefs2` and MUST NOT have `atomRefs4`.
9
+ class BondStereoWedgeHashMustHaveAtomRefs2 < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: `<bondStereo>` value W (wedge) or H (hatch) MUST have `atomRefs2` and MUST NOT have `atomRefs4`.'
11
+ applies_to Chemicalml::Cml::Role::BondStereo
12
+ WEDGE_HASH_VALUES = %w[W H].freeze
13
+
14
+ def check_node(node, path)
15
+ value = node.content.to_s.upcase
16
+ return [] unless WEDGE_HASH_VALUES.include?(value)
17
+
18
+ violations = []
19
+ if node.atom_refs2.to_s.strip.empty?
20
+ violations << violation(
21
+ path: (path + [describe(node)]).join('/'),
22
+ message: "bondStereo value #{value.inspect} must have atomRefs2 " \
23
+ '(first atom = sharp end, second = blunt end)'
24
+ )
25
+ end
26
+ unless node.atom_refs4.to_s.strip.empty?
27
+ violations << violation(
28
+ path: (path + [describe(node)]).join('/'),
29
+ message: "bondStereo value #{value.inspect} must not have atomRefs4 " \
30
+ '(only C/T cis-trans values use atomRefs4)'
31
+ )
32
+ end
33
+ violations
34
+ end
35
+
36
+ private
37
+
38
+ def bond_stereo?(node)
39
+ node.is_a?(Chemicalml::Cml::Role::BondStereo)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Walks the document and warns on any element whose `dictRef`
8
+ # attribute cannot be resolved against the built-in
9
+ # dictionaries. Catches typos like `dictRef="cml:bpingpoint"`.
10
+ #
11
+ # DocumentConstraint — needs the whole tree, but the resolver
12
+ # works per-node so it's just a wrapper around the per-node
13
+ # check.
14
+ class DictRefShouldResolve < Chemicalml::Convention::Constraint::NodeConstraint
15
+ self.description = 'Walks the document and warns on any element whose `dictRef` attribute cannot be resolved against the built-in dictionaries. Catches typos like `dictRef="cml:bpingpoint"`.'
16
+ applies_to Chemicalml::Cml::Role::Atom,
17
+ Chemicalml::Cml::Role::Bond,
18
+ Chemicalml::Cml::Role::Molecule,
19
+ Chemicalml::Cml::Role::Property,
20
+ Chemicalml::Cml::Role::Scalar,
21
+ Chemicalml::Cml::Role::Array,
22
+ Chemicalml::Cml::Role::Matrix,
23
+ Chemicalml::Cml::Role::Name,
24
+ Chemicalml::Cml::Role::Label
25
+
26
+ def check_node(node, path)
27
+ dict_ref = node.dict_ref.to_s.strip
28
+ return [] if dict_ref.empty?
29
+ return [] if resolves?(dict_ref)
30
+
31
+ [violation(path: path_fingerprint(node, path),
32
+ message: "#{node.element_name} #{node_id_or_blank(node).inspect} " \
33
+ "dictRef #{dict_ref.inspect} does not resolve against any built-in dictionary",
34
+ severity: :warning,
35
+ value: dict_ref)]
36
+ end
37
+
38
+ private
39
+
40
+ def resolves?(dict_ref)
41
+ !Chemicalml::Dictionary::Registry.lookup(dict_ref).nil?
42
+ rescue StandardError
43
+ false
44
+ end
45
+
46
+ def path_fingerprint(node, path)
47
+ base = path.empty? ? node.element_name : path.join('/')
48
+ id = node.node_id
49
+ id ? "#{base}[#{id}]" : base
50
+ end
51
+
52
+ def node_id_or_blank(node)
53
+ node.node_id || ''
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: a `<molecule>` MAY hold an `atomArray`
8
+ # OR child `<molecule>` elements, but not both. The two are
9
+ # mutually exclusive ways of describing composition.
10
+ class MoleculeAtomArrayMutuallyExclusiveWithChildren < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'Molecular convention: a `<molecule>` MAY hold an `atomArray` OR child `<molecule>` elements, but not both. The two are mutually exclusive ways of describing composition.'
12
+ applies_to Chemicalml::Cml::Role::Molecule
13
+ def check_node(node, path)
14
+ return [] if node.atom_array.nil?
15
+ return [] if (node.molecules || []).empty?
16
+
17
+ [violation(
18
+ path: (path + [describe(node)]).join('/'),
19
+ message: "molecule #{node.id.inspect} has both an atomArray " \
20
+ 'and child molecule elements — these are mutually exclusive'
21
+ )]
22
+ end
23
+
24
+ private
25
+
26
+ def molecule?(node)
27
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: a `<molecule>` MAY hold a `bondArray`
8
+ # OR child `<molecule>` elements, but not both.
9
+ class MoleculeBondArrayMutuallyExclusiveWithChildren < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Molecular convention: a `<molecule>` MAY hold a `bondArray` OR child `<molecule>` elements, but not both.'
11
+ applies_to Chemicalml::Cml::Role::Molecule
12
+ def check_node(node, path)
13
+ return [] if node.bond_array.nil?
14
+ return [] if (node.molecules || []).empty?
15
+
16
+ [violation(
17
+ path: (path + [describe(node)]).join('/'),
18
+ message: "molecule #{node.id.inspect} has both a bondArray " \
19
+ 'and child molecule elements — these are mutually exclusive'
20
+ )]
21
+ end
22
+
23
+ private
24
+
25
+ def molecule?(node)
26
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ 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 `<molecule>`'s `chirality` attribute SHOULD be one of the
8
+ # XSD chiralityType enum values:
9
+ # enantiomer/racemate/unknown/other. Warning severity.
10
+ class MoleculeChiralityShouldBeInEnum < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<molecule>`'s `chirality` attribute SHOULD be one of the XSD chiralityType enum values: enantiomer/racemate/unknown/other. Warning severity."
12
+ include Chemicalml::Cml::Enums
13
+
14
+ applies_to Chemicalml::Cml::Role::Molecule
15
+
16
+ def check_node(node, _path)
17
+ chirality = node.chirality.to_s
18
+ return [] if chirality.empty?
19
+ return [] if CHIRALITY_VALUES.include?(chirality)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "molecule #{node.id.inspect} chirality #{chirality.inspect} should be one of " \
23
+ "#{CHIRALITY_VALUES.to_a.sort.inspect}",
24
+ severity: :warning,
25
+ value: chirality)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "molecule[#{id}]" : 'molecule'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Molecular convention: `count` MUST NOT appear on top-level
8
+ # molecules (direct children of `<cml>`). It is REQUIRED on
9
+ # molecules nested inside another `<molecule>`.
10
+ class MoleculeCountMustNotAppearOnTopLevel < Chemicalml::Convention::Constraint::DocumentConstraint
11
+ self.description = 'Molecular convention: `count` MUST NOT appear on top-level molecules (direct children of `<cml>`). It is REQUIRED on molecules nested inside another `<molecule>`.'
12
+ def check(document)
13
+ violations = []
14
+ top_level_molecules(document).each do |mol, path|
15
+ next if mol.count.to_s.empty?
16
+
17
+ violations << violation(
18
+ path: (path + [describe(mol)]).join('/'),
19
+ message: "top-level molecule #{mol.id.inspect} must not have " \
20
+ 'a count attribute (only nested molecules may carry count)'
21
+ )
22
+ end
23
+ violations
24
+ end
25
+
26
+ private
27
+
28
+ def top_level_molecules(document)
29
+ (document.molecules || []).map { |m| [m, []] }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ 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 `<molecule>`'s `id` attribute SHOULD match the XSD
8
+ # moleculeIDType pattern (letter/underscore start, alphanumeric
9
+ # body, optional namespace prefix). Warning severity.
10
+ class MoleculeIdShouldMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = "A `<molecule>`'s `id` attribute SHOULD match the XSD moleculeIDType pattern (letter/underscore start, alphanumeric body, optional namespace prefix). Warning severity."
12
+ include Chemicalml::Cml::Patterns
13
+
14
+ applies_to Chemicalml::Cml::Role::Molecule
15
+
16
+ def check_node(node, _path)
17
+ id = node.id.to_s
18
+ return [] if id.empty?
19
+ return [] if id.match?(/\A#{MOLECULE_ID_PATTERN.source}\z/)
20
+
21
+ [violation(path: yield_path(node),
22
+ message: "molecule id #{id.inspect} should match the moleculeIDType pattern " \
23
+ '(letter start, alphanumeric body, optional prefix)',
24
+ severity: :warning,
25
+ value: id)]
26
+ end
27
+
28
+ private
29
+
30
+ def yield_path(node)
31
+ id = node.node_id
32
+ id ? "molecule[#{id}]" : 'molecule'
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -5,13 +5,13 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class MoleculeMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <molecule> in a molecular document MUST have an id attribute unique within document scope.'
9
+ applies_to Chemicalml::Cml::Role::Molecule
8
10
  def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Molecule)
10
-
11
11
  return [] unless node.id.to_s.empty?
12
12
 
13
- [violation(path: path.empty? ? "molecule" : path.join("/"),
14
- message: "molecule must have an id attribute")]
13
+ [violation(path: path.empty? ? 'molecule' : path.join('/'),
14
+ message: 'molecule must have an id attribute')]
15
15
  end
16
16
  end
17
17
  end
@@ -5,13 +5,13 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class PropertyMustHaveDictRef < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <property> MUST have a dictRef attribute that identifies the property type via a dictionary entry.'
9
+ applies_to Chemicalml::Cml::Role::Property
8
10
  def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Property)
10
-
11
11
  return [] unless node.dict_ref.to_s.empty?
12
12
 
13
- [violation(path: path.empty? ? "property" : path.join("/"),
14
- message: "property must have a dictRef attribute")]
13
+ [violation(path: path.empty? ? 'property' : path.join('/'),
14
+ message: 'property must have a dictRef attribute')]
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # A `<property>` with a `dictRef` whose dictionary entry declares
8
+ # a `dataType` SHOULD have a child `<scalar>` whose `dataType`
9
+ # attribute matches. Catches inconsistencies like a property
10
+ # declaring `dictRef="cml:bp"` (boiling point, xsd:float) but
11
+ # carrying a `<scalar dataType="xsd:string">`.
12
+ #
13
+ # Warning severity — some dictionaries allow multiple dataTypes.
14
+ class PropertyScalarDataTypeMatchesDictionary < Chemicalml::Convention::Constraint::NodeConstraint
15
+ self.description = 'A `<property>` with a `dictRef` whose dictionary entry declares a `dataType` SHOULD have a child `<scalar>` whose `dataType` attribute matches. Catches inconsistencies like a property'
16
+ applies_to Chemicalml::Cml::Role::Property
17
+
18
+ def check_node(node, _path)
19
+ dict_ref = node.dict_ref.to_s
20
+ return [] if dict_ref.empty?
21
+
22
+ entry = lookup_entry(dict_ref)
23
+ return [] unless entry
24
+
25
+ expected_type = entry.data_type.to_s
26
+ return [] if expected_type.empty?
27
+
28
+ scalar = node.scalar
29
+ return [] unless scalar
30
+
31
+ actual_type = scalar.data_type.to_s
32
+ return [] if actual_type.empty?
33
+ return [] if actual_type == expected_type
34
+
35
+ [violation(path: yield_path(node),
36
+ message: "property #{node.id.inspect} dictRef #{dict_ref.inspect} " \
37
+ "expects dataType #{expected_type.inspect} but scalar has #{actual_type.inspect}",
38
+ severity: :warning,
39
+ value: { expected: expected_type, actual: actual_type }.freeze)]
40
+ end
41
+
42
+ private
43
+
44
+ def lookup_entry(dict_ref)
45
+ Chemicalml::Dictionary::Registry.lookup(dict_ref)
46
+ rescue StandardError
47
+ nil
48
+ end
49
+
50
+ def yield_path(node)
51
+ id = node.node_id
52
+ id ? "property[#{id}]" : 'property'
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Walks the document via `Cml::ReferenceResolver` and reports
8
+ # every bond whose `atomRefs2` references atoms that don't
9
+ # exist in the parent molecule. Warning severity — there are
10
+ # edge cases (inter-molecular bonds in transition states).
11
+ #
12
+ # DocumentConstraint because it needs the whole tree to
13
+ # resolve references.
14
+ class ReferencesShouldResolve < Chemicalml::Convention::Constraint::DocumentConstraint
15
+ self.description = "Walks the document via `Cml::ReferenceResolver` and reports every bond whose `atomRefs2` references atoms that don't exist in the parent molecule. Warning severity — there are"
16
+ def check(document)
17
+ resolver = Chemicalml::Cml::ReferenceResolver.new(document)
18
+ resolver.unresolved_refs.map do |entry|
19
+ violation(path: describe(entry[:node]),
20
+ message: "bond #{entry[:node].id.inspect} #{entry[:attr]} references " \
21
+ "missing atoms: #{entry[:missing].inspect}",
22
+ severity: :warning,
23
+ value: entry[:missing])
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def describe(node)
30
+ id = node.node_id
31
+ id ? "#{node.element_name}[#{id}]" : node.element_name
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -5,13 +5,13 @@ module Chemicalml
5
5
  module Molecular
6
6
  module Constraints
7
7
  class ScalarMustHaveDataType < Chemicalml::Convention::Constraint::NodeConstraint
8
+ self.description = 'A <scalar> child of a <property> MUST have a dataType attribute (e.g. xsd:float, xsd:integer).'
9
+ applies_to Chemicalml::Cml::Role::Scalar
8
10
  def check_node(node, path)
9
- return [] unless node.is_a?(Chemicalml::Cml::Role::Scalar)
10
-
11
11
  return [] unless node.data_type.to_s.empty?
12
12
 
13
- [violation(path: path.empty? ? "scalar" : path.join("/"),
14
- message: "scalar must have a dataType attribute")]
13
+ [violation(path: path.empty? ? 'scalar' : path.join('/'),
14
+ message: 'scalar must have a dataType attribute')]
15
15
  end
16
16
  end
17
17
  end