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,33 @@
1
+ # 68 — Detection coverage for all 8 conventions
2
+
3
+ ## Why
4
+
5
+ `Convention::Detection.convention_of` lists 6 root roles (Document,
6
+ Module, Molecule, Dictionary, UnitList, UnitTypeList). With 8
7
+ conventions now registered, several conventions have no auto-detection
8
+ path:
9
+
10
+ | Convention | Expected root role | Currently detected? |
11
+ |--------------|--------------------|---------------------|
12
+ | molecular | Document / Module / Molecule | yes |
13
+ | compchem | Module | yes |
14
+ | dictionary | Dictionary | yes |
15
+ | unit-dictionary | UnitList | yes |
16
+ | unitType-dictionary | UnitTypeList | yes |
17
+ | spectroscopy | Spectrum / SpectrumList | **no** |
18
+ | cascade | ReactionScheme / ReactionList | **no** |
19
+ | simpleUnit | UnitList | yes |
20
+
21
+ ## Work
22
+
23
+ 1. Extend `CONVENTION_ROOTS` to include:
24
+ - `Role::Spectrum`, `Role::SpectrumList`
25
+ - `Role::ReactionScheme`, `Role::ReactionList`
26
+ 2. Add specs covering each convention's auto-detection from a root
27
+ element with the right `convention` attribute.
28
+
29
+ ## Acceptance
30
+
31
+ - `Detection.convention_of(spectrum)` returns the convention string
32
+ when the spectrum declares one.
33
+ - All 8 conventions are auto-detectable from at least one root role.
@@ -0,0 +1,27 @@
1
+ # 69 — JSON / YAML round-trip proof
2
+
3
+ ## Why
4
+
5
+ CLAUDE.md claims "lutaml-model is format-agnostic (XML/JSON/YAML/TOML
6
+ via adapters), so the same class supports whatever serialization
7
+ lutaml-model supports". We have no specs proving this. If a downstream
8
+ caller wants to serialize CML to JSON (e.g. for a REST API), we should
9
+ verify it actually works.
10
+
11
+ ## Work
12
+
13
+ Add `spec/chemicalml/cml/json_yaml_roundtrip_spec.rb` that:
14
+
15
+ 1. Constructs a representative CML model (Document with a Molecule
16
+ containing an AtomArray).
17
+ 2. Calls `to_json` / `from_json`.
18
+ 3. Calls `to_yaml` / `from_yaml`.
19
+ 4. Asserts the round-tripped model has the same data.
20
+ 5. Asserts `from_json(to_json(model))` is structurally equivalent to
21
+ `from_xml(to_xml(model))` for the same source model.
22
+
23
+ ## Acceptance
24
+
25
+ - JSON round-trip spec passes.
26
+ - YAML round-trip spec passes.
27
+ - The spec proves the format-agnostic claim is true.
@@ -0,0 +1,40 @@
1
+ # 70 — Iterative tree walker
2
+
3
+ ## Why
4
+
5
+ `Chemicalml::Convention::Constraint#walk_nodes` recurses through the
6
+ wire tree. CML documents can be deeply nested (cascading modules,
7
+ large reaction cascades, etc.) — recursion risks `SystemStackError`
8
+ on real-world files.
9
+
10
+ The constraint walker is also called from `Coordinator.validate`, so
11
+ a stack overflow there aborts validation entirely.
12
+
13
+ ## Work
14
+
15
+ Replace the recursive walk in `lib/chemicalml/convention/constraint.rb`
16
+ with an iterative worklist:
17
+
18
+ ```ruby
19
+ def walk_nodes(node, path = [], &block)
20
+ return unless visitable?(node)
21
+ worklist = [[node, path]]
22
+ until worklist.empty?
23
+ current, current_path = worklist.shift
24
+ yield(current, current_path)
25
+ children = current.wire_children.map do |child|
26
+ [child, current_path + [describe(child)]]
27
+ end
28
+ worklist.unshift(*children)
29
+ end
30
+ end
31
+ ```
32
+
33
+ This preserves the same traversal order (DFS pre-order) without
34
+ recursion.
35
+
36
+ ## Acceptance
37
+
38
+ - A deeply-nested CML document (100+ levels of nested Module) walks
39
+ without stack overflow.
40
+ - All existing constraint specs pass unchanged.
@@ -0,0 +1,25 @@
1
+ # 71 — Enrich Violation with offending value
2
+
3
+ ## Why
4
+
5
+ `Violation` carries `path`, `message`, `severity`, `constraint`.
6
+ Debugging is harder than necessary because the actual offending value
7
+ is not on the violation — only mentioned inside `message`. Callers
8
+ that want to render violations in a UI or aggregate them need to
9
+ parse the message string to extract the value.
10
+
11
+ ## Work
12
+
13
+ 1. Add an optional `value:` keyword to `Violation#initialize`. The
14
+ field is the literal offending value (e.g. the duplicated atom id
15
+ `"a1"`).
16
+ 2. Update `Constraint#violation` to forward `value:` to `Violation.new`.
17
+ 3. Constraint classes pass the offending value where natural
18
+ (e.g. duplicate id constraints pass the duplicate id).
19
+ 4. Specs assert the value is carried.
20
+
21
+ ## Acceptance
22
+
23
+ - `Violation.new(path: "x", message: "y", value: "a1").value == "a1"`.
24
+ - No existing spec breaks (value defaults to nil).
25
+ - At least 3 constraints populate `value` with the offending value.
@@ -0,0 +1,66 @@
1
+ # 72 — Close remaining Schema24 child gaps
2
+
3
+ ## Why
4
+
5
+ After TODO 64 closed the bulk of Schema24 child gaps via
6
+ `CommonChildren`, ~140 element-specific children remain unmodelled.
7
+ These are concrete child declarations the XSD permits and real CML
8
+ documents use.
9
+
10
+ ## Work
11
+
12
+ Add element-specific children to each Base module below. For each,
13
+ add a round-trip spec covering at least one new child.
14
+
15
+ | Element | Children to add |
16
+ |---|---|
17
+ | reactiveCentre | atomSet, atomTypeList, bondSet, bondTypeList |
18
+ | sample | molecule, substance, substanceList |
19
+ | spectator | molecule, object |
20
+ | transitionState | molecule, propertyList |
21
+ | substance | amount, property |
22
+ | substanceList | amount, propertyList |
23
+ | lattice | matrix, scalar, symmetry |
24
+ | table | arrayList, tableRowList |
25
+ | trow | tcell |
26
+ | unit | metadata, unitType (annotation via CommonChildren) |
27
+ | unitType | dimension (annotation via CommonChildren) |
28
+ | dictionary | unitList (annotation via CommonChildren) |
29
+ | dictionaryEntry | alternative, enumeration, relatedEntry |
30
+ | gradient | array, matrix, property, scalar |
31
+ | eigen | array, matrix |
32
+ | xaxis, yaxis | array |
33
+ | zMatrix | angle, length, torsion |
34
+ | symmetry | matrix, transform3 |
35
+ | map | link |
36
+ | join | angle, length, torsion, molecule, arg |
37
+ | fragment | fragmentList, join |
38
+ | fragmentList | join |
39
+ | formula | formula (self-ref) |
40
+ | parameter | expression, gradient, property |
41
+ | potential | arg |
42
+ | atomicBasisFunction | gradient |
43
+ | isotope | abundance |
44
+ | atomType | atom, molecule, scalar, array, matrix, property |
45
+ | bondType | bond, molecule, scalar, array, matrix, property |
46
+ | arg | atom, atomType, expression, scalar, array, matrix |
47
+ | expression | operator, parameter |
48
+ | potentialForm | arg, expression, parameter |
49
+ | annotation | appinfo |
50
+ | enumeration | annotation |
51
+ | metadataList | metadataList (self-ref) |
52
+ | moleculeList | list, moleculeList (self-ref) |
53
+ | parameterList | parameterList (self-ref) |
54
+ | productList | productList (self-ref) |
55
+ | reactantList | reactantList (self-ref) |
56
+ | reactionList | reactionScheme |
57
+ | reactionScheme | identifier, reactionScheme (self-ref) |
58
+ | reactionStep | reactionScheme |
59
+ | peakGroup | atom, bond, molecule, peakGroup (self-ref) |
60
+ | peakStructure | peakStructure (self-ref) |
61
+ | spectrumList | list, spectrumList (self-ref) |
62
+
63
+ ## Acceptance
64
+
65
+ - Static XSD child gap analysis (accounting for CommonChildren) reports < 20 remaining gaps.
66
+ - All new child declarations have at least one round-trip spec.
@@ -0,0 +1,64 @@
1
+ # 73 — Document Schema24 nested-parse limitation
2
+
3
+ ## Why
4
+
5
+ `Chemicalml::Cml::Schema24::Document.from_xml` fails on any document
6
+ with non-trivial nested content:
7
+
8
+ ```ruby
9
+ Chemicalml::Cml::Schema24::Document.from_xml(
10
+ %(<cml xmlns="http://www.xml-cml.org/schema"><molecule id="m1"/></cml>),
11
+ register: :chemicalml_schema24
12
+ )
13
+ # => Lutaml::Model::InvalidFormatError: Document has no root element.
14
+ ```
15
+
16
+ Empty `<cml/>` parses; any child element triggers the failure.
17
+
18
+ ## Root cause
19
+
20
+ Tracing the failure, the XML parser is invoked twice for nested
21
+ content. The second call receives the *attribute value* (e.g. `"m1"`)
22
+ instead of an XML fragment — indicating lutaml-model's TypeResolver
23
+ is mis-resolving the child type. The error surfaces as
24
+ `Unknown type 'molecule' in context 'default'`.
25
+
26
+ Schema3 does not exhibit this. Both Schema3 and Schema24 register
27
+ `:molecule` in their own contexts; the `chemicalml_schema24` context
28
+ resolves `:molecule` to `Chemicalml::Cml::Schema24::Molecule`
29
+ correctly when inspected directly. The failure is in the
30
+ *attribute cast* path inside `Lutaml::Model::Attribute#cast`, which
31
+ uses a different lookup mechanism that appears to default to the
32
+ `:default` context.
33
+
34
+ This looks like a lutaml-model issue with how `Attribute#cast` resolves
35
+ child types when multiple schema-version contexts are registered
36
+ globally. Investigating further requires changes inside lutaml-model.
37
+
38
+ ## Workaround
39
+
40
+ Schema24 wire classes themselves work correctly when constructed
41
+ directly:
42
+
43
+ ```ruby
44
+ Chemicalml::Cml::Schema24::Molecule.new(id: "m1", atom_array: ...) # works
45
+ Chemicalml::Cml::Schema24::Molecule#to_xml # works
46
+ ```
47
+
48
+ So Schema24 round-trips through *construction → serialize*, just not
49
+ through *parse → re-serialize*. Existing Schema24 specs use direct
50
+ construction; they continue to pass.
51
+
52
+ ## Work
53
+
54
+ 1. Document this limitation in `CLAUDE.md` and in a comment on
55
+ `Schema24::Configuration`.
56
+ 2. Add a `pending: true` spec that asserts the behaviour fails today
57
+ and will catch the regression automatically when lutaml-model is
58
+ fixed.
59
+ 3. File an issue upstream in `lutaml/lutaml-model`.
60
+
61
+ ## Acceptance
62
+
63
+ - CLAUDE.md notes the limitation.
64
+ - Pending spec exists and is marked `pending`.
@@ -0,0 +1,15 @@
1
+ # 74 — Final verification round 18
2
+
3
+ ## Work
4
+
5
+ 1. Full rspec suite — must be green.
6
+ 2. rubocop on touched files — must be clean.
7
+ 3. Forbidden-pattern scan: 0 hits for require_relative, def to_h/from_h/to_xml/from_xml on model classes, double( in specs, send(/instance_variable_*, respond_to? in lib/.
8
+ 4. Update CLAUDE.md with: Detection covers 8 conventions; JSON/YAML round-trip proven; iterative walker; enriched Violation; documented Schema24 limitation.
9
+ 5. Write `TODO.align/README.round18.md` summarising this round.
10
+
11
+ ## Acceptance
12
+
13
+ - All four scans clean.
14
+ - CLAUDE.md current.
15
+ - README.round18.md exists.
@@ -0,0 +1,43 @@
1
+ # 75 — key_value mappings for JSON / YAML wire names
2
+
3
+ ## Why
4
+
5
+ `lutaml-model` supports a `key_value do ... end` block alongside
6
+ `xml do ... end` to declare JSON/YAML wire names. Previously Base
7
+ modules declared only XML mappings, so JSON output used Ruby
8
+ snake_case names (`element_type`, `atom_array`) instead of CML wire
9
+ names (`elementType`, `atomArray`).
10
+
11
+ ## Solution
12
+
13
+ Each `Base::*` module now has an explicit `key_value do ... end`
14
+ block written by hand, mirroring its `xml do ... end` block. The
15
+ two blocks are kept in sync manually — no runtime introspection,
16
+ no DSL, no generation.
17
+
18
+ This is more verbose than the auto-generation approach
19
+ (`Base::WireMappings.auto_key_value_mapping!`) that was tried and
20
+ reverted. The user explicitly preferred manual blocks for clarity
21
+ and explicitness over DRY.
22
+
23
+ ## Work done
24
+
25
+ 1. Generated initial `key_value do ... end` blocks from each
26
+ `Schema3::*` and `Schema24::*` wire class's XML mappings via a
27
+ one-shot script that introspected the loaded classes.
28
+ 2. Inserted each block into the corresponding `Base::*` file right
29
+ after the `xml do ... end` block.
30
+ 3. Removed `Base::WireMappings` module and all
31
+ `auto_key_value_mapping!` calls from wire classes.
32
+ 4. Verified JSON output carries CML wire names (`elementType`,
33
+ `atomArray`, `formalCharge`).
34
+ 5. Verified YAML also carries wire names.
35
+ 6. All 422 specs pass.
36
+
37
+ ## Acceptance
38
+
39
+ - `Atom.new(element_type: "C").to_json` includes `"elementType":"C"`.
40
+ - `Molecule.from_json(json)` round-trips wire names correctly.
41
+ - All existing specs pass.
42
+ - No runtime introspection or auto-generation — every key_value
43
+ block is hand-written in its Base module.
@@ -0,0 +1,57 @@
1
+ # 76 — Schema24 parse bug: ROOT CAUSE FOUND AND FIXED
2
+
3
+ ## Status: FIXED
4
+
5
+ ## Root cause
6
+
7
+ The Schema24 nested-XML parse bug had nothing to do with
8
+ lutaml-model internals. The root cause was a **type-name collision**
9
+ in the Schema24 context registry.
10
+
11
+ Schema 2.4 declares legacy elements `<string>`, `<integer>`,
12
+ `<float>` that share their XML names with lutaml-model primitive
13
+ type names (`:string`, `:integer`, `:float`). The Schema24
14
+ Configuration registered these CML element classes as types in the
15
+ Schema24 context — shadowing the primitives.
16
+
17
+ Every `attribute :foo, :string` cast in a Schema24 wire class then
18
+ resolved `:string` to `Chemicalml::Cml::Schema24::String` (the CML
19
+ element class, a `Lutaml::Model::Serializable`) instead of
20
+ `Lutaml::Model::Type::String` (the primitive, which just casts to
21
+ Ruby String). The Serializable.cast path on a String value tried to
22
+ deserialize it as a CML `<string>` element, which failed.
23
+
24
+ Schema3 was unaffected because Schema 3's XSD doesn't declare
25
+ `<string>`, `<integer>`, `<float>` elements.
26
+
27
+ ## Fix
28
+
29
+ `lib/chemicalml/cml/elements.rb` adds a new constant:
30
+
31
+ ```ruby
32
+ SCHEMA24_TYPE_COLLISIONS = %i[Float Integer String].freeze
33
+ ```
34
+
35
+ `lib/chemicalml/cml/schema24/configuration.rb#register_models!` now
36
+ passes this list as `except:` when registering SCHEMA24_ONLY:
37
+
38
+ ```ruby
39
+ register_elements!(
40
+ only: true,
41
+ except: Chemicalml::Cml::Elements::SCHEMA24_TYPE_COLLISIONS
42
+ )
43
+ ```
44
+
45
+ The CML `<string>`, `<integer>`, `<float>` wire classes remain
46
+ defined and parseable as document roots (via VersionedParser), but
47
+ they are no longer registered as types — so `:string` attribute
48
+ casts resolve correctly to the primitive.
49
+
50
+ ## Verification
51
+
52
+ - `Chemicalml::Cml::Schema24::Document.from_xml` now parses nested
53
+ content correctly.
54
+ - `spec/chemicalml/cml/schema24_nested_parse_spec.rb` has 3 passing
55
+ tests including one with parallel-array atomArray.
56
+ - Full suite: 422 examples, 0 failures (previously 2 pending — both
57
+ now pass).
@@ -0,0 +1,31 @@
1
+ # 77 — Convention coverage matrix spec
2
+
3
+ ## Why
4
+
5
+ The gem registers 8 conventions, each with multiple constraints.
6
+ Currently each convention has its own spec file, but there is no
7
+ single source of truth asserting that every convention:
8
+
9
+ - is in the Registry
10
+ - is detectable via at least one root role
11
+ - has at least one violation case for at least one of its constraints
12
+ - has at least one passing-case example
13
+
14
+ A matrix spec catches gaps when adding a new convention (the new
15
+ convention's matrix entry fails until it has specs).
16
+
17
+ ## Work
18
+
19
+ Add `spec/chemicalml/convention/coverage_matrix_spec.rb` that:
20
+
21
+ 1. Iterates `Convention::Registry.builtin_qnames`.
22
+ 2. For each, asserts the convention is detectable from its declared
23
+ root role.
24
+ 3. For each constraint class registered against the convention,
25
+ asserts a spec file exists that exercises it (grep for the class
26
+ name in `spec/`).
27
+
28
+ ## Acceptance
29
+
30
+ - Matrix spec passes for all 8 conventions.
31
+ - Adding a 9th convention without specs makes the matrix spec fail.
@@ -0,0 +1,23 @@
1
+ # 78 — Fixture coverage audit
2
+
3
+ ## Why
4
+
5
+ `spec/fixtures/` holds CML examples. Current round-trip specs cover a
6
+ subset. After recent additions (parallel-array attrs, CommonChildren,
7
+ 8 conventions), some features may have no fixture exercising them.
8
+
9
+ ## Work
10
+
11
+ Audit `spec/fixtures/`:
12
+
13
+ 1. Identify fixtures that exercise parallel-array atomArray/bondArray.
14
+ 2. Identify fixtures covering each convention.
15
+ 3. Identify fixtures using new child elements (reaction.mechanism,
16
+ reactiveCentre.atomSet, etc.).
17
+ 4. For features with no fixture, add one under `spec/fixtures/`.
18
+
19
+ ## Acceptance
20
+
21
+ - Every convention has at least one fixture.
22
+ - Parallel-array form has at least one fixture.
23
+ - Schema24-only legacy elements have at least one fixture.
@@ -0,0 +1,15 @@
1
+ # 79 — Final verification round 19
2
+
3
+ ## Work
4
+
5
+ 1. Full rspec — must be green (or pending for documented limitations).
6
+ 2. rubocop on touched files.
7
+ 3. Forbidden-pattern scan.
8
+ 4. Update CLAUDE.md noting key_value support and JSON wire names.
9
+ 5. Write `TODO.align/README.round19.md`.
10
+
11
+ ## Acceptance
12
+
13
+ - All four scans clean.
14
+ - CLAUDE.md current.
15
+ - README.round19.md exists.
@@ -0,0 +1,35 @@
1
+ # 80 — XSD enum constants
2
+
3
+ ## Why
4
+
5
+ The XSD declares ~30 simpleTypes that restrict to enumeration sets
6
+ (orderType, stereoType, chiralityType, latticeType, matrixType,
7
+ stateType, peakMultiplicityType, etc.). We model every attribute as
8
+ `:string` and accept any value.
9
+
10
+ Without a single source of truth for the allowed values, downstream
11
+ callers that want to validate CML have to scrape the XSD themselves
12
+ or hard-code the enums (which drift from the schema).
13
+
14
+ ## Solution
15
+
16
+ `Chemicalml::Cml::Enums` — frozen-set constants for each XSD enum
17
+ simpleType. The constants are the canonical Ruby source of truth
18
+ for "what values does this attribute accept".
19
+
20
+ ## Work
21
+
22
+ 1. Create `lib/chemicalml/cml/enums.rb` with one constant per XSD
23
+ enum simpleType. Names match the XSD type name with `Type`
24
+ suffix stripped (e.g. `ORDER_VALUES` for `orderType`).
25
+ 2. Each constant is a frozen Set of allowed string values.
26
+ 3. Add specs asserting the constants match the XSD exactly.
27
+
28
+ ## Acceptance
29
+
30
+ - `Chemicalml::Cml::Enums::ORDER_VALUES` includes `"S"`, `"D"`, `"T"`,
31
+ `"A"`, etc.
32
+ - `Chemicalml::Cml::Enums::STEREO_VALUES` includes `"C"`, `"T"`, `"W"`,
33
+ `"H"`.
34
+ - Constants are frozen.
35
+ - Generated from the XSD (no hand-maintenance).
@@ -0,0 +1,29 @@
1
+ # 81 — Enum validation constraints
2
+
3
+ ## Why
4
+
5
+ With enum constants defined (TODO 80), add warning-severity
6
+ constraints that flag attribute values outside the XSD-declared
7
+ enum set. Warning (not error) because CML allows extension values.
8
+
9
+ ## Work
10
+
11
+ Add constraints under the molecular convention (where most enum
12
+ attributes live):
13
+
14
+ 1. `BondOrderShouldBeInEnum` (applies_to Role::Bond) — warning if
15
+ `bond.order` not in `Cml::Enums::ORDER_VALUES`.
16
+ 2. `BondStereoShouldBeInEnum` (applies_to Role::BondStereo) —
17
+ warning if `bondStereo.value` not in `Cml::Enums::STEREO_VALUES`.
18
+ 3. `MoleculeChiralityShouldBeInEnum` (applies_to Role::Molecule) —
19
+ warning if `molecule.chirality` not in `Cml::Enums::CHIRALITY_VALUES`.
20
+ 4. `LatticeTypeShouldBeInEnum` (applies_to Role::Lattice) —
21
+ warning if not in `LATTICE_VALUES`.
22
+
23
+ Each constraint is registered against the molecular convention.
24
+
25
+ ## Acceptance
26
+
27
+ - A bond with `order="X"` triggers a warning.
28
+ - A bond with `order="S"` passes silently.
29
+ - All existing specs pass.
@@ -0,0 +1,33 @@
1
+ # 82 — Reference resolver
2
+
3
+ ## Why
4
+
5
+ CML documents reference atoms/bonds/molecules by id via attributes
6
+ like `atomRefs2`, `atomRefs4`, `bondRefs`, `moleculeRefs`, `ref`.
7
+ Currently callers must walk the document themselves to resolve
8
+ these to actual instances.
9
+
10
+ ## Solution
11
+
12
+ `Chemicalml::Cml::ReferenceResolver` — walks a document, builds an
13
+ id → node index, then exposes lookup methods.
14
+
15
+ ## Work
16
+
17
+ 1. Create `lib/chemicalml/cml/reference_resolver.rb`.
18
+ 2. API:
19
+ ```ruby
20
+ resolver = Chemicalml::Cml::ReferenceResolver.new(document)
21
+ resolver.find_atom(molecule, "a1") # → Atom or nil
22
+ resolver.resolve_atom_refs2(bond) # → [Atom, Atom] or nils
23
+ resolver.unresolved_refs(document) # → [{ source: Bond, attr: :atom_refs2, missing: ["a99"] }]
24
+ ```
25
+ 3. Add a constraint `ReferencesShouldResolve` (warning severity)
26
+ that lists unresolved refs in a document.
27
+ 4. Specs covering resolve + unresolved detection.
28
+
29
+ ## Acceptance
30
+
31
+ - Resolver resolves a valid atomRefs2 to actual atoms.
32
+ - `unresolved_refs` returns missing atom ids.
33
+ - All existing specs pass.
@@ -0,0 +1,20 @@
1
+ # 83 — TOML round-trip spec
2
+
3
+ ## Why
4
+
5
+ lutaml-model supports TOML as a key-value format. CLAUDE.md claims
6
+ format-agnosticism — XML/JSON/YAML are tested. Add TOML to complete
7
+ the set.
8
+
9
+ ## Work
10
+
11
+ Add `spec/chemicalml/cml/toml_roundtrip_spec.rb` that round-trips
12
+ a Molecule through `to_toml` / `from_toml`. TOML's flat key/value
13
+ structure means nested objects get flattened — verify the same
14
+ data survives.
15
+
16
+ ## Acceptance
17
+
18
+ - `Molecule#to_toml` produces a TOML string.
19
+ - `Molecule.from_toml(toml)` returns a Molecule with the same id
20
+ and atom data.
@@ -0,0 +1,16 @@
1
+ # 84 — Final verification round 21
2
+
3
+ ## Work
4
+
5
+ 1. Full rspec — must be green.
6
+ 2. rubocop on touched files.
7
+ 3. Forbidden-pattern scan.
8
+ 4. Update CLAUDE.md noting enum validation, reference resolver,
9
+ TOML round-trip.
10
+ 5. Write `TODO.align/README.round21.md`.
11
+
12
+ ## Acceptance
13
+
14
+ - All scans clean.
15
+ - CLAUDE.md current.
16
+ - README.round21.md exists.
@@ -0,0 +1,25 @@
1
+ # 85 — Bond atomRefs2 distinct constraint
2
+
3
+ ## Why
4
+
5
+ The molecular spec at `reference-docs/conventions/molecular.md` says:
6
+
7
+ > `atomRefs2` — REQUIRED, two distinct atom ids in same molecule
8
+
9
+ No current constraint enforces distinctness. A `<bond atomRefs2="a1 a1"/>`
10
+ (zero-length self-bond) is silently accepted.
11
+
12
+ ## Work
13
+
14
+ Add `Molecular::Constraints::BondAtomRefs2ShouldBeDistinct` (warning
15
+ severity — there are edge cases in non-classical chemistry). The
16
+ constraint parses `node.atom_refs2`, splits on whitespace, and warns
17
+ if the two ids are identical.
18
+
19
+ Register against molecular convention.
20
+
21
+ ## Acceptance
22
+
23
+ - `<bond atomRefs2="a1 a1"/>` triggers a warning.
24
+ - `<bond atomRefs2="a1 a2"/>` passes silently.
25
+ - All existing specs pass.
@@ -0,0 +1,20 @@
1
+ # 86 — References-should-resolve constraint
2
+
3
+ ## Why
4
+
5
+ With `Cml::ReferenceResolver` (TODO 82), we can now detect bonds
6
+ whose `atomRefs2` reference atoms that don't exist in the parent
7
+ molecule. Currently no constraint flags this — typos like
8
+ `atomRefs2="a1 a99"` (where `a99` doesn't exist) pass silently.
9
+
10
+ ## Work
11
+
12
+ Add `Molecular::Constraints::ReferencesShouldResolve` as a
13
+ `DocumentConstraint`. It instantiates a `ReferenceResolver` and
14
+ reports each unresolved reference as a warning violation.
15
+
16
+ ## Acceptance
17
+
18
+ - A bond referencing a missing atom triggers a warning.
19
+ - A bond referencing existing atoms passes silently.
20
+ - All existing specs pass.
@@ -0,0 +1,26 @@
1
+ # 87 — CLI utility for validation
2
+
3
+ ## Why
4
+
5
+ A user-facing CLI makes the gem immediately useful for command-line
6
+ validation of CML files. Without a CLI, users have to write Ruby
7
+ to use the gem.
8
+
9
+ ## Work
10
+
11
+ Create `exe/chemicalml` (executable) with subcommands:
12
+
13
+ - `chemicalml validate <file>` — auto-detect convention, print
14
+ violations to stderr, exit non-zero if errors.
15
+ - `chemicalml conventions` — list registered conventions.
16
+ - `chemicalml dictionaries` — list builtin dictionaries.
17
+
18
+ Wire it through `Chemicalml::Cli` module under
19
+ `lib/chemicalml/cli.rb` so the logic is reusable.
20
+
21
+ ## Acceptance
22
+
23
+ - `bundle exec chemicalml validate spec/fixtures/schema3/molecular/water.cml`
24
+ parses, detects convention:molecular, prints nothing, exits 0.
25
+ - `chemicalml conventions` lists all 8 conventions.
26
+ - `chemicalml dictionaries` lists all built-in dictionaries.