Skip to content

Data Models

models

Data models for PyFDS.

This module contains helper data models that are used by namelists but are not namelists themselves.

Classes

PyrolysisProduct

Bases: BaseModel

Product specification for a pyrolysis reaction.

Represents either a gaseous species, solid residue, or particle produced by a pyrolysis reaction.

Functions
validate_product
validate_product()

Ensure at least one product type is specified.

Source code in src/pyfds/core/models/pyrolysis.py
@model_validator(mode="after")
def validate_product(self) -> "PyrolysisProduct":
    """Ensure at least one product type is specified."""
    has_gas = self.spec_id is not None
    has_solid = self.matl_id is not None
    has_particle = self.part_id is not None

    if not (has_gas or has_solid or has_particle):
        raise ValueError("Product must specify spec_id, matl_id, or part_id")

    # Validate yields are specified with IDs
    if has_gas and self.nu_spec is None:
        raise ValueError("spec_id requires nu_spec yield fraction")
    if has_solid and self.nu_matl is None:
        raise ValueError("matl_id requires nu_matl yield fraction")
    if has_particle and self.nu_part is None:
        raise ValueError("part_id requires nu_part yield fraction")

    return self

PyrolysisReaction

Bases: BaseModel

Single pyrolysis reaction definition.

Represents one decomposition reaction for a material. A material can have multiple reactions (e.g., moisture evaporation, primary pyrolysis, char oxidation).

Kinetic Specification Methods (mutually exclusive): 1. Arrhenius: Specify both A and E 2. Simplified with rate: Specify REFERENCE_TEMPERATURE and REFERENCE_RATE 3. Simplified with range: Specify REFERENCE_TEMPERATURE and PYROLYSIS_RANGE 4. Auto-derive: Specify only REFERENCE_TEMPERATURE (FDS derives A and E)

Functions
validate_kinetics
validate_kinetics()

Validate kinetic parameter combinations.

FDS User Guide states: - Do not specify A and E if you specify REFERENCE_TEMPERATURE - Do not specify PYROLYSIS_RANGE if you specify REFERENCE_RATE

Source code in src/pyfds/core/models/pyrolysis.py
@model_validator(mode="after")
def validate_kinetics(self) -> "PyrolysisReaction":
    """
    Validate kinetic parameter combinations.

    FDS User Guide states:
    - Do not specify A and E if you specify REFERENCE_TEMPERATURE
    - Do not specify PYROLYSIS_RANGE if you specify REFERENCE_RATE
    """
    has_arrhenius = self.a is not None or self.e is not None
    has_simplified = self.reference_temperature is not None
    has_reference_rate = self.reference_rate is not None
    has_pyrolysis_range = self.pyrolysis_range is not None

    # Cannot mix Arrhenius and simplified kinetics
    if has_arrhenius and has_simplified:
        raise ValueError(
            "Cannot specify both Arrhenius (A, E) and simplified "
            "(REFERENCE_TEMPERATURE) kinetics"
        )

    # If using Arrhenius, both A and E required
    if has_arrhenius and (self.a is None or self.e is None):
        raise ValueError("Arrhenius kinetics requires both A and E")

    # Cannot specify both REFERENCE_RATE and PYROLYSIS_RANGE
    if has_reference_rate and has_pyrolysis_range:
        raise ValueError(
            "Cannot specify both REFERENCE_RATE and PYROLYSIS_RANGE. "
            "Use REFERENCE_RATE for known rates, or PYROLYSIS_RANGE to estimate from temperature range."
        )

    # REFERENCE_RATE and PYROLYSIS_RANGE only valid with REFERENCE_TEMPERATURE
    if (has_reference_rate or has_pyrolysis_range) and not has_simplified:
        raise ValueError(
            "REFERENCE_RATE and PYROLYSIS_RANGE require REFERENCE_TEMPERATURE to be specified"
        )

    # Validate total yield
    total_yield = sum(
        (p.nu_spec or 0) + (p.nu_matl or 0) + (p.nu_part or 0) for p in self.products
    )
    if total_yield > 1.01:  # Allow small floating point tolerance
        raise ValueError(f"Total product yield ({total_yield:.3f}) exceeds 1.0")

    return self
get_gas_products
get_gas_products()

Get all gaseous products from this reaction.

Source code in src/pyfds/core/models/pyrolysis.py
def get_gas_products(self) -> list[PyrolysisProduct]:
    """Get all gaseous products from this reaction."""
    return [p for p in self.products if p.spec_id is not None]
get_solid_products
get_solid_products()

Get all solid residue products from this reaction.

Source code in src/pyfds/core/models/pyrolysis.py
def get_solid_products(self) -> list[PyrolysisProduct]:
    """Get all solid residue products from this reaction."""
    return [p for p in self.products if p.matl_id is not None]
get_particle_products
get_particle_products()

Get all particle products from this reaction.

Source code in src/pyfds/core/models/pyrolysis.py
def get_particle_products(self) -> list[PyrolysisProduct]:
    """Get all particle products from this reaction."""
    return [p for p in self.products if p.part_id is not None]

Overview

Data models are non-namelist Pydantic classes used to represent complex domain concepts. Unlike namelist classes that map directly to FDS input, models provide structured data for specific use cases like pyrolysis reactions.

Pyrolysis Models

PyrolysisReaction

Represents a single pyrolysis reaction within a material.

from pyfds.core.models import PyrolysisReaction

# Define a pyrolysis reaction
reaction = PyrolysisReaction(
    a=1e10,              # Pre-exponential factor (1/s)
    e=80000,             # Activation energy (J/mol)
    n_s=1.0,             # Reaction order for solid
    nu_spec={"FUEL": 1.0},  # Product species with yields
)

Parameters:

  • a (float): Pre-exponential factor in Arrhenius equation (1/s)
  • e (float): Activation energy (J/mol)
  • n_s (float, optional): Reaction order for solid reactant (default: 1.0)
  • n_o2 (float, optional): Reaction order for oxygen (default: 0.0)
  • n_h2o (float, optional): Reaction order for water (default: 0.0)
  • nu_spec (dict[str, float], optional): Product species and their stoichiometric coefficients
  • nu_matl (dict[str, float], optional): Residue materials and their yields
  • heat_of_reaction (float, optional): Heat of reaction (J/kg)

PyrolysisProduct

Represents a product from a pyrolysis reaction.

from pyfds.core.models import PyrolysisProduct

# Gas product
gas_product = PyrolysisProduct(
    species_id="FUEL",
    yield_fraction=0.8
)

# Solid residue
residue = PyrolysisProduct(
    material_id="CHAR",
    yield_fraction=0.2
)

Parameters:

  • species_id (str, optional): ID of gas/vapor species produced
  • material_id (str, optional): ID of solid residue material
  • yield_fraction (float): Mass fraction yield (0.0 to 1.0)

Note: Either species_id OR material_id must be specified, not both.

Usage with MaterialBuilder

The models are primarily used with MaterialBuilder to define complex pyrolysis behavior:

from pyfds.builders import MaterialBuilder
from pyfds.core.models import PyrolysisReaction

# Create material with pyrolysis
material = (
    MaterialBuilder("FOAM")
    .density(40)
    .conductivity(0.04)
    .specific_heat(1.5)
    .add_pyrolysis_reaction(
        PyrolysisReaction(
            a=1e10,
            e=80000,
            nu_spec={"FUEL": 0.8},
            nu_matl={"CHAR": 0.2}
        )
    )
    .build()
)

Or using the fluent API methods:

material = (
    MaterialBuilder("FOAM")
    .density(40)
    .conductivity(0.04)
    .specific_heat(1.5)
    .add_pyrolysis_reaction(
        a=1e10,
        e=80000,
        product_species="FUEL",
        product_yield=0.8,
        residue_material="CHAR",
        residue_yield=0.2
    )
    .build()
)

Pyrolysis Reaction Examples

Simple Single-Step Pyrolysis

Complete decomposition to gas:

from pyfds.core.models import PyrolysisReaction

reaction = PyrolysisReaction(
    a=1e10,
    e=80000,
    nu_spec={"FUEL": 1.0}  # 100% gas
)

Two-Step Pyrolysis

Initial decomposition followed by char oxidation:

# Step 1: Decomposition to fuel + char
step1 = PyrolysisReaction(
    a=1e10,
    e=80000,
    nu_spec={"FUEL": 0.7},
    nu_matl={"CHAR": 0.3}
)

# Step 2: Char oxidation (oxygen-dependent)
step2 = PyrolysisReaction(
    a=5e8,
    e=120000,
    n_o2=1.0,  # Requires oxygen
    nu_spec={"PRODUCTS": 1.0}
)

material = (
    MaterialBuilder("WOOD")
    .density(500)
    .add_pyrolysis_reaction(step1)
    .add_pyrolysis_reaction(step2)
    .build()
)

Temperature-Dependent Decomposition

Multiple reactions at different activation energies:

# Low-temperature volatiles
low_temp = PyrolysisReaction(
    a=1e8,
    e=60000,
    nu_spec={"VOLATILES": 0.3}
)

# High-temperature decomposition
high_temp = PyrolysisReaction(
    a=1e12,
    e=150000,
    nu_spec={"FUEL": 0.5},
    nu_matl={"CHAR": 0.2}
)

material = (
    MaterialBuilder("POLYMER")
    .density(1200)
    .add_pyrolysis_reaction(low_temp)
    .add_pyrolysis_reaction(high_temp)
    .build()
)

Endothermic Pyrolysis

Reaction that absorbs heat:

reaction = PyrolysisReaction(
    a=1e10,
    e=80000,
    heat_of_reaction=-500000,  # Negative = endothermic (J/kg)
    nu_spec={"FUEL": 0.8},
    nu_matl={"CHAR": 0.2}
)

Validation

Models are validated using Pydantic:

from pyfds.core.models import PyrolysisReaction

# This will raise a validation error
try:
    invalid = PyrolysisReaction(
        a=-1e10,  # Must be positive
        e=80000
    )
except ValueError as e:
    print(f"Validation error: {e}")

# Yield fractions must sum appropriately
try:
    invalid = PyrolysisReaction(
        a=1e10,
        e=80000,
        nu_spec={"FUEL": 0.5, "OTHER": 0.7}  # Sums to 1.2 > 1.0
    )
except ValueError as e:
    print(f"Yields exceed 100%: {e}")

Type Safety

Full type hints enable IDE support:

from pyfds.core.models import PyrolysisReaction, PyrolysisProduct

# Type-safe construction
reaction: PyrolysisReaction = PyrolysisReaction(
    a=1e10,
    e=80000,
    nu_spec={"FUEL": 1.0}
)

# IDE provides autocomplete
print(reaction.a)  # IDE knows this is float
print(reaction.e)  # IDE knows this is float

See Also