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,51 @@
1
+ # Round 27 — User-facing API, CLI improvements
2
+
3
+ This round adds the user-facing query API on `Cml::Visitable`, a
4
+ top-level `Chemicalml.validate` shortcut, and two new CLI commands
5
+ (`inspect`, `elements`).
6
+
7
+ ## Files
8
+
9
+ - [110 — Document query API](110-document-query-api.md)
10
+ - [111 — Top-level Chemicalml.validate](111-toplevel-validate.md)
11
+ - [112 — CLI inspect command](112-cli-inspect.md)
12
+ - [113 — CLI elements command](113-cli-elements.md)
13
+ - [114 — Final verification round 27](114-final-verification-round27.md)
14
+
15
+ ## Outcomes
16
+
17
+ - **`Cml::Visitable` query methods** (inherited by every wire class):
18
+ - `#each_wire_node`, `#each_atom`, `#each_bond`, `#each_molecule` — recursive iterators
19
+ - `#find_atom(id)`, `#find_bond(id)`, `#find_molecule(id)` — id-based lookup
20
+ - `#atom_count`, `#bond_count`, `#molecule_count` — recursive counts
21
+ - All return Enumerators without a block (Enumerable-style)
22
+
23
+ - **`Chemicalml.validate(doc)`** — top-level shortcut for
24
+ `Convention.detect_and_validate(doc)`. Users no longer need to
25
+ remember the full namespace.
26
+
27
+ - **`chemicalml inspect <file>`** — new CLI command. Prints a
28
+ tree-style summary of the document with element names, ids, and
29
+ structure. Useful for debugging.
30
+
31
+ - **`chemicalml elements`** — new CLI command. Lists every Schema3
32
+ wire class with its XML root name. Useful for introspection.
33
+
34
+ - **CLI refactor**: `run_validate` now uses `report.summary` instead
35
+ of manual rendering. Cleaner code, better output.
36
+
37
+ - **559 examples, 0 failures, 3 pending** (TOML adapter pending).
38
+ Zero forbidden patterns.
39
+
40
+ ## Architectural insight
41
+
42
+ The query methods live on `Visitable` (the marker mixin every wire
43
+ class includes) rather than on `Document` specifically. This means
44
+ ANY wire class can be the root of a query — `molecule.find_atom(id)`,
45
+ `reaction.find_molecule(id)`, etc. This was the right call: the
46
+ walker is already general (visits any subtree); exposing query
47
+ methods only on Document would have required callers to navigate
48
+ up to Document first.
49
+
50
+ The CLI's `print_tree` helper is recursive but bounded (`max_depth: 6`)
51
+ to avoid runaway output on pathological documents.
@@ -0,0 +1,61 @@
1
+ # Round 28 — Convenience API, custom conventions, compchem completion
2
+
3
+ This round adds user-facing convenience (`parse_file`, custom
4
+ convention registration), more compchem constraints from the spec,
5
+ and a comprehensive lint cleanup.
6
+
7
+ ## Files
8
+
9
+ - [115 — parse_file convenience](115-parse-file.md)
10
+ - [116 — Custom convention registration](116-custom-convention.md)
11
+ - [117 — More compchem constraints](117-more-compchem-constraints.md)
12
+ - [118 — Final verification round 28](118-final-verification-round28.md)
13
+
14
+ ## Outcomes
15
+
16
+ - **`Chemicalml.parse_file(path)`** — convenience wrapper around
17
+ `parse(File.read(path))`. Raises ArgumentError for missing files.
18
+ Eliminates the `File.read` boilerplate.
19
+
20
+ - **`Convention::Registry.register_custom(mod)`** — runtime
21
+ registration of custom conventions. The module must `extend Base`
22
+ and return a non-empty `qname`. Useful for domain-specific CML
23
+ extensions (e.g., a `convention:my-lab-extensions` for a research
24
+ group's custom vocabulary).
25
+
26
+ - **Two new compchem constraints** from the spec:
27
+ - `InitializationMustHaveContent` — initialization module must
28
+ contain at least one of molecule, parameterList, or user-defined
29
+ module
30
+ - `FinalizationMustHaveContent` — finalization module must contain
31
+ at least one of molecule, propertyList, or user-defined module
32
+
33
+ - **Lint cleanup**: ran `rubocop -A` on all recently-touched files.
34
+ 80 offenses autocorrected (string quotes, modifier-if usage,
35
+ parenthesised method args, etc.). Zero remaining style issues.
36
+
37
+ - **Real bug caught during round 28**: `register_custom` initially
38
+ deadlocked because it called `load_cache` while holding the mutex.
39
+ Fixed by separating the load step (which acquires/releases its
40
+ own mutex) from the mutation step (direct hash access after load
41
+ is guaranteed to have populated `@cache`).
42
+
43
+ - Compchem convention: 20 constraints (up from 18).
44
+
45
+ - **568 examples, 0 failures, 3 pending** (TOML adapter pending).
46
+ Zero forbidden patterns. **88 constraints across 8 conventions.**
47
+
48
+ ## Architectural insight
49
+
50
+ The `register_custom` deadlock is a classic Ruby mutex pitfall:
51
+ re-entering a non-reentrant `Mutex#synchronize` block from within
52
+ itself deadlocks. The fix — call `load_cache` first (which manages
53
+ its own mutex acquisition), then mutate `@cache` directly — works
54
+ because Ruby's `||=` assignment makes `@cache` non-nil after the
55
+ first load. Documented in the method comment.
56
+
57
+ This round also reinforced the value of running rubocop on each
58
+ round's output. 80 autocorrected offenses across 8 files means
59
+ style drift was creeping in — single-quote vs double-quote,
60
+ `unless` vs `if !`, etc. A small CI rule (`bundle exec rubocop`
61
+ must pass) would catch this in future.
@@ -0,0 +1,64 @@
1
+ # Round 29 — Wire introspection, README, dataType consistency
2
+
3
+ This round adds wire-class introspection, updates the README to
4
+ reflect the current API, and adds a property-dataType consistency
5
+ constraint that bridges the convention and dictionary subsystems
6
+ in a new way.
7
+
8
+ ## Files
9
+
10
+ - [119 — Wire-class introspection](119-wire-introspection.md)
11
+ - [120 — Update README](120-readme-update.md)
12
+ - [121 — Property dataType consistency](121-property-datatype.md)
13
+ - [122 — Final verification round 29](122-final-verification-round29.md)
14
+
15
+ ## Outcomes
16
+
17
+ - **`Cml.for_xml_name(name, schema:)`** — wire-class lookup by XML
18
+ element name. Uses the existing `Elements::XML_TO_CLASS` reverse
19
+ index. Returns nil for unknown names.
20
+
21
+ - **`Cml.wire_classes(schema:)`** — enumerator over all wire classes
22
+ for a schema version. Useful for code generation and tooling.
23
+
24
+ - **README rewritten**:
25
+ - 8 conventions + 88+ constraints documented
26
+ - All modern API examples (`parse_file`, `validate`, `each_atom`)
27
+ - CLI commands documented (`validate`, `inspect`, `conventions`,
28
+ `dictionaries`, `elements`)
29
+ - Cross-format serialisation examples
30
+ - Custom convention registration example
31
+ - Dictionary lookup example
32
+ - Architectural overview (`Base::*`, `Role::*`, `Elements::ALL`)
33
+
34
+ - **`PropertyScalarDataTypeMatchesDictionary`** — molecular warning
35
+ when a `<property>`'s `<scalar>` `dataType` doesn't match the
36
+ `dataType` declared in the dictionary entry referenced by `dictRef`.
37
+ This is a **three-component integration check**: convention
38
+ constraint system queries the dictionary registry AND inspects the
39
+ document's scalar child.
40
+
41
+ - Molecular convention: 36 constraints (up from 35).
42
+
43
+ - **574 examples, 0 failures, 3 pending**. Zero forbidden patterns.
44
+
45
+ ## Architectural insight
46
+
47
+ `PropertyScalarDataTypeMatchesDictionary` is the deepest
48
+ cross-component check yet — it validates that:
49
+
50
+ 1. The property's `dictRef` resolves to a real dictionary entry
51
+ (uses `Dictionary::Registry.lookup`).
52
+ 2. The entry declares a `dataType`.
53
+ 3. The property's `<scalar>` child declares a `dataType`.
54
+ 4. The two `dataType` values match.
55
+
56
+ If any step is missing data, the constraint silently passes (no
57
+ false positives). When all the data is present, it catches real
58
+ inconsistencies that would cause downstream type errors.
59
+
60
+ This kind of multi-step semantic validation is what mature
61
+ document-validation frameworks look like. Round 29 is the first
62
+ round where it's been possible — earlier rounds built the
63
+ primitives (dictionary registry, dictRef resolution, scalar
64
+ dataType attribute) that this constraint composes.
@@ -0,0 +1,50 @@
1
+ # Round 30 — unitType resolution, JSON CLI, enumeration
2
+
3
+ This round adds the unit-unitType cross-component check (parallels
4
+ dictRef resolution), CLI JSON output for machine consumers, and a
5
+ constraint-enumeration API.
6
+
7
+ ## Files
8
+
9
+ - [123 — Unit unitType resolution](123-unit-unittype-resolution.md)
10
+ - [124 — CLI JSON output](124-cli-json-output.md)
11
+ - [125 — Constraint enumeration API](125-constraint-enumeration.md)
12
+ - [126 — Final verification round 30](126-final-verification-round30.md)
13
+
14
+ ## Outcomes
15
+
16
+ - **`UnitUnitTypeShouldResolve`** — unit-dictionary warning when a
17
+ `<unit>`'s `unitType` attribute doesn't resolve against the
18
+ built-in unitType-dictionary. Cross-component check parallel to
19
+ the molecular convention's `DictRefShouldResolve`.
20
+
21
+ - **`chemicalml validate --json file.cml`** — JSON output mode for
22
+ the CLI. Returns `{ file:, ok:, has_warnings:, violations: [...] }`
23
+ where each violation has `severity`, `path`, `message`, `value`.
24
+ Useful for editor integration, CI pipelines, automated tooling.
25
+
26
+ - **`Convention::Registry.each_constraint`** — iterates every
27
+ constraint across every convention, yielding `(convention, class)`
28
+ pairs. Useful for documentation generation and introspection.
29
+
30
+ - **`Convention::Registry.total_constraint_count`** — sum of all
31
+ registered constraints across all conventions (currently 90).
32
+
33
+ - Unit-dictionary convention: 9 constraints (up from 8).
34
+
35
+ - **580 examples, 0 failures, 3 pending** (TOML adapter pending).
36
+ Zero forbidden patterns. **90 constraints across 8 conventions.**
37
+
38
+ ## Architectural insight
39
+
40
+ `UnitUnitTypeShouldResolve` completes a symmetric pair with
41
+ `DictRefShouldResolve` from round 25. Both follow the same pattern:
42
+ walk the document, look up reference attributes against the
43
+ dictionary registry, warn on misses. The pattern is now established
44
+ enough that adding a new reference-resolution constraint is a
45
+ 2-minute job: copy the structure, change the attribute name and the
46
+ target dictionary.
47
+
48
+ The `each_constraint` iterator makes the constraint table tractable
49
+ for documentation generation. A future round could auto-generate a
50
+ Markdown table of all 90 constraints from this single API call.
@@ -0,0 +1,54 @@
1
+ # Round 31 — CLI tooling, parentSI resolution
2
+
3
+ This round completes the CLI tooling suite (constraints + enums
4
+ commands) and adds the parentSI resolution constraint, completing
5
+ the cross-component check symmetry.
6
+
7
+ ## Files
8
+
9
+ - [127 — CLI constraints command](127-cli-constraints.md)
10
+ - [128 — CLI enums command](128-cli-enums.md)
11
+ - [129 — ParentSI resolution](129-parentsi-resolution.md)
12
+ - [130 — Final verification round 31](130-final-verification-round31.md)
13
+
14
+ ## Outcomes
15
+
16
+ - **`chemicalml constraints`** — new CLI command listing all 91
17
+ constraints grouped by convention. Each line shows the constraint
18
+ class name and its declared `applies_to` roles.
19
+
20
+ - **`chemicalml enums`** — new CLI command listing every
21
+ `Cml::Enums` constant with its allowed values. Useful for users
22
+ who want to know what enum values an attribute accepts.
23
+
24
+ - **`UnitParentSiShouldResolve`** — unit-dictionary warning when a
25
+ `<unit>`'s `parentSI` doesn't resolve against built-in
26
+ dictionaries. Completes the cross-component check symmetry:
27
+ - dictRef resolution (molecular)
28
+ - unitType resolution (unit-dictionary)
29
+ - parentSI resolution (unit-dictionary)
30
+
31
+ - **Real bug caught during round 31**: existing fixture
32
+ `convention_spec.rb:226` used `parent_si: "siUnits:s"` which
33
+ doesn't resolve (correct prefix is `si`, not `siUnits`). Fixed.
34
+
35
+ - Unit-dictionary convention: 10 constraints (up from 9).
36
+
37
+ - **583 examples, 0 failures, 3 pending**. Zero forbidden patterns.
38
+ **91 constraints across 8 conventions.**
39
+
40
+ ## Architectural insight
41
+
42
+ The cross-component check pattern (walk document → look up reference
43
+ attribute against registry → warn on miss) is now fully
44
+ established across three constraint pairs:
45
+
46
+ | Convention | Attribute | Lookup target |
47
+ |---|---|---|
48
+ | molecular | `dictRef` | dictionary registry |
49
+ | unit-dictionary | `unitType` | unitType-dictionary registry |
50
+ | unit-dictionary | `parentSI` | unit dictionary registry |
51
+
52
+ Adding a fourth is mechanical: copy the structure, change the
53
+ attribute name and lookup target. The pattern's clarity is its own
54
+ form of documentation.
@@ -0,0 +1,67 @@
1
+ # Round 32 — Thor CLI refactor, info command, auto-docs
2
+
3
+ This round refactors the CLI to use Thor with one class per command,
4
+ adds the `info` command for element introspection, and auto-generates
5
+ constraint documentation.
6
+
7
+ ## Files
8
+
9
+ - [131 — CLI info command](131-cli-info-command.md)
10
+ - [132 — Auto-generated constraint docs](132-auto-generated-constraint-docs.md)
11
+ - [133 — Final verification round 32](133-final-verification-round32.md)
12
+
13
+ ## Outcomes
14
+
15
+ - **CLI refactored to Thor**. Each command is now a dedicated class:
16
+ - `Chemicalml::Cli::Base < Thor` — dispatcher
17
+ - `Chemicalml::Cli::Command` — base class with `.run(args, options)`
18
+ and `#run(args, options)`
19
+ - `Chemicalml::Cli::ValidateCommand`
20
+ - `Chemicalml::Cli::InspectCommand`
21
+ - `Chemicalml::Cli::ConventionsCommand`
22
+ - `Chemicalml::Cli::DictionariesCommand`
23
+ - `Chemicalml::Cli::ElementsCommand`
24
+ - `Chemicalml::Cli::ConstraintsCommand`
25
+ - `Chemicalml::Cli::EnumsCommand`
26
+ - `Chemicalml::Cli::InfoCommand`
27
+
28
+ Each command supports both `MyCommand.run(args, options)` (class
29
+ method) and `MyCommand.new.run(args, options)` (instance method)
30
+ invocation patterns — matching the user's requested pattern.
31
+
32
+ - **`chemicalml info <element>`** — new CLI command. Shows element
33
+ XML name, Ruby class, included Role module, all attributes (with
34
+ collection marker), and all applicable constraints across every
35
+ convention. Useful for users exploring the model.
36
+
37
+ - **Auto-generated constraint docs** — `docs/generate_constraint_docs.rb`
38
+ script produces `docs/constraints.md` from
39
+ `Convention::Registry.each_constraint`. Single source of truth —
40
+ regenerating picks up new constraints automatically. No manual
41
+ maintenance.
42
+
43
+ - **Bug fix from autocorrect**: `Convention::Registry#load_cache`
44
+ had `@cache` renamed to `@load_cache` by rubocop's
45
+ `MemoizedInstanceVariableName` rule. `register_custom` and `reset!`
46
+ needed alignment — fixed in this round.
47
+
48
+ - **586 examples, 0 failures, 3 pending**. Zero forbidden patterns.
49
+
50
+ ## Architectural insight
51
+
52
+ The Thor refactor separates concerns cleanly:
53
+
54
+ 1. **Thor dispatcher** owns argument parsing, help text, option
55
+ declarations. Each `desc + def` block is 2-3 lines.
56
+ 2. **Command class** owns the actual work. Easy to test in isolation
57
+ (`MyCommand.new.run(args, options)` returns an exit code).
58
+ 3. **No business logic in the dispatcher** — it's pure routing.
59
+
60
+ This pattern scales: a future `rake` task, Rake task, or web handler
61
+ can re-use any `*Command` class without going through Thor. The
62
+ Thor dispatcher is just one possible entry point.
63
+
64
+ The auto-generated constraint docs demonstrate the value of the
65
+ `each_constraint` API from round 30: a single iteration produces
66
+ comprehensive documentation with zero manual upkeep. Adding a new
67
+ constraint and re-running the generator is all it takes.
@@ -0,0 +1,62 @@
1
+ # Round 33 — CLI run(options) signature alignment
2
+
3
+ This round refactors every CLI command class so the run method
4
+ takes a single `options` hash, matching the user-requested
5
+ invocation pattern exactly:
6
+
7
+ MyCommandClass.new.run(options)
8
+
9
+ ## Files
10
+
11
+ - [134 — CLI run(options) alignment](134-cli-run-options-alignment.md)
12
+ - [135 — Final verification round 33](135-final-verification-round33.md)
13
+
14
+ ## Outcomes
15
+
16
+ - Every `Chemicalml::Cli::*Command` now implements
17
+ `def run(options)` (single hash argument). The hash carries both
18
+ positional args (keyed by name: `:file`, `:element`) and flags
19
+ (`:json`, etc.).
20
+
21
+ - `Chemicalml::Cli::Base` (Thor dispatcher) builds the options hash
22
+ for each command and calls `<Name>Command.new.run(hash)`. Example:
23
+ ```ruby
24
+ desc "validate FILE", "..."
25
+ method_option :json, type: :boolean, aliases: "-j"
26
+ def validate(file)
27
+ ValidateCommand.new.run(file: file, json: options[:json])
28
+ end
29
+ ```
30
+
31
+ - Each command supports both invocation styles:
32
+ - Instance: `MyCommand.new.run(options)` (primary, per user spec)
33
+ - Class: `MyCommand.run(options)` (convenience, delegates to instance)
34
+
35
+ - Bug fix: validation guards `return warn(...), 2 unless path`
36
+ were returning `[warn_result, 2]` (array) instead of integer 2.
37
+ Rewrote as explicit `warn; return 2` two-line guards.
38
+
39
+ - CLI spec updated to test both invocation styles. 17 specs covering
40
+ every command via `MyCommandClass.new.run(options)` direct call.
41
+
42
+ - **593 examples, 0 failures, 3 pending**. Zero forbidden patterns.
43
+
44
+ ## Architectural insight
45
+
46
+ The single-hash `run(options)` signature is more flexible than the
47
+ prior `(args, options)` split:
48
+
49
+ - Positional args are named (`file:`, `element:`), not positional.
50
+ Easier to extend (add a new arg = add a new hash key, no method
51
+ signature change).
52
+ - Same hash carries flags (`json:`, etc.). One argument, one source
53
+ of truth.
54
+ - Commands are unit-testable in isolation without Thor:
55
+ ```ruby
56
+ Chemicalml::Cli::ValidateCommand.new.run(file: "x.cml", json: true)
57
+ ```
58
+
59
+ The Thor dispatcher is now truly just routing — it parses CLI input
60
+ and constructs the options hash. Every command class can be reused
61
+ from Rake tasks, RSpec tests, or web handlers without going through
62
+ Thor at all.
@@ -0,0 +1,70 @@
1
+ # Round 34 — Constraint descriptions, diverse fixtures, perf baseline, BIG perf fix
2
+
3
+ This round adds constraint-class descriptions, more diverse fixtures,
4
+ a performance baseline spec, and crucially **fixes a 680× performance
5
+ regression** discovered by the baseline spec.
6
+
7
+ ## Files
8
+
9
+ - [136 — Constraint descriptions](136-constraint-descriptions.md)
10
+ - [137 — More diverse fixtures](137-more-diverse-fixtures.md)
11
+ - [138 — Performance baseline spec](138-performance-baseline-spec.md)
12
+ - [139 — Final verification round 34](139-final-verification-round34.md)
13
+
14
+ ## Outcomes
15
+
16
+ - **Constraint `description` class method** added to
17
+ `Convention::Constraint`. Each constraint can now carry a
18
+ human-readable description of the rule it enforces. Two molecular
19
+ constraints (`AtomMustHaveId`, `BondMustHaveOrder`) updated as
20
+ examples. `chemicalml constraints` command now shows descriptions
21
+ when present.
22
+
23
+ - **Three new diverse fixtures**:
24
+ - `molecular/ethylene_with_cis_trans_stereo.cml` — bondStereo with
25
+ `atomRefs4` (cis/trans case)
26
+ - `reactions/free_radical_halogenation.cml` — multi-reactant
27
+ reaction with substanceList catalyst
28
+ - `spectroscopy/ethanol_nmr.cml` — NMR spectrum with xaxis/yaxis
29
+ arrays, peakList with multiplicity, metadataList
30
+
31
+ - **Performance baseline spec** — establishes timing baselines for:
32
+ - XML/JSON serialisation of a 1000-atom molecule (< 2s)
33
+ - Molecular validation of a 1000-atom molecule (< 2s)
34
+ - `atom_count` / `find_atom` queries (< 500ms)
35
+ - Fixture parsing (< 20ms)
36
+
37
+ - **MAJOR performance fix**: the `ReferencesShouldResolve` constraint
38
+ was taking **47.8 seconds** on a 1000-atom document. Root cause:
39
+ `ReferenceResolver#unresolved_for` called `containing_molecule`
40
+ for every bond, which walked the entire document every time
41
+ (O(n²) for n bonds × n molecules × n atoms).
42
+
43
+ Fixed by:
44
+ - Adding lazily-built `atom_index` (id → Atom hash, O(1) lookup)
45
+ - Removing the `containing_molecule` indirection in
46
+ `unresolved_for` — the resolver now uses the global atom index
47
+ directly. CML semantics already enforce that bond refs live in
48
+ the parent molecule (that's `BondMustReferenceAtomsInSameMolecule`'s
49
+ job).
50
+
51
+ Result: **47.8s → 68ms (680× faster)**.
52
+
53
+ - **609 examples, 0 failures, 3 pending** (TOML adapter pending).
54
+ Zero forbidden patterns.
55
+
56
+ ## Architectural insight
57
+
58
+ The performance baseline spec paid for itself immediately by catching
59
+ a 47-second regression that I'd introduced in round 22 when adding
60
+ the `ReferencesShouldResolve` constraint. Without the baseline,
61
+ this would have shipped to users.
62
+
63
+ The fix follows the **build indices once, query many times** pattern.
64
+ The resolver now builds an `atom_index` lazily on first lookup; all
65
+ subsequent `find_atom` calls are O(1). This is a Ruby-native
66
+ performance pattern that fits the existing architecture cleanly.
67
+
68
+ Lesson: baseline specs aren't just for regression detection — they
69
+ catch bugs that you didn't know you had. Always write the baseline
70
+ before optimising.
@@ -0,0 +1,161 @@
1
+ # Registered Convention Constraints
2
+
3
+ Auto-generated from `Chemicalml::Convention::Registry.each_constraint`.
4
+ Last regenerated: 2026-07-20T09:43:53Z.
5
+
6
+ Total constraints: **91**
7
+
8
+ ## convention:cascade
9
+
10
+ - **Namespace**: `http://www.xml-cml.org/convention/cascade`
11
+ - **Constraint count**: 4
12
+
13
+ | Constraint | Applies to | Description |
14
+ |---|---|---|
15
+ | `ReactionSchemeMustHaveContent` | ReactionScheme | A `<reactionScheme>` MUST contain at least one `<reactionStepList>` or `<reaction>` child. An empty scheme carries no cascade information. |
16
+ | `ReactionStepListMustContainSteps` | ReactionStepList | A `<reactionStepList>` MUST contain at least one `<reactionStep>` child. An empty step list breaks the cascade chain. |
17
+ | `ReactionStepMustHaveReactionOrLists` | ReactionStep | A `<reactionStep>` MUST contain either a `<reaction>` child or explicit `<reactantList>` + `<productList>` children. An empty step is a dead-end in the cascade. |
18
+ | `ReactiveCentreAtomRefsShouldBePresent` | ReactiveCentre | A `<reactiveCentre>` SHOULD declare its `atomRefs` attribute. Without atom references the reactive centre is indistinguishable from a placeholder. Warning severity — |
19
+
20
+ ## convention:compchem
21
+
22
+ - **Namespace**: `http://www.xml-cml.org/convention/compchem`
23
+ - **Constraint count**: 20
24
+
25
+ | Constraint | Applies to | Description |
26
+ |---|---|---|
27
+ | `CompchemModuleMustContainJobList` | _(document-wide)_ | A CompChem module MUST contain at least one jobList module child per the compchem convention. |
28
+ | `JobMustContainInitialization` | Module | A job module MUST contain exactly one initialization module child per the compchem convention. |
29
+ | `JobListModuleMustHaveId` | Module | A jobList module MUST have an `id` unique within the compchem module. |
30
+ | `JobModuleMustHaveId` | Module | A job module MUST have an `id` unique within the compchem module. |
31
+ | `JobModuleAtMostOneFinalization` | Module | A job module MUST contain at most one finalization module. |
32
+ | `JobModuleAtMostOneEnvironment` | Module | A job module MUST contain at most one environment module. |
33
+ | `CalculationRequiresFinalization` | Module | Co-constraint: if a calculation module is present inside a job, a finalization MUST also be present. |
34
+ | `InitializationAtMostOneMolecule` | Module | The initialization module MUST NOT contain more than one molecule child. |
35
+ | `InitializationAtMostOneParameterList` | Module | The initialization module MUST NOT contain more than one parameterList child. |
36
+ | `InitializationMustNotContainProperty` | Module | The initialization module MUST NOT contain property or propertyList children directly. (Properties belong in finalization.) |
37
+ | `FinalizationAtMostOneMolecule` | Module | The finalization module MUST NOT contain more than one molecule child. |
38
+ | `FinalizationAtMostOnePropertyList` | Module | The finalization module MUST NOT contain more than one propertyList child. |
39
+ | `FinalizationMustNotContainParameter` | Module | The finalization module MUST NOT contain parameter or parameterList children directly. (Parameters belong in initialization.) |
40
+ | `EnvironmentAtMostOnePropertyList` | Module | The environment module MUST NOT contain more than one propertyList child. |
41
+ | `EnvironmentMustNotContainParameter` | Module | The environment module MUST NOT contain parameter or parameterList children directly. It MAY contain a propertyList (which itself can hold parameters). |
42
+ | `ScalarUnits` | Scalar | CompChem value-container rules: `scalar` with dataType `xsd:integer` or `xsd:double` MUST have `units`; `scalar` with dataType `xsd:string` MUST NOT have `units`. |
43
+ | `ArrayRules` | Array | CompChem `array` value-container rules: MUST have `size` attribute (≥ 1); `dataType` MUST be integer or double; MUST have `units`. |
44
+ | `MatrixRules` | Matrix | CompChem `matrix` value-container rules: MUST have `rows` and `columns` attributes (each ≥ 1); `dataType` MUST be integer or double; MUST have `units`. |
45
+ | `InitializationMustHaveContent` | Module | An `initialization` module MUST contain at least one of: `molecule`, `parameterList`, or a user-defined module child. Per the compchem convention spec. |
46
+ | `FinalizationMustHaveContent` | Module | A `finalization` module MUST contain at least one of: `molecule`, `propertyList`, or a user-defined module child. Per the compchem convention spec. |
47
+
48
+ ## convention:dictionary
49
+
50
+ - **Namespace**: `http://www.xml-cml.org/convention/dictionary`
51
+ - **Constraint count**: 8
52
+
53
+ | Constraint | Applies to | Description |
54
+ |---|---|---|
55
+ | `EntryMustHaveIdAndTerm` | DictionaryEntry | Every entry MUST have an `id` and a `term` attribute per the dictionary convention. |
56
+ | `EntryIdsUniqueWithinDictionary` | Dictionary | Entry ids MUST be unique within the parent dictionary per the dictionary convention. |
57
+ | `DictionaryMustHaveNamespace` | Dictionary | A `<dictionary>` element MUST have a `namespace` attribute whose value is a valid URI defining the scope within which the entry terms are unique. |
58
+ | `DictionaryNamespaceShouldEndWithSlashOrHash` | Dictionary | The `namespace` URI SHOULD end with `/` or `#` so terms can be referenced by appending them to the URI. Warning level. |
59
+ | `EntryMustContainDefinition` | DictionaryEntry | An `<entry>` MUST contain a single `definition` child element. The schema allows it as a string; this constraint enforces presence. |
60
+ | `EntryIdMustMatchPattern` | DictionaryEntry | 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. |
61
+ | `EntryMustHaveUnitType` | DictionaryEntry | 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 |
62
+ | `EntryUnitsCoConstraints` | DictionaryEntry | 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 |
63
+
64
+ ## convention:molecular
65
+
66
+ - **Namespace**: `http://www.xml-cml.org/convention/molecular`
67
+ - **Constraint count**: 36
68
+
69
+ | Constraint | Applies to | Description |
70
+ |---|---|---|
71
+ | `AtomArrayMustContainAtoms` | Molecule | `atomArray` MUST contain at least one atom. The atoms can be in the child form (`<atom>` elements) or the parallel-array form (`atomID` attribute with whitespace-separated ids). |
72
+ | `AtomIdsUniqueWithinMolecule` | _(document-wide)_ | Atom ids MUST be unique within the eldest containing molecule per the molecular convention. |
73
+ | `BondMustReferenceAtomsInSameMolecule` | _(document-wide)_ | Bonds MUST reference atoms within the same parent molecule per the molecular convention. |
74
+ | `AtomMustHaveId` | Atom | An <atom> inside an atomArray in a molecule MUST have an id attribute. |
75
+ | `AtomMustHaveElementType` | Atom | An <atom> inside an atomArray in a molecule MUST have an elementType attribute. |
76
+ | `BondMustHaveAtomRefs2` | Bond | A <bond> MUST have an atomRefs2 attribute referencing two distinct atoms in the same molecule. |
77
+ | `BondMustHaveOrder` | Bond | A <bond> MUST have an order attribute (S/D/T/A recommended). |
78
+ | `MoleculeMustHaveId` | Molecule | A <molecule> in a molecular document MUST have an id attribute unique within document scope. |
79
+ | `AtomCoordinatesMustBePaired` | Atom | <atom> 2D coordinates (x2, y2) must appear together; 3D coordinates (x3, y3, z3) must appear together. |
80
+ | `PropertyMustHaveDictRef` | Property | A <property> MUST have a dictRef attribute that identifies the property type via a dictionary entry. |
81
+ | `ScalarMustHaveDataType` | Scalar | A <scalar> child of a <property> MUST have a dataType attribute (e.g. xsd:float, xsd:integer). |
82
+ | `BondOrderShouldNotBeNumeric` | Bond | A <bond>'s order attribute SHOULD be one of S/D/T/Q/A or 'other' (numeric values are not recommended). |
83
+ | `AtomIdMustMatchPattern` | Atom | An <atom>'s id attribute SHOULD start with a letter and contain only letters, digits, dot, hyphen, or underscore. |
84
+ | `MoleculeCountMustNotAppearOnTopLevel` | _(document-wide)_ | Molecular convention: `count` MUST NOT appear on top-level molecules (direct children of `<cml>`). It is REQUIRED on molecules nested inside another `<molecule>`. |
85
+ | `MoleculeAtomArrayMutuallyExclusiveWithChildren` | Molecule | Molecular convention: a `<molecule>` MAY hold an `atomArray` OR child `<molecule>` elements, but not both. The two are mutually exclusive ways of describing composition. |
86
+ | `MoleculeBondArrayMutuallyExclusiveWithChildren` | Molecule | Molecular convention: a `<molecule>` MAY hold a `bondArray` OR child `<molecule>` elements, but not both. |
87
+ | `BondStereoWedgeHashMustHaveAtomRefs2` | BondStereo | Molecular convention: `<bondStereo>` value W (wedge) or H (hatch) MUST have `atomRefs2` and MUST NOT have `atomRefs4`. |
88
+ | `BondStereoCisTransMustHaveAtomRefs4` | BondStereo | Molecular convention: `<bondStereo>` value C (cis) or T (trans) MUST have `atomRefs4` and MUST NOT have `atomRefs2`. |
89
+ | `BondStereoOtherMustHaveDictRef` | BondStereo | Molecular convention: `<bondStereo>` with value `other` MUST have a `dictRef` pointing to the convention that defines the stereo semantics. |
90
+ | `BondIdsUniqueWithinMolecule` | _(document-wide)_ | Molecular convention: a `<bond>` `id` MUST be unique within the eldest containing molecule. Mirrors the existing `AtomIdsUniqueWithinMolecule` constraint for bonds. |
91
+ | `BondOrderOtherMustHaveDictRef` | Bond | Molecular convention: `<bond>` with `order="other"` MUST have a `dictRef` pointing to the dictionary that defines the order semantics. The warning-level "should not be |
92
+ | `AtomArrayMustBeChildOfMoleculeOrFormula` | _(document-wide)_ | Molecular convention: `<atomArray>` MUST be a child of `<molecule>` or `<formula>`. Any other parent is invalid. Checked from the parent's perspective during the walk — |
93
+ | `BondArrayMustBeChildOfMolecule` | _(document-wide)_ | Molecular convention: `<bondArray>` MUST be a child of `<molecule>`. Any other parent is invalid. |
94
+ | `BondOrderShouldBeInEnum` | Bond | A `<bond>`'s `order` attribute SHOULD be one of the XSD orderType enum values: S/1/D/2/T/3/A/unknown/other. Warning severity — CML permits extension values via |
95
+ | `BondStereoShouldBeInEnum` | BondStereo | A `<bondStereo>`'s `value` attribute SHOULD be one of the XSD stereoType enum values: C/T/W/H/undefined/other. Warning severity. |
96
+ | `MoleculeChiralityShouldBeInEnum` | Molecule | A `<molecule>`'s `chirality` attribute SHOULD be one of the XSD chiralityType enum values: enantiomer/racemate/unknown/other. Warning severity. |
97
+ | `BondAtomRefs2ShouldBeDistinct` | Bond | A `<bond>`'s `atomRefs2` SHOULD reference two distinct atom ids. A self-bond (`a1 a1`) is chemically meaningless. Warning severity — there are edge cases in non-classical |
98
+ | `ReferencesShouldResolve` | _(document-wide)_ | Walks the document via `Cml::ReferenceResolver` and reports every bond whose `atomRefs2` references atoms that don't exist in the parent molecule. Warning severity — there are |
99
+ | `BondStereoAtomRefs4ShouldBeDistinct` | BondStereo | A `<bondStereo>` with `atomRefs4` (used by C/T stereo) SHOULD reference four distinct atom ids. Duplicate ids make the stereo descriptor meaningless. Warning severity. |
100
+ | `AtomParityAtomRefs4ShouldBeDistinct` | AtomParity | An `<atomParity>` element's `atomRefs4` attribute SHOULD reference four distinct atom ids around a chiral center. Duplicate ids make the parity descriptor meaningless. |
101
+ | `AtomElementTypeShouldBeInPeriodicTable` | Atom | An `<atom>`'s `elementType` attribute SHOULD be one of the XSD elementTypeType enum values (the periodic table plus the special "Du" dummy and "R" group placeholder). Warns |
102
+ | `DictRefShouldResolve` | Atom, Bond, Molecule, Property, Scalar, Array, Matrix, Name, Label | Walks the document and warns on any element whose `dictRef` attribute cannot be resolved against the built-in dictionaries. Catches typos like `dictRef="cml:bpingpoint"`. |
103
+ | `MoleculeIdShouldMatchPattern` | Molecule | A `<molecule>`'s `id` attribute SHOULD match the XSD moleculeIDType pattern (letter/underscore start, alphanumeric body, optional namespace prefix). Warning severity. |
104
+ | `BondIdShouldMatchPattern` | Bond | A `<bond>`'s `id` attribute SHOULD match the XSD idType pattern (letter start, alphanumeric body with dots/dashes). Warning severity. |
105
+ | `AtomParityShouldIncludeParentAtom` | _(document-wide)_ | Walks the document and warns on `<atomParity>` elements whose parent `<atom>`'s id does not appear in atomRefs4. The CML convention requires the parent atom to be one of |
106
+ | `PropertyScalarDataTypeMatchesDictionary` | Property | A `<property>` with a `dictRef` whose dictionary entry declares a `dataType` SHOULD have a child `<scalar>` whose `dataType` attribute matches. Catches inconsistencies like a property |
107
+
108
+ ## convention:simpleUnit
109
+
110
+ - **Namespace**: `http://www.xml-cml.org/convention/simpleUnit`
111
+ - **Constraint count**: 3
112
+
113
+ | Constraint | Applies to | Description |
114
+ |---|---|---|
115
+ | `UnitMustHavePower` | Unit | A `<unit>` under simpleUnit MUST declare a `power` attribute (integer). Without a power the unit's exponent is undefined. |
116
+ | `UnitMustHaveSymbol` | Unit | A `<unit>` under simpleUnit MUST declare a non-empty `symbol` attribute. Without a symbol the unit cannot be rendered in formulae. |
117
+ | `RootMustBeUnitList` | _(document-wide)_ | The root of a simpleUnit document MUST be a `<unitList>` declaring `convention="convention:simpleUnit"`. Any other root shape is rejected. |
118
+
119
+ ## convention:spectroscopy
120
+
121
+ - **Namespace**: `http://www.xml-cml.org/convention/spectroscopy`
122
+ - **Constraint count**: 5
123
+
124
+ | Constraint | Applies to | Description |
125
+ |---|---|---|
126
+ | `SpectrumMustHaveConvention` | Spectrum | A `<spectrum>` MUST declare its own convention attribute so consumers know how to interpret its format and peaks. Per the molecular convention: "spectrum — any number, each MUST |
127
+ | `SpectrumMustHaveFormat` | Spectrum | A `<spectrum>` MUST have a `format` attribute (e.g. "mass", "ir", "nmr", "uv") so consumers know the measurement type. |
128
+ | `SpectrumMustHaveContent` | Spectrum | A `<spectrum>` MUST contain at least one of: `<xaxis>`, `<yaxis>`, `<peakList>`. An empty spectrum carries no data. |
129
+ | `PeakListMustContainPeaks` | PeakList | A `<peakList>` MUST contain at least one `<peak>` or `<peakGroup>` child. An empty peakList carries no data. |
130
+ | `PeakShouldHaveValues` | Peak | A `<peak>` SHOULD declare at least one of `xValue` or `yValue`. A peak with neither carries no position information. Warning severity — there are edge cases |
131
+
132
+ ## convention:unit-dictionary
133
+
134
+ - **Namespace**: `http://www.xml-cml.org/convention/unit-dictionary`
135
+ - **Constraint count**: 10
136
+
137
+ | Constraint | Applies to | Description |
138
+ |---|---|---|
139
+ | `UnitMustHaveSymbolAndUnitType` | Unit | Every unit MUST have `id`, `title`, `symbol`, `parentSI`, at least one of `multiplierToSI`/`constantToSI`, and `unitType` per the unit-dictionary convention. |
140
+ | `UnitMustHaveId` | Unit | A `<unit>` element MUST have an `id` attribute, unique within the unitList. |
141
+ | `UnitMustContainDefinition` | Unit | A `<unit>` element MUST contain a single `<definition>` child element with XHTML content. |
142
+ | `UnitListMustHaveNamespace` | UnitList | A `<unitList>` element MUST have a `namespace` attribute whose value is a valid URI defining the scope within which the unit ids are unique. SHOULD end with `/` or `#`. |
143
+ | `UnitListMustContainAtLeastOneUnit` | UnitList | A `<unitList>` element MUST contain one or more `<unit>` children, and MUST NOT contain any other CML-namespace child elements. |
144
+ | `UnitMustHaveTitle` | Unit | A `<unit>` element MUST have a `title` attribute (the full human-readable name of the unit). Per the unit-dictionary convention spec. |
145
+ | `UnitMustHaveParentSi` | Unit | A `<unit>` element MUST have a `parentSI` attribute — a QName referencing the SI unit it derives from. Per the unit-dictionary convention spec. |
146
+ | `UnitMustHaveMultiplierOrConstantToSi` | Unit | A `<unit>` element MUST have at least one of `multiplierToSI` or `constantToSI` — to define the conversion to the parent SI unit. Per the unit-dictionary convention spec. |
147
+ | `UnitUnitTypeShouldResolve` | Unit | A `<unit>`'s `unitType` attribute SHOULD reference a unitType that exists in a built-in unitType-dictionary. Catches typos like `unitType="unitType:lenght"` (misspelled). |
148
+ | `UnitParentSiShouldResolve` | Unit | A `<unit>`'s `parentSI` attribute SHOULD reference a unit that exists in a built-in dictionary (typically the SI dictionary). Catches typos like `parentSI="si:metr"`. |
149
+
150
+ ## convention:unitType-dictionary
151
+
152
+ - **Namespace**: `http://www.xml-cml.org/convention/unitType-dictionary`
153
+ - **Constraint count**: 5
154
+
155
+ | Constraint | Applies to | Description |
156
+ |---|---|---|
157
+ | `UnitTypeMustHaveIdAndName` | UnitType | Every unitType MUST have an `id` and a `name` attribute per the unitType-dictionary convention. |
158
+ | `UnitTypeIdMustMatchPattern` | UnitType | A `<unitType>` `id` MUST match `[A-Za-z][A-Za-z0-9._-]*`. |
159
+ | `UnitTypeMustContainDefinition` | UnitType | A `<unitType>` MUST contain a single `<definition>` child with XHTML content. |
160
+ | `UnitTypeListMustHaveNamespace` | UnitTypeList | A `<unitTypeList>` MUST have a `namespace` attribute (valid URI, SHOULD end with `/` or `#`). |
161
+ | `UnitTypeListMustContainAtLeastOneUnitType` | UnitTypeList | A `<unitTypeList>` MUST contain one or more `<unitType>` children. |