Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f794cb7
Added new chemked schema for new experiment types
LekiaAnonim Mar 28, 2026
d3c3807
fix: match PyKED convention for composition units in batch converter
LekiaAnonim Mar 28, 2026
3b44a86
fix: address PR review feedback
LekiaAnonim Mar 28, 2026
c204570
add uncertainty, evaluated-standard-deviation, and reactor geometry p…
LekiaAnonim Mar 28, 2026
c8b2542
refactor: inline uncertainty on property and composition amount fields
LekiaAnonim Mar 28, 2026
b6e4383
docs: update uncertainty_schema.yaml comment to reflect inline refact…
LekiaAnonim Mar 28, 2026
cdd775a
fix: equivalence-ratio as value-unit-optional, flow-style values, 2-s…
LekiaAnonim Mar 28, 2026
0239951
Working on PyKED package upgrade to allow celebrus validation
LekiaAnonim Mar 30, 2026
d327f44
feat: add rate coefficient support and fix cerberus/crossref compat i…
LekiaAnonim Mar 31, 2026
efae6c2
Modified pyked for direct measurement data such as rate coefficient
LekiaAnonim Apr 1, 2026
5080454
Added new ignition delay type d/dt min extrapolated to pyked schema a…
LekiaAnonim Apr 2, 2026
711d152
Fix batch_convert ReSpecTh conversion issues and extend ignition sche…
LekiaAnonim Apr 2, 2026
fdfd7a9
Changed stirred reaction to stirred reactor in chemked schema
LekiaAnonim Apr 2, 2026
55f8f2a
fix: reject empty uncertainty dicts; add missing property_units entries
LekiaAnonim Apr 2, 2026
45ff61f
Removed volumetric flow in reference state from dataproperties in con…
LekiaAnonim Apr 2, 2026
81f06af
fix: strip semicolons from ignition targets; add relative concentrati…
LekiaAnonim Apr 2, 2026
fc40b8c
feat: capture amount field for relative concentration ignition type
LekiaAnonim Apr 2, 2026
27c2b54
Fix HTML entity escaping in CrossRef journal names
LekiaAnonim Apr 5, 2026
0ff6e79
Expand schemas for ReSpecTh batch conversion compatibility
LekiaAnonim Apr 5, 2026
6f5b483
Make apparatus mode to accept multiple values
LekiaAnonim Apr 6, 2026
8fc4ac3
Made all re import global
LekiaAnonim Apr 6, 2026
09abd79
Include new experiment schema to PyKED docs
LekiaAnonim Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
794 changes: 794 additions & 0 deletions pyked/batch_convert.py

Large diffs are not rendered by default.

59 changes: 45 additions & 14 deletions pyked/chemked.py
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,8 @@ class DataPoint(object):
"""
value_unit_props = [
'ignition-delay', 'first-stage-ignition-delay', 'temperature', 'pressure',
'pressure-rise',
'pressure-rise', 'laminar-burning-velocity', 'distance', 'flow-rate',
'residence-time', 'volumetric-flow-in-reference-state',
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reactor-volume was added to the schema/common-properties and validation.property_units, but DataPoint.value_unit_props doesn’t include it. As a result, any reactor-volume values present in YAML will be ignored when constructing DataPoint objects (information loss), and downstream code can’t access it as a parsed pint.Quantity. Add reactor-volume to value_unit_props (and ensure it’s handled consistently with the other value+unit properties).

Suggested change
'residence-time', 'volumetric-flow-in-reference-state',
'residence-time', 'volumetric-flow-in-reference-state', 'reactor-volume',

Copilot uses AI. Check for mistakes.
]

rcm_data_props = [
Expand Down Expand Up @@ -656,19 +657,49 @@ def __init__(self, properties):
else:
self.rcm_data = None

self.composition_type = properties['composition']['kind']
composition = {}
for species in properties['composition']['species']:
species_name = species['species-name']
amount = self.process_quantity(species['amount'])
InChI = species.get('InChI')
SMILES = species.get('SMILES')
atomic_composition = species.get('atomic-composition')
composition[species_name] = Composition(
species_name=species_name, InChI=InChI, SMILES=SMILES,
atomic_composition=atomic_composition, amount=amount)

setattr(self, 'composition', composition)
if 'composition' in properties:
self.composition_type = properties['composition']['kind']
composition = {}
for species in properties['composition']['species']:
species_name = species['species-name']
amount = self.process_quantity(species['amount'])
InChI = species.get('InChI')
SMILES = species.get('SMILES')
atomic_composition = species.get('atomic-composition')
composition[species_name] = Composition(
species_name=species_name, InChI=InChI, SMILES=SMILES,
atomic_composition=atomic_composition, amount=amount)
setattr(self, 'composition', composition)
else:
self.composition_type = None
self.composition = {}

# Measured composition (for JSR, OCM, BSFSM experiment types)
if 'measured-composition' in properties:
self.measured_composition_type = properties['measured-composition']['kind']
measured = {}
for species in properties['measured-composition']['species']:
species_name = species['species-name']
amount = self.process_quantity(species['amount'])
InChI = species.get('InChI')
SMILES = species.get('SMILES')
atomic_composition = species.get('atomic-composition')
measured[species_name] = Composition(
species_name=species_name, InChI=InChI, SMILES=SMILES,
atomic_composition=atomic_composition, amount=amount)
self.measured_composition = measured
else:
self.measured_composition_type = None
self.measured_composition = {}

# Concentration profiles (for concentration time profile measurement)
self.concentration_profiles = []
if 'concentration-profiles' in properties:
for profile in properties['concentration-profiles']:
self.concentration_profiles.append(profile)

# Time shift (for concentration time profile measurement)
self.time_shift = properties.get('time-shift')

self.equivalence_ratio = properties.get('equivalence-ratio')
self.ignition_type = deepcopy(properties.get('ignition-type'))
Expand Down
71 changes: 47 additions & 24 deletions pyked/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@

# Valid properties for ReSpecTh dataGroup
datagroup_properties = ['temperature', 'pressure', 'ignition delay',
'pressure rise',
'pressure rise', 'laminar burning velocity',
'distance', 'flow rate', 'residence time',
'volumetric flow in reference state',
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

datagroup_properties includes volumetric flow in reference state, but elsewhere in this PR (e.g., batch_convert.py) the property name is volumetric flow rate in reference state. With the current list, a ReSpecTh file using the ...flow rate... spelling will be rejected as an invalid property in both get_common_properties() and get_datapoints(). Consider accepting both spellings (or standardizing on the ReSpecTh-canonical name) and mapping both to the ChemKED key volumetric-flow-in-reference-state.

Suggested change
'volumetric flow in reference state',
'volumetric flow in reference state',
'volumetric flow rate in reference state',

Copilot uses AI. Check for mistakes.
]
"""`list`: Valid properties for a ReSpecTh dataGroup"""

Expand Down Expand Up @@ -159,20 +161,38 @@ def get_experiment_kind(root):
properties (`dict`): Dictionary with experiment type and apparatus information.
"""
properties = {}
if root.find('experimentType').text == 'Ignition delay measurement':
properties['experiment-type'] = 'ignition delay'
else:
raise NotImplementedError(root.find('experimentType').text + ' not (yet) supported')

exp_type_text = getattr(root.find('experimentType'), 'text', '')
exp_type_map = {
'Ignition delay measurement': 'ignition delay',
'Laminar burning velocity measurement': 'laminar burning velocity measurement',
'Concentration time profile measurement': 'concentration time profile measurement',
'Jet stirred reactor measurement': 'jet stirred reactor measurement',
'Outlet concentration measurement': 'outlet concentration measurement',
'Burner stabilized flame speciation measurement': 'burner stabilized flame speciation measurement',
}
matched_type = exp_type_map.get(exp_type_text)
if matched_type is None:
# Try case-insensitive match
for key, val in exp_type_map.items():
if key.lower() == exp_type_text.lower():
matched_type = val
break
if matched_type is None:
raise NotImplementedError(exp_type_text + ' not (yet) supported')
properties['experiment-type'] = matched_type
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exp_type_text is taken directly from root.find('experimentType').text without stripping whitespace. If the XML contains leading/trailing whitespace/newlines (common in pretty-printed files), the lookup and the case-insensitive fallback will fail and raise NotImplementedError for an otherwise supported experiment type. Using root.findtext('experimentType', '').strip() (and raising MissingElementError('experimentType') when empty) would make this parsing robust and align error handling with other required elements like apparatus/kind.

Copilot uses AI. Check for mistakes.

properties['apparatus'] = {'kind': '', 'institution': '', 'facility': ''}
kind = getattr(root.find('apparatus/kind'), 'text', False)
# Test for missing attribute or empty string
if not kind:
raise MissingElementError('apparatus/kind')
elif kind in ['shock tube', 'rapid compression machine']:
properties['apparatus']['kind'] = kind
else:
raise NotImplementedError(kind + ' experiment not (yet) supported')
properties['apparatus']['kind'] = kind

mode = getattr(root.find('apparatus/mode'), 'text', None)
if mode:
properties['apparatus']['mode'] = mode
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_experiment_kind() now accepts any apparatus/kind (and apparatus/mode) text and passes it through unchanged. Since the ChemKED schema constrains apparatus.kind/mode to an allowed set, this can cause ReSpecTh_to_ChemKED(..., validate=True) or later ChemKED validation to fail with an unhelpful error for unexpected but mappable ReSpecTh values. Consider normalizing/mapping known ReSpecTh variants to the schema’s allowed strings and raising a targeted NotImplementedError (or KeywordError) when the kind/mode can’t be mapped.

Copilot uses AI. Check for mistakes.

return properties
Comment on lines +165 to 201
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

get_experiment_kind() behavior changes (new supported experiment types, relaxed apparatus kind validation, optional apparatus.mode) will require updating pyked/tests/test_converters.py expectations and adding coverage for at least one non-ignition experiment type + apparatus/mode parsing. Without corresponding test updates, the existing converter test suite will fail or no longer verify the intended behavior for these new branches.

Copilot uses AI. Check for mistakes.

Expand Down Expand Up @@ -503,25 +523,28 @@ def ReSpecTh_to_ChemKED(filename_xml, file_author='', file_author_orcid='', *, v
# Get properties shared across the file
properties['common-properties'] = get_common_properties(root)

# Determine definition of ignition delay
properties['common-properties']['ignition-type'] = get_ignition_type(root)
# Determine definition of ignition delay (only for ignition delay experiments)
if properties['experiment-type'] == 'ignition delay':
properties['common-properties']['ignition-type'] = get_ignition_type(root)

# Now parse ignition delay datapoints
# Now parse datapoints
properties['datapoints'] = get_datapoints(root)

Comment on lines -532 to 552
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReSpecTh_to_ChemKED() now allows non-ignition experiment types via get_experiment_kind(), but it still unconditionally calls get_datapoints(), whose docstring/logic is ignition-delay-specific (e.g., it only supports the ignition-delay dataGroup structure and will reject other required columns like time for concentration time profiles). This means supported experiment types can fail later with unrelated KeyError/KeywordError instead of a clear “not implemented” message, and it doesn’t produce datapoints matching the new non-IDT schemas. Either keep rejecting non-ignition types in this converter until per-type datapoint parsing is implemented, or add experiment-type-specific datapoint parsing that matches the new schemas.

Copilot uses AI. Check for mistakes.
# Ensure inclusion of pressure rise or volume history matches apparatus.
has_pres_rise = ('pressure-rise' in properties['common-properties'] or
any([True for dp in properties['datapoints'] if 'pressure-rise' in dp])
)
if has_pres_rise and properties['apparatus']['kind'] == 'rapid compression machine':
raise KeywordError('Pressure rise cannot be defined for RCM.')

has_vol_hist = any(
[t.get('type') == 'volume' for dp in properties['datapoints']
for t in dp.get('time-histories', [{}])]
)
if has_vol_hist and properties['apparatus']['kind'] == 'shock tube':
raise KeywordError('Volume history cannot be defined for shock tube.')
# Ensure inclusion of pressure rise or volume history matches apparatus
# (only relevant for ignition delay experiments)
if properties['experiment-type'] == 'ignition delay':
has_pres_rise = ('pressure-rise' in properties['common-properties'] or
any([True for dp in properties['datapoints'] if 'pressure-rise' in dp])
)
if has_pres_rise and properties['apparatus']['kind'] == 'rapid compression machine':
raise KeywordError('Pressure rise cannot be defined for RCM.')

has_vol_hist = any(
[t.get('type') == 'volume' for dp in properties['datapoints']
for t in dp.get('time-histories', [{}])]
)
if has_vol_hist and properties['apparatus']['kind'] == 'shock tube':
raise KeywordError('Volume history cannot be defined for shock tube.')

# add any additional file authors
if file_author_orcid and not file_author:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Schema for burner stabilized flame speciation measurement datapoints
burner-stabilized-flame-speciation-measurement-schema: &burner-stabilized-flame-speciation-measurement-schema
type: list
minlength: 1
schema:
type: dict
schema:
pressure: *value-unit-required
temperature: *value-unit-required
composition: *composition
equivalence-ratio:
type: float
min: 0.0
distance: *value-unit-required
flow-rate: *value-unit-optional
measured-composition: *composition
51 changes: 51 additions & 0 deletions pyked/schemas/chemked_schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
!include value_unit_schema.yaml
!include composition_schema.yaml
!include ignition_delay_schema.yaml
!include laminar_burning_velocity_measurement_schema.yaml
!include concentration_time_profile_measurement_schema.yaml
!include jet_stirred_reactor_measurement_schema.yaml
!include outlet_concentration_measurement_schema.yaml
!include burner_stabilized_flame_speciation_measurement_schema.yaml
######################################################

# Common reference for authors' information
Expand All @@ -26,9 +31,16 @@ common-properties:
type: dict
schema:
pressure: *value-unit-optional
temperature: *value-unit-optional
ignition-type: *ignition-type
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common-properties includes ignition-type: *ignition-type, but the ignition-type anchor (from ignition_delay_schema.yaml) is marked required: true. With the newly supported non-ignition experiment types, it’s valid to have common-properties without an ignition definition; however, this schema will still require ignition-type whenever common-properties is present, causing non-ignition YAML files to fail validation. Consider making ignition-type optional in common-properties (e.g., override required: false there or define a separate non-required ignition-type reference for common use).

Suggested change
ignition-type: *ignition-type
ignition-type:
<<: *ignition-type
required: false

Copilot uses AI. Check for mistakes.
composition: *composition
pressure-rise: *value-unit-optional
residence-time: *value-unit-optional
reactor-volume: *value-unit-optional
flow-rate: *value-unit-optional
equivalence-ratio:
type: float
min: 0.0

apparatus:
required: true
Expand All @@ -38,8 +50,37 @@ apparatus:
allowed:
- shock tube
- rapid compression machine
- stirred reactor
- stirred reactor (quartz)
- stirred reactor (fused silica)
- stirred reaction
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apparatus.kind allowed values include "stirred reaction", which looks like a typo (likely meant "stirred reactor"). Keeping the misspelling in the enum will allow invalid/undesired values and make downstream normalization harder.

Suggested change
- stirred reaction

Copilot uses AI. Check for mistakes.
- jet stirred reactor
- flow reactor
- flow reactor (quartz)
- flow reactor (alumina)
- flow reactor (recrystallized alumina)
- flame
- outwardly propagating spherical flame
- heat flux burner
required: true
type: string
mode:
type: string
allowed:
- reflected shock
- incident shock
- laminar
- burner stabilized
- constant volume combustion chamber
- premixed
- unstretched
- extrapolation method to zero stretch : LS
- extrapolation method to zero stretch : NQ
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The apparatus.mode.allowed list contains entries with : (e.g., extrapolation method to zero stretch : LS). In YAML, an unquoted : followed by a space is parsed as a mapping separator, so these list items will not be treated as plain strings and can break schema loading/validation. Quote these values (or remove the colon/space pattern) so they remain string literals in the allowed list.

Suggested change
- extrapolation method to zero stretch : LS
- extrapolation method to zero stretch : NQ
- "extrapolation method to zero stretch : LS"
- "extrapolation method to zero stretch : NQ"

Copilot uses AI. Check for mistakes.
- counterflow
- OPF
- HFM
- CTF
- SFF
institution:
type: string
facility:
Expand All @@ -48,6 +89,11 @@ datapoints:
required: true
oneof:
- *ignition-delay-schema
- *laminar-burning-velocity-measurement-schema
- *concentration-time-profile-measurement-schema
- *jet-stirred-reactor-measurement-schema
- *outlet-concentration-measurement-schema
- *burner-stabilized-flame-speciation-measurement-schema
reference:
required: true
type: dict
Expand Down Expand Up @@ -93,6 +139,11 @@ chemked-version: # TODO: Implement proper version comparison
experiment-type:
allowed:
- ignition delay
- laminar burning velocity measurement
- concentration time profile measurement
- jet stirred reactor measurement
- outlet concentration measurement
- burner stabilized flame speciation measurement
required: true
type: string
file-authors:
Expand Down
70 changes: 70 additions & 0 deletions pyked/schemas/concentration_time_profile_measurement_schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Schema for concentration time profile measurement datapoints
#
# time-shift defines the t=0 reference for the profile
time-shift: &time-shift
type: dict
schema:
target:
required: true
type: string
type:
required: true
type: string
allowed:
- half decrease
- relative decrease
amount: *value-unit-optional

concentration-time-profile-measurement-schema: &concentration-time-profile-measurement-schema
type: list
minlength: 1
schema:
type: dict
schema:
pressure: *value-unit-required
temperature: *value-unit-required
composition: *composition
equivalence-ratio:
type: float
min: 0.0
concentration-profiles:
type: list
required: true
minlength: 1
schema:
type: dict
schema:
species-name:
type: string
required: true
InChI:
type: string
SMILES:
type: string
quantity:
required: true
type: dict
schema:
units:
required: true
type: string
time:
required: true
type: dict
schema:
units:
required: true
type: string
values:
required: true
type: list
minlength: 2
schema:
type: list
oneof_items:
- - type: float
- type: float
- - type: float
- type: float
- type: float
time-shift: *time-shift
14 changes: 14 additions & 0 deletions pyked/schemas/jet_stirred_reactor_measurement_schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Schema for jet stirred reactor measurement datapoints
jet-stirred-reactor-measurement-schema: &jet-stirred-reactor-measurement-schema
type: list
minlength: 1
schema:
type: dict
schema:
pressure: *value-unit-required
temperature: *value-unit-required
composition: *composition
equivalence-ratio:
type: float
min: 0.0
measured-composition: *composition
15 changes: 15 additions & 0 deletions pyked/schemas/laminar_burning_velocity_measurement_schema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Schema for laminar burning velocity measurement datapoints
laminar-burning-velocity-measurement-schema: &laminar-burning-velocity-measurement-schema
type: list
minlength: 1
schema:
type: dict
schema:
pressure: *value-unit-required
temperature: *value-unit-required
laminar-burning-velocity: *value-unit-required
pressure-rise: *value-unit-optional
composition: *composition
equivalence-ratio:
type: float
min: 0.0
Loading
Loading