chemicalml 0.2.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +7 -1
- data/CLAUDE.md +48 -41
- data/TODO.align/36-molecular-convention-coverage.md +46 -0
- data/TODO.align/37-compchem-convention-coverage.md +55 -0
- data/TODO.align/38-dictionary-convention-coverage.md +34 -0
- data/TODO.align/39-unit-dictionary-convention-coverage.md +33 -0
- data/TODO.align/40-unit-type-dictionary-convention-coverage.md +28 -0
- data/TODO.align/41-expand-canonical-model-layer.md +43 -0
- data/TODO.align/42-translator-wire-new-models.md +31 -0
- data/TODO.align/43-dictionary-model-no-to-h.md +39 -0
- data/TODO.align/44-schema3-only-and-schema24-module.md +44 -0
- data/TODO.align/45-documentation-and-final-verification.md +23 -0
- data/TODO.align/46-lattice-vector-content.md +24 -0
- data/TODO.align/47-molecule-crystal-spectra-property-lists.md +23 -0
- data/TODO.align/48-model-list-and-module-lists.md +21 -0
- data/TODO.align/49-more-chemistry-models.md +34 -0
- data/TODO.align/50-validation-report.md +29 -0
- data/TODO.align/51-convention-auto-detection.md +30 -0
- data/TODO.align/52-schema24-legacy-elements.md +28 -0
- data/TODO.align/53-round-trip-integration-specs.md +30 -0
- data/TODO.align/54-document-versioned-parser-shim.md +38 -0
- data/TODO.align/55-molecule-missing-attributes.md +20 -0
- data/TODO.align/56-geometry-models.md +24 -0
- data/TODO.align/57-compchem-models.md +26 -0
- data/TODO.align/58-remaining-models.md +24 -0
- data/TODO.align/59-wire-new-models-translator.md +18 -0
- data/TODO.align/60-docs-and-final-verification.md +19 -0
- data/lib/chemicalml/cml/base/abundance.rb +6 -0
- data/lib/chemicalml/cml/base/action.rb +20 -0
- data/lib/chemicalml/cml/base/action_list.rb +18 -0
- data/lib/chemicalml/cml/base/alternative.rb +28 -0
- data/lib/chemicalml/cml/base/amount.rb +2 -0
- data/lib/chemicalml/cml/base/angle.rb +12 -0
- data/lib/chemicalml/cml/base/annotation.rb +30 -0
- data/lib/chemicalml/cml/base/appinfo.rb +32 -0
- data/lib/chemicalml/cml/base/arg.rb +46 -0
- data/lib/chemicalml/cml/base/array.rb +22 -0
- data/lib/chemicalml/cml/base/array_list.rb +2 -0
- data/lib/chemicalml/cml/base/atom.rb +18 -0
- data/lib/chemicalml/cml/base/atom_array.rb +38 -0
- data/lib/chemicalml/cml/base/atom_parity.rb +8 -0
- data/lib/chemicalml/cml/base/atom_set.rb +2 -0
- data/lib/chemicalml/cml/base/atom_type.rb +4 -0
- data/lib/chemicalml/cml/base/atom_type_list.rb +2 -0
- data/lib/chemicalml/cml/base/atomic_basis_function.rb +4 -0
- data/lib/chemicalml/cml/base/band.rb +2 -0
- data/lib/chemicalml/cml/base/basis_set.rb +4 -0
- data/lib/chemicalml/cml/base/bond.rb +6 -0
- data/lib/chemicalml/cml/base/bond_array.rb +16 -0
- data/lib/chemicalml/cml/base/bond_set.rb +2 -0
- data/lib/chemicalml/cml/base/bond_stereo.rb +10 -0
- data/lib/chemicalml/cml/base/bond_type.rb +2 -0
- data/lib/chemicalml/cml/base/bond_type_list.rb +2 -0
- data/lib/chemicalml/cml/base/cell_parameter.rb +4 -0
- data/lib/chemicalml/cml/base/complex_object.rb +30 -0
- data/lib/chemicalml/cml/base/condition_list.rb +4 -0
- data/lib/chemicalml/cml/base/crystal.rb +2 -0
- data/lib/chemicalml/cml/base/description.rb +2 -0
- data/lib/chemicalml/cml/base/dictionary.rb +6 -0
- data/lib/chemicalml/cml/base/dimension.rb +10 -0
- data/lib/chemicalml/cml/base/eigen.rb +2 -0
- data/lib/chemicalml/cml/base/electron.rb +6 -0
- data/lib/chemicalml/cml/base/enumeration.rb +32 -0
- data/lib/chemicalml/cml/base/expression.rb +32 -0
- data/lib/chemicalml/cml/base/float.rb +40 -0
- data/lib/chemicalml/cml/base/float_array.rb +42 -0
- data/lib/chemicalml/cml/base/fragment.rb +6 -0
- data/lib/chemicalml/cml/base/fragment_list.rb +4 -0
- data/lib/chemicalml/cml/base/identifier.rb +8 -0
- data/lib/chemicalml/cml/base/integer.rb +40 -0
- data/lib/chemicalml/cml/base/integer_array.rb +42 -0
- data/lib/chemicalml/cml/base/isotope.rb +2 -0
- data/lib/chemicalml/cml/base/isotope_list.rb +2 -0
- data/lib/chemicalml/cml/base/join.rb +6 -0
- data/lib/chemicalml/cml/base/kpoint.rb +2 -0
- data/lib/chemicalml/cml/base/label.rb +2 -0
- data/lib/chemicalml/cml/base/lattice.rb +2 -0
- data/lib/chemicalml/cml/base/lattice_vector.rb +2 -0
- data/lib/chemicalml/cml/base/length.rb +10 -0
- data/lib/chemicalml/cml/base/line3.rb +6 -0
- data/lib/chemicalml/cml/base/link.rb +20 -0
- data/lib/chemicalml/cml/base/list.rb +2 -0
- data/lib/chemicalml/cml/base/map.rb +12 -0
- data/lib/chemicalml/cml/base/matrix.rb +12 -0
- data/lib/chemicalml/cml/base/metadata.rb +4 -0
- data/lib/chemicalml/cml/base/metadata_list.rb +6 -0
- data/lib/chemicalml/cml/base/molecule.rb +10 -0
- data/lib/chemicalml/cml/base/molecule_list.rb +2 -0
- data/lib/chemicalml/cml/base/name.rb +2 -0
- data/lib/chemicalml/cml/base/object.rb +4 -0
- data/lib/chemicalml/cml/base/observation.rb +4 -0
- data/lib/chemicalml/cml/base/operator.rb +32 -0
- data/lib/chemicalml/cml/base/parameter.rb +10 -0
- data/lib/chemicalml/cml/base/parameter_list.rb +6 -0
- data/lib/chemicalml/cml/base/particle.rb +6 -0
- data/lib/chemicalml/cml/base/peak.rb +30 -0
- data/lib/chemicalml/cml/base/peak_group.rb +38 -0
- data/lib/chemicalml/cml/base/peak_list.rb +2 -0
- data/lib/chemicalml/cml/base/peak_structure.rb +14 -0
- data/lib/chemicalml/cml/base/plane3.rb +2 -0
- data/lib/chemicalml/cml/base/point3.rb +2 -0
- data/lib/chemicalml/cml/base/potential_form.rb +2 -0
- data/lib/chemicalml/cml/base/product.rb +16 -0
- data/lib/chemicalml/cml/base/product_list.rb +14 -0
- data/lib/chemicalml/cml/base/property.rb +6 -0
- data/lib/chemicalml/cml/base/property_list.rb +6 -0
- data/lib/chemicalml/cml/base/reactant.rb +16 -0
- data/lib/chemicalml/cml/base/reactant_list.rb +14 -0
- data/lib/chemicalml/cml/base/reaction.rb +16 -0
- data/lib/chemicalml/cml/base/reaction_list.rb +12 -0
- data/lib/chemicalml/cml/base/reaction_scheme.rb +10 -0
- data/lib/chemicalml/cml/base/reaction_step.rb +6 -0
- data/lib/chemicalml/cml/base/reaction_step_list.rb +8 -0
- data/lib/chemicalml/cml/base/region.rb +8 -0
- data/lib/chemicalml/cml/base/related_entry.rb +26 -0
- data/lib/chemicalml/cml/base/sample.rb +4 -0
- data/lib/chemicalml/cml/base/scalar.rb +18 -0
- data/lib/chemicalml/cml/base/spectator.rb +2 -0
- data/lib/chemicalml/cml/base/spectrum.rb +12 -0
- data/lib/chemicalml/cml/base/spectrum_data.rb +2 -0
- data/lib/chemicalml/cml/base/spectrum_list.rb +4 -0
- data/lib/chemicalml/cml/base/sphere3.rb +2 -0
- data/lib/chemicalml/cml/base/string.rb +32 -0
- data/lib/chemicalml/cml/base/string_array.rb +40 -0
- data/lib/chemicalml/cml/base/substance.rb +12 -0
- data/lib/chemicalml/cml/base/substance_list.rb +6 -0
- data/lib/chemicalml/cml/base/symmetry.rb +4 -0
- data/lib/chemicalml/cml/base/system.rb +4 -0
- data/lib/chemicalml/cml/base/table.rb +10 -0
- data/lib/chemicalml/cml/base/table_content.rb +2 -0
- data/lib/chemicalml/cml/base/table_header_cell.rb +10 -0
- data/lib/chemicalml/cml/base/tcell.rb +34 -0
- data/lib/chemicalml/cml/base/torsion.rb +12 -0
- data/lib/chemicalml/cml/base/trow.rb +32 -0
- data/lib/chemicalml/cml/base/unit.rb +12 -0
- data/lib/chemicalml/cml/base/unit_list.rb +12 -0
- data/lib/chemicalml/cml/base/unit_type.rb +10 -0
- data/lib/chemicalml/cml/base/unit_type_list.rb +10 -0
- data/lib/chemicalml/cml/base/vector3.rb +2 -0
- data/lib/chemicalml/cml/base/xaxis.rb +6 -0
- data/lib/chemicalml/cml/base/yaxis.rb +6 -0
- data/lib/chemicalml/cml/base.rb +17 -0
- data/lib/chemicalml/cml/elements.rb +39 -4
- data/lib/chemicalml/cml/role/alternative.rb +10 -0
- data/lib/chemicalml/cml/role/annotation.rb +10 -0
- data/lib/chemicalml/cml/role/appinfo.rb +10 -0
- data/lib/chemicalml/cml/role/arg.rb +10 -0
- data/lib/chemicalml/cml/role/cml_module.rb +2 -3
- data/lib/chemicalml/cml/role/complex_object.rb +10 -0
- data/lib/chemicalml/cml/role/enumeration.rb +10 -0
- data/lib/chemicalml/cml/role/expression.rb +10 -0
- data/lib/chemicalml/cml/role/float.rb +10 -0
- data/lib/chemicalml/cml/role/float_array.rb +10 -0
- data/lib/chemicalml/cml/role/integer.rb +10 -0
- data/lib/chemicalml/cml/role/integer_array.rb +10 -0
- data/lib/chemicalml/cml/role/operator.rb +10 -0
- data/lib/chemicalml/cml/role/related_entry.rb +10 -0
- data/lib/chemicalml/cml/role/string.rb +10 -0
- data/lib/chemicalml/cml/role/string_array.rb +10 -0
- data/lib/chemicalml/cml/role/tcell.rb +10 -0
- data/lib/chemicalml/cml/role/trow.rb +10 -0
- data/lib/chemicalml/cml/role.rb +17 -0
- data/lib/chemicalml/cml/schema24/alternative.rb +14 -0
- data/lib/chemicalml/cml/schema24/annotation.rb +15 -0
- data/lib/chemicalml/cml/schema24/appinfo.rb +15 -0
- data/lib/chemicalml/cml/schema24/arg.rb +14 -0
- data/lib/chemicalml/cml/schema24/cml_module.rb +18 -0
- data/lib/chemicalml/cml/schema24/complex_object.rb +14 -0
- data/lib/chemicalml/cml/schema24/configuration.rb +5 -2
- data/lib/chemicalml/cml/schema24/enumeration.rb +16 -0
- data/lib/chemicalml/cml/schema24/expression.rb +14 -0
- data/lib/chemicalml/cml/schema24/float.rb +14 -0
- data/lib/chemicalml/cml/schema24/float_array.rb +14 -0
- data/lib/chemicalml/cml/schema24/integer.rb +14 -0
- data/lib/chemicalml/cml/schema24/integer_array.rb +14 -0
- data/lib/chemicalml/cml/schema24/operator.rb +14 -0
- data/lib/chemicalml/cml/schema24/related_entry.rb +14 -0
- data/lib/chemicalml/cml/schema24/string.rb +14 -0
- data/lib/chemicalml/cml/schema24/string_array.rb +14 -0
- data/lib/chemicalml/cml/schema24/tcell.rb +14 -0
- data/lib/chemicalml/cml/schema24/trow.rb +14 -0
- data/lib/chemicalml/cml/schema24.rb +33 -5
- data/lib/chemicalml/cml.rb +1 -3
- data/lib/chemicalml/context_configuration.rb +8 -2
- data/lib/chemicalml/convention/base.rb +17 -3
- data/lib/chemicalml/convention/compchem/constraints/array_rules.rb +44 -0
- data/lib/chemicalml/convention/compchem/constraints/calculation_requires_finalization.rb +28 -0
- data/lib/chemicalml/convention/compchem/constraints/compchem_module_must_contain_job_list.rb +4 -4
- data/lib/chemicalml/convention/compchem/constraints/environment_at_most_one_property_list.rb +26 -0
- data/lib/chemicalml/convention/compchem/constraints/environment_must_not_contain_parameter.rb +28 -0
- data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_molecule.rb +26 -0
- data/lib/chemicalml/convention/compchem/constraints/finalization_at_most_one_property_list.rb +26 -0
- data/lib/chemicalml/convention/compchem/constraints/finalization_must_not_contain_parameter.rb +27 -0
- data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_molecule.rb +26 -0
- data/lib/chemicalml/convention/compchem/constraints/initialization_at_most_one_parameter_list.rb +26 -0
- data/lib/chemicalml/convention/compchem/constraints/initialization_must_not_contain_property.rb +28 -0
- data/lib/chemicalml/convention/compchem/constraints/job_list_module_must_have_id.rb +24 -0
- data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_environment.rb +25 -0
- data/lib/chemicalml/convention/compchem/constraints/job_module_at_most_one_finalization.rb +25 -0
- data/lib/chemicalml/convention/compchem/constraints/job_module_must_have_id.rb +24 -0
- data/lib/chemicalml/convention/compchem/constraints/job_must_contain_initialization.rb +6 -9
- data/lib/chemicalml/convention/compchem/constraints/matrix_rules.rb +48 -0
- data/lib/chemicalml/convention/compchem/constraints/module_predicates.rb +50 -0
- data/lib/chemicalml/convention/compchem/constraints/scalar_units.rb +42 -0
- data/lib/chemicalml/convention/compchem/constraints.rb +36 -2
- data/lib/chemicalml/convention/compchem.rb +16 -0
- data/lib/chemicalml/convention/constraint.rb +31 -18
- data/lib/chemicalml/convention/coordinator.rb +89 -0
- data/lib/chemicalml/convention/detection.rb +33 -0
- data/lib/chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace.rb +28 -0
- data/lib/chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash.rb +32 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern.rb +33 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary.rb +3 -3
- data/lib/chemicalml/convention/dictionary/constraints/entry_must_contain_definition.rb +28 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term.rb +5 -5
- data/lib/chemicalml/convention/dictionary/constraints/entry_must_have_unit_type.rb +30 -0
- data/lib/chemicalml/convention/dictionary/constraints/entry_units_co_constraints.rb +44 -0
- data/lib/chemicalml/convention/dictionary/constraints.rb +12 -0
- data/lib/chemicalml/convention/dictionary.rb +6 -0
- data/lib/chemicalml/convention/molecular/constraints/atom_array_must_be_child_of_molecule_or_formula.rb +40 -0
- data/lib/chemicalml/convention/molecular/constraints/atom_array_must_contain_atoms.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/atom_coordinates_must_be_paired.rb +5 -5
- data/lib/chemicalml/convention/molecular/constraints/atom_id_must_match_pattern.rb +4 -4
- data/lib/chemicalml/convention/molecular/constraints/atom_ids_unique_within_molecule.rb +2 -2
- data/lib/chemicalml/convention/molecular/constraints/atom_must_have_element_type.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/atom_must_have_id.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/bond_array_must_be_child_of_molecule.rb +36 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_ids_unique_within_molecule.rb +43 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_must_have_atom_refs2.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/bond_must_have_order.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/bond_must_reference_atoms_in_same_molecule.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/bond_order_other_must_have_dict_ref.rb +33 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_order_should_not_be_numeric.rb +2 -2
- data/lib/chemicalml/convention/molecular/constraints/bond_stereo_cis_trans_must_have_atom_refs4.rb +45 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_stereo_other_must_have_dict_ref.rb +32 -0
- data/lib/chemicalml/convention/molecular/constraints/bond_stereo_wedge_hash_must_have_atom_refs2.rb +45 -0
- data/lib/chemicalml/convention/molecular/constraints/molecule_atom_array_mutually_exclusive_with_children.rb +32 -0
- data/lib/chemicalml/convention/molecular/constraints/molecule_bond_array_mutually_exclusive_with_children.rb +31 -0
- data/lib/chemicalml/convention/molecular/constraints/molecule_count_must_not_appear_on_top_level.rb +34 -0
- data/lib/chemicalml/convention/molecular/constraints/molecule_must_have_id.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/property_must_have_dict_ref.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints/scalar_must_have_data_type.rb +3 -3
- data/lib/chemicalml/convention/molecular/constraints.rb +20 -0
- data/lib/chemicalml/convention/molecular.rb +10 -0
- data/lib/chemicalml/convention/registry.rb +17 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_contain_at_least_one_unit.rb +28 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_list_must_have_namespace.rb +36 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_contain_definition.rb +27 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_id.rb +27 -0
- data/lib/chemicalml/convention/unit_dictionary/constraints/unit_must_have_symbol_and_unit_type.rb +3 -3
- data/lib/chemicalml/convention/unit_dictionary/constraints.rb +9 -1
- data/lib/chemicalml/convention/unit_dictionary.rb +4 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_id_must_match_pattern.rb +31 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_contain_at_least_one_unit_type.rb +27 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_list_must_have_namespace.rb +35 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_contain_definition.rb +27 -0
- data/lib/chemicalml/convention/unit_type_dictionary/constraints/unit_type_must_have_id_and_name.rb +5 -5
- data/lib/chemicalml/convention/unit_type_dictionary/constraints.rb +9 -1
- data/lib/chemicalml/convention/unit_type_dictionary.rb +4 -0
- data/lib/chemicalml/convention/validation_report.rb +63 -0
- data/lib/chemicalml/convention.rb +11 -0
- data/lib/chemicalml/dictionary/entry.rb +6 -6
- data/lib/chemicalml/dictionary/enum.rb +1 -1
- data/lib/chemicalml/dictionary/link.rb +3 -3
- data/lib/chemicalml/dictionary/loader.rb +19 -19
- data/lib/chemicalml/dictionary/model.rb +5 -5
- data/lib/chemicalml/dictionary/registry.rb +9 -9
- data/lib/chemicalml/version.rb +1 -1
- data/lib/chemicalml/versioned_parser.rb +18 -0
- data/lib/chemicalml.rb +1 -2
- metadata +123 -34
- data/lib/chemicalml/cml/child_mappings.rb +0 -168
- data/lib/chemicalml/cml/translator/value_translations.rb +0 -270
- data/lib/chemicalml/cml/translator.rb +0 -484
- data/lib/chemicalml/model/atom.rb +0 -64
- data/lib/chemicalml/model/atom_parity.rb +0 -21
- data/lib/chemicalml/model/bond.rb +0 -39
- data/lib/chemicalml/model/bond_stereo.rb +0 -29
- data/lib/chemicalml/model/cml_array.rb +0 -27
- data/lib/chemicalml/model/cml_module.rb +0 -45
- data/lib/chemicalml/model/document.rb +0 -29
- data/lib/chemicalml/model/formula.rb +0 -34
- data/lib/chemicalml/model/identifier.rb +0 -21
- data/lib/chemicalml/model/label.rb +0 -21
- data/lib/chemicalml/model/matrix.rb +0 -29
- data/lib/chemicalml/model/metadata.rb +0 -23
- data/lib/chemicalml/model/metadata_list.rb +0 -25
- data/lib/chemicalml/model/molecule.rb +0 -45
- data/lib/chemicalml/model/name.rb +0 -21
- data/lib/chemicalml/model/node.rb +0 -45
- data/lib/chemicalml/model/parameter.rb +0 -27
- data/lib/chemicalml/model/parameter_list.rb +0 -25
- data/lib/chemicalml/model/product.rb +0 -21
- data/lib/chemicalml/model/product_list.rb +0 -21
- data/lib/chemicalml/model/property.rb +0 -28
- data/lib/chemicalml/model/property_list.rb +0 -25
- data/lib/chemicalml/model/reactant.rb +0 -21
- data/lib/chemicalml/model/reactant_list.rb +0 -21
- data/lib/chemicalml/model/reaction.rb +0 -41
- data/lib/chemicalml/model/reaction_list.rb +0 -23
- data/lib/chemicalml/model/scalar.rb +0 -25
- data/lib/chemicalml/model/substance.rb +0 -25
- data/lib/chemicalml/model.rb +0 -40
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# Single-pass constraint dispatcher. Replaces the previous
|
|
6
|
+
# O(N*C) walk (every constraint walked the whole tree) with
|
|
7
|
+
# O(N + total_applicable) — one tree walk per validation, with
|
|
8
|
+
# each node dispatched only to constraints that declared
|
|
9
|
+
# `applies_to` matching one of the node's ancestors.
|
|
10
|
+
#
|
|
11
|
+
# Constraints that don't declare `applies_to` fall into a
|
|
12
|
+
# default "all nodes" bucket and run for every visited node,
|
|
13
|
+
# preserving backward compatibility.
|
|
14
|
+
class Coordinator
|
|
15
|
+
attr_reader :document_constraints, :node_constraints
|
|
16
|
+
|
|
17
|
+
def initialize(constraint_classes)
|
|
18
|
+
@document_constraints = constraint_classes
|
|
19
|
+
.select { |c| c < Constraint::DocumentConstraint }
|
|
20
|
+
.map(&:new)
|
|
21
|
+
@node_constraints = constraint_constraints(constraint_classes)
|
|
22
|
+
.map(&:new)
|
|
23
|
+
@always_applicable = []
|
|
24
|
+
@by_role = Hash.new { |h, k| h[k] = [] }
|
|
25
|
+
@node_constraints.each { |c| register(c) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def validate(document)
|
|
29
|
+
violations = @document_constraints.flat_map do |constraint|
|
|
30
|
+
Array(constraint.check(document))
|
|
31
|
+
end
|
|
32
|
+
return violations unless visitable?(document)
|
|
33
|
+
|
|
34
|
+
walk(document) do |node, path|
|
|
35
|
+
applicable_for(node).each do |constraint|
|
|
36
|
+
violations.concat(Array(constraint.check_node(node, path)))
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
violations
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def constraint_constraints(classes)
|
|
45
|
+
classes.select { |c| c < Constraint::NodeConstraint }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def register(constraint)
|
|
49
|
+
roles = constraint.class.applies_to_roles
|
|
50
|
+
if roles.nil? || roles.empty?
|
|
51
|
+
@always_applicable << constraint
|
|
52
|
+
else
|
|
53
|
+
roles.each { |role| @by_role[role] << constraint }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def visitable?(node)
|
|
58
|
+
node.is_a?(Chemicalml::Cml::Visitable)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def applicable_for(node)
|
|
62
|
+
applicable = @always_applicable
|
|
63
|
+
node.class.ancestors.each do |ancestor|
|
|
64
|
+
next unless ancestor.is_a?(::Module)
|
|
65
|
+
bucket = @by_role[ancestor]
|
|
66
|
+
next unless bucket
|
|
67
|
+
|
|
68
|
+
applicable += bucket
|
|
69
|
+
end
|
|
70
|
+
applicable
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def walk(node, path = [], &block)
|
|
74
|
+
return unless node
|
|
75
|
+
return unless visitable?(node)
|
|
76
|
+
|
|
77
|
+
yield(node, path)
|
|
78
|
+
node.wire_children.each do |child|
|
|
79
|
+
walk(child, path + [describe(child)], &block)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def describe(node)
|
|
84
|
+
id_value = node.node_id
|
|
85
|
+
id_value ? "#{node.element_name}[#{id_value}]" : node.element_name
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
# Convention detection from a wire document. Reads the
|
|
6
|
+
# `convention` attribute from the root element (Document, Module,
|
|
7
|
+
# Molecule, Dictionary, UnitList, or UnitTypeList) and returns
|
|
8
|
+
# the QName string (e.g. "convention:molecular").
|
|
9
|
+
#
|
|
10
|
+
# Used by `Registry.detect_and_validate` and reusable by callers
|
|
11
|
+
# that want to know which convention a document declares without
|
|
12
|
+
# running validation.
|
|
13
|
+
module Detection
|
|
14
|
+
CONVENTION_ROOTS = [
|
|
15
|
+
Chemicalml::Cml::Role::Document,
|
|
16
|
+
Chemicalml::Cml::Role::Module,
|
|
17
|
+
Chemicalml::Cml::Role::Molecule,
|
|
18
|
+
Chemicalml::Cml::Role::Dictionary,
|
|
19
|
+
Chemicalml::Cml::Role::UnitList,
|
|
20
|
+
Chemicalml::Cml::Role::UnitTypeList
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
def self.convention_of(document)
|
|
24
|
+
CONVENTION_ROOTS.each do |role|
|
|
25
|
+
next unless document.is_a?(role)
|
|
26
|
+
|
|
27
|
+
return document.convention if document.convention
|
|
28
|
+
end
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Dictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# A `<dictionary>` element MUST have a `namespace` attribute
|
|
8
|
+
# whose value is a valid URI defining the scope within which
|
|
9
|
+
# the entry terms are unique.
|
|
10
|
+
class DictionaryMustHaveNamespace < Chemicalml::Convention::Constraint::NodeConstraint
|
|
11
|
+
applies_to Chemicalml::Cml::Role::Dictionary
|
|
12
|
+
def check_node(node, path)
|
|
13
|
+
return [] unless node.namespace.to_s.empty?
|
|
14
|
+
|
|
15
|
+
[violation(path: path.join('/'),
|
|
16
|
+
message: 'dictionary must have a namespace attribute')]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def dictionary?(node)
|
|
22
|
+
node.is_a?(Chemicalml::Cml::Role::Dictionary)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Dictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# The `namespace` URI SHOULD end with `/` or `#` so terms can
|
|
8
|
+
# be referenced by appending them to the URI. Warning level.
|
|
9
|
+
class DictionaryNamespaceShouldEndWithSlashOrHash < Chemicalml::Convention::Constraint::NodeConstraint
|
|
10
|
+
applies_to Chemicalml::Cml::Role::Dictionary
|
|
11
|
+
def check_node(node, path)
|
|
12
|
+
|
|
13
|
+
ns = node.namespace.to_s
|
|
14
|
+
return [] if ns.empty?
|
|
15
|
+
return [] if ns.end_with?('/', '#')
|
|
16
|
+
|
|
17
|
+
[violation(path: path.join('/'),
|
|
18
|
+
message: "dictionary namespace #{ns.inspect} should end with / or # " \
|
|
19
|
+
'so terms can be referenced by appending',
|
|
20
|
+
severity: :warning)]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def dictionary?(node)
|
|
26
|
+
node.is_a?(Chemicalml::Cml::Role::Dictionary)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Dictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# An `<entry>` `id` MUST match `[A-Za-z][A-Za-z0-9._-]*` —
|
|
8
|
+
# start with a letter, followed by letters/digits/dot/hyphen/
|
|
9
|
+
# underscore. Mirrors the upstream IdStartChar / IdChar BNF.
|
|
10
|
+
class EntryIdMustMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
|
|
11
|
+
applies_to Chemicalml::Cml::Role::DictionaryEntry
|
|
12
|
+
PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/
|
|
13
|
+
|
|
14
|
+
def check_node(node, path)
|
|
15
|
+
|
|
16
|
+
id = node.id.to_s
|
|
17
|
+
return [] if id.empty?
|
|
18
|
+
return [] if id.match?(PATTERN)
|
|
19
|
+
|
|
20
|
+
[violation(path: path.join('/'),
|
|
21
|
+
message: "entry id #{id.inspect} must match [A-Za-z][A-Za-z0-9._-]*")]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def entry?(node)
|
|
27
|
+
node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -7,16 +7,16 @@ module Chemicalml
|
|
|
7
7
|
# Entry ids MUST be unique within the parent dictionary per
|
|
8
8
|
# the dictionary convention.
|
|
9
9
|
class EntryIdsUniqueWithinDictionary < Chemicalml::Convention::Constraint::NodeConstraint
|
|
10
|
+
applies_to Chemicalml::Cml::Role::Dictionary
|
|
10
11
|
def check_node(node, path)
|
|
11
|
-
return [] unless dictionary?(node)
|
|
12
12
|
|
|
13
13
|
entries = node.entries || []
|
|
14
|
-
ids = entries.map
|
|
14
|
+
ids = entries.map(&:id).compact
|
|
15
15
|
duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
|
|
16
16
|
return [] if duplicates.empty?
|
|
17
17
|
|
|
18
18
|
duplicates.map do |dup|
|
|
19
|
-
violation(path: path.join(
|
|
19
|
+
violation(path: path.join('/'),
|
|
20
20
|
message: "duplicate entry id #{dup.inspect} within dictionary")
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Dictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# An `<entry>` MUST contain a single `definition` child
|
|
8
|
+
# element. The schema allows it as a string; this constraint
|
|
9
|
+
# enforces presence.
|
|
10
|
+
class EntryMustContainDefinition < Chemicalml::Convention::Constraint::NodeConstraint
|
|
11
|
+
applies_to Chemicalml::Cml::Role::DictionaryEntry
|
|
12
|
+
def check_node(node, path)
|
|
13
|
+
return [] unless node.definition.to_s.strip.empty?
|
|
14
|
+
|
|
15
|
+
[violation(path: path.join('/'),
|
|
16
|
+
message: "entry #{node.id.inspect} must contain a single definition child")]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def entry?(node)
|
|
22
|
+
node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -7,17 +7,17 @@ module Chemicalml
|
|
|
7
7
|
# Every entry MUST have an `id` and a `term` attribute per the
|
|
8
8
|
# dictionary convention.
|
|
9
9
|
class EntryMustHaveIdAndTerm < Chemicalml::Convention::Constraint::NodeConstraint
|
|
10
|
+
applies_to Chemicalml::Cml::Role::DictionaryEntry
|
|
10
11
|
def check_node(node, path)
|
|
11
|
-
return [] unless entry?(node)
|
|
12
12
|
|
|
13
13
|
violations = []
|
|
14
14
|
if node.id.to_s.empty?
|
|
15
|
-
violations << violation(path: path.join(
|
|
16
|
-
message:
|
|
15
|
+
violations << violation(path: path.join('/'),
|
|
16
|
+
message: 'entry must have an id attribute')
|
|
17
17
|
end
|
|
18
18
|
if node.term.to_s.empty?
|
|
19
|
-
violations << violation(path: path.join(
|
|
20
|
-
message:
|
|
19
|
+
violations << violation(path: path.join('/'),
|
|
20
|
+
message: 'entry must have a term attribute')
|
|
21
21
|
end
|
|
22
22
|
violations
|
|
23
23
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Dictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# An `<entry>` MUST have a `unitType` attribute. Entries
|
|
8
|
+
# describing concepts that should not have units (e.g. name
|
|
9
|
+
# of a program) should reference `none` in the standard CML
|
|
10
|
+
# unitType dictionary.
|
|
11
|
+
class EntryMustHaveUnitType < Chemicalml::Convention::Constraint::NodeConstraint
|
|
12
|
+
applies_to Chemicalml::Cml::Role::DictionaryEntry
|
|
13
|
+
def check_node(node, path)
|
|
14
|
+
return [] unless node.unit_type.to_s.empty?
|
|
15
|
+
|
|
16
|
+
[violation(path: path.join('/'),
|
|
17
|
+
message: "entry #{node.id.inspect} must have a unitType attribute " \
|
|
18
|
+
"(use 'none' for concepts without units)")]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def entry?(node)
|
|
24
|
+
node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Dictionary
|
|
6
|
+
module Constraints
|
|
7
|
+
# Co-constraint between `unitType` and `units`:
|
|
8
|
+
#
|
|
9
|
+
# - If `unitType` is `unknown`, `units` MUST NOT be present.
|
|
10
|
+
# - If `unitType` is `none`, `units` MUST be present and point
|
|
11
|
+
# to `http://www.xml-cml.org/unit/si#none`.
|
|
12
|
+
class EntryUnitsCoConstraints < Chemicalml::Convention::Constraint::NodeConstraint
|
|
13
|
+
applies_to Chemicalml::Cml::Role::DictionaryEntry
|
|
14
|
+
NONE_UNITS = 'http://www.xml-cml.org/unit/si#none'
|
|
15
|
+
|
|
16
|
+
def check_node(node, path)
|
|
17
|
+
|
|
18
|
+
unit_type = node.unit_type.to_s
|
|
19
|
+
units = node.units.to_s
|
|
20
|
+
violations = []
|
|
21
|
+
|
|
22
|
+
if unit_type == 'unknown' && !units.empty?
|
|
23
|
+
violations << violation(path: path.join('/'),
|
|
24
|
+
message: "entry #{node.id.inspect} has unitType 'unknown' " \
|
|
25
|
+
'and therefore must not have units')
|
|
26
|
+
end
|
|
27
|
+
if unit_type == 'none' && units != NONE_UNITS
|
|
28
|
+
violations << violation(path: path.join('/'),
|
|
29
|
+
message: "entry #{node.id.inspect} has unitType 'none' — " \
|
|
30
|
+
"units must be #{NONE_UNITS.inspect}")
|
|
31
|
+
end
|
|
32
|
+
violations
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def entry?(node)
|
|
38
|
+
node.is_a?(Chemicalml::Cml::Role::DictionaryEntry)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -8,6 +8,18 @@ module Chemicalml
|
|
|
8
8
|
"chemicalml/convention/dictionary/constraints/entry_must_have_id_and_term"
|
|
9
9
|
autoload :EntryIdsUniqueWithinDictionary,
|
|
10
10
|
"chemicalml/convention/dictionary/constraints/entry_ids_unique_within_dictionary"
|
|
11
|
+
autoload :DictionaryMustHaveNamespace,
|
|
12
|
+
"chemicalml/convention/dictionary/constraints/dictionary_must_have_namespace"
|
|
13
|
+
autoload :DictionaryNamespaceShouldEndWithSlashOrHash,
|
|
14
|
+
"chemicalml/convention/dictionary/constraints/dictionary_namespace_should_end_with_slash_or_hash"
|
|
15
|
+
autoload :EntryMustContainDefinition,
|
|
16
|
+
"chemicalml/convention/dictionary/constraints/entry_must_contain_definition"
|
|
17
|
+
autoload :EntryIdMustMatchPattern,
|
|
18
|
+
"chemicalml/convention/dictionary/constraints/entry_id_must_match_pattern"
|
|
19
|
+
autoload :EntryMustHaveUnitType,
|
|
20
|
+
"chemicalml/convention/dictionary/constraints/entry_must_have_unit_type"
|
|
21
|
+
autoload :EntryUnitsCoConstraints,
|
|
22
|
+
"chemicalml/convention/dictionary/constraints/entry_units_co_constraints"
|
|
11
23
|
end
|
|
12
24
|
end
|
|
13
25
|
end
|
|
@@ -23,6 +23,12 @@ module Chemicalml
|
|
|
23
23
|
|
|
24
24
|
register Constraints::EntryMustHaveIdAndTerm
|
|
25
25
|
register Constraints::EntryIdsUniqueWithinDictionary
|
|
26
|
+
register Constraints::DictionaryMustHaveNamespace
|
|
27
|
+
register Constraints::DictionaryNamespaceShouldEndWithSlashOrHash
|
|
28
|
+
register Constraints::EntryMustContainDefinition
|
|
29
|
+
register Constraints::EntryIdMustMatchPattern
|
|
30
|
+
register Constraints::EntryMustHaveUnitType
|
|
31
|
+
register Constraints::EntryUnitsCoConstraints
|
|
26
32
|
end
|
|
27
33
|
end
|
|
28
34
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Molecular
|
|
6
|
+
module Constraints
|
|
7
|
+
# Molecular convention: `<atomArray>` MUST be a child of
|
|
8
|
+
# `<molecule>` or `<formula>`. Any other parent is invalid.
|
|
9
|
+
# Checked from the parent's perspective during the walk —
|
|
10
|
+
# when visiting a non-molecule/non-formula node, look for
|
|
11
|
+
# misplaced atomArray children.
|
|
12
|
+
class AtomArrayMustBeChildOfMoleculeOrFormula < Chemicalml::Convention::Constraint::NodeConstraint
|
|
13
|
+
def check_node(node, path)
|
|
14
|
+
return [] if allowed_parent?(node)
|
|
15
|
+
|
|
16
|
+
misplaced = node.wire_children.select { |c| atom_array?(c) }
|
|
17
|
+
misplaced.map do |child|
|
|
18
|
+
violation(
|
|
19
|
+
path: (path + [describe(node), describe(child)]).join('/'),
|
|
20
|
+
message: 'atomArray must be a child of molecule or formula, ' \
|
|
21
|
+
"but appears under #{node.element_name.inspect}"
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def allowed_parent?(node)
|
|
29
|
+
node.is_a?(Chemicalml::Cml::Role::Molecule) ||
|
|
30
|
+
node.is_a?(Chemicalml::Cml::Role::Formula)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def atom_array?(node)
|
|
34
|
+
node.is_a?(Chemicalml::Cml::Role::AtomArray)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -7,15 +7,15 @@ module Chemicalml
|
|
|
7
7
|
# `atomArray` MUST contain at least one `atom` child per the
|
|
8
8
|
# molecular convention.
|
|
9
9
|
class AtomArrayMustContainAtoms < Chemicalml::Convention::Constraint::NodeConstraint
|
|
10
|
+
applies_to Chemicalml::Cml::Role::Molecule
|
|
10
11
|
def check_node(node, path)
|
|
11
|
-
return [] unless molecule?(node)
|
|
12
12
|
return [] unless node.atom_array
|
|
13
13
|
|
|
14
14
|
atoms = node.atom_array.atoms || []
|
|
15
15
|
return [] unless atoms.empty?
|
|
16
16
|
|
|
17
|
-
[violation(path: path.empty? ?
|
|
18
|
-
message:
|
|
17
|
+
[violation(path: path.empty? ? 'molecule' : path.join('/'),
|
|
18
|
+
message: 'atomArray must contain at least one atom')]
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
private
|
|
@@ -5,23 +5,23 @@ module Chemicalml
|
|
|
5
5
|
module Molecular
|
|
6
6
|
module Constraints
|
|
7
7
|
class AtomCoordinatesMustBePaired < Chemicalml::Convention::Constraint::NodeConstraint
|
|
8
|
+
applies_to Chemicalml::Cml::Role::Atom
|
|
8
9
|
def check_node(node, path)
|
|
9
|
-
return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
|
|
10
10
|
|
|
11
11
|
violations = []
|
|
12
12
|
has_x2 = !node.x2.to_s.empty?
|
|
13
13
|
has_y2 = !node.y2.to_s.empty?
|
|
14
14
|
if has_x2 != has_y2
|
|
15
|
-
violations << violation(path: path.empty? ?
|
|
16
|
-
message:
|
|
15
|
+
violations << violation(path: path.empty? ? 'atom' : path.join('/'),
|
|
16
|
+
message: 'x2 and y2 must both be present or both absent')
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
has_x3 = !node.x3.to_s.empty?
|
|
20
20
|
has_y3 = !node.y3.to_s.empty?
|
|
21
21
|
has_z3 = !node.z3.to_s.empty?
|
|
22
22
|
if [has_x3, has_y3, has_z3].any? && ![has_x3, has_y3, has_z3].all?
|
|
23
|
-
violations << violation(path: path.empty? ?
|
|
24
|
-
message:
|
|
23
|
+
violations << violation(path: path.empty? ? 'atom' : path.join('/'),
|
|
24
|
+
message: 'x3, y3, and z3 must all be present together')
|
|
25
25
|
end
|
|
26
26
|
violations
|
|
27
27
|
end
|
|
@@ -5,18 +5,18 @@ module Chemicalml
|
|
|
5
5
|
module Molecular
|
|
6
6
|
module Constraints
|
|
7
7
|
class AtomIdMustMatchPattern < Chemicalml::Convention::Constraint::NodeConstraint
|
|
8
|
-
|
|
8
|
+
applies_to Chemicalml::Cml::Role::Atom
|
|
9
|
+
ID_PATTERN = /\A[A-Za-z][A-Za-z0-9._-]*\z/
|
|
9
10
|
|
|
10
11
|
def check_node(node, path)
|
|
11
|
-
return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
|
|
12
12
|
|
|
13
13
|
id = node.id.to_s
|
|
14
14
|
return [] if id.empty?
|
|
15
15
|
return [] if id.match?(ID_PATTERN)
|
|
16
16
|
|
|
17
|
-
[violation(path: path.empty? ?
|
|
17
|
+
[violation(path: path.empty? ? 'atom' : path.join('/'),
|
|
18
18
|
message: "atom id #{id.inspect} must start with a letter and " \
|
|
19
|
-
|
|
19
|
+
'contain only letters, digits, dot, hyphen, or underscore',
|
|
20
20
|
severity: :warning)]
|
|
21
21
|
end
|
|
22
22
|
end
|
|
@@ -16,7 +16,7 @@ module Chemicalml
|
|
|
16
16
|
duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
|
|
17
17
|
duplicates.each do |dup|
|
|
18
18
|
violations << violation(
|
|
19
|
-
path: path.empty? ? "molecule[#{node.id}]" : path.join(
|
|
19
|
+
path: path.empty? ? "molecule[#{node.id}]" : path.join('/'),
|
|
20
20
|
message: "duplicate atom id #{dup.inspect} within molecule #{node.id.inspect}"
|
|
21
21
|
)
|
|
22
22
|
end
|
|
@@ -33,7 +33,7 @@ module Chemicalml
|
|
|
33
33
|
def atom_ids_within(molecule)
|
|
34
34
|
return [] unless molecule.atom_array
|
|
35
35
|
|
|
36
|
-
(molecule.atom_array.atoms || []).map
|
|
36
|
+
(molecule.atom_array.atoms || []).map(&:id).compact
|
|
37
37
|
end
|
|
38
38
|
end
|
|
39
39
|
end
|
|
@@ -5,13 +5,13 @@ module Chemicalml
|
|
|
5
5
|
module Molecular
|
|
6
6
|
module Constraints
|
|
7
7
|
class AtomMustHaveElementType < Chemicalml::Convention::Constraint::NodeConstraint
|
|
8
|
+
applies_to Chemicalml::Cml::Role::Atom
|
|
8
9
|
def check_node(node, path)
|
|
9
|
-
return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
|
|
10
10
|
|
|
11
11
|
return [] unless node.element_type.to_s.empty?
|
|
12
12
|
|
|
13
|
-
[violation(path: path.empty? ?
|
|
14
|
-
message:
|
|
13
|
+
[violation(path: path.empty? ? 'atom' : path.join('/'),
|
|
14
|
+
message: 'atom must have an elementType attribute')]
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -5,13 +5,13 @@ module Chemicalml
|
|
|
5
5
|
module Molecular
|
|
6
6
|
module Constraints
|
|
7
7
|
class AtomMustHaveId < Chemicalml::Convention::Constraint::NodeConstraint
|
|
8
|
+
applies_to Chemicalml::Cml::Role::Atom
|
|
8
9
|
def check_node(node, path)
|
|
9
|
-
return [] unless node.is_a?(Chemicalml::Cml::Role::Atom)
|
|
10
10
|
|
|
11
11
|
return [] unless node.id.to_s.empty?
|
|
12
12
|
|
|
13
|
-
[violation(path: path.empty? ?
|
|
14
|
-
message:
|
|
13
|
+
[violation(path: path.empty? ? 'atom' : path.join('/'),
|
|
14
|
+
message: 'atom must have an id attribute')]
|
|
15
15
|
end
|
|
16
16
|
end
|
|
17
17
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Molecular
|
|
6
|
+
module Constraints
|
|
7
|
+
# Molecular convention: `<bondArray>` MUST be a child of
|
|
8
|
+
# `<molecule>`. Any other parent is invalid.
|
|
9
|
+
class BondArrayMustBeChildOfMolecule < Chemicalml::Convention::Constraint::NodeConstraint
|
|
10
|
+
def check_node(node, path)
|
|
11
|
+
return [] if molecule?(node)
|
|
12
|
+
|
|
13
|
+
misplaced = node.wire_children.select { |c| bond_array?(c) }
|
|
14
|
+
misplaced.map do |child|
|
|
15
|
+
violation(
|
|
16
|
+
path: (path + [describe(node), describe(child)]).join('/'),
|
|
17
|
+
message: 'bondArray must be a child of molecule, ' \
|
|
18
|
+
"but appears under #{node.element_name.inspect}"
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def molecule?(node)
|
|
26
|
+
node.is_a?(Chemicalml::Cml::Role::Molecule)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def bond_array?(node)
|
|
30
|
+
node.is_a?(Chemicalml::Cml::Role::BondArray)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Chemicalml
|
|
4
|
+
module Convention
|
|
5
|
+
module Molecular
|
|
6
|
+
module Constraints
|
|
7
|
+
# Molecular convention: a `<bond>` `id` MUST be unique within
|
|
8
|
+
# the eldest containing molecule. Mirrors the existing
|
|
9
|
+
# `AtomIdsUniqueWithinMolecule` constraint for bonds.
|
|
10
|
+
class BondIdsUniqueWithinMolecule < Chemicalml::Convention::Constraint::DocumentConstraint
|
|
11
|
+
def check(document)
|
|
12
|
+
violations = []
|
|
13
|
+
walk_nodes(document) do |node, path|
|
|
14
|
+
next unless molecule?(node)
|
|
15
|
+
|
|
16
|
+
ids = bond_ids_within(node)
|
|
17
|
+
duplicates = ids.group_by { |x| x }.select { |_, v| v.length > 1 }.keys
|
|
18
|
+
duplicates.each do |dup|
|
|
19
|
+
violations << violation(
|
|
20
|
+
path: (path + [describe(node)]).join('/'),
|
|
21
|
+
message: "duplicate bond id #{dup.inspect} within molecule #{node.id.inspect}"
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
violations
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def molecule?(node)
|
|
31
|
+
node.is_a?(Chemicalml::Cml::Role::Molecule)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bond_ids_within(molecule)
|
|
35
|
+
return [] unless molecule.bond_array
|
|
36
|
+
|
|
37
|
+
(molecule.bond_array.bonds || []).map(&:id).compact
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|