Skip to content

Commit 8f09048

Browse files
Updates Tests
1 parent f78408e commit 8f09048

6 files changed

Lines changed: 86 additions & 48 deletions

File tree

src/catcore/datamodel/catcore.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Auto generated from catcore.yaml by pythongen.py version: 0.0.1
2-
# Generation date: 2025-12-11T14:30:24
2+
# Generation date: 2026-01-21T11:56:35
33
# Schema: catcore-metadata
44
#
55
# id: https://w3id.org/nfdi4cat/catcore

tests/data/valid/Characterization-001.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ sample_pretreatment: ["calcined at 450C for 4 hours", "none", "degassed at 300C
1313
characterization_technique:
1414
# Powder XRD
1515
- identifier: "CHAR_TECH_001"
16+
"@type": "PowderXRD"
1617
xray_source: ["Cu Kalpha (lambda = 1.5406 A)"]
1718
atmosphere: ["ambient air"]
1819
operation_mode: ["continuous scan"]
@@ -26,6 +27,7 @@ characterization_technique:
2627

2728
# XPS
2829
- identifier: "CHAR_TECH_002"
30+
"@type": "XPS"
2931
xray_source: ["Al Kalpha (1486.6 eV)"]
3032
total_acquisition_time: [1800.0] # seconds
3133
number_of_scans: [10]
@@ -40,6 +42,7 @@ characterization_technique:
4042

4143
# BET Surface Area
4244
- identifier: "CHAR_TECH_003"
45+
"@type": "BET"
4346
adsorbate_gas: ["N2"]
4447
degassing_temperature: [300.0] # Celsius
4548
measurement_temperature: [77.0] # Kelvin
@@ -48,6 +51,7 @@ characterization_technique:
4851

4952
# Temperature Programmed Reduction
5053
- identifier: "CHAR_TECH_004"
54+
"@type": "TPR"
5155
reducing_gas_composition: ["5% H2/Ar"]
5256
heating_rate: [10.0] # Celsius/min
5357
minimum_temperature: [50.0] # Celsius

tests/data/valid/Reaction-001.yaml

Lines changed: 0 additions & 36 deletions
This file was deleted.

tests/data/valid/Simulation-001.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ software_package: ["VASP 6.3.0", "ASE 3.22.1"]
77

88
simulation_method:
99
- identifier: "SIM_METHOD_001"
10+
"@type": "DFT"
1011
exchange_correlation_functional: ["PBE (Perdew-Burke-Ernzerhof)"]
1112
energy_cutoff: [450.0] # eV
1213
convergence_criteria: ["energy: 1x10-5 eV, forces: 0.02 eV/A"]
@@ -17,6 +18,7 @@ simulation_method:
1718
calculated_property:
1819
# Surface properties
1920
- identifier: "CALC_PROP_001"
21+
"@type": "Surfaces"
2022
surface_energy: [0.089] # J/m²
2123
miller_indices: ["(111)"]
2224
slab_thickness: [12.75] # Angstrom (5 layers)
@@ -25,17 +27,19 @@ calculated_property:
2527

2628
# Electronic structure
2729
- identifier: "CALC_PROP_002"
30+
"@type": "ElectronicStructure"
2831
material_composition: ["Pt(111) surface with adsorbed CO"]
2932
crystal_structure: ["fcc Pt substrate"]
3033
k_point_mesh: ["5x5x1 for surface calculations"]
3134
energy_cutoff: [450.0] # eV
3235
smearing_method: ["Methfessel-Paxton, sigma=0.2 eV"]
3336
spin_polarized: [true]
34-
band_path: ["|-M-K-|"]
37+
band_path: ["Γ-M-K-Γ"]
3538
fermi_energy: [5.42] # eV
3639

3740
# Adsorption energies (as additional property)
3841
- identifier: "CALC_PROP_003"
42+
"@type": "ThermodynamicStability"
3943
formation_energy: [-1.68] # eV (CO adsorption energy)
4044
reference_energies: ["E(Pt_slab) = -261.70 eV, E(CO_gas) = -14.84 eV, E(Pt_slab+CO) = -278.22 eV"]
4145
energy_above_hull: [0.0] # eV (most stable configuration)

tests/data/valid/Synthesis-001.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ precursor:
1212

1313
preparation_method:
1414
- identifier: "PREP_001"
15+
# Add the @type field to specify the concrete class
16+
"@type": "Impregnation"
1517
impregnation_type: ["wet_impregnation"]
1618
impregnation_duration: [4.0] # hours
1719
impregnation_temperature: [25.0] # Celsius

tests/test_data.py

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
VALID_EXAMPLE_FILES = glob.glob(os.path.join(DATA_DIR_VALID, '*.yaml'))
1515
INVALID_EXAMPLE_FILES = glob.glob(os.path.join(DATA_DIR_INVALID, '*.yaml'))
1616

17-
1817
# Mapping from identifier patterns or context to concrete class names
1918
CHARACTERIZATION_TECHNIQUE_MAP = {
2019
'xray_source': {
@@ -24,11 +23,30 @@
2423
'adsorbate_gas': 'BET',
2524
'reducing_gas_composition': 'TPR',
2625
'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',
2745
}
2846

2947
PREPARATION_METHOD_MAP = {
3048
'impregnation_type': 'Impregnation',
31-
'precipitating_agent': 'CoPrecipitation', # or DepositionPrecipitation
49+
'precipitating_agent': 'CoPrecipitation',
3250
'hydrolysis_ratio': 'SolGel',
3351
'filling_volume': 'Solvothermal',
3452
'plasma_type': 'PlasmaAssisted',
@@ -38,7 +56,6 @@
3856
'sonication_power': 'SonochemicalSynthesis',
3957
'flame_type': 'FlameSprayPyrolysis',
4058
'ball_material': 'MechanochemicalSynthesis',
41-
'synthesis_pressure': 'Sublimation',
4259
'reaction_vessel': 'MolecularSynthesis',
4360
}
4461

@@ -57,10 +74,25 @@
5774
'band_path': 'ElectronicStructure',
5875
'polarization_direction': 'Ferroelectrics',
5976
'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',
6092
}
6193

6294

63-
def infer_class_type(data: Dict[str, Any], type_map: Dict[str, Any]) -> str:
95+
def infer_class_type(data: Dict[str, Any], type_map: Dict[str, Any], default_class: str = None) -> str:
6496
"""Infer the concrete class type based on present fields."""
6597
for key, class_name in type_map.items():
6698
if key in data:
@@ -71,7 +103,9 @@ def infer_class_type(data: Dict[str, Any], type_map: Dict[str, Any]) -> str:
71103
return cn
72104
else:
73105
return class_name
74-
return None
106+
107+
# Return default class if no specific fields found
108+
return default_class
75109

76110

77111
def instantiate_polymorphic_objects(data: Union[Dict, List], parent_key: str = None) -> Union[Dict, List]:
@@ -82,32 +116,51 @@ def instantiate_polymorphic_objects(data: Union[Dict, List], parent_key: str = N
82116
if not isinstance(data, dict):
83117
return data
84118

119+
# Check for explicit type hint (both 'type' and '@type' for LinkML compatibility)
120+
explicit_type = data.get('type') or data.get('@type')
121+
85122
# Recursively process nested structures first
86123
result = {}
87124
for key, value in data.items():
125+
if key in ('type', '@type'): # Skip the type hint fields
126+
continue
88127
result[key] = instantiate_polymorphic_objects(value, key)
89128

90129
# Now handle polymorphic instantiation for specific keys
91130
if parent_key == 'characterization_technique':
92-
class_name = infer_class_type(result, CHARACTERIZATION_TECHNIQUE_MAP)
131+
class_name = explicit_type or infer_class_type(result, CHARACTERIZATION_TECHNIQUE_MAP)
93132
if class_name:
94133
cls = getattr(catcore, class_name)
95134
return cls(**result)
96135

97136
elif parent_key == 'preparation_method':
98-
class_name = infer_class_type(result, PREPARATION_METHOD_MAP)
137+
class_name = explicit_type or infer_class_type(result, PREPARATION_METHOD_MAP)
99138
if class_name:
100139
cls = getattr(catcore, class_name)
101140
return cls(**result)
102141

103142
elif parent_key == 'simulation_method':
104-
class_name = infer_class_type(result, SIMULATION_METHOD_MAP)
143+
class_name = explicit_type or infer_class_type(result, SIMULATION_METHOD_MAP)
105144
if class_name:
106145
cls = getattr(catcore, class_name)
107146
return cls(**result)
108147

109148
elif parent_key == 'calculated_property':
110-
class_name = infer_class_type(result, CALCULATED_PROPERTY_MAP)
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')
111164
if class_name:
112165
cls = getattr(catcore, class_name)
113166
return cls(**result)
@@ -126,7 +179,18 @@ def test_valid_data_files(filepath):
126179
data_dict = yaml.safe_load(f)
127180

128181
# Handle polymorphic fields by instantiating concrete classes
129-
for poly_field in ['characterization_technique', 'preparation_method', 'simulation_method', 'calculated_property']:
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:
130194
if poly_field in data_dict and data_dict[poly_field]:
131195
data_dict[poly_field] = instantiate_polymorphic_objects(
132196
data_dict[poly_field],

0 commit comments

Comments
 (0)