Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ This repository contains DFODE-kit, a Python toolkit for sampling combustion sta
- If adding a new invariant, encode it in tests or CI.

## Structure
- `dfode_kit/cli_tools/`: CLI entrypoints and subcommands
- `dfode_kit/cli/`: CLI entrypoints and subcommands
- `dfode_kit/data_operations/`: dataset I/O, labeling, augmentation, integration utilities
- `dfode_kit/dfode_core/`: models, training, preprocessing
- `dfode_kit/df_interface/`: DeepFlame/OpenFOAM-facing helpers
Expand All @@ -45,3 +45,4 @@ This repository contains DFODE-kit, a Python toolkit for sampling combustion sta
- `docs/agents/verification.md`
- `docs/agents/worktrees.md`
- `docs/agents/roadmap.md`
- `docs/agents/package-topology-spec.md`
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ source /path/to/conda/etc/profile.d/conda.sh
conda activate deepflame
source /path/to/deepflame-dev/bashrc

python -m dfode_kit.cli_tools.main init oneD-flame \
python -m dfode_kit.cli.main init oneD-flame \
--mech /path/to/mechanisms/CH4/gri30.yaml \
--fuel CH4:1 \
--oxidizer air \
Expand All @@ -105,15 +105,15 @@ python -m dfode_kit.cli_tools.main init oneD-flame \
### 3. Run the case

```bash
python -m dfode_kit.cli_tools.main run-case \
python -m dfode_kit.cli.main run-case \
--case /path/to/run/oneD_flame_CH4_phi1 \
--apply --json
```

### 4. Sample the finished case into HDF5

```bash
python -m dfode_kit.cli_tools.main sample \
python -m dfode_kit.cli.main sample \
--mech /path/to/mechanisms/CH4/gri30.yaml \
--case /path/to/run/oneD_flame_CH4_phi1 \
--save /path/to/run/oneD_flame_CH4_phi1/ch4_phi1_sample.h5 \
Expand All @@ -133,10 +133,15 @@ If you are working on the repository itself, see:

## Repository layout

- `dfode_kit/cli_tools/` — CLI entrypoints and subcommands
- `dfode_kit/df_interface/` — DeepFlame/OpenFOAM-facing helpers and case setup
- `dfode_kit/data_operations/` — dataset I/O, sampling, augmentation, labeling
- `dfode_kit/dfode_core/` — model and training code
- `dfode_kit/cli/` — canonical CLI entrypoints and subcommands
- `dfode_kit/cli_tools/` — legacy compatibility shims for older CLI import paths
- `dfode_kit/cases/` — canonical case init/preset/sampling boundaries for DeepFlame/OpenFOAM workflows
- `dfode_kit/df_interface/` — legacy compatibility shims for case-facing helpers during the cases migration
- `dfode_kit/data/` — emerging canonical package for data contracts and HDF5 I/O helpers
- `dfode_kit/data_operations/` — legacy and transitional dataset I/O, augmentation, labeling, and integration helpers
- `dfode_kit/models/` — canonical model package
- `dfode_kit/training/` — canonical training package
- `dfode_kit/dfode_core/` — legacy compatibility surface for model/training code during migration
- `canonical_cases/` — canonical flame case templates
- `tutorials/` — tutorial notebooks and workflow examples
- `docs/` — published project documentation
Expand Down
8 changes: 4 additions & 4 deletions dfode_kit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@
"inverse_BCT": ("dfode_kit.utils", "inverse_BCT"),
"BCT_torch": ("dfode_kit.utils", "BCT_torch"),
"inverse_BCT_torch": ("dfode_kit.utils", "inverse_BCT_torch"),
"gather_species_arrays": ("dfode_kit.df_interface.sample_case", "gather_species_arrays"),
"df_to_h5": ("dfode_kit.df_interface.sample_case", "df_to_h5"),
"touch_h5": ("dfode_kit.data_operations.h5_kit", "touch_h5"),
"get_TPY_from_h5": ("dfode_kit.data_operations.h5_kit", "get_TPY_from_h5"),
"gather_species_arrays": ("dfode_kit.cases.sampling", "gather_species_arrays"),
"df_to_h5": ("dfode_kit.cases.sampling", "df_to_h5"),
"touch_h5": ("dfode_kit.data.io_hdf5", "touch_h5"),
"get_TPY_from_h5": ("dfode_kit.data.io_hdf5", "get_TPY_from_h5"),
"advance_reactor": ("dfode_kit.data_operations.h5_kit", "advance_reactor"),
"load_model": ("dfode_kit.data_operations.h5_kit", "load_model"),
"predict_Y": ("dfode_kit.data_operations.h5_kit", "predict_Y"),
Expand Down
56 changes: 56 additions & 0 deletions dfode_kit/cases/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from importlib import import_module


__all__ = [
'AIR_OXIDIZER',
'DEFAULT_ONE_D_FLAME_PRESET',
'DEFAULT_ONE_D_FLAME_TEMPLATE',
'ONE_D_FLAME_PRESETS',
'OneDFlameInitInputs',
'OneDFlamePreset',
'OneDFreelyPropagatingFlameConfig',
'df_to_h5',
'dump_plan_json',
'gather_species_arrays',
'get_one_d_flame_preset',
'load_plan_json',
'one_d_flame_inputs_from_plan',
'one_d_flame_overrides_from_plan',
'one_d_flame_plan_dict',
'resolve_oxidizer',
'setup_one_d_flame_case',
]

_ATTRIBUTE_MODULES = {
'AIR_OXIDIZER': ('dfode_kit.cases.init', 'AIR_OXIDIZER'),
'DEFAULT_ONE_D_FLAME_PRESET': ('dfode_kit.cases.init', 'DEFAULT_ONE_D_FLAME_PRESET'),
'DEFAULT_ONE_D_FLAME_TEMPLATE': ('dfode_kit.cases.init', 'DEFAULT_ONE_D_FLAME_TEMPLATE'),
'ONE_D_FLAME_PRESETS': ('dfode_kit.cases.init', 'ONE_D_FLAME_PRESETS'),
'OneDFlameInitInputs': ('dfode_kit.cases.init', 'OneDFlameInitInputs'),
'OneDFlamePreset': ('dfode_kit.cases.init', 'OneDFlamePreset'),
'OneDFreelyPropagatingFlameConfig': (
'dfode_kit.cases.presets',
'OneDFreelyPropagatingFlameConfig',
),
'df_to_h5': ('dfode_kit.cases.sampling', 'df_to_h5'),
'dump_plan_json': ('dfode_kit.cases.init', 'dump_plan_json'),
'gather_species_arrays': ('dfode_kit.cases.sampling', 'gather_species_arrays'),
'get_one_d_flame_preset': ('dfode_kit.cases.init', 'get_one_d_flame_preset'),
'load_plan_json': ('dfode_kit.cases.init', 'load_plan_json'),
'one_d_flame_inputs_from_plan': ('dfode_kit.cases.init', 'one_d_flame_inputs_from_plan'),
'one_d_flame_overrides_from_plan': ('dfode_kit.cases.init', 'one_d_flame_overrides_from_plan'),
'one_d_flame_plan_dict': ('dfode_kit.cases.init', 'one_d_flame_plan_dict'),
'resolve_oxidizer': ('dfode_kit.cases.init', 'resolve_oxidizer'),
'setup_one_d_flame_case': ('dfode_kit.cases.deepflame', 'setup_one_d_flame_case'),
}


def __getattr__(name):
if name not in _ATTRIBUTE_MODULES:
raise AttributeError(f"module 'dfode_kit.cases' has no attribute '{name}'")

module_name, attribute_name = _ATTRIBUTE_MODULES[name]
module = import_module(module_name)
value = getattr(module, attribute_name)
globals()[name] = value
return value
105 changes: 105 additions & 0 deletions dfode_kit/cases/deepflame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import shutil
from pathlib import Path

from dfode_kit.cases.presets import OneDFreelyPropagatingFlameConfig


def update_one_d_sample_config(cfg: OneDFreelyPropagatingFlameConfig, case_path):
case_path = Path(case_path).resolve()
orig_file_path = case_path / 'system/sampleConfigDict.orig'
new_file_path = case_path / 'system/sampleConfigDict'
shutil.copy(orig_file_path, new_file_path)

replacements = {
'CanteraMechanismFile_': f'"{Path(cfg.mech_path).resolve()}"',
'inertSpecie_': f'"{cfg.inert_specie}"',
'domainWidth': cfg.domain_width,
'domainLength': cfg.domain_length,
'ignitionRegion': cfg.ignition_region,
'simTimeStep': cfg.sim_time_step,
'simTime': cfg.sim_time,
'simWriteInterval': cfg.sim_write_interval,
'UInlet': cfg.inlet_speed,
'pInternal': cfg.p0,
}

with open(new_file_path, 'r') as file:
lines = file.readlines()

for i, line in enumerate(lines):
for key, value in replacements.items():
if key in line:
lines[i] = line.replace('placeHolder', str(value))

if 'unburntStates' in line:
state_strings = [f'{"TUnburnt":<20}{cfg.initial_gas.T:>16.10f};']
state_strings += [
f'{species}Unburnt'.ljust(20) + f'{cfg.initial_gas.Y[idx]:>16.10f};'
for idx, species in enumerate(cfg.species_names)
]
lines[i] = '\n'.join(state_strings) + '\n\n'

if 'equilibriumStates' in line:
state_strings = [f'{"TBurnt":<20}{cfg.burnt_gas.T:>16.10f};']
state_strings += [
f'{species}Burnt'.ljust(20) + f'{cfg.burnt_gas.Y[idx]:>16.10f};'
for idx, species in enumerate(cfg.species_names)
]
lines[i] = '\n'.join(state_strings) + '\n\n'

with open(new_file_path, 'w') as file:
file.writelines(lines)


def create_0_species_files(cfg: OneDFreelyPropagatingFlameConfig, case_path):
case_path = Path(case_path).resolve()
orig_0_file_path = case_path / '0/Ydefault.orig'

for idx, species in enumerate(cfg.species_names):
new_0_file_path = case_path / '0' / f'{species}.orig'
shutil.copy(orig_0_file_path, new_0_file_path)

with open(new_0_file_path, 'r') as file:
lines = file.readlines()

for i, line in enumerate(lines):
if 'Ydefault' in line:
lines[i] = line.replace('Ydefault', f'{species}')
if 'uniform 0' in line:
lines[i] = line.replace('0', f'{cfg.initial_gas.Y[idx]}')

with open(new_0_file_path, 'w') as file:
file.writelines(lines)


def update_set_fields_dict(cfg: OneDFreelyPropagatingFlameConfig, case_path):
case_path = Path(case_path).resolve()
orig_setFieldsDict_path = case_path / 'system/setFieldsDict.orig'
new_setFieldsDict_path = case_path / 'system/setFieldsDict'
shutil.copy(orig_setFieldsDict_path, new_setFieldsDict_path)

with open(new_setFieldsDict_path, 'r') as file:
lines = file.readlines()

for i, line in enumerate(lines):
if 'unburntStatesPlaceHolder' in line:
state_strings = [f'\tvolScalarFieldValue {"T":<10} $TUnburnt']
for _, species in enumerate(cfg.species_names):
state_strings.append(f'volScalarFieldValue {species:<10} ${species}Unburnt')
lines[i] = '\n\t'.join(state_strings) + '\n'
if 'equilibriumStatesPlaceHolder' in line:
state_strings = [f'\t\t\tvolScalarFieldValue {"T":<10} $TBurnt']
for _, species in enumerate(cfg.species_names):
state_strings.append(f'volScalarFieldValue {species:<10} ${species}Burnt')
lines[i] = '\n\t\t\t'.join(state_strings) + '\n'

with open(new_setFieldsDict_path, 'w') as file:
file.writelines(lines)


def setup_one_d_flame_case(cfg: OneDFreelyPropagatingFlameConfig, case_path):
case_path = Path(case_path).resolve()
update_one_d_sample_config(cfg, case_path)
create_0_species_files(cfg, case_path)
update_set_fields_dict(cfg, case_path)
print(f'One-dimensional flame case setup completed at: {case_path}')
148 changes: 148 additions & 0 deletions dfode_kit/cases/init.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from __future__ import annotations

import json
from dataclasses import asdict, dataclass
from pathlib import Path
from typing import Any

from dfode_kit import DFODE_ROOT


DEFAULT_ONE_D_FLAME_TEMPLATE = (
DFODE_ROOT / 'canonical_cases' / 'oneD_freely_propagating_flame'
)
DEFAULT_ONE_D_FLAME_PRESET = 'premixed-defaults-v1'
AIR_OXIDIZER = 'O2:1, N2:3.76'


@dataclass(frozen=True)
class OneDFlamePreset:
name: str
summary: str
assumptions: dict[str, str]
notes: list[str]


ONE_D_FLAME_PRESETS: dict[str, OneDFlamePreset] = {
DEFAULT_ONE_D_FLAME_PRESET: OneDFlamePreset(
name=DEFAULT_ONE_D_FLAME_PRESET,
summary=(
'Current DFODE-kit empirical defaults for one-dimensional freely '
'propagating premixed flames.'
),
assumptions={
'domain_length': 'flame_thickness / 10 * 500',
'domain_width': 'domain_length / 10',
'ignition_region': 'domain_length / 2',
'sim_time_step': '1e-6',
'num_output_steps': '100',
'sim_write_interval': '(flame_thickness / flame_speed) * 10 / num_output_steps',
'sim_time': 'sim_write_interval * (num_output_steps + 1)',
'inlet_speed': 'flame_speed',
'inert_specie': '"N2"',
},
notes=[
'These values preserve the current hardcoded logic in OneDFreelyPropagatingFlameConfig.update_config().',
'They are recommended starter defaults, not universal best practices.',
'Override any resolved field explicitly when domain knowledge requires it.',
],
)
}


@dataclass(frozen=True)
class OneDFlameInitInputs:
mechanism: str
fuel: str
oxidizer: str
eq_ratio: float
T0: float
p0: float
preset: str = DEFAULT_ONE_D_FLAME_PRESET
template: str = str(DEFAULT_ONE_D_FLAME_TEMPLATE)
inert_specie: str = 'N2'


def resolve_oxidizer(oxidizer: str) -> str:
if oxidizer.strip().lower() == 'air':
return AIR_OXIDIZER
return oxidizer


def get_one_d_flame_preset(name: str) -> OneDFlamePreset:
try:
return ONE_D_FLAME_PRESETS[name]
except KeyError as exc:
raise ValueError(
f"Unknown oneD-flame preset: {name}. Available presets: {', '.join(sorted(ONE_D_FLAME_PRESETS))}"
) from exc


def one_d_flame_plan_dict(
*,
inputs: OneDFlameInitInputs,
resolved: dict[str, Any],
output_dir: str | None,
config_path: str | None = None,
) -> dict[str, Any]:
preset = get_one_d_flame_preset(inputs.preset)
return {
'schema_version': 1,
'case_type': 'oneD-flame',
'preset': preset.name,
'preset_summary': preset.summary,
'template': str(Path(inputs.template).resolve()),
'output_dir': str(Path(output_dir).resolve()) if output_dir else None,
'config_path': str(Path(config_path).resolve()) if config_path else None,
'inputs': {
**asdict(inputs),
'oxidizer': resolve_oxidizer(inputs.oxidizer),
'template': str(Path(inputs.template).resolve()),
},
'assumptions': preset.assumptions,
'notes': preset.notes,
'resolved': resolved,
}


def dump_plan_json(plan: dict[str, Any], path: str | Path) -> Path:
output_path = Path(path).resolve()
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(plan, indent=2, sort_keys=True) + '\n', encoding='utf-8')
return output_path


def load_plan_json(path: str | Path) -> dict[str, Any]:
input_path = Path(path).resolve()
return json.loads(input_path.read_text(encoding='utf-8'))


def one_d_flame_inputs_from_plan(plan: dict[str, Any]) -> OneDFlameInitInputs:
if plan.get('case_type') != 'oneD-flame':
raise ValueError(f"Unsupported case_type in config: {plan.get('case_type')}")

inputs = plan['inputs']
return OneDFlameInitInputs(
mechanism=inputs['mechanism'],
fuel=inputs['fuel'],
oxidizer=inputs['oxidizer'],
eq_ratio=float(inputs['eq_ratio']),
T0=float(inputs['T0']),
p0=float(inputs['p0']),
preset=inputs.get('preset', plan.get('preset', DEFAULT_ONE_D_FLAME_PRESET)),
template=inputs.get('template', str(DEFAULT_ONE_D_FLAME_TEMPLATE)),
inert_specie=inputs.get('inert_specie', 'N2'),
)


def one_d_flame_overrides_from_plan(plan: dict[str, Any]) -> dict[str, Any]:
overrides = dict(plan.get('resolved', {}))
overrides.pop('mechanism', None)
overrides.pop('fuel', None)
overrides.pop('oxidizer', None)
overrides.pop('eq_ratio', None)
overrides.pop('T0', None)
overrides.pop('p0', None)
overrides.pop('preset', None)
overrides.pop('template', None)
return overrides
Loading
Loading