chemicalml 0.2.1 → 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 (347) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +23 -0
  3. data/CLAUDE.md +49 -1
  4. data/README.adoc +154 -26
  5. data/TODO.align/61-parallel-array-attrs.md +54 -0
  6. data/TODO.align/62-per-element-attribute-gaps.md +49 -0
  7. data/TODO.align/63-universal-children-mixin.md +42 -0
  8. data/TODO.align/64-per-element-child-gaps.md +81 -0
  9. data/TODO.align/65-simple-unit-convention.md +40 -0
  10. data/TODO.align/66-schema24-legacy-children.md +32 -0
  11. data/TODO.align/67-final-verification.md +32 -0
  12. data/TODO.align/68-detection-coverage.md +33 -0
  13. data/TODO.align/69-json-yaml-roundtrip.md +27 -0
  14. data/TODO.align/70-iterative-walker.md +40 -0
  15. data/TODO.align/71-violation-value.md +25 -0
  16. data/TODO.align/72-close-child-gaps.md +66 -0
  17. data/TODO.align/73-schema24-parse-limitation.md +64 -0
  18. data/TODO.align/74-final-verification-round18.md +15 -0
  19. data/TODO.align/75-key-value-mappings.md +43 -0
  20. data/TODO.align/76-schema24-workaround.md +57 -0
  21. data/TODO.align/77-convention-coverage-matrix.md +31 -0
  22. data/TODO.align/78-fixture-audit.md +23 -0
  23. data/TODO.align/79-final-verification-round19.md +15 -0
  24. data/TODO.align/80-xsd-enum-constants.md +35 -0
  25. data/TODO.align/81-enum-validation-constraints.md +29 -0
  26. data/TODO.align/82-reference-resolver.md +33 -0
  27. data/TODO.align/83-toml-roundtrip.md +20 -0
  28. data/TODO.align/84-final-verification-round21.md +16 -0
  29. data/TODO.align/85-bond-distinct-atoms.md +25 -0
  30. data/TODO.align/86-references-should-resolve.md +20 -0
  31. data/TODO.align/87-cli-utility.md +26 -0
  32. data/TODO.align/88-diverse-fixtures.md +26 -0
  33. data/TODO.align/89-final-verification-round22.md +15 -0
  34. data/TODO.align/90-bond-stereo-completeness.md +31 -0
  35. data/TODO.align/91-atomparity-distinct.md +21 -0
  36. data/TODO.align/92-unit-dictionary-completeness.md +28 -0
  37. data/TODO.align/93-yard-docs.md +23 -0
  38. data/TODO.align/94-final-verification-round23.md +15 -0
  39. data/TODO.align/README.round17.md +28 -0
  40. data/TODO.align/README.round18.md +37 -0
  41. data/TODO.align/README.round19.md +40 -0
  42. data/TODO.align/README.round20.md +49 -0
  43. data/TODO.align/README.round21.md +35 -0
  44. data/TODO.align/README.round22.md +48 -0
  45. data/TODO.align/README.round23.md +46 -0
  46. data/TODO.align/README.round24.md +53 -0
  47. data/TODO.align/README.round25.md +66 -0
  48. data/TODO.align/README.round26.md +55 -0
  49. data/TODO.align/README.round27.md +51 -0
  50. data/TODO.align/README.round28.md +61 -0
  51. data/TODO.align/README.round29.md +64 -0
  52. data/TODO.align/README.round30.md +50 -0
  53. data/TODO.align/README.round31.md +54 -0
  54. data/TODO.align/README.round32.md +67 -0
  55. data/TODO.align/README.round33.md +62 -0
  56. data/TODO.align/README.round34.md +70 -0
  57. data/docs/constraints.md +161 -0
  58. data/docs/generate_constraint_docs.rb +44 -0
  59. data/exe/chemicalml +6 -0
  60. data/lib/chemicalml/cli/base.rb +68 -0
  61. data/lib/chemicalml/cli/command.rb +43 -0
  62. data/lib/chemicalml/cli/constraints_command.rb +27 -0
  63. data/lib/chemicalml/cli/conventions_command.rb +14 -0
  64. data/lib/chemicalml/cli/dictionaries_command.rb +14 -0
  65. data/lib/chemicalml/cli/elements_command.rb +17 -0
  66. data/lib/chemicalml/cli/enums_command.rb +17 -0
  67. data/lib/chemicalml/cli/info_command.rb +77 -0
  68. data/lib/chemicalml/cli/inspect_command.rb +39 -0
  69. data/lib/chemicalml/cli/validate_command.rb +54 -0
  70. data/lib/chemicalml/cli.rb +43 -0
  71. data/lib/chemicalml/cml/base/abundance.rb +18 -8
  72. data/lib/chemicalml/cml/base/action.rb +34 -16
  73. data/lib/chemicalml/cml/base/action_list.rb +35 -17
  74. data/lib/chemicalml/cml/base/alternative.rb +13 -4
  75. data/lib/chemicalml/cml/base/amount.rb +14 -6
  76. data/lib/chemicalml/cml/base/angle.rb +28 -13
  77. data/lib/chemicalml/cml/base/annotation.rb +14 -6
  78. data/lib/chemicalml/cml/base/any_cml.rb +12 -5
  79. data/lib/chemicalml/cml/base/appinfo.rb +14 -6
  80. data/lib/chemicalml/cml/base/arg.rb +47 -13
  81. data/lib/chemicalml/cml/base/array.rb +40 -19
  82. data/lib/chemicalml/cml/base/array_list.rb +14 -6
  83. data/lib/chemicalml/cml/base/atom.rb +84 -28
  84. data/lib/chemicalml/cml/base/atom_array.rb +60 -23
  85. data/lib/chemicalml/cml/base/atom_parity.rb +14 -6
  86. data/lib/chemicalml/cml/base/atom_set.rb +14 -6
  87. data/lib/chemicalml/cml/base/atom_type.rb +25 -9
  88. data/lib/chemicalml/cml/base/atom_type_list.rb +22 -8
  89. data/lib/chemicalml/cml/base/atomic_basis_function.rb +33 -13
  90. data/lib/chemicalml/cml/base/band.rb +21 -8
  91. data/lib/chemicalml/cml/base/band_list.rb +15 -7
  92. data/lib/chemicalml/cml/base/basis_set.rb +24 -9
  93. data/lib/chemicalml/cml/base/bond.rb +36 -10
  94. data/lib/chemicalml/cml/base/bond_array.rb +34 -11
  95. data/lib/chemicalml/cml/base/bond_set.rb +14 -6
  96. data/lib/chemicalml/cml/base/bond_stereo.rb +20 -9
  97. data/lib/chemicalml/cml/base/bond_type.rb +23 -8
  98. data/lib/chemicalml/cml/base/bond_type_list.rb +22 -8
  99. data/lib/chemicalml/cml/base/cell_parameter.rb +28 -10
  100. data/lib/chemicalml/cml/base/cml_module.rb +36 -11
  101. data/lib/chemicalml/cml/base/common_children.rb +35 -0
  102. data/lib/chemicalml/cml/base/complex_object.rb +12 -5
  103. data/lib/chemicalml/cml/base/condition_list.rb +26 -10
  104. data/lib/chemicalml/cml/base/crystal.rb +26 -10
  105. data/lib/chemicalml/cml/base/definition.rb +12 -5
  106. data/lib/chemicalml/cml/base/description.rb +14 -6
  107. data/lib/chemicalml/cml/base/dictionary.rb +27 -10
  108. data/lib/chemicalml/cml/base/dictionary_entry.rb +67 -8
  109. data/lib/chemicalml/cml/base/dimension.rb +26 -12
  110. data/lib/chemicalml/cml/base/document.rb +18 -8
  111. data/lib/chemicalml/cml/base/documentation.rb +12 -5
  112. data/lib/chemicalml/cml/base/eigen.rb +32 -8
  113. data/lib/chemicalml/cml/base/electron.rb +26 -12
  114. data/lib/chemicalml/cml/base/enumeration.rb +16 -7
  115. data/lib/chemicalml/cml/base/expression.rb +21 -6
  116. data/lib/chemicalml/cml/base/float.rb +22 -10
  117. data/lib/chemicalml/cml/base/float_array.rb +24 -11
  118. data/lib/chemicalml/cml/base/formula.rb +31 -10
  119. data/lib/chemicalml/cml/base/fragment.rb +30 -12
  120. data/lib/chemicalml/cml/base/fragment_list.rb +24 -9
  121. data/lib/chemicalml/cml/base/gradient.rb +19 -6
  122. data/lib/chemicalml/cml/base/identifier.rb +18 -8
  123. data/lib/chemicalml/cml/base/integer.rb +22 -10
  124. data/lib/chemicalml/cml/base/integer_array.rb +24 -11
  125. data/lib/chemicalml/cml/base/isotope.rb +28 -9
  126. data/lib/chemicalml/cml/base/isotope_list.rb +17 -8
  127. data/lib/chemicalml/cml/base/join.rb +25 -9
  128. data/lib/chemicalml/cml/base/kpoint.rb +16 -7
  129. data/lib/chemicalml/cml/base/kpoint_list.rb +15 -7
  130. data/lib/chemicalml/cml/base/label.rb +14 -6
  131. data/lib/chemicalml/cml/base/lattice.rb +35 -10
  132. data/lib/chemicalml/cml/base/lattice_vector.rb +16 -7
  133. data/lib/chemicalml/cml/base/length.rb +26 -12
  134. data/lib/chemicalml/cml/base/line3.rb +18 -8
  135. data/lib/chemicalml/cml/base/link.rb +41 -20
  136. data/lib/chemicalml/cml/base/list.rb +22 -10
  137. data/lib/chemicalml/cml/base/map.rb +32 -11
  138. data/lib/chemicalml/cml/base/matrix.rb +32 -15
  139. data/lib/chemicalml/cml/base/mechanism.rb +20 -7
  140. data/lib/chemicalml/cml/base/mechanism_component.rb +14 -6
  141. data/lib/chemicalml/cml/base/metadata.rb +18 -8
  142. data/lib/chemicalml/cml/base/metadata_list.rb +27 -8
  143. data/lib/chemicalml/cml/base/molecule.rb +90 -26
  144. data/lib/chemicalml/cml/base/molecule_list.rb +26 -8
  145. data/lib/chemicalml/cml/base/name.rb +10 -4
  146. data/lib/chemicalml/cml/base/object.rb +18 -8
  147. data/lib/chemicalml/cml/base/observation.rb +16 -7
  148. data/lib/chemicalml/cml/base/operator.rb +14 -6
  149. data/lib/chemicalml/cml/base/parameter.rb +33 -13
  150. data/lib/chemicalml/cml/base/parameter_list.rb +27 -8
  151. data/lib/chemicalml/cml/base/particle.rb +20 -9
  152. data/lib/chemicalml/cml/base/peak.rb +71 -26
  153. data/lib/chemicalml/cml/base/peak_group.rb +62 -26
  154. data/lib/chemicalml/cml/base/peak_list.rb +24 -9
  155. data/lib/chemicalml/cml/base/peak_structure.rb +40 -13
  156. data/lib/chemicalml/cml/base/plane3.rb +14 -6
  157. data/lib/chemicalml/cml/base/point3.rb +14 -6
  158. data/lib/chemicalml/cml/base/potential.rb +17 -6
  159. data/lib/chemicalml/cml/base/potential_form.rb +30 -7
  160. data/lib/chemicalml/cml/base/potential_list.rb +15 -7
  161. data/lib/chemicalml/cml/base/product.rb +45 -10
  162. data/lib/chemicalml/cml/base/product_list.rb +29 -9
  163. data/lib/chemicalml/cml/base/property.rb +29 -11
  164. data/lib/chemicalml/cml/base/property_list.rb +23 -8
  165. data/lib/chemicalml/cml/base/reactant.rb +45 -10
  166. data/lib/chemicalml/cml/base/reactant_list.rb +29 -9
  167. data/lib/chemicalml/cml/base/reaction.rb +74 -20
  168. data/lib/chemicalml/cml/base/reaction_list.rb +23 -8
  169. data/lib/chemicalml/cml/base/reaction_scheme.rb +43 -13
  170. data/lib/chemicalml/cml/base/reaction_step.rb +30 -12
  171. data/lib/chemicalml/cml/base/reaction_step_list.rb +30 -11
  172. data/lib/chemicalml/cml/base/reactive_centre.rb +31 -6
  173. data/lib/chemicalml/cml/base/region.rb +20 -9
  174. data/lib/chemicalml/cml/base/related_entry.rb +11 -3
  175. data/lib/chemicalml/cml/base/sample.rb +30 -7
  176. data/lib/chemicalml/cml/base/scalar.rb +32 -15
  177. data/lib/chemicalml/cml/base/spectator.rb +25 -6
  178. data/lib/chemicalml/cml/base/spectator_list.rb +20 -7
  179. data/lib/chemicalml/cml/base/spectrum.rb +57 -18
  180. data/lib/chemicalml/cml/base/spectrum_data.rb +19 -9
  181. data/lib/chemicalml/cml/base/spectrum_list.rb +28 -9
  182. data/lib/chemicalml/cml/base/sphere3.rb +14 -6
  183. data/lib/chemicalml/cml/base/stmml.rb +12 -5
  184. data/lib/chemicalml/cml/base/string.rb +14 -6
  185. data/lib/chemicalml/cml/base/string_array.rb +22 -10
  186. data/lib/chemicalml/cml/base/substance.rb +36 -14
  187. data/lib/chemicalml/cml/base/substance_list.rb +29 -10
  188. data/lib/chemicalml/cml/base/symmetry.rb +31 -9
  189. data/lib/chemicalml/cml/base/system.rb +21 -10
  190. data/lib/chemicalml/cml/base/table.rb +38 -13
  191. data/lib/chemicalml/cml/base/table_cell.rb +12 -5
  192. data/lib/chemicalml/cml/base/table_content.rb +19 -9
  193. data/lib/chemicalml/cml/base/table_header.rb +15 -7
  194. data/lib/chemicalml/cml/base/table_header_cell.rb +22 -10
  195. data/lib/chemicalml/cml/base/table_row.rb +15 -7
  196. data/lib/chemicalml/cml/base/table_row_list.rb +15 -7
  197. data/lib/chemicalml/cml/base/tcell.rb +16 -7
  198. data/lib/chemicalml/cml/base/torsion.rb +28 -13
  199. data/lib/chemicalml/cml/base/transform3.rb +12 -5
  200. data/lib/chemicalml/cml/base/transition_state.rb +23 -5
  201. data/lib/chemicalml/cml/base/trow.rb +22 -6
  202. data/lib/chemicalml/cml/base/unit.rb +39 -16
  203. data/lib/chemicalml/cml/base/unit_list.rb +34 -12
  204. data/lib/chemicalml/cml/base/unit_type.rb +27 -10
  205. data/lib/chemicalml/cml/base/unit_type_list.rb +29 -11
  206. data/lib/chemicalml/cml/base/vector3.rb +14 -6
  207. data/lib/chemicalml/cml/base/xaxis.rb +30 -10
  208. data/lib/chemicalml/cml/base/yaxis.rb +30 -10
  209. data/lib/chemicalml/cml/base/z_matrix.rb +26 -5
  210. data/lib/chemicalml/cml/base.rb +139 -138
  211. data/lib/chemicalml/cml/canonical_comparison.rb +54 -0
  212. data/lib/chemicalml/cml/elements.rb +145 -136
  213. data/lib/chemicalml/cml/enums.rb +55 -0
  214. data/lib/chemicalml/cml/namespace.rb +2 -3
  215. data/lib/chemicalml/cml/patterns.rb +33 -0
  216. data/lib/chemicalml/cml/reference_resolver.rb +137 -0
  217. data/lib/chemicalml/cml/role.rb +138 -138
  218. data/lib/chemicalml/cml/schema24/configuration.rb +11 -1
  219. data/lib/chemicalml/cml/schema24.rb +139 -139
  220. data/lib/chemicalml/cml/schema3.rb +123 -123
  221. data/lib/chemicalml/cml/visitable.rb +77 -3
  222. data/lib/chemicalml/cml/wire_class_registry.rb +3 -3
  223. data/lib/chemicalml/cml.rb +163 -129
  224. data/lib/chemicalml/context_configuration.rb +4 -2
  225. data/lib/chemicalml/convention/cascade/constraints/reaction_scheme_must_have_content.rb +33 -0
  226. data/lib/chemicalml/convention/cascade/constraints/reaction_step_list_must_contain_steps.rb +31 -0
  227. data/lib/chemicalml/convention/cascade/constraints/reaction_step_must_have_reaction_or_lists.rb +33 -0
  228. data/lib/chemicalml/convention/cascade/constraints/reactive_centre_atom_refs_should_be_present.rb +34 -0
  229. data/lib/chemicalml/convention/cascade/constraints.rb +18 -0
  230. data/lib/chemicalml/convention/cascade.rb +43 -0
  231. data/lib/chemicalml/convention/compchem/constraints/array_rules.rb +1 -1
  232. data/lib/chemicalml/convention/compchem/constraints/calculation_requires_finalization.rb +1 -1
  233. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +2 -1
  234. data/lib/chemicalml/convention/compchem/constraints/environment_at_most_one_property_list.rb +1 -1
  235. data/lib/chemicalml/convention/compchem/constraints/environment_must_not_contain_parameter.rb +1 -1
  236. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_molecule.rb +1 -1
  237. data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_property_list.rb +1 -1
  238. data/lib/chemicalml/convention/compchem/constraints/finalization_must_have_content.rb +44 -0
  239. data/lib/chemicalml/convention/compchem/constraints/finalization_must_not_contain_parameter.rb +1 -1
  240. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_molecule.rb +1 -1
  241. data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_parameter_list.rb +1 -1
  242. data/lib/chemicalml/convention/compchem/constraints/initialization_must_have_content.rb +54 -0
  243. data/lib/chemicalml/convention/compchem/constraints/initialization_must_not_contain_property.rb +1 -1
  244. data/lib/chemicalml/convention/compchem/constraints/job_list_module_must_have_id.rb +1 -0
  245. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_environment.rb +1 -1
  246. data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_finalization.rb +1 -1
  247. data/lib/chemicalml/convention/compchem/constraints/job_module_must_have_id.rb +1 -0
  248. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +1 -0
  249. data/lib/chemicalml/convention/compchem/constraints/matrix_rules.rb +1 -1
  250. data/lib/chemicalml/convention/compchem/constraints/scalar_units.rb +1 -1
  251. data/lib/chemicalml/convention/compchem/constraints.rb +4 -0
  252. data/lib/chemicalml/convention/compchem.rb +4 -2
  253. data/lib/chemicalml/convention/constraint.rb +40 -9
  254. data/lib/chemicalml/convention/coordinator.rb +4 -3
  255. data/lib/chemicalml/convention/detection.rb +10 -4
  256. data/lib/chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace.rb +1 -0
  257. data/lib/chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash.rb +1 -1
  258. data/lib/chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern.rb +1 -1
  259. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +1 -1
  260. data/lib/chemicalml/convention/dictionary/constraints/entry_must_contain_definition.rb +1 -0
  261. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +1 -1
  262. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_unit_type.rb +1 -0
  263. data/lib/chemicalml/convention/dictionary/constraints/entry_units_co_constraints.rb +1 -1
  264. data/lib/chemicalml/convention/dictionary/constraints.rb +8 -8
  265. data/lib/chemicalml/convention/dictionary.rb +2 -2
  266. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula.rb +1 -0
  267. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +15 -6
  268. data/lib/chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired.rb +1 -1
  269. data/lib/chemicalml/convention/molecular/constraints/atom_element_type_should_be_in_periodic_table.rb +39 -0
  270. data/lib/chemicalml/convention/molecular/constraints/atom_id_must_match_pattern.rb +3 -2
  271. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +1 -0
  272. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_element_type.rb +1 -1
  273. data/lib/chemicalml/convention/molecular/constraints/atom_must_have_id.rb +2 -1
  274. data/lib/chemicalml/convention/molecular/constraints/atom_parity_atom_refs4_should_be_distinct.rb +44 -0
  275. data/lib/chemicalml/convention/molecular/constraints/atom_parity_should_include_parent_atom.rb +65 -0
  276. data/lib/chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule.rb +1 -0
  277. data/lib/chemicalml/convention/molecular/constraints/bond_atom_refs2_should_be_distinct.rb +44 -0
  278. data/lib/chemicalml/convention/molecular/constraints/bond_id_should_match_pattern.rb +38 -0
  279. data/lib/chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule.rb +1 -0
  280. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2.rb +1 -1
  281. data/lib/chemicalml/convention/molecular/constraints/bond_must_have_order.rb +2 -1
  282. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +1 -0
  283. data/lib/chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref.rb +1 -0
  284. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_be_in_enum.rb +39 -0
  285. data/lib/chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric.rb +1 -1
  286. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_atom_refs4_should_be_distinct.rb +43 -0
  287. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4.rb +1 -1
  288. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref.rb +1 -0
  289. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_should_be_in_enum.rb +38 -0
  290. data/lib/chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2.rb +1 -1
  291. data/lib/chemicalml/convention/molecular/constraints/dict_ref_should_resolve.rb +59 -0
  292. data/lib/chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children.rb +1 -0
  293. data/lib/chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children.rb +1 -0
  294. data/lib/chemicalml/convention/molecular/constraints/molecule_chirality_should_be_in_enum.rb +38 -0
  295. data/lib/chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level.rb +1 -0
  296. data/lib/chemicalml/convention/molecular/constraints/molecule_id_should_match_pattern.rb +38 -0
  297. data/lib/chemicalml/convention/molecular/constraints/molecule_must_have_id.rb +1 -1
  298. data/lib/chemicalml/convention/molecular/constraints/property_must_have_dict_ref.rb +1 -1
  299. data/lib/chemicalml/convention/molecular/constraints/property_scalar_data_type_matches_dictionary.rb +58 -0
  300. data/lib/chemicalml/convention/molecular/constraints/references_should_resolve.rb +37 -0
  301. data/lib/chemicalml/convention/molecular/constraints/scalar_must_have_data_type.rb +1 -1
  302. data/lib/chemicalml/convention/molecular/constraints.rb +49 -23
  303. data/lib/chemicalml/convention/molecular.rb +15 -2
  304. data/lib/chemicalml/convention/registry.rb +79 -11
  305. data/lib/chemicalml/convention/simple_unit/constraints/root_must_be_unit_list.rb +24 -0
  306. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_power.rb +32 -0
  307. data/lib/chemicalml/convention/simple_unit/constraints/unit_must_have_symbol.rb +31 -0
  308. data/lib/chemicalml/convention/simple_unit/constraints.rb +16 -0
  309. data/lib/chemicalml/convention/simple_unit.rb +37 -0
  310. data/lib/chemicalml/convention/spectroscopy/constraints/peak_list_must_contain_peaks.rb +31 -0
  311. data/lib/chemicalml/convention/spectroscopy/constraints/peak_should_have_values.rb +34 -0
  312. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_content.rb +31 -0
  313. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_convention.rb +32 -0
  314. data/lib/chemicalml/convention/spectroscopy/constraints/spectrum_must_have_format.rb +31 -0
  315. data/lib/chemicalml/convention/spectroscopy/constraints.rb +20 -0
  316. data/lib/chemicalml/convention/spectroscopy.rb +41 -0
  317. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_contain_at_least_one_unit.rb +1 -0
  318. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_have_namespace.rb +1 -1
  319. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_contain_definition.rb +1 -0
  320. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_id.rb +1 -0
  321. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_multiplier_or_constant_to_si.rb +34 -0
  322. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_parent_si.rb +32 -0
  323. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +1 -1
  324. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_title.rb +32 -0
  325. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_parent_si_should_resolve.rb +45 -0
  326. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_unit_type_should_resolve.rb +46 -0
  327. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +10 -0
  328. data/lib/chemicalml/convention/unit_dictionary.rb +7 -2
  329. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_id_must_match_pattern.rb +1 -1
  330. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_contain_at_least_one_unit_type.rb +1 -0
  331. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_have_namespace.rb +1 -1
  332. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_contain_definition.rb +1 -0
  333. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +1 -1
  334. data/lib/chemicalml/convention/unit_type_dictionary.rb +2 -2
  335. data/lib/chemicalml/convention/validation_report.rb +30 -3
  336. data/lib/chemicalml/convention/violation.rb +9 -3
  337. data/lib/chemicalml/convention.rb +41 -14
  338. data/lib/chemicalml/dictionary.rb +13 -9
  339. data/lib/chemicalml/logger.rb +47 -0
  340. data/lib/chemicalml/schema/definition.rb +2 -2
  341. data/lib/chemicalml/schema.rb +7 -7
  342. data/lib/chemicalml/version.rb +1 -1
  343. data/lib/chemicalml/versioned_parser.rb +10 -10
  344. data/lib/chemicalml.rb +78 -15
  345. data/lib/tasks/reference_fetch.rake +30 -31
  346. data/lib/tasks/scrape_fixtures.rake +23 -24
  347. metadata +114 -3
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # The Cascade convention. A chemicalml-specific convention for CML
6
+ # reaction cascades — multi-step reaction networks encoded via
7
+ # `<reactionScheme>`, `<reactionStepList>`, `<reactionStep>`,
8
+ # `<reaction>`, and `<reactiveCentre>`.
9
+ #
10
+ # Upstream CML does not publish a dedicated cascade convention; the
11
+ # wire elements exist in the XSD but no constraints guard their
12
+ # well-formedness. This convention covers the gaps:
13
+ #
14
+ # - reactionScheme MUST contain at least one reactionStepList or
15
+ # reaction (an empty scheme carries no cascade)
16
+ # - reactionStepList MUST contain at least one reactionStep
17
+ # - reactionStep MUST contain a reaction or explicit reactant/product
18
+ # lists (an empty step is a dead-end in the cascade)
19
+ # - reactiveCentre atomRefs SHOULD be parseable atom references
20
+ # (warning — gracefully degrades when context is missing)
21
+ module Cascade
22
+ extend Base
23
+
24
+ autoload :Constraints, 'chemicalml/convention/cascade/constraints'
25
+
26
+ QNAME = 'convention:cascade'
27
+ NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}cascade".freeze
28
+
29
+ def self.qname
30
+ QNAME
31
+ end
32
+
33
+ def self.namespace_uri
34
+ NAMESPACE_URI
35
+ end
36
+
37
+ register Constraints::ReactionSchemeMustHaveContent
38
+ register Constraints::ReactionStepListMustContainSteps
39
+ register Constraints::ReactionStepMustHaveReactionOrLists
40
+ register Constraints::ReactiveCentreAtomRefsShouldBePresent
41
+ end
42
+ end
43
+ end
@@ -8,13 +8,13 @@ module Chemicalml
8
8
  # attribute (≥ 1); `dataType` MUST be integer or double;
9
9
  # MUST have `units`.
10
10
  class ArrayRules < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'CompChem `array` value-container rules: MUST have `size` attribute (≥ 1); `dataType` MUST be integer or double; MUST have `units`.'
11
12
  applies_to Chemicalml::Cml::Role::Array
12
13
  include ModulePredicates
13
14
 
14
15
  ALLOWED_TYPES = %w[xsd:integer xsd:double].freeze
15
16
 
16
17
  def check_node(node, path)
17
-
18
18
  violations = []
19
19
  if node.size.to_s.empty? || node.size.to_i < 1
20
20
  violations << violation(path: path.join('/'),
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # Co-constraint: if a calculation module is present inside a
8
8
  # job, a finalization MUST also be present.
9
9
  class CalculationRequiresFinalization < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Co-constraint: if a calculation module is present inside a job, a finalization MUST also be present.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  calculations = child_modules_with(node, 'compchem:calculation')
16
16
  finalizations = child_modules_with(node, 'compchem:finalization')
17
17
  return [] if calculations.empty?
@@ -7,10 +7,11 @@ module Chemicalml
7
7
  # A CompChem module MUST contain at least one jobList module
8
8
  # child per the compchem convention.
9
9
  class CompchemModuleMustContainJobList < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'A CompChem module MUST contain at least one jobList module child per the compchem convention.'
10
11
  def check_node(node, path)
11
12
  return [] unless compchem_root?(node)
12
13
 
13
- job_lists = node.modules.select { |m| m.dict_ref == 'compchem:jobList' }
14
+ job_lists = (node.modules || []).select { |m| m.dict_ref == 'compchem:jobList' }
14
15
  return [] unless job_lists.empty?
15
16
 
16
17
  [violation(path: path.join('/'),
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # The environment module MUST NOT contain more than one
8
8
  # propertyList child.
9
9
  class EnvironmentAtMostOnePropertyList < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'The environment module MUST NOT contain more than one propertyList child.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  lists = node.property_lists || []
16
16
  return [] if lists.length <= 1
17
17
 
@@ -8,11 +8,11 @@ module Chemicalml
8
8
  # parameterList children directly. It MAY contain a
9
9
  # propertyList (which itself can hold parameters).
10
10
  class EnvironmentMustNotContainParameter < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'The environment module MUST NOT contain parameter or parameterList children directly. It MAY contain a propertyList (which itself can hold parameters).'
11
12
  applies_to Chemicalml::Cml::Role::Module
12
13
  include ModulePredicates
13
14
 
14
15
  def check_node(node, path)
15
-
16
16
  violations = []
17
17
  unless (node.parameter_lists || []).empty?
18
18
  violations << violation(path: path.join('/'),
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # The finalization module MUST NOT contain more than one
8
8
  # molecule child.
9
9
  class FinalizationAtMostOneMolecule < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'The finalization module MUST NOT contain more than one molecule child.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  molecules = node.molecules || []
16
16
  return [] if molecules.length <= 1
17
17
 
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # The finalization module MUST NOT contain more than one
8
8
  # propertyList child.
9
9
  class FinalizationAtMostOnePropertyList < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'The finalization module MUST NOT contain more than one propertyList child.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  lists = node.property_lists || []
16
16
  return [] if lists.length <= 1
17
17
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Compchem
6
+ module Constraints
7
+ # A `finalization` module MUST contain at least one of:
8
+ # `molecule`, `propertyList`, or a user-defined module child.
9
+ # Per the compchem convention spec.
10
+ class FinalizationMustHaveContent < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'A `finalization` module MUST contain at least one of: `molecule`, `propertyList`, or a user-defined module child. Per the compchem convention spec.'
12
+ applies_to Chemicalml::Cml::Role::Module
13
+
14
+ def check_node(node, path)
15
+ return [] unless node.dict_ref == 'compchem:finalization'
16
+
17
+ has_molecule = (node.molecules || []).any?
18
+ has_prop_list = (node.property_lists || []).any?
19
+ has_user_module = user_module_count(node).positive?
20
+ return [] if has_molecule || has_prop_list || has_user_module
21
+
22
+ [violation(path: path.join('/'),
23
+ message: 'finalization module must contain at least one of: ' \
24
+ 'molecule, propertyList, or user-defined module')]
25
+ end
26
+
27
+ private
28
+
29
+ COMPCHEM_ROLES = %w[
30
+ compchem:jobList compchem:job compchem:initialization
31
+ compchem:calculation compchem:finalization compchem:environment
32
+ ].freeze
33
+
34
+ def user_module_count(node)
35
+ (node.modules || []).count do |m|
36
+ dr = m.dict_ref.to_s
37
+ !dr.empty? && !COMPCHEM_ROLES.include?(dr)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -8,11 +8,11 @@ module Chemicalml
8
8
  # parameterList children directly. (Parameters belong in
9
9
  # initialization.)
10
10
  class FinalizationMustNotContainParameter < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'The finalization module MUST NOT contain parameter or parameterList children directly. (Parameters belong in initialization.)'
11
12
  applies_to Chemicalml::Cml::Role::Module
12
13
  include ModulePredicates
13
14
 
14
15
  def check_node(node, path)
15
-
16
16
  violations = []
17
17
  unless (node.parameter_lists || []).empty?
18
18
  violations << violation(path: path.join('/'),
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # The initialization module MUST NOT contain more than one
8
8
  # molecule child.
9
9
  class InitializationAtMostOneMolecule < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'The initialization module MUST NOT contain more than one molecule child.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  molecules = node.molecules || []
16
16
  return [] if molecules.length <= 1
17
17
 
@@ -7,11 +7,11 @@ module Chemicalml
7
7
  # The initialization module MUST NOT contain more than one
8
8
  # parameterList child.
9
9
  class InitializationAtMostOneParameterList < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'The initialization module MUST NOT contain more than one parameterList child.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
13
14
  def check_node(node, path)
14
-
15
15
  lists = node.parameter_lists || []
16
16
  return [] if lists.length <= 1
17
17
 
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Compchem
6
+ module Constraints
7
+ # An `initialization` module MUST contain at least one of:
8
+ # `molecule`, `parameterList`, or a user-defined module child.
9
+ # Per the compchem convention spec.
10
+ class InitializationMustHaveContent < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'An `initialization` module MUST contain at least one of: `molecule`, `parameterList`, or a user-defined module child. Per the compchem convention spec.'
12
+ applies_to Chemicalml::Cml::Role::Module
13
+
14
+ def check_node(node, path)
15
+ return [] unless node.dict_ref == 'compchem:initialization'
16
+
17
+ has_molecule = molecule_count(node).positive?
18
+ has_param_list = parameter_list_count(node).positive?
19
+ has_user_module = user_module_count(node).positive?
20
+ return [] if has_molecule || has_param_list || has_user_module
21
+
22
+ [violation(path: path.join('/'),
23
+ message: 'initialization module must contain at least one of: ' \
24
+ 'molecule, parameterList, or user-defined module')]
25
+ end
26
+
27
+ private
28
+
29
+ def molecule_count(node)
30
+ (node.molecules || []).size
31
+ end
32
+
33
+ def parameter_list_count(node)
34
+ (node.parameter_lists || []).size
35
+ end
36
+
37
+ # User-defined modules: any module child whose dict_ref is
38
+ # not one of the compchem-defined roles.
39
+ COMPCHEM_ROLES = %w[
40
+ compchem:jobList compchem:job compchem:initialization
41
+ compchem:calculation compchem:finalization compchem:environment
42
+ ].freeze
43
+
44
+ def user_module_count(node)
45
+ (node.modules || []).count do |m|
46
+ dr = m.dict_ref.to_s
47
+ !dr.empty? && !COMPCHEM_ROLES.include?(dr)
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -8,11 +8,11 @@ module Chemicalml
8
8
  # propertyList children directly. (Properties belong in
9
9
  # finalization.)
10
10
  class InitializationMustNotContainProperty < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'The initialization module MUST NOT contain property or propertyList children directly. (Properties belong in finalization.)'
11
12
  applies_to Chemicalml::Cml::Role::Module
12
13
  include ModulePredicates
13
14
 
14
15
  def check_node(node, path)
15
-
16
16
  violations = []
17
17
  unless (node.property_lists || []).empty?
18
18
  violations << violation(path: path.join('/'),
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # A jobList module MUST have an `id` unique within the
8
8
  # compchem module.
9
9
  class JobListModuleMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'A jobList module MUST have an `id` unique within the compchem module.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
@@ -6,11 +6,11 @@ module Chemicalml
6
6
  module Constraints
7
7
  # A job module MUST contain at most one environment module.
8
8
  class JobModuleAtMostOneEnvironment < Chemicalml::Convention::Constraint::NodeConstraint
9
+ self.description = 'A job module MUST contain at most one environment module.'
9
10
  applies_to Chemicalml::Cml::Role::Module
10
11
  include ModulePredicates
11
12
 
12
13
  def check_node(node, path)
13
-
14
14
  environments = child_modules_with(node, 'compchem:environment')
15
15
  return [] if environments.length <= 1
16
16
 
@@ -6,11 +6,11 @@ module Chemicalml
6
6
  module Constraints
7
7
  # A job module MUST contain at most one finalization module.
8
8
  class JobModuleAtMostOneFinalization < Chemicalml::Convention::Constraint::NodeConstraint
9
+ self.description = 'A job module MUST contain at most one finalization module.'
9
10
  applies_to Chemicalml::Cml::Role::Module
10
11
  include ModulePredicates
11
12
 
12
13
  def check_node(node, path)
13
-
14
14
  finalizations = child_modules_with(node, 'compchem:finalization')
15
15
  return [] if finalizations.length <= 1
16
16
 
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # A job module MUST have an `id` unique within the compchem
8
8
  # module.
9
9
  class JobModuleMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'A job module MUST have an `id` unique within the compchem module.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  include ModulePredicates
12
13
 
@@ -7,6 +7,7 @@ module Chemicalml
7
7
  # A job module MUST contain exactly one initialization module
8
8
  # child per the compchem convention.
9
9
  class JobMustContainInitialization < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'A job module MUST contain exactly one initialization module child per the compchem convention.'
10
11
  applies_to Chemicalml::Cml::Role::Module
11
12
  def check_node(node, path)
12
13
  return [] unless node.dict_ref == 'compchem:job'
@@ -8,13 +8,13 @@ module Chemicalml
8
8
  # and `columns` attributes (each ≥ 1); `dataType` MUST be
9
9
  # integer or double; MUST have `units`.
10
10
  class MatrixRules < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'CompChem `matrix` value-container rules: MUST have `rows` and `columns` attributes (each ≥ 1); `dataType` MUST be integer or double; MUST have `units`.'
11
12
  applies_to Chemicalml::Cml::Role::Matrix
12
13
  include ModulePredicates
13
14
 
14
15
  ALLOWED_TYPES = %w[xsd:integer xsd:double].freeze
15
16
 
16
17
  def check_node(node, path)
17
-
18
18
  violations = []
19
19
  if node.rows.to_s.empty? || node.rows.to_i < 1
20
20
  violations << violation(path: path.join('/'),
@@ -8,13 +8,13 @@ module Chemicalml
8
8
  # `xsd:integer` or `xsd:double` MUST have `units`; `scalar`
9
9
  # with dataType `xsd:string` MUST NOT have `units`.
10
10
  class ScalarUnits < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'CompChem value-container rules: `scalar` with dataType `xsd:integer` or `xsd:double` MUST have `units`; `scalar` with dataType `xsd:string` MUST NOT have `units`.'
11
12
  applies_to Chemicalml::Cml::Role::Scalar
12
13
  include ModulePredicates
13
14
 
14
15
  NUMERIC_TYPES = %w[xsd:integer xsd:double].freeze
15
16
 
16
17
  def check_node(node, path)
17
-
18
18
  data_type = node.data_type.to_s
19
19
  units = node.units.to_s
20
20
  violations = []
@@ -42,6 +42,10 @@ module Chemicalml
42
42
  'chemicalml/convention/compchem/constraints/array_rules'
43
43
  autoload :MatrixRules,
44
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'
45
49
  end
46
50
  end
47
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
@@ -39,6 +39,8 @@ module Chemicalml
39
39
  register Constraints::ScalarUnits
40
40
  register Constraints::ArrayRules
41
41
  register Constraints::MatrixRules
42
+ register Constraints::InitializationMustHaveContent
43
+ register Constraints::FinalizationMustHaveContent
42
44
  end
43
45
  end
44
46
  end
@@ -15,9 +15,7 @@ module Chemicalml
15
15
  class << self
16
16
  # Returns the Role module(s) this constraint applies to, or
17
17
  # nil if it must run for every node.
18
- def applies_to_roles
19
- @applies_to_roles
20
- end
18
+ attr_reader :applies_to_roles
21
19
 
22
20
  # Declare one or more Role modules this constraint handles.
23
21
  # applies_to Chemicalml::Cml::Role::Atom
@@ -25,6 +23,28 @@ module Chemicalml
25
23
  def applies_to(*roles)
26
24
  @applies_to_roles = roles.flatten.freeze
27
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
28
48
  end
29
49
 
30
50
  def initialize
@@ -38,21 +58,32 @@ module Chemicalml
38
58
 
39
59
  protected
40
60
 
41
- def violation(path:, message:, severity: :error)
61
+ def violation(path:, message:, severity: :error, value: nil)
42
62
  Violation.new(path: path, message: message,
43
- severity: severity, constraint: self.class)
63
+ severity: severity, constraint: self.class,
64
+ value: value)
44
65
  end
45
66
 
46
67
  # Walk every wire node reachable from `node`. Yields (node, path)
47
68
  # pairs. Path is a slash-joined breadcrumb. Nodes that don't
48
69
  # include `Chemicalml::Cml::Visitable` are skipped.
49
- 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 = [])
50
75
  return unless node
51
76
  return unless visitable?(node)
52
77
 
53
- yield(node, path)
54
- node.wire_children.each do |child|
55
- 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
56
87
  end
57
88
  end
58
89
 
@@ -16,10 +16,10 @@ module Chemicalml
16
16
 
17
17
  def initialize(constraint_classes)
18
18
  @document_constraints = constraint_classes
19
- .select { |c| c < Constraint::DocumentConstraint }
20
- .map(&:new)
19
+ .select { |c| c < Constraint::DocumentConstraint }
20
+ .map(&:new)
21
21
  @node_constraints = constraint_constraints(constraint_classes)
22
- .map(&:new)
22
+ .map(&:new)
23
23
  @always_applicable = []
24
24
  @by_role = Hash.new { |h, k| h[k] = [] }
25
25
  @node_constraints.each { |c| register(c) }
@@ -62,6 +62,7 @@ module Chemicalml
62
62
  applicable = @always_applicable
63
63
  node.class.ancestors.each do |ancestor|
64
64
  next unless ancestor.is_a?(::Module)
65
+
65
66
  bucket = @by_role[ancestor]
66
67
  next unless bucket
67
68
 
@@ -3,21 +3,27 @@
3
3
  module Chemicalml
4
4
  module Convention
5
5
  # Convention detection from a wire document. Reads the
6
- # `convention` attribute from the root element (Document, Module,
7
- # Molecule, Dictionary, UnitList, or UnitTypeList) and returns
8
- # the QName string (e.g. "convention:molecular").
6
+ # `convention` attribute from the root element and returns the
7
+ # QName string (e.g. "convention:molecular").
9
8
  #
10
9
  # Used by `Registry.detect_and_validate` and reusable by callers
11
10
  # that want to know which convention a document declares without
12
11
  # running validation.
13
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.
14
16
  CONVENTION_ROOTS = [
15
17
  Chemicalml::Cml::Role::Document,
16
18
  Chemicalml::Cml::Role::Module,
17
19
  Chemicalml::Cml::Role::Molecule,
18
20
  Chemicalml::Cml::Role::Dictionary,
19
21
  Chemicalml::Cml::Role::UnitList,
20
- Chemicalml::Cml::Role::UnitTypeList
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
21
27
  ].freeze
22
28
 
23
29
  def self.convention_of(document)
@@ -8,6 +8,7 @@ module Chemicalml
8
8
  # whose value is a valid URI defining the scope within which
9
9
  # the entry terms are unique.
10
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.'
11
12
  applies_to Chemicalml::Cml::Role::Dictionary
12
13
  def check_node(node, path)
13
14
  return [] unless node.namespace.to_s.empty?
@@ -7,9 +7,9 @@ module Chemicalml
7
7
  # The `namespace` URI SHOULD end with `/` or `#` so terms can
8
8
  # be referenced by appending them to the URI. Warning level.
9
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.'
10
11
  applies_to Chemicalml::Cml::Role::Dictionary
11
12
  def check_node(node, path)
12
-
13
13
  ns = node.namespace.to_s
14
14
  return [] if ns.empty?
15
15
  return [] if ns.end_with?('/', '#')
@@ -8,11 +8,11 @@ module Chemicalml
8
8
  # start with a letter, followed by letters/digits/dot/hyphen/
9
9
  # underscore. Mirrors the upstream IdStartChar / IdChar BNF.
10
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.'
11
12
  applies_to Chemicalml::Cml::Role::DictionaryEntry
12
13
  PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/
13
14
 
14
15
  def check_node(node, path)
15
-
16
16
  id = node.id.to_s
17
17
  return [] if id.empty?
18
18
  return [] if id.match?(PATTERN)
@@ -7,9 +7,9 @@ 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.'
10
11
  applies_to Chemicalml::Cml::Role::Dictionary
11
12
  def check_node(node, path)
12
-
13
13
  entries = node.entries || []
14
14
  ids = entries.map(&:id).compact
15
15
  duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
@@ -8,6 +8,7 @@ module Chemicalml
8
8
  # element. The schema allows it as a string; this constraint
9
9
  # enforces presence.
10
10
  class EntryMustContainDefinition < Chemicalml::Convention::Constraint::NodeConstraint
11
+ self.description = 'An `<entry>` MUST contain a single `definition` child element. The schema allows it as a string; this constraint enforces presence.'
11
12
  applies_to Chemicalml::Cml::Role::DictionaryEntry
12
13
  def check_node(node, path)
13
14
  return [] unless node.definition.to_s.strip.empty?
@@ -7,9 +7,9 @@ module Chemicalml
7
7
  # Every entry MUST have an `id` and a `term` attribute per the
8
8
  # dictionary convention.
9
9
  class EntryMustHaveIdAndTerm < Chemicalml::Convention::Constraint::NodeConstraint
10
+ self.description = 'Every entry MUST have an `id` and a `term` attribute per the dictionary convention.'
10
11
  applies_to Chemicalml::Cml::Role::DictionaryEntry
11
12
  def check_node(node, path)
12
-
13
13
  violations = []
14
14
  if node.id.to_s.empty?
15
15
  violations << violation(path: path.join('/'),
@@ -9,6 +9,7 @@ module Chemicalml
9
9
  # of a program) should reference `none` in the standard CML
10
10
  # unitType dictionary.
11
11
  class EntryMustHaveUnitType < Chemicalml::Convention::Constraint::NodeConstraint
12
+ self.description = 'An `<entry>` MUST have a `unitType` attribute. Entries describing concepts that should not have units (e.g. name of a program) should reference `none` in the standard CML'
12
13
  applies_to Chemicalml::Cml::Role::DictionaryEntry
13
14
  def check_node(node, path)
14
15
  return [] unless node.unit_type.to_s.empty?