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
@@ -4,10 +4,48 @@ module Chemicalml
4
4
  module Convention
5
5
  module Compchem
6
6
  module Constraints
7
+ autoload :ModulePredicates,
8
+ 'chemicalml/convention/compchem/constraints/module_predicates'
7
9
  autoload :CompchemModuleMustContainJobList,
8
- "chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list"
10
+ 'chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list'
9
11
  autoload :JobMustContainInitialization,
10
- "chemicalml/convention/compchem/constraints/job_must_contain_initialization"
12
+ 'chemicalml/convention/compchem/constraints/job_must_contain_initialization'
13
+ autoload :JobListModuleMustHaveId,
14
+ 'chemicalml/convention/compchem/constraints/job_list_module_must_have_id'
15
+ autoload :JobModuleMustHaveId,
16
+ 'chemicalml/convention/compchem/constraints/job_module_must_have_id'
17
+ autoload :JobModuleAtMostOneFinalization,
18
+ 'chemicalml/convention/compchem/constraints/job_module_at_most_one_finalization'
19
+ autoload :JobModuleAtMostOneEnvironment,
20
+ 'chemicalml/convention/compchem/constraints/job_module_at_most_one_environment'
21
+ autoload :CalculationRequiresFinalization,
22
+ 'chemicalml/convention/compchem/constraints/calculation_requires_finalization'
23
+ autoload :InitializationAtMostOneMolecule,
24
+ 'chemicalml/convention/compchem/constraints/initialization_at_most_one_molecule'
25
+ autoload :InitializationAtMostOneParameterList,
26
+ 'chemicalml/convention/compchem/constraints/initialization_at_most_one_parameter_list'
27
+ autoload :InitializationMustNotContainProperty,
28
+ 'chemicalml/convention/compchem/constraints/initialization_must_not_contain_property'
29
+ autoload :FinalizationAtMostOneMolecule,
30
+ 'chemicalml/convention/compchem/constraints/finalization_at_most_one_molecule'
31
+ autoload :FinalizationAtMostOnePropertyList,
32
+ 'chemicalml/convention/compchem/constraints/finalization_at_most_one_property_list'
33
+ autoload :FinalizationMustNotContainParameter,
34
+ 'chemicalml/convention/compchem/constraints/finalization_must_not_contain_parameter'
35
+ autoload :EnvironmentAtMostOnePropertyList,
36
+ 'chemicalml/convention/compchem/constraints/environment_at_most_one_property_list'
37
+ autoload :EnvironmentMustNotContainParameter,
38
+ 'chemicalml/convention/compchem/constraints/environment_must_not_contain_parameter'
39
+ autoload :ScalarUnits,
40
+ 'chemicalml/convention/compchem/constraints/scalar_units'
41
+ autoload :ArrayRules,
42
+ 'chemicalml/convention/compchem/constraints/array_rules'
43
+ autoload :MatrixRules,
44
+ 'chemicalml/convention/compchem/constraints/matrix_rules'
45
+ autoload :InitializationMustHaveContent,
46
+ 'chemicalml/convention/compchem/constraints/initialization_must_have_content'
47
+ autoload :FinalizationMustHaveContent,
48
+ 'chemicalml/convention/compchem/constraints/finalization_must_have_content'
11
49
  end
12
50
  end
13
51
  end
@@ -8,9 +8,9 @@ module Chemicalml
8
8
  module Compchem
9
9
  extend Base
10
10
 
11
- autoload :Constraints, "chemicalml/convention/compchem/constraints"
11
+ autoload :Constraints, 'chemicalml/convention/compchem/constraints'
12
12
 
13
- QNAME = "convention:compchem".freeze
13
+ QNAME = 'convention:compchem'
14
14
  NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}compchem".freeze
15
15
 
16
16
  def self.qname
@@ -23,6 +23,24 @@ module Chemicalml
23
23
 
24
24
  register Constraints::CompchemModuleMustContainJobList
25
25
  register Constraints::JobMustContainInitialization
26
+ register Constraints::JobListModuleMustHaveId
27
+ register Constraints::JobModuleMustHaveId
28
+ register Constraints::JobModuleAtMostOneFinalization
29
+ register Constraints::JobModuleAtMostOneEnvironment
30
+ register Constraints::CalculationRequiresFinalization
31
+ register Constraints::InitializationAtMostOneMolecule
32
+ register Constraints::InitializationAtMostOneParameterList
33
+ register Constraints::InitializationMustNotContainProperty
34
+ register Constraints::FinalizationAtMostOneMolecule
35
+ register Constraints::FinalizationAtMostOnePropertyList
36
+ register Constraints::FinalizationMustNotContainParameter
37
+ register Constraints::EnvironmentAtMostOnePropertyList
38
+ register Constraints::EnvironmentMustNotContainParameter
39
+ register Constraints::ScalarUnits
40
+ register Constraints::ArrayRules
41
+ register Constraints::MatrixRules
42
+ register Constraints::InitializationMustHaveContent
43
+ register Constraints::FinalizationMustHaveContent
26
44
  end
27
45
  end
28
46
  end
@@ -3,45 +3,87 @@
3
3
  module Chemicalml
4
4
  module Convention
5
5
  # Abstract base class for a single constraint. Subclasses implement
6
- # `check_node` (one node at a time) or `check_document` (whole
7
- # document at once). The base class provides traversal and
8
- # collection plumbing.
6
+ # `check_node` (one node at a time) or `check` (whole document).
9
7
  #
10
- # To add a new constraint:
11
- #
12
- # 1. Subclass `Constraint::NodeConstraint` or
13
- # `Constraint::DocumentConstraint`
14
- # 2. Implement `check_node` / `check_document`
15
- # 3. Register it on the convention: `register(MyConstraint)`
16
- #
17
- # No framework code changes.
8
+ # A constraint subclass MAY declare `applies_to` — one or more
9
+ # `Cml::Role::*` modules. When it does, the Coordinator only
10
+ # invokes `check_node` for nodes matching that role. Without
11
+ # `applies_to`, the constraint receives every node (legacy
12
+ # behaviour). Declaring `applies_to` is the fast path: the
13
+ # dispatch is O(node.ancestors) per node, not O(constraint_count).
18
14
  class Constraint
19
- def self.name_for_display
20
- to_s.split("::").last
15
+ class << self
16
+ # Returns the Role module(s) this constraint applies to, or
17
+ # nil if it must run for every node.
18
+ attr_reader :applies_to_roles
19
+
20
+ # Declare one or more Role modules this constraint handles.
21
+ # applies_to Chemicalml::Cml::Role::Atom
22
+ # applies_to Chemicalml::Cml::Role::Bond, Chemicalml::Cml::Role::AtomParity
23
+ def applies_to(*roles)
24
+ @applies_to_roles = roles.flatten.freeze
25
+ end
26
+
27
+ # Human-readable description of the rule this constraint
28
+ # enforces. Subclasses MAY override. Falls back to the class
29
+ # name. Used by `chemicalml constraints` and the auto-generated
30
+ # constraint docs.
31
+ #
32
+ # @return [String]
33
+ def description
34
+ @description || name.split('::').last
35
+ end
36
+
37
+ # Set the description (DSL for subclasses).
38
+ # description "A <bond> must have an order attribute"
39
+ attr_writer :description
40
+
41
+ # Severity this constraint emits. Subclasses MAY override.
42
+ # Default `:error` — warning subclasses override to `:warning`.
43
+ #
44
+ # @return [Symbol] `:error` or `:warning`.
45
+ def default_severity
46
+ :error
47
+ end
48
+ end
49
+
50
+ def initialize
51
+ freeze
21
52
  end
22
53
 
23
54
  def check(_document)
24
55
  raise NotImplementedError,
25
- "#{self.class.name_for_display} must implement #check(document)"
56
+ "#{self.class} must implement #check(document)"
26
57
  end
27
58
 
28
59
  protected
29
60
 
30
- def violation(path:, message:, severity: :error)
61
+ def violation(path:, message:, severity: :error, value: nil)
31
62
  Violation.new(path: path, message: message,
32
- severity: severity, constraint: self.class)
63
+ severity: severity, constraint: self.class,
64
+ value: value)
33
65
  end
34
66
 
35
67
  # Walk every wire node reachable from `node`. Yields (node, path)
36
68
  # pairs. Path is a slash-joined breadcrumb. Nodes that don't
37
69
  # include `Chemicalml::Cml::Visitable` are skipped.
38
- def walk_nodes(node, path = [], &block)
70
+ #
71
+ # Iterative (worklist) implementation — does not recurse, so
72
+ # deeply-nested documents cannot overflow the stack. Traversal
73
+ # order is DFS pre-order, matching the prior recursive walker.
74
+ def walk_nodes(node, path = [])
39
75
  return unless node
40
76
  return unless visitable?(node)
41
77
 
42
- yield(node, path)
43
- node.wire_children.each do |child|
44
- walk_nodes(child, path + [describe(child)], &block)
78
+ worklist = [[node, path]]
79
+ until worklist.empty?
80
+ current, current_path = worklist.shift
81
+ yield(current, current_path)
82
+ current.wire_children.reverse_each do |child|
83
+ next unless visitable?(child)
84
+
85
+ worklist.unshift([child, current_path + [describe(child)]])
86
+ end
45
87
  end
46
88
  end
47
89
 
@@ -54,8 +96,9 @@ module Chemicalml
54
96
  id_value ? "#{node.element_name}[#{id_value}]" : node.element_name
55
97
  end
56
98
 
57
- # Constraint that walks the document tree once per node and calls
58
- # `check_node(node, path)`. Subclasses get traversal for free.
99
+ # NodeConstraint: walks the document tree once per node and
100
+ # calls `check_node(node, path)`. Subclasses get traversal for
101
+ # free plus declarative role dispatch via `applies_to`.
59
102
  class NodeConstraint < Constraint
60
103
  def check(document)
61
104
  violations = []
@@ -70,8 +113,9 @@ module Chemicalml
70
113
  end
71
114
  end
72
115
 
73
- # Constraint that operates on the whole document at once — used
74
- # for cross-cutting rules like "atom ids unique within molecule".
116
+ # DocumentConstraint: operates on the whole document at once —
117
+ # used for cross-cutting rules like "atom ids unique within
118
+ # molecule". Walks the tree itself; not subject to dispatch.
75
119
  class DocumentConstraint < Constraint
76
120
  end
77
121
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # Single-pass constraint dispatcher. Replaces the previous
6
+ # O(N*C) walk (every constraint walked the whole tree) with
7
+ # O(N + total_applicable) — one tree walk per validation, with
8
+ # each node dispatched only to constraints that declared
9
+ # `applies_to` matching one of the node's ancestors.
10
+ #
11
+ # Constraints that don't declare `applies_to` fall into a
12
+ # default "all nodes" bucket and run for every visited node,
13
+ # preserving backward compatibility.
14
+ class Coordinator
15
+ attr_reader :document_constraints, :node_constraints
16
+
17
+ def initialize(constraint_classes)
18
+ @document_constraints = constraint_classes
19
+ .select { |c| c < Constraint::DocumentConstraint }
20
+ .map(&:new)
21
+ @node_constraints = constraint_constraints(constraint_classes)
22
+ .map(&:new)
23
+ @always_applicable = []
24
+ @by_role = Hash.new { |h, k| h[k] = [] }
25
+ @node_constraints.each { |c| register(c) }
26
+ end
27
+
28
+ def validate(document)
29
+ violations = @document_constraints.flat_map do |constraint|
30
+ Array(constraint.check(document))
31
+ end
32
+ return violations unless visitable?(document)
33
+
34
+ walk(document) do |node, path|
35
+ applicable_for(node).each do |constraint|
36
+ violations.concat(Array(constraint.check_node(node, path)))
37
+ end
38
+ end
39
+ violations
40
+ end
41
+
42
+ private
43
+
44
+ def constraint_constraints(classes)
45
+ classes.select { |c| c < Constraint::NodeConstraint }
46
+ end
47
+
48
+ def register(constraint)
49
+ roles = constraint.class.applies_to_roles
50
+ if roles.nil? || roles.empty?
51
+ @always_applicable << constraint
52
+ else
53
+ roles.each { |role| @by_role[role] << constraint }
54
+ end
55
+ end
56
+
57
+ def visitable?(node)
58
+ node.is_a?(Chemicalml::Cml::Visitable)
59
+ end
60
+
61
+ def applicable_for(node)
62
+ applicable = @always_applicable
63
+ node.class.ancestors.each do |ancestor|
64
+ next unless ancestor.is_a?(::Module)
65
+
66
+ bucket = @by_role[ancestor]
67
+ next unless bucket
68
+
69
+ applicable += bucket
70
+ end
71
+ applicable
72
+ end
73
+
74
+ def walk(node, path = [], &block)
75
+ return unless node
76
+ return unless visitable?(node)
77
+
78
+ yield(node, path)
79
+ node.wire_children.each do |child|
80
+ walk(child, path + [describe(child)], &block)
81
+ end
82
+ end
83
+
84
+ def describe(node)
85
+ id_value = node.node_id
86
+ id_value ? "#{node.element_name}[#{id_value}]" : node.element_name
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # Convention detection from a wire document. Reads the
6
+ # `convention` attribute from the root element and returns the
7
+ # QName string (e.g. "convention:molecular").
8
+ #
9
+ # Used by `Registry.detect_and_validate` and reusable by callers
10
+ # that want to know which convention a document declares without
11
+ # running validation.
12
+ module Detection
13
+ # Wire roles that may carry a top-level convention attribute.
14
+ # Adding a new convention whose root is a new element type =
15
+ # adding one role here. OCP — no existing code changes.
16
+ CONVENTION_ROOTS = [
17
+ Chemicalml::Cml::Role::Document,
18
+ Chemicalml::Cml::Role::Module,
19
+ Chemicalml::Cml::Role::Molecule,
20
+ Chemicalml::Cml::Role::Dictionary,
21
+ Chemicalml::Cml::Role::UnitList,
22
+ Chemicalml::Cml::Role::UnitTypeList,
23
+ Chemicalml::Cml::Role::Spectrum,
24
+ Chemicalml::Cml::Role::SpectrumList,
25
+ Chemicalml::Cml::Role::ReactionScheme,
26
+ Chemicalml::Cml::Role::ReactionList
27
+ ].freeze
28
+
29
+ def self.convention_of(document)
30
+ CONVENTION_ROOTS.each do |role|
31
+ next unless document.is_a?(role)
32
+
33
+ return document.convention if document.convention
34
+ end
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # A `<dictionary>` element MUST have a `namespace` attribute
8
+ # whose value is a valid URI defining the scope within which
9
+ # the entry terms are unique.
10
+ class DictionaryMustHaveNamespace < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'A `<dictionary>` element MUST have a `namespace` attribute whose value is a valid URI defining the scope within which the entry terms are unique.'
12
+ applies_to Chemicalml::Cml::Role::Dictionary
13
+ def check_node(node, path)
14
+ return [] unless node.namespace.to_s.empty?
15
+
16
+ [violation(path: path.join('/'),
17
+ message: 'dictionary must have a namespace attribute')]
18
+ end
19
+
20
+ private
21
+
22
+ def dictionary?(node)
23
+ node.is_a?(Chemicalml::Cml::Role::Dictionary)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # The `namespace` URI SHOULD end with `/` or `#` so terms can
8
+ # be referenced by appending them to the URI. Warning level.
9
+ class DictionaryNamespaceShouldEndWithSlashOrHash < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'The `namespace` URI SHOULD end with `/` or `#` so terms can be referenced by appending them to the URI. Warning level.'
11
+ applies_to Chemicalml::Cml::Role::Dictionary
12
+ def check_node(node, path)
13
+ ns = node.namespace.to_s
14
+ return [] if ns.empty?
15
+ return [] if ns.end_with?('/', '#')
16
+
17
+ [violation(path: path.join('/'),
18
+ message: "dictionary namespace #{ns.inspect} should end with / or # " \
19
+ 'so terms can be referenced by appending',
20
+ severity: :warning)]
21
+ end
22
+
23
+ private
24
+
25
+ def dictionary?(node)
26
+ node.is_a?(Chemicalml::Cml::Role::Dictionary)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # An `<entry>` `id` MUST match `[A-Za-z][A-Za-z0-9._-]*` —
8
+ # start with a letter, followed by letters/digits/dot/hyphen/
9
+ # underscore. Mirrors the upstream IdStartChar / IdChar BNF.
10
+ class EntryIdMustMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'An `<entry>` `id` MUST match `[A-Za-z][A-Za-z0-9._-]*` — start with a letter, followed by letters/digits/dot/hyphen/ underscore. Mirrors the upstream IdStartChar / IdChar BNF.'
12
+ applies_to Chemicalml::Cml::Role::DictionaryEntry
13
+ PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/
14
+
15
+ def check_node(node, path)
16
+ id = node.id.to_s
17
+ return [] if id.empty?
18
+ return [] if id.match?(PATTERN)
19
+
20
+ [violation(path: path.join('/'),
21
+ message: "entry id #{id.inspect} must match [A-Za-z][A-Za-z0-9._-]*")]
22
+ end
23
+
24
+ private
25
+
26
+ def entry?(node)
27
+ node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -7,16 +7,16 @@ module Chemicalml
7
7
  # Entry ids MUST be unique within the parent dictionary per
8
8
  # the dictionary convention.
9
9
  class EntryIdsUniqueWithinDictionary < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Entry ids MUST be unique within the parent dictionary per the dictionary convention.'
11
+ applies_to Chemicalml::Cml::Role::Dictionary
10
12
  def check_node(node, path)
11
- return [] unless dictionary?(node)
12
-
13
13
  entries = node.entries || []
14
- ids = entries.map { |e| e.id }.compact
14
+ ids = entries.map(&:id).compact
15
15
  duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
16
16
  return [] if duplicates.empty?
17
17
 
18
18
  duplicates.map do |dup|
19
- violation(path: path.join("/"),
19
+ violation(path: path.join('/'),
20
20
  message: "duplicate entry id #{dup.inspect} within dictionary")
21
21
  end
22
22
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # An `<entry>` MUST contain a single `definition` child
8
+ # element. The schema allows it as a string; this constraint
9
+ # enforces presence.
10
+ class EntryMustContainDefinition < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'An `<entry>` MUST contain a single `definition` child element. The schema allows it as a string; this constraint enforces presence.'
12
+ applies_to Chemicalml::Cml::Role::DictionaryEntry
13
+ def check_node(node, path)
14
+ return [] unless node.definition.to_s.strip.empty?
15
+
16
+ [violation(path: path.join('/'),
17
+ message: "entry #{node.id.inspect} must contain a single definition child")]
18
+ end
19
+
20
+ private
21
+
22
+ def entry?(node)
23
+ node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,17 +7,17 @@ module Chemicalml
7
7
  # Every entry MUST have an `id` and a `term` attribute per the
8
8
  # dictionary convention.
9
9
  class EntryMustHaveIdAndTerm < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Every entry MUST have an `id` and a `term` attribute per the dictionary convention.'
11
+ applies_to Chemicalml::Cml::Role::DictionaryEntry
10
12
  def check_node(node, path)
11
- return [] unless entry?(node)
12
-
13
13
  violations = []
14
14
  if node.id.to_s.empty?
15
- violations << violation(path: path.join("/"),
16
- message: "entry must have an id attribute")
15
+ violations << violation(path: path.join('/'),
16
+ message: 'entry must have an id attribute')
17
17
  end
18
18
  if node.term.to_s.empty?
19
- violations << violation(path: path.join("/"),
20
- message: "entry must have a term attribute")
19
+ violations << violation(path: path.join('/'),
20
+ message: 'entry must have a term attribute')
21
21
  end
22
22
  violations
23
23
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # An `<entry>` MUST have a `unitType` attribute. Entries
8
+ # describing concepts that should not have units (e.g. name
9
+ # of a program) should reference `none` in the standard CML
10
+ # unitType dictionary.
11
+ class EntryMustHaveUnitType < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = 'An `<entry>` MUST have a `unitType` attribute. Entries describing concepts that should not have units (e.g. name of a program) should reference `none` in the standard CML'
13
+ applies_to Chemicalml::Cml::Role::DictionaryEntry
14
+ def check_node(node, path)
15
+ return [] unless node.unit_type.to_s.empty?
16
+
17
+ [violation(path: path.join('/'),
18
+ message: "entry #{node.id.inspect} must have a unitType attribute " \
19
+ "(use 'none' for concepts without units)")]
20
+ end
21
+
22
+ private
23
+
24
+ def entry?(node)
25
+ node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # Co-constraint between `unitType` and `units`:
8
+ #
9
+ # - If `unitType` is `unknown`, `units` MUST NOT be present.
10
+ # - If `unitType` is `none`, `units` MUST be present and point
11
+ # to `http://www.xml-cml.org/unit/si#none`.
12
+ class EntryUnitsCoConstraints < Chemicalml::Convention::Constraint::NodeConstraint
13
+ self.description = 'Co-constraint between `unitType` and `units`: - If `unitType` is `unknown`, `units` MUST NOT be present. - If `unitType` is `none`, `units` MUST be present and point'
14
+ applies_to Chemicalml::Cml::Role::DictionaryEntry
15
+ NONE_UNITS = 'http://www.xml-cml.org/unit/si#none'
16
+
17
+ def check_node(node, path)
18
+ unit_type = node.unit_type.to_s
19
+ units = node.units.to_s
20
+ violations = []
21
+
22
+ if unit_type == 'unknown' && !units.empty?
23
+ violations << violation(path: path.join('/'),
24
+ message: "entry #{node.id.inspect} has unitType 'unknown' " \
25
+ 'and therefore must not have units')
26
+ end
27
+ if unit_type == 'none' && units != NONE_UNITS
28
+ violations << violation(path: path.join('/'),
29
+ message: "entry #{node.id.inspect} has unitType 'none' — " \
30
+ "units must be #{NONE_UNITS.inspect}")
31
+ end
32
+ violations
33
+ end
34
+
35
+ private
36
+
37
+ def entry?(node)
38
+ node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -5,9 +5,21 @@ module Chemicalml
5
5
  module Dictionary
6
6
  module Constraints
7
7
  autoload :EntryMustHaveIdAndTerm,
8
- "chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term"
8
+ 'chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term'
9
9
  autoload :EntryIdsUniqueWithinDictionary,
10
- "chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary"
10
+ 'chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary'
11
+ autoload :DictionaryMustHaveNamespace,
12
+ 'chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace'
13
+ autoload :DictionaryNamespaceShouldEndWithSlashOrHash,
14
+ 'chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash'
15
+ autoload :EntryMustContainDefinition,
16
+ 'chemicalml/convention/dictionary/constraints/entry_must_contain_definition'
17
+ autoload :EntryIdMustMatchPattern,
18
+ 'chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern'
19
+ autoload :EntryMustHaveUnitType,
20
+ 'chemicalml/convention/dictionary/constraints/entry_must_have_unit_type'
21
+ autoload :EntryUnitsCoConstraints,
22
+ 'chemicalml/convention/dictionary/constraints/entry_units_co_constraints'
11
23
  end
12
24
  end
13
25
  end
@@ -8,9 +8,9 @@ module Chemicalml
8
8
  module Dictionary
9
9
  extend Base
10
10
 
11
- autoload :Constraints, "chemicalml/convention/dictionary/constraints"
11
+ autoload :Constraints, 'chemicalml/convention/dictionary/constraints'
12
12
 
13
- QNAME = "convention:dictionary".freeze
13
+ QNAME = 'convention:dictionary'
14
14
  NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}dictionary".freeze
15
15
 
16
16
  def self.qname
@@ -23,6 +23,12 @@ module Chemicalml
23
23
 
24
24
  register Constraints::EntryMustHaveIdAndTerm
25
25
  register Constraints::EntryIdsUniqueWithinDictionary
26
+ register Constraints::DictionaryMustHaveNamespace
27
+ register Constraints::DictionaryNamespaceShouldEndWithSlashOrHash
28
+ register Constraints::EntryMustContainDefinition
29
+ register Constraints::EntryIdMustMatchPattern
30
+ register Constraints::EntryMustHaveUnitType
31
+ register Constraints::EntryUnitsCoConstraints
26
32
  end
27
33
  end
28
34
  end