chemicalml 0.1.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 (248) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rubocop.yml +20 -0
  4. data/CHANGELOG.md +21 -0
  5. data/CLAUDE.md +198 -0
  6. data/Gemfile +14 -0
  7. data/LICENSE +24 -0
  8. data/NOTICES.adoc +115 -0
  9. data/README.adoc +87 -0
  10. data/Rakefile +8 -0
  11. data/TODO.align/01-cml-base-mixins.md +122 -0
  12. data/TODO.align/02-real-schema-hierarchies.md +88 -0
  13. data/TODO.align/03-configuration.md +50 -0
  14. data/TODO.align/04-parse-entry-points.md +59 -0
  15. data/TODO.align/05-schema-aware-translator.md +33 -0
  16. data/TODO.align/06-no-respond-to.md +44 -0
  17. data/TODO.align/07-canon-xml-comparison.md +43 -0
  18. data/TODO.align/08-expand-fixtures.md +37 -0
  19. data/TODO.align/09-final-spec-lint.md +29 -0
  20. data/TODO.align/10-role-marker-modules.md +54 -0
  21. data/TODO.align/11-dry-configuration.md +63 -0
  22. data/TODO.align/12-wire-class-macro.md +51 -0
  23. data/TODO.align/13-wire-namespace.md +44 -0
  24. data/TODO.align/14-schema24-correctness.md +37 -0
  25. data/TODO.align/15-canon-fixture-roundtrip.md +38 -0
  26. data/TODO.align/16-schema24-convention-coverage.md +33 -0
  27. data/TODO.align/17-final-spec-lint.md +28 -0
  28. data/TODO.align/README.md +27 -0
  29. data/TODO.align/README.round2.md +24 -0
  30. data/TODO.cml-full/01-reference-docs.md +43 -0
  31. data/TODO.cml-full/02-schema-versioned-model.md +70 -0
  32. data/TODO.cml-full/03-fixtures-scrape.md +52 -0
  33. data/TODO.cml-full/04-convention-framework.md +69 -0
  34. data/TODO.cml-full/05-dictionary-layer.md +77 -0
  35. data/TODO.cml-full/06-extend-canonical-translator.md +60 -0
  36. data/TODO.cml-full/07-attribution-notices.md +42 -0
  37. data/TODO.cml-full/08-autoload-only.md +48 -0
  38. data/TODO.cml-full/09-spec-coverage.md +38 -0
  39. data/TODO.cml-full/README.md +23 -0
  40. data/chemicalml.gemspec +35 -0
  41. data/data/dictionaries/_index.yaml +14 -0
  42. data/data/dictionaries/compchem.yaml +481 -0
  43. data/data/dictionaries/unit_non_si.yaml +53 -0
  44. data/data/dictionaries/unit_si.yaml +87 -0
  45. data/data/dictionaries/unit_type.yaml +113 -0
  46. data/lib/chemicalml/cml/array.rb +10 -0
  47. data/lib/chemicalml/cml/atom.rb +10 -0
  48. data/lib/chemicalml/cml/atom_array.rb +10 -0
  49. data/lib/chemicalml/cml/atom_parity.rb +10 -0
  50. data/lib/chemicalml/cml/base/array.rb +36 -0
  51. data/lib/chemicalml/cml/base/atom.rb +38 -0
  52. data/lib/chemicalml/cml/base/atom_array.rb +22 -0
  53. data/lib/chemicalml/cml/base/atom_parity.rb +24 -0
  54. data/lib/chemicalml/cml/base/bond.rb +28 -0
  55. data/lib/chemicalml/cml/base/bond_array.rb +22 -0
  56. data/lib/chemicalml/cml/base/bond_stereo.rb +28 -0
  57. data/lib/chemicalml/cml/base/cml_module.rb +40 -0
  58. data/lib/chemicalml/cml/base/dictionary.rb +32 -0
  59. data/lib/chemicalml/cml/base/dictionary_entry.rb +34 -0
  60. data/lib/chemicalml/cml/base/document.rb +26 -0
  61. data/lib/chemicalml/cml/base/formula.rb +38 -0
  62. data/lib/chemicalml/cml/base/identifier.rb +26 -0
  63. data/lib/chemicalml/cml/base/label.rb +28 -0
  64. data/lib/chemicalml/cml/base/list.rb +36 -0
  65. data/lib/chemicalml/cml/base/matrix.rb +38 -0
  66. data/lib/chemicalml/cml/base/metadata.rb +30 -0
  67. data/lib/chemicalml/cml/base/metadata_list.rb +28 -0
  68. data/lib/chemicalml/cml/base/molecule.rb +40 -0
  69. data/lib/chemicalml/cml/base/name.rb +26 -0
  70. data/lib/chemicalml/cml/base/parameter.rb +34 -0
  71. data/lib/chemicalml/cml/base/parameter_list.rb +28 -0
  72. data/lib/chemicalml/cml/base/product.rb +22 -0
  73. data/lib/chemicalml/cml/base/product_list.rb +22 -0
  74. data/lib/chemicalml/cml/base/property.rb +34 -0
  75. data/lib/chemicalml/cml/base/property_list.rb +28 -0
  76. data/lib/chemicalml/cml/base/reactant.rb +22 -0
  77. data/lib/chemicalml/cml/base/reactant_list.rb +22 -0
  78. data/lib/chemicalml/cml/base/reaction.rb +32 -0
  79. data/lib/chemicalml/cml/base/reaction_list.rb +22 -0
  80. data/lib/chemicalml/cml/base/scalar.rb +32 -0
  81. data/lib/chemicalml/cml/base/substance.rb +30 -0
  82. data/lib/chemicalml/cml/base/unit.rb +38 -0
  83. data/lib/chemicalml/cml/base/unit_list.rb +30 -0
  84. data/lib/chemicalml/cml/base/unit_type.rb +28 -0
  85. data/lib/chemicalml/cml/base/unit_type_list.rb +30 -0
  86. data/lib/chemicalml/cml/base.rb +51 -0
  87. data/lib/chemicalml/cml/bond.rb +10 -0
  88. data/lib/chemicalml/cml/bond_array.rb +10 -0
  89. data/lib/chemicalml/cml/bond_stereo.rb +10 -0
  90. data/lib/chemicalml/cml/cml_module.rb +10 -0
  91. data/lib/chemicalml/cml/dictionary.rb +10 -0
  92. data/lib/chemicalml/cml/dictionary_entry.rb +10 -0
  93. data/lib/chemicalml/cml/document.rb +10 -0
  94. data/lib/chemicalml/cml/elements.rb +58 -0
  95. data/lib/chemicalml/cml/formula.rb +10 -0
  96. data/lib/chemicalml/cml/identifier.rb +10 -0
  97. data/lib/chemicalml/cml/label.rb +10 -0
  98. data/lib/chemicalml/cml/list.rb +10 -0
  99. data/lib/chemicalml/cml/matrix.rb +10 -0
  100. data/lib/chemicalml/cml/metadata.rb +10 -0
  101. data/lib/chemicalml/cml/metadata_list.rb +10 -0
  102. data/lib/chemicalml/cml/molecule.rb +10 -0
  103. data/lib/chemicalml/cml/name.rb +10 -0
  104. data/lib/chemicalml/cml/namespace.rb +17 -0
  105. data/lib/chemicalml/cml/parameter.rb +10 -0
  106. data/lib/chemicalml/cml/parameter_list.rb +10 -0
  107. data/lib/chemicalml/cml/product.rb +10 -0
  108. data/lib/chemicalml/cml/product_list.rb +10 -0
  109. data/lib/chemicalml/cml/property.rb +10 -0
  110. data/lib/chemicalml/cml/property_list.rb +10 -0
  111. data/lib/chemicalml/cml/reactant.rb +10 -0
  112. data/lib/chemicalml/cml/reactant_list.rb +10 -0
  113. data/lib/chemicalml/cml/reaction.rb +10 -0
  114. data/lib/chemicalml/cml/reaction_list.rb +10 -0
  115. data/lib/chemicalml/cml/role/array.rb +13 -0
  116. data/lib/chemicalml/cml/role/atom.rb +13 -0
  117. data/lib/chemicalml/cml/role/atom_array.rb +13 -0
  118. data/lib/chemicalml/cml/role/atom_parity.rb +13 -0
  119. data/lib/chemicalml/cml/role/bond.rb +13 -0
  120. data/lib/chemicalml/cml/role/bond_array.rb +13 -0
  121. data/lib/chemicalml/cml/role/bond_stereo.rb +13 -0
  122. data/lib/chemicalml/cml/role/cml_module.rb +14 -0
  123. data/lib/chemicalml/cml/role/dictionary.rb +13 -0
  124. data/lib/chemicalml/cml/role/dictionary_entry.rb +13 -0
  125. data/lib/chemicalml/cml/role/document.rb +13 -0
  126. data/lib/chemicalml/cml/role/formula.rb +13 -0
  127. data/lib/chemicalml/cml/role/identifier.rb +13 -0
  128. data/lib/chemicalml/cml/role/label.rb +13 -0
  129. data/lib/chemicalml/cml/role/list.rb +13 -0
  130. data/lib/chemicalml/cml/role/matrix.rb +13 -0
  131. data/lib/chemicalml/cml/role/metadata.rb +13 -0
  132. data/lib/chemicalml/cml/role/metadata_list.rb +13 -0
  133. data/lib/chemicalml/cml/role/molecule.rb +13 -0
  134. data/lib/chemicalml/cml/role/name.rb +13 -0
  135. data/lib/chemicalml/cml/role/parameter.rb +13 -0
  136. data/lib/chemicalml/cml/role/parameter_list.rb +13 -0
  137. data/lib/chemicalml/cml/role/product.rb +13 -0
  138. data/lib/chemicalml/cml/role/product_list.rb +13 -0
  139. data/lib/chemicalml/cml/role/property.rb +13 -0
  140. data/lib/chemicalml/cml/role/property_list.rb +13 -0
  141. data/lib/chemicalml/cml/role/reactant.rb +13 -0
  142. data/lib/chemicalml/cml/role/reactant_list.rb +13 -0
  143. data/lib/chemicalml/cml/role/reaction.rb +13 -0
  144. data/lib/chemicalml/cml/role/reaction_list.rb +13 -0
  145. data/lib/chemicalml/cml/role/scalar.rb +13 -0
  146. data/lib/chemicalml/cml/role/substance.rb +13 -0
  147. data/lib/chemicalml/cml/role/unit.rb +13 -0
  148. data/lib/chemicalml/cml/role/unit_list.rb +13 -0
  149. data/lib/chemicalml/cml/role/unit_type.rb +13 -0
  150. data/lib/chemicalml/cml/role/unit_type_list.rb +13 -0
  151. data/lib/chemicalml/cml/role.rb +54 -0
  152. data/lib/chemicalml/cml/scalar.rb +10 -0
  153. data/lib/chemicalml/cml/schema24/configuration.rb +19 -0
  154. data/lib/chemicalml/cml/schema24.rb +40 -0
  155. data/lib/chemicalml/cml/schema3/configuration.rb +17 -0
  156. data/lib/chemicalml/cml/schema3.rb +40 -0
  157. data/lib/chemicalml/cml/substance.rb +10 -0
  158. data/lib/chemicalml/cml/translator/value_translations.rb +265 -0
  159. data/lib/chemicalml/cml/translator.rb +233 -0
  160. data/lib/chemicalml/cml/unit.rb +10 -0
  161. data/lib/chemicalml/cml/unit_list.rb +10 -0
  162. data/lib/chemicalml/cml/unit_type.rb +10 -0
  163. data/lib/chemicalml/cml/unit_type_list.rb +10 -0
  164. data/lib/chemicalml/cml/visitable.rb +65 -0
  165. data/lib/chemicalml/cml/wire_class_macro.rb +45 -0
  166. data/lib/chemicalml/cml.rb +55 -0
  167. data/lib/chemicalml/context_configuration.rb +127 -0
  168. data/lib/chemicalml/convention/base.rb +48 -0
  169. data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +33 -0
  170. data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +38 -0
  171. data/lib/chemicalml/convention/compchem/constraints.rb +14 -0
  172. data/lib/chemicalml/convention/compchem.rb +28 -0
  173. data/lib/chemicalml/convention/constraint.rb +79 -0
  174. data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +33 -0
  175. data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +34 -0
  176. data/lib/chemicalml/convention/dictionary/constraints.rb +14 -0
  177. data/lib/chemicalml/convention/dictionary.rb +28 -0
  178. data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +30 -0
  179. data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +42 -0
  180. data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +54 -0
  181. data/lib/chemicalml/convention/molecular/constraints.rb +19 -0
  182. data/lib/chemicalml/convention/molecular.rb +29 -0
  183. data/lib/chemicalml/convention/registry.rb +50 -0
  184. data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +46 -0
  185. data/lib/chemicalml/convention/unit_dictionary/constraints.rb +12 -0
  186. data/lib/chemicalml/convention/unit_dictionary.rb +27 -0
  187. data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +34 -0
  188. data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +12 -0
  189. data/lib/chemicalml/convention/unit_type_dictionary.rb +27 -0
  190. data/lib/chemicalml/convention/violation.rb +36 -0
  191. data/lib/chemicalml/convention.rb +39 -0
  192. data/lib/chemicalml/dictionary/entry.rb +59 -0
  193. data/lib/chemicalml/dictionary/enum.rb +51 -0
  194. data/lib/chemicalml/dictionary/link.rb +32 -0
  195. data/lib/chemicalml/dictionary/loader.rb +60 -0
  196. data/lib/chemicalml/dictionary/model.rb +64 -0
  197. data/lib/chemicalml/dictionary/registry.rb +75 -0
  198. data/lib/chemicalml/dictionary.rb +26 -0
  199. data/lib/chemicalml/errors.rb +7 -0
  200. data/lib/chemicalml/model/atom.rb +40 -0
  201. data/lib/chemicalml/model/bond.rb +33 -0
  202. data/lib/chemicalml/model/cml_array.rb +27 -0
  203. data/lib/chemicalml/model/cml_module.rb +45 -0
  204. data/lib/chemicalml/model/document.rb +29 -0
  205. data/lib/chemicalml/model/formula.rb +34 -0
  206. data/lib/chemicalml/model/identifier.rb +21 -0
  207. data/lib/chemicalml/model/label.rb +21 -0
  208. data/lib/chemicalml/model/matrix.rb +29 -0
  209. data/lib/chemicalml/model/metadata.rb +23 -0
  210. data/lib/chemicalml/model/metadata_list.rb +25 -0
  211. data/lib/chemicalml/model/molecule.rb +38 -0
  212. data/lib/chemicalml/model/name.rb +21 -0
  213. data/lib/chemicalml/model/node.rb +45 -0
  214. data/lib/chemicalml/model/parameter.rb +27 -0
  215. data/lib/chemicalml/model/parameter_list.rb +25 -0
  216. data/lib/chemicalml/model/product.rb +21 -0
  217. data/lib/chemicalml/model/product_list.rb +21 -0
  218. data/lib/chemicalml/model/property.rb +28 -0
  219. data/lib/chemicalml/model/property_list.rb +25 -0
  220. data/lib/chemicalml/model/reactant.rb +21 -0
  221. data/lib/chemicalml/model/reactant_list.rb +21 -0
  222. data/lib/chemicalml/model/reaction.rb +41 -0
  223. data/lib/chemicalml/model/reaction_list.rb +23 -0
  224. data/lib/chemicalml/model/scalar.rb +25 -0
  225. data/lib/chemicalml/model/substance.rb +25 -0
  226. data/lib/chemicalml/model.rb +38 -0
  227. data/lib/chemicalml/schema/definition.rb +41 -0
  228. data/lib/chemicalml/schema/registry.rb +30 -0
  229. data/lib/chemicalml/schema.rb +35 -0
  230. data/lib/chemicalml/version.rb +5 -0
  231. data/lib/chemicalml/versioned_parser.rb +43 -0
  232. data/lib/chemicalml.rb +47 -0
  233. data/lib/tasks/reference_fetch.rake +105 -0
  234. data/lib/tasks/scrape_fixtures.rake +133 -0
  235. data/reference-docs/conventions/compchem.md +141 -0
  236. data/reference-docs/conventions/dictionary.md +119 -0
  237. data/reference-docs/conventions/index.md +56 -0
  238. data/reference-docs/conventions/molecular.md +115 -0
  239. data/reference-docs/conventions/unit-dictionary.md +92 -0
  240. data/reference-docs/conventions/unitType-dictionary.md +51 -0
  241. data/reference-docs/dictionaries/compchem.md +335 -0
  242. data/reference-docs/dictionaries/index.md +52 -0
  243. data/reference-docs/examples/schema24-index.md +50 -0
  244. data/reference-docs/examples/schema3-compchem-index.md +14 -0
  245. data/reference-docs/examples/schema3-molecular-index.md +28 -0
  246. data/reference-docs/schemas/schema24/schema.xsd +10624 -0
  247. data/reference-docs/schemas/schema3/schema.xsd +11459 -0
  248. metadata +306 -0
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ # CML model layer. The wire classes live under `Schema3::` and
5
+ # `Schema24::` namespaces; the top-level `Cml::*` constants are
6
+ # backward-compatible aliases pointing at the Schema3 versions.
7
+ # Shared attribute + xml-mapping declarations live under `Base::*`
8
+ # and are included by both schema-versioned class hierarchies.
9
+ module Cml
10
+ autoload :Array, "chemicalml/cml/array"
11
+ autoload :Atom, "chemicalml/cml/atom"
12
+ autoload :AtomArray, "chemicalml/cml/atom_array"
13
+ autoload :AtomParity, "chemicalml/cml/atom_parity"
14
+ autoload :Base, "chemicalml/cml/base"
15
+ autoload :Bond, "chemicalml/cml/bond"
16
+ autoload :BondArray, "chemicalml/cml/bond_array"
17
+ autoload :BondStereo, "chemicalml/cml/bond_stereo"
18
+ autoload :Dictionary, "chemicalml/cml/dictionary"
19
+ autoload :DictionaryEntry, "chemicalml/cml/dictionary_entry"
20
+ autoload :Document, "chemicalml/cml/document"
21
+ autoload :Elements, "chemicalml/cml/elements"
22
+ autoload :Formula, "chemicalml/cml/formula"
23
+ autoload :Identifier, "chemicalml/cml/identifier"
24
+ autoload :Label, "chemicalml/cml/label"
25
+ autoload :List, "chemicalml/cml/list"
26
+ autoload :Matrix, "chemicalml/cml/matrix"
27
+ autoload :Metadata, "chemicalml/cml/metadata"
28
+ autoload :MetadataList, "chemicalml/cml/metadata_list"
29
+ autoload :Module, "chemicalml/cml/cml_module"
30
+ autoload :Molecule, "chemicalml/cml/molecule"
31
+ autoload :Name, "chemicalml/cml/name"
32
+ autoload :Namespace, "chemicalml/cml/namespace"
33
+ autoload :Parameter, "chemicalml/cml/parameter"
34
+ autoload :ParameterList, "chemicalml/cml/parameter_list"
35
+ autoload :Product, "chemicalml/cml/product"
36
+ autoload :ProductList, "chemicalml/cml/product_list"
37
+ autoload :Property, "chemicalml/cml/property"
38
+ autoload :PropertyList, "chemicalml/cml/property_list"
39
+ autoload :Reaction, "chemicalml/cml/reaction"
40
+ autoload :ReactionList, "chemicalml/cml/reaction_list"
41
+ autoload :Reactant, "chemicalml/cml/reactant"
42
+ autoload :ReactantList, "chemicalml/cml/reactant_list"
43
+ autoload :Role, "chemicalml/cml/role"
44
+ autoload :Scalar, "chemicalml/cml/scalar"
45
+ autoload :Schema3, "chemicalml/cml/schema3"
46
+ autoload :Schema24, "chemicalml/cml/schema24"
47
+ autoload :Substance, "chemicalml/cml/substance"
48
+ autoload :Translator, "chemicalml/cml/translator"
49
+ autoload :Unit, "chemicalml/cml/unit"
50
+ autoload :UnitList, "chemicalml/cml/unit_list"
51
+ autoload :UnitType, "chemicalml/cml/unit_type"
52
+ autoload :UnitTypeList, "chemicalml/cml/unit_type_list"
53
+ autoload :Visitable, "chemicalml/cml/visitable"
54
+ end
55
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ # Shared behaviour for versioned schema configurations. Each schema
5
+ # version (`Cml::Schema3`, `Cml::Schema24`) extends this module via
6
+ # its own `Configuration` sub-module, giving it:
7
+ #
8
+ # - `context_id` — the lutaml-model `GlobalContext` id
9
+ # - `register_model(klass, id:)` — record one wire class
10
+ # - `populate_context!` — rebuild the type registry on demand
11
+ # - `ensure_registered!` — lazily populate on first parse
12
+ #
13
+ # Mirrors `Mml::ContextConfiguration`. Adapted to Chemicalml's
14
+ # schema-registry terminology.
15
+ module ContextConfiguration
16
+ def context_id
17
+ self::CONTEXT_ID
18
+ end
19
+
20
+ def context
21
+ Lutaml::Model::GlobalContext.context(context_id)
22
+ end
23
+
24
+ def ensure_registered!
25
+ return if @models_registered
26
+
27
+ register_models!
28
+ @models_registered = true
29
+ end
30
+
31
+ # Default: subclasses override with a centralized registration
32
+ # block (V4-style). The fallback eager-loads every autoloaded
33
+ # constant on the parent module.
34
+ def register_models!
35
+ parent = version_parent_module
36
+ parent.constants.each { |name| parent.const_get(name) }
37
+ end
38
+
39
+ # Walk the shared `Chemicalml::Cml::Elements::ALL` table, look up
40
+ # each class on the parent (schema) module, and register it. Pass
41
+ # `except:` to skip specific elements (used by Schema 2.4 which
42
+ # lacks `<module>`).
43
+ def register_elements!(except: [])
44
+ parent = version_parent_module
45
+ skip = except.to_set
46
+ Chemicalml::Cml::Elements::ALL.each do |class_name, element_id|
47
+ next if skip.include?(class_name)
48
+ next unless parent.const_defined?(class_name)
49
+
50
+ register_model(parent.const_get(class_name), id: element_id)
51
+ end
52
+ end
53
+
54
+ def register_model(klass, id:)
55
+ normalized_id = id.to_sym
56
+ registered_models[normalized_id] = klass
57
+ target_context = context || create_base_context
58
+ target_context.registry.register(normalized_id, klass)
59
+ klass
60
+ end
61
+
62
+ def populate_context!
63
+ ensure_version_registered
64
+ Lutaml::Model::GlobalContext.unregister_context(context_id) if context
65
+ register_models_in(base_type_context)
66
+ end
67
+
68
+ def create_context(id:, registry: nil, fallback_to: [context_id])
69
+ normalized_id = id.to_sym
70
+ ensure_version_registered
71
+ Lutaml::Model::GlobalContext.unregister_context(normalized_id) if Lutaml::Model::GlobalContext.context(normalized_id)
72
+ Lutaml::Model::GlobalContext.create_context(
73
+ id: normalized_id,
74
+ registry: registry || Lutaml::Model::TypeRegistry.new,
75
+ fallback_to: Array(fallback_to).map(&:to_sym)
76
+ ).tap { Lutaml::Model::GlobalContext.clear_caches }
77
+ end
78
+
79
+ def default_context_id
80
+ context_id
81
+ end
82
+
83
+ private
84
+
85
+ def registered_models
86
+ @registered_models ||= {}
87
+ end
88
+
89
+ def create_base_context
90
+ Lutaml::Model::GlobalContext.create_context(
91
+ id: context_id,
92
+ registry: Lutaml::Model::TypeRegistry.new,
93
+ fallback_to: [:default]
94
+ )
95
+ end
96
+
97
+ def base_type_context
98
+ create_base_context
99
+ end
100
+
101
+ def register_models_in(type_context)
102
+ registered_models.each do |model_id, klass|
103
+ type_context.registry.register(model_id, klass)
104
+ end
105
+ Lutaml::Model::GlobalContext.clear_caches
106
+ type_context
107
+ end
108
+
109
+ def ensure_version_registered
110
+ parent = version_parent_module
111
+ return unless parent
112
+
113
+ parent.ensure_registered!
114
+ end
115
+
116
+ def version_parent_module
117
+ return nil unless name
118
+
119
+ parent_const_name = name.split("::")[0..-2].join("::")
120
+ return nil if parent_const_name.empty?
121
+
122
+ Object.const_get(parent_const_name)
123
+ rescue NameError
124
+ nil
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # Mixin that gives a convention module its registry behavior:
6
+ #
7
+ # - `qname` / `namespace_uri` (must be implemented by the host)
8
+ # - `constraints` (lazy array)
9
+ # - `register(SomeConstraintClass)`
10
+ # - `validate(document)` -> [Violation, ...]
11
+ #
12
+ # The framework never inspects which constraints a convention has;
13
+ # it just walks whatever was registered. Adding a new rule = a new
14
+ # constraint subclass + a `register` call.
15
+ module Base
16
+ def qname
17
+ raise NotImplementedError, "#{name} must define its QName"
18
+ end
19
+
20
+ def namespace_uri
21
+ raise NotImplementedError, "#{name} must define its namespace URI"
22
+ end
23
+
24
+ def constraints
25
+ @constraints ||= []
26
+ end
27
+
28
+ def register(constraint_class)
29
+ constraints << constraint_class unless constraints.include?(constraint_class)
30
+ self
31
+ end
32
+
33
+ def validate(document)
34
+ constraints.flat_map do |klass|
35
+ klass.new.check(document)
36
+ end
37
+ end
38
+
39
+ def constraint_count
40
+ constraints.length
41
+ end
42
+
43
+ def reset_constraints!
44
+ @constraints = []
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Compchem
6
+ module Constraints
7
+ # A CompChem module MUST contain at least one jobList module
8
+ # child per the compchem convention.
9
+ class CompchemModuleMustContainJobList < Chemicalml::Convention::Constraint::NodeConstraint
10
+ def check_node(node, path)
11
+ return [] unless compchem_root?(node)
12
+
13
+ job_lists = node.modules.select { |m| m.dict_ref == "compchem:jobList" }
14
+ return [] unless job_lists.empty?
15
+
16
+ [violation(path: path.join("/"),
17
+ message: "compchem module must contain at least one jobList module")]
18
+ end
19
+
20
+ private
21
+
22
+ def compchem_root?(node)
23
+ module?(node) && node.convention == "convention:compchem"
24
+ end
25
+
26
+ def module?(node)
27
+ node.is_a?(Chemicalml::Cml::Role::Module)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Compchem
6
+ module Constraints
7
+ # A job module MUST contain exactly one initialization module
8
+ # child per the compchem convention.
9
+ class JobMustContainInitialization < Chemicalml::Convention::Constraint::NodeConstraint
10
+ def check_node(node, path)
11
+ return [] unless job_module?(node)
12
+
13
+ modules = node.modules || []
14
+ inits = modules.select { |m| m.dict_ref == "compchem:initialization" }
15
+ return [] if inits.length == 1
16
+
17
+ message = if inits.empty?
18
+ "job module must contain exactly one initialization module (found 0)"
19
+ else
20
+ "job module must contain exactly one initialization module (found #{inits.length})"
21
+ end
22
+ [violation(path: path.join("/"), message: message)]
23
+ end
24
+
25
+ private
26
+
27
+ def job_module?(node)
28
+ module?(node) && node.dict_ref == "compchem:job"
29
+ end
30
+
31
+ def module?(node)
32
+ node.is_a?(Chemicalml::Cml::Role::Module)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Compchem
6
+ module Constraints
7
+ autoload :CompchemModuleMustContainJobList,
8
+ "chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list"
9
+ autoload :JobMustContainInitialization,
10
+ "chemicalml/convention/compchem/constraints/job_must_contain_initialization"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # The CompChem convention. Constraints based on
6
+ # http://www.xml-cml.org/convention/compchem (archived in
7
+ # `reference-docs/conventions/compchem.md`).
8
+ module Compchem
9
+ extend Base
10
+
11
+ autoload :Constraints, "chemicalml/convention/compchem/constraints"
12
+
13
+ QNAME = "convention:compchem".freeze
14
+ NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}compchem".freeze
15
+
16
+ def self.qname
17
+ QNAME
18
+ end
19
+
20
+ def self.namespace_uri
21
+ NAMESPACE_URI
22
+ end
23
+
24
+ register Constraints::CompchemModuleMustContainJobList
25
+ register Constraints::JobMustContainInitialization
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # Abstract base class for a single constraint. Subclasses implement
6
+ # `check_node` (one node at a time) or `check_document` (whole
7
+ # document at once). The base class provides traversal and
8
+ # collection plumbing.
9
+ #
10
+ # To add a new constraint:
11
+ #
12
+ # 1. Subclass `Constraint::NodeConstraint` or
13
+ # `Constraint::DocumentConstraint`
14
+ # 2. Implement `check_node` / `check_document`
15
+ # 3. Register it on the convention: `register(MyConstraint)`
16
+ #
17
+ # No framework code changes.
18
+ class Constraint
19
+ def self.name_for_display
20
+ to_s.split("::").last
21
+ end
22
+
23
+ def check(_document)
24
+ raise NotImplementedError,
25
+ "#{self.class.name_for_display} must implement #check(document)"
26
+ end
27
+
28
+ protected
29
+
30
+ def violation(path:, message:, severity: :error)
31
+ Violation.new(path: path, message: message,
32
+ severity: severity, constraint: self.class)
33
+ end
34
+
35
+ # Walk every wire node reachable from `node`. Yields (node, path)
36
+ # pairs. Path is a slash-joined breadcrumb. Nodes that don't
37
+ # include `Chemicalml::Cml::Visitable` are skipped.
38
+ def walk_nodes(node, path = [], &block)
39
+ return unless node
40
+ return unless visitable?(node)
41
+
42
+ yield(node, path)
43
+ node.wire_children.each do |child|
44
+ walk_nodes(child, path + [describe(child)], &block)
45
+ end
46
+ end
47
+
48
+ def visitable?(node)
49
+ node.is_a?(Chemicalml::Cml::Visitable)
50
+ end
51
+
52
+ def describe(node)
53
+ id_value = node.node_id
54
+ id_value ? "#{node.element_name}[#{id_value}]" : node.element_name
55
+ end
56
+
57
+ # Constraint that walks the document tree once per node and calls
58
+ # `check_node(node, path)`. Subclasses get traversal for free.
59
+ class NodeConstraint < Constraint
60
+ def check(document)
61
+ violations = []
62
+ walk_nodes(document) do |node, path|
63
+ violations.concat(Array(check_node(node, path)))
64
+ end
65
+ violations
66
+ end
67
+
68
+ def check_node(_node, _path)
69
+ []
70
+ end
71
+ end
72
+
73
+ # Constraint that operates on the whole document at once — used
74
+ # for cross-cutting rules like "atom ids unique within molecule".
75
+ class DocumentConstraint < Constraint
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # Entry ids MUST be unique within the parent dictionary per
8
+ # the dictionary convention.
9
+ class EntryIdsUniqueWithinDictionary < Chemicalml::Convention::Constraint::NodeConstraint
10
+ def check_node(node, path)
11
+ return [] unless dictionary?(node)
12
+
13
+ entries = node.entries || []
14
+ ids = entries.map { |e| e.id }.compact
15
+ duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
16
+ return [] if duplicates.empty?
17
+
18
+ duplicates.map do |dup|
19
+ violation(path: path.join("/"),
20
+ message: "duplicate entry id #{dup.inspect} within dictionary")
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def dictionary?(node)
27
+ node.is_a?(Chemicalml::Cml::Role::Dictionary)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ # Every entry MUST have an `id` and a `term` attribute per the
8
+ # dictionary convention.
9
+ class EntryMustHaveIdAndTerm < Chemicalml::Convention::Constraint::NodeConstraint
10
+ def check_node(node, path)
11
+ return [] unless entry?(node)
12
+
13
+ violations = []
14
+ if node.id.to_s.empty?
15
+ violations << violation(path: path.join("/"),
16
+ message: "entry must have an id attribute")
17
+ end
18
+ if node.term.to_s.empty?
19
+ violations << violation(path: path.join("/"),
20
+ message: "entry must have a term attribute")
21
+ end
22
+ violations
23
+ end
24
+
25
+ private
26
+
27
+ def entry?(node)
28
+ node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Dictionary
6
+ module Constraints
7
+ autoload :EntryMustHaveIdAndTerm,
8
+ "chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term"
9
+ autoload :EntryIdsUniqueWithinDictionary,
10
+ "chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ # The dictionary convention. Constraints based on
6
+ # http://www.xml-cml.org/convention/dictionary (archived in
7
+ # `reference-docs/conventions/dictionary.md`).
8
+ module Dictionary
9
+ extend Base
10
+
11
+ autoload :Constraints, "chemicalml/convention/dictionary/constraints"
12
+
13
+ QNAME = "convention:dictionary".freeze
14
+ NAMESPACE_URI = "#{Chemicalml::Convention::CONVENTION_NAMESPACE}dictionary".freeze
15
+
16
+ def self.qname
17
+ QNAME
18
+ end
19
+
20
+ def self.namespace_uri
21
+ NAMESPACE_URI
22
+ end
23
+
24
+ register Constraints::EntryMustHaveIdAndTerm
25
+ register Constraints::EntryIdsUniqueWithinDictionary
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # `atomArray` MUST contain at least one `atom` child per the
8
+ # molecular convention.
9
+ class AtomArrayMustContainAtoms < Chemicalml::Convention::Constraint::NodeConstraint
10
+ def check_node(node, path)
11
+ return [] unless molecule?(node)
12
+ return [] unless node.atom_array
13
+
14
+ atoms = node.atom_array.atoms || []
15
+ return [] unless atoms.empty?
16
+
17
+ [violation(path: path.empty? ? "molecule" : path.join("/"),
18
+ message: "atomArray must contain at least one atom")]
19
+ end
20
+
21
+ private
22
+
23
+ def molecule?(node)
24
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Atom ids MUST be unique within the eldest containing molecule
8
+ # per the molecular convention.
9
+ class AtomIdsUniqueWithinMolecule < Chemicalml::Convention::Constraint::DocumentConstraint
10
+ def check(document)
11
+ violations = []
12
+ walk_nodes(document) do |node, path|
13
+ next unless molecule?(node)
14
+
15
+ ids = atom_ids_within(node)
16
+ duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
17
+ duplicates.each do |dup|
18
+ violations << violation(
19
+ path: path.empty? ? "molecule[#{node.id}]" : path.join("/"),
20
+ message: "duplicate atom id #{dup.inspect} within molecule #{node.id.inspect}"
21
+ )
22
+ end
23
+ end
24
+ violations
25
+ end
26
+
27
+ private
28
+
29
+ def molecule?(node)
30
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
31
+ end
32
+
33
+ def atom_ids_within(molecule)
34
+ return [] unless molecule.atom_array
35
+
36
+ (molecule.atom_array.atoms || []).map { |a| a.id }.compact
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Chemicalml
4
+ module Convention
5
+ module Molecular
6
+ module Constraints
7
+ # Bonds MUST reference atoms within the same parent molecule
8
+ # per the molecular convention.
9
+ class BondMustReferenceAtomsInSameMolecule < Chemicalml::Convention::Constraint::DocumentConstraint
10
+ def check(document)
11
+ violations = []
12
+ walk_nodes(document) do |node, path|
13
+ next unless molecule?(node)
14
+
15
+ known = atom_ids_within(node).to_set
16
+ bonds_within(node).each do |bond|
17
+ missing = atom_refs_of(bond).reject { |r| known.include?(r) }
18
+ next if missing.empty?
19
+
20
+ violations << violation(
21
+ path: path.empty? ? "molecule[#{node.id}]" : path.join("/"),
22
+ message: "bond #{bond.id.inspect} references atoms not in the same molecule: #{missing.inspect}"
23
+ )
24
+ end
25
+ end
26
+ violations
27
+ end
28
+
29
+ private
30
+
31
+ def molecule?(node)
32
+ node.is_a?(Chemicalml::Cml::Role::Molecule)
33
+ end
34
+
35
+ def atom_ids_within(molecule)
36
+ return [] unless molecule.atom_array
37
+
38
+ (molecule.atom_array.atoms || []).map { |a| a.id }
39
+ end
40
+
41
+ def bonds_within(molecule)
42
+ return [] unless molecule.bond_array
43
+
44
+ molecule.bond_array.bonds || []
45
+ end
46
+
47
+ def atom_refs_of(bond)
48
+ bond.atom_refs2.to_s.split(" ")
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end