Skip to content

Commit 4016688

Browse files
Update docs, test fixtures, and test loader
Adjust docs and mkdocs config (switch Simulation emoji token and enable pymdownx.emoji with twemoji/svg). Add Reaction-001 fixture, remove Simulation-001, and normalize/update Characterization-001 and Synthesis-001 YAML fixtures (IDs, units, new fields like realized_plan/evaluated_entity/had_input_entity, clearer descriptions). Refactor tests/test_data.py to import src.catcore.datamodel.catcore and use linkml_runtime.yaml_loader.load for loading target classes, removing the previous custom polymorphic-instantiation logic and yaml dependency. These changes align test data with the loader/schema and simplify test loading.
1 parent da7722a commit 4016688

7 files changed

Lines changed: 97 additions & 225 deletions

File tree

docs/overview.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ description: Comprehensive Metadata Guidelines for Catalysis Research Data
1919

2020
Eight reactor design types, flattened operation parameter slots, and product identification links.
2121

22-
- :material-cpu-64-bit: **Simulation**
22+
- :octicons-cpu-16: **Simulation**
2323

2424
Four computational methods (DFT, MD, Microkinetics, Monte Carlo) with 12 calculated property classes.
2525

mkdocs.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,6 @@ markdown_extensions:
7070
- md_in_html
7171
- admonition
7272
- attr_list
73+
- pymdownx.emoji:
74+
emoji_index: !!python/name:material.extensions.emoji.twemoji
75+
emoji_generator: !!python/name:material.extensions.emoji.to_svg
Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,31 @@
11
---
2-
# Complete Characterization Instance
3-
# This represents a Characterization object as defined in the schema
2+
# Characterization-001 -- Powder XRD of 5 wt% Pt/Al2O3
3+
# Target class: Characterization
4+
#
5+
# NOTE: realized_plan range is CharacterizationTechnique (abstract, no domain slots).
6+
# Technique parameters from catcore_common (atmosphere, temperature, step_size, etc.)
7+
# are accepted directly on Characterization. XRD-specific slots (xray_source etc.)
8+
# live on PowderXRD subclass — not reachable by the loader, so omitted here.
9+
# realized_plan: title/description only.
410

5-
identifier: "CHAR_001_Pt_Al2O3"
6-
equipment: ["Bruker D8 Advance diffractometer"]
7-
sample_state: ["powder"]
8-
sample_description: ["5 wt% Pt/Al2O3 catalyst after calcination at 450C"]
9-
detector_type: ["Lynxeye detector"]
10-
sample_preparation: ["pressed into sample holder without binder"]
11-
sample_pretreatment: ["calcined at 450C for 4 hours"]
11+
id: "catcore:CHAR_001_PXRD_Pt_Al2O3"
12+
equipment:
13+
- "Bruker D8 Advance diffractometer"
14+
sample_state:
15+
- "powder"
16+
sample_description:
17+
- "5 wt% Pt/Al2O3 catalyst after calcination at 450 deg C"
18+
detector_type:
19+
- "Lynxeye XE-T detector"
20+
sample_preparation:
21+
- "pressed into flat sample holder without binder"
22+
sample_pretreatment:
23+
- "calcined at 450 deg C for 4 hours in static air"
24+
25+
evaluated_entity:
26+
- id: "catcore:CATSAMPLE_001_Pt_Al2O3"
27+
title: "5 wt% Pt/Al2O3 catalyst"
1228

13-
characterization_technique:
14-
- identifier: "CHAR_TECH_001"
29+
realized_plan:
30+
title: "powder XRD, Cu Kalpha, 5-80 deg 2theta"
31+
description: "Bruker D8 Advance, Cu Kalpha 40kV/40mA, Ni beta-filter, Lynxeye detector"

tests/data/valid/Reaction-001.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
# Reaction-001 -- CO oxidation over 5 wt% Pt/Al2O3 in a fixed bed reactor
3+
# Target class: Reaction
4+
# carried_out_by: list, keyed by id (ReactorDesignType is_a Device/AgenticEntity, keyed)
5+
# product_identification_method: list, no id (ProductIdentificationMethod is_a Plan, not keyed)
6+
7+
id: "catcore:REACT_001_CO_oxidation_Pt_Al2O3"
8+
catalyst_quantity:
9+
- 0.1
10+
catalyst_type:
11+
- "heterogeneous catalyst"
12+
reactant:
13+
- "CO, 1 vol% in N2"
14+
- "O2, 2 vol% in N2"
15+
atmosphere:
16+
- "1 vol% CO, 2 vol% O2, balance N2"
17+
feed_composition_range:
18+
- "1 vol% CO, 2 vol% O2, balance N2"
19+
reactor_temperature_range:
20+
- "100-300 deg C"
21+
experiment_pressure:
22+
- 1.0
23+
experiment_duration:
24+
- 5.0
25+
26+
carried_out_by:
27+
- id: "catcore:REACTOR_001_fixedbed"
28+
title: "quartz fixed bed reactor, 6 mm inner diameter"
29+
30+
product_identification_method:
31+
- title: "online GC-MS analysis of reactor effluent"

tests/data/valid/Simulation-001.yaml

Lines changed: 0 additions & 12 deletions
This file was deleted.
Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
---
2-
# Complete Synthesis Instance
3-
# This represents a Synthesis object as defined in the schema
2+
# Synthesis-001 -- Incipient wetness impregnation of 5 wt% Pt/Al2O3
3+
# Target class: Synthesis
4+
#
5+
# NOTE: realized_plan range is PreparationMethod (abstract, no domain slots).
6+
# Domain synthesis parameters that live on catcore_common ARE accepted on Synthesis directly.
7+
# realized_plan: title/description only — the loader cannot dispatch to Impregnation subclass.
48

9+
id: "catcore:SYNTH_001_Pt_Al2O3"
10+
nominal_composition:
11+
- "5 wt% Pt/Al2O3"
12+
catalyst_measured_properties:
13+
- "BET surface area: 185 m2/g, Pt particle size: 2.3 nm (TEM), Pt loading: 4.8 wt% (ICP-AES)"
14+
storage_conditions:
15+
- "stored in desiccator under argon atmosphere at room temperature"
16+
support:
17+
- "gamma-Al2O3, Sasol Puralox, 200 m2/g"
18+
solvent:
19+
- "deionized water"
20+
sample_pretreatment:
21+
- "reduction in H2 at 400 deg C for 2 hours prior to catalytic testing"
22+
had_input_entity:
23+
- id: "catcore:PREC_001_H2PtCl6"
24+
title: "chloroplatinic acid hexahydrate"
25+
precursor_quantity:
26+
- 0.0485
527

6-
7-
identifier: "SYNTH_001_Pt_Al2O3"
8-
nominal_composition: "5 wt% Pt/Al2O3"
9-
catalyst_measured_properties: "BET surface area: 185 m²/g, Pt particle size: 2.3 nm (TEM), Pt loading: 4.8 wt% (ICP-AES)"
10-
storage_conditions: ["stored in desiccator under argon atmosphere at room temperature"]
11-
support: ["γ-Al2O3, Sasol Puralox, 200 m²/g"]
12-
solvent: ["deionized water", "ethanol"]
13-
sample_pretreatment: ["reduction in H2 at 400°C for 2 hours prior to catalytic testing"]
14-
15-
precursor:
16-
- identifier: "PREC_001"
17-
precursor_quantity: [0.0485]
18-
19-
preparation_method:
20-
- identifier: "PREP_001"
28+
realized_plan:
29+
title: "incipient wetness impregnation"
30+
description: "incipient_wetness, 25 deg C, 12 h"

tests/test_data.py

Lines changed: 7 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1+
"""Data test."""
12
import os
23
import glob
34
import pytest
4-
import yaml
55
from pathlib import Path
6-
from typing import Dict, Any, Union, List
76

8-
from catcore.datamodel import catcore
7+
import src.catcore.datamodel.catcore
98
from linkml_runtime.loaders import yaml_loader
109

1110
DATA_DIR_VALID = Path(__file__).parent / "data" / "valid"
@@ -14,190 +13,14 @@
1413
VALID_EXAMPLE_FILES = glob.glob(os.path.join(DATA_DIR_VALID, '*.yaml'))
1514
INVALID_EXAMPLE_FILES = glob.glob(os.path.join(DATA_DIR_INVALID, '*.yaml'))
1615

17-
# Mapping from identifier patterns or context to concrete class names
18-
CHARACTERIZATION_TECHNIQUE_MAP = {
19-
'xray_source': {
20-
'Cu Kalpha': 'PowderXRD',
21-
'Al Kalpha': 'XPS',
22-
},
23-
'adsorbate_gas': 'BET',
24-
'reducing_gas_composition': 'TPR',
25-
'oxidizing_gas_composition': 'TPO',
26-
'excitation_laser_wavelength': 'RamanSpectroscopy',
27-
'minimum_wavenumber': 'InfraredSpectroscopy',
28-
'element_analyzed': 'XRayAbsorptionSpectroscopy',
29-
'nucleus': 'NMRSpectroscopy',
30-
'gun_type': 'TransmissionElectronMicroscopy',
31-
'image_resolution': 'ScanningElectronMicroscopy',
32-
'initial_temperature': 'Thermogravimetry',
33-
'combustion_temperature': 'ElementalAnalysis',
34-
'minimum_wavelength': 'UVVisSpectroscopy',
35-
'adsorption_gas': 'DRIFTS',
36-
'scan_rate': 'CyclicVoltammetry',
37-
'light_wavelength': 'DynamicLightScattering',
38-
'spray_voltage': 'ESI_MS',
39-
'excitation_wavelength': 'PhotoluminescenceSpectroscopy',
40-
'lifetime_fitting_model': 'PhotoluminescenceLifetime',
41-
'eluent': 'SizeExclusionChromatography',
42-
'gradient_program': 'HPLC_MS',
43-
'primary_energy': 'EDX',
44-
'electrode_configuration': 'ConductivityMeasurement',
45-
}
46-
47-
PREPARATION_METHOD_MAP = {
48-
'impregnation_type': 'Impregnation',
49-
'precipitating_agent': 'CoPrecipitation',
50-
'hydrolysis_ratio': 'SolGel',
51-
'filling_volume': 'Solvothermal',
52-
'plasma_type': 'PlasmaAssisted',
53-
'fuel': 'CombustionSynthesis',
54-
'substrate': 'AtomicLayerDeposition',
55-
'microwave_frequency': 'MicrowaveAssisted',
56-
'sonication_power': 'SonochemicalSynthesis',
57-
'flame_type': 'FlameSprayPyrolysis',
58-
'ball_material': 'MechanochemicalSynthesis',
59-
'reaction_vessel': 'MolecularSynthesis',
60-
}
61-
62-
SIMULATION_METHOD_MAP = {
63-
'exchange_correlation_functional': 'DFT',
64-
'force_field': 'MolecularDynamics',
65-
'rate_constants': 'Microkinetics',
66-
'interaction_potential': 'MonteCarlo',
67-
}
68-
69-
CALCULATED_PROPERTY_MAP = {
70-
'formation_energy': 'ThermodynamicStability',
71-
'piezoelectric_tensor': 'Piezoelectricity',
72-
'elastic_tensor': 'ElasticConstants',
73-
'surface_energy': 'Surfaces',
74-
'band_path': 'ElectronicStructure',
75-
'polarization_direction': 'Ferroelectrics',
76-
'direct_indirect': 'BandGap',
77-
'material_composition': 'DielectricTensors',
78-
'force_constant_method': 'PhononDispersion',
79-
'fit_method': 'EquationsOfState',
80-
'ph_range': 'AqueousStability',
81-
'grain_boundary_plane': 'GrainBoundaries',
82-
}
83-
84-
REACTOR_DESIGN_MAP = {
85-
'gas_distributor_type': 'FluidizedBedReactor',
86-
}
87-
88-
# Default classes for abstract types when no specific fields are found
89-
DEFAULT_CLASSES = {
90-
'reactor_design_type': 'FixedBedReactor',
91-
'product_identification_method': 'GCMS',
92-
}
93-
94-
95-
def infer_class_type(data: Dict[str, Any], type_map: Dict[str, Any], default_class: str = None) -> str:
96-
"""Infer the concrete class type based on present fields."""
97-
for key, class_name in type_map.items():
98-
if key in data:
99-
if isinstance(class_name, dict):
100-
# Need to check value
101-
for value_pattern, cn in class_name.items():
102-
if value_pattern in str(data[key]):
103-
return cn
104-
else:
105-
return class_name
106-
107-
# Return default class if no specific fields found
108-
return default_class
109-
110-
111-
def instantiate_polymorphic_objects(data: Union[Dict, List], parent_key: str = None) -> Union[Dict, List]:
112-
"""Recursively instantiate concrete classes for polymorphic fields."""
113-
if isinstance(data, list):
114-
return [instantiate_polymorphic_objects(item, parent_key) for item in data]
115-
116-
if not isinstance(data, dict):
117-
return data
118-
119-
# Check for explicit type hint (both 'type' and '@type' for LinkML compatibility)
120-
explicit_type = data.get('type') or data.get('@type')
121-
122-
# Recursively process nested structures first
123-
result = {}
124-
for key, value in data.items():
125-
if key in ('type', '@type'): # Skip the type hint fields
126-
continue
127-
result[key] = instantiate_polymorphic_objects(value, key)
128-
129-
# Now handle polymorphic instantiation for specific keys
130-
if parent_key == 'characterization_technique':
131-
class_name = explicit_type or infer_class_type(result, CHARACTERIZATION_TECHNIQUE_MAP)
132-
if class_name:
133-
cls = getattr(catcore, class_name)
134-
return cls(**result)
135-
136-
elif parent_key == 'preparation_method':
137-
class_name = explicit_type or infer_class_type(result, PREPARATION_METHOD_MAP)
138-
if class_name:
139-
cls = getattr(catcore, class_name)
140-
return cls(**result)
141-
142-
elif parent_key == 'simulation_method':
143-
class_name = explicit_type or infer_class_type(result, SIMULATION_METHOD_MAP)
144-
if class_name:
145-
cls = getattr(catcore, class_name)
146-
return cls(**result)
147-
148-
elif parent_key == 'calculated_property':
149-
class_name = explicit_type or infer_class_type(result, CALCULATED_PROPERTY_MAP)
150-
if class_name:
151-
cls = getattr(catcore, class_name)
152-
return cls(**result)
153-
154-
elif parent_key == 'reactor_design_type':
155-
class_name = explicit_type or infer_class_type(result, REACTOR_DESIGN_MAP,
156-
DEFAULT_CLASSES.get('reactor_design_type'))
157-
if class_name:
158-
cls = getattr(catcore, class_name)
159-
return cls(**result)
160-
161-
elif parent_key == 'product_identification_method':
162-
# For now, use GCMS as default if no specific fields found
163-
class_name = explicit_type or DEFAULT_CLASSES.get('product_identification_method')
164-
if class_name:
165-
cls = getattr(catcore, class_name)
166-
return cls(**result)
167-
168-
return result
169-
17016

17117
@pytest.mark.parametrize("filepath", VALID_EXAMPLE_FILES)
17218
def test_valid_data_files(filepath):
17319
"""Test loading of all valid data files."""
17420
target_class_name = Path(filepath).stem.split("-")[0]
175-
tgt_class = getattr(catcore, target_class_name)
176-
177-
# Load the YAML content
178-
with open(filepath, 'r') as f:
179-
data_dict = yaml.safe_load(f)
180-
181-
# Handle polymorphic fields by instantiating concrete classes
182-
poly_fields = [
183-
'characterization_technique',
184-
'preparation_method',
185-
'simulation_method',
186-
'calculated_property',
187-
'reactor_design_type',
188-
'product_identification_method',
189-
'operation_parameters',
190-
'precursor'
191-
]
192-
193-
for poly_field in poly_fields:
194-
if poly_field in data_dict and data_dict[poly_field]:
195-
data_dict[poly_field] = instantiate_polymorphic_objects(
196-
data_dict[poly_field],
197-
poly_field
198-
)
199-
200-
# Instantiate the target class
201-
obj = tgt_class(**data_dict)
202-
21+
tgt_class = getattr(
22+
src.catcore.datamodel.catcore,
23+
target_class_name,
24+
)
25+
obj = yaml_loader.load(filepath, target_class=tgt_class)
20326
assert obj

0 commit comments

Comments
 (0)