Skip to content

Simulation

The Simulation class is the main entry point for creating FDS simulations with PyFDS.

Overview

Simulation provides a Pythonic interface for building FDS input files. It manages all namelist groups and provides methods for adding simulation components.

from pyfds import Simulation

# Create a new simulation
sim = Simulation(chid='my_fire', title='My Fire Simulation')

# Add components
sim.add(Time(t_end=600.0))
sim.add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)))
sim.add(Surface(id='FIRE', hrrpua=1000.0))
sim.add(Obstruction(xb=Bounds3D.of(2, 3, 2, 3, 0, 0.1), surf_id='FIRE'))

# Write FDS file
sim.write('my_fire.fds')

Class Reference

Simulation

Simulation(chid, title=None, eager_validation=False)

Main class for building FDS simulations.

This class mirrors the structure of an FDS input file, which consists of namelist groups like MESH, SURF, MATL, etc. All components are added using the unified add() method.

PARAMETER DESCRIPTION
chid

Case identifier (filename prefix for all output files). Must be 50 characters or less, no spaces or periods (FDS requirement).

TYPE: str

title

Descriptive title for the simulation (256 characters max).

TYPE: str DEFAULT: None

eager_validation

If True, validate cross-references when items are added. If False (default), validation only occurs at write time.

TYPE: bool DEFAULT: False

ATTRIBUTE DESCRIPTION
meshes

Read-only view of registered meshes

TYPE: RegistryView[Mesh]

surfaces

Read-only view of registered surfaces

TYPE: RegistryView[Surface]

materials

Read-only view of registered materials

TYPE: RegistryView[Material]

obstructions

Read-only view of registered obstructions

TYPE: RegistryView[Obstruction]

vents

Read-only view of registered vents

TYPE: RegistryView[Vent]

holes

Read-only view of registered holes

TYPE: RegistryView[Hole]

devices

Read-only view of registered devices

TYPE: RegistryView[Device]

props

Read-only view of registered properties

TYPE: RegistryView[Property]

ctrls

Read-only view of registered controls

TYPE: RegistryView[Control]

species

Read-only view of registered species

TYPE: RegistryView[Species]

reactions

Read-only view of registered reactions

TYPE: RegistryView[Reaction]

ramps

Read-only view of registered ramps

TYPE: RegistryView[Ramp]

mults

Read-only view of registered multipliers

TYPE: RegistryView[Multiplier]

inits

Read-only view of registered initial conditions

TYPE: RegistryView[Initialization]

Examples:

Create a basic room fire simulation:

>>> from pyfds import Simulation
>>> from pyfds.core.geometry import Bounds3D, Grid3D
>>> from pyfds.core.namelists import Time, Mesh, Surface, Obstruction
>>>
>>> sim = Simulation(chid='room_fire', title='Simple Room Fire')
>>> sim.add(
...     Time(t_end=600.0),
...     Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)),
...     Surface(id='FIRE', hrrpua=1000.0, color='RED'),
...     Obstruction(xb=Bounds3D.of(2, 3, 2, 3, 0, 0.1), surf_id='FIRE')
... )
>>> sim.write('room_fire.fds')
Notes

This class uses a unified registry system to ensure all IDs are unique across the entire simulation, matching FDS behavior. Validation is performed automatically before writing or running simulations.

Initialize a new FDS simulation.

Source code in src/pyfds/core/simulation.py
def __init__(
    self,
    chid: str,
    title: str | None = None,
    eager_validation: bool = False,
):
    """Initialize a new FDS simulation."""
    # Validate CHID
    chid = validate_chid(chid)
    logger.debug(f"Creating simulation with CHID: {chid}")

    # Initialize registry
    self._registry = SimulationRegistry()
    self._eager_validation = eager_validation

    # Core simulation metadata
    self._head = Head(chid=chid, title=title)
    self._time: Time | None = None

    # Metadata storage (comments, source info, etc.)
    self._metadata: dict[str, Any] = {}

    # Register head in registry
    self._registry.head = self._head

    # Initialize validator
    self._validator = Validator(self)

Functions

set_misc
set_misc(misc=None, **kwargs)

Set MISC parameters for the simulation.

Can be called with a Misc object or with keyword arguments to create one.

PARAMETER DESCRIPTION
misc

Misc object to set (if None, kwargs are used to create one)

TYPE: Misc DEFAULT: None

**kwargs

Keyword arguments to pass to Misc constructor

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
Simulation

Self for method chaining

Examples:

>>> # Using a Misc object
>>> misc = Misc(tmpa=25.0, humidity=70.0)
>>> sim.set_misc(misc)
>>> # Using keyword arguments
>>> sim.set_misc(tmpa=25.0, humidity=70.0, solid_phase_only=True)
Notes

Only one MISC namelist is allowed per simulation. Calling this method multiple times will raise an error.

Source code in src/pyfds/core/simulation.py
def set_misc(self, misc: Misc | None = None, **kwargs: Any) -> "Simulation":
    """
    Set MISC parameters for the simulation.

    Can be called with a Misc object or with keyword arguments to create one.

    Parameters
    ----------
    misc : Misc, optional
        Misc object to set (if None, kwargs are used to create one)
    **kwargs
        Keyword arguments to pass to Misc constructor

    Returns
    -------
    Simulation
        Self for method chaining

    Examples
    --------
    >>> # Using a Misc object
    >>> misc = Misc(tmpa=25.0, humidity=70.0)
    >>> sim.set_misc(misc)

    >>> # Using keyword arguments
    >>> sim.set_misc(tmpa=25.0, humidity=70.0, solid_phase_only=True)

    Notes
    -----
    Only one MISC namelist is allowed per simulation. Calling this method
    multiple times will raise an error.
    """
    if misc is None:
        misc = Misc(**kwargs)
    self.add(misc)
    return self
validate
validate()

Validate the simulation configuration.

RETURNS DESCRIPTION
List[str]

List of validation warnings (empty if no issues)

Examples:

>>> sim = Simulation('test')
>>> warnings = sim.validate()
>>> if warnings:
...     for w in warnings:
...         print(f"Warning: {w}")
Source code in src/pyfds/core/simulation.py
def validate(self) -> list[str]:
    """
    Validate the simulation configuration.

    Returns
    -------
    List[str]
        List of validation warnings (empty if no issues)

    Examples
    --------
    >>> sim = Simulation('test')
    >>> warnings = sim.validate()
    >>> if warnings:
    ...     for w in warnings:
    ...         print(f"Warning: {w}")
    """
    issues = self._validator.validate()
    return [str(issue) for issue in issues]
to_fds
to_fds()

Generate the complete FDS input file content.

RETURNS DESCRIPTION
str

FDS input file content

Examples:

>>> sim = Simulation('test')
>>> sim.add(Time(t_end=100.0))
>>> sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
>>> content = sim.to_fds()
Source code in src/pyfds/core/simulation.py
def to_fds(self) -> str:
    """
    Generate the complete FDS input file content.

    Returns
    -------
    str
        FDS input file content

    Examples
    --------
    >>> sim = Simulation('test')
    >>> sim.add(Time(t_end=100.0))
    >>> sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
    >>> content = sim.to_fds()
    """
    lines = []

    # Add header comment
    lines.append("! FDS input file generated by PyFDS")
    if self._head.title:
        lines.append(f"! {self._head.title}")
    lines.append("")

    # HEAD namelist
    lines.append(self._head.to_fds())

    # TIME namelist
    if self._time:
        lines.append(self._time.to_fds())

    # MISC namelist (should appear near top)
    if self._registry.misc:
        lines.append("! --- Miscellaneous Parameters ---")
        lines.append(self._registry.misc.to_fds())

    # MESH namelists
    if self.meshes:
        lines.append("! --- Meshes ---")
        for mesh in self.meshes:
            lines.append(mesh.to_fds())

    # MULT namelists (must come before objects that reference them)
    if self.mults:
        lines.append("! --- Multipliers ---")
        for mult in self.mults:
            lines.append(mult.to_fds())

    # RAMP namelists (must come before MATL that reference them)
    if self.ramps:
        lines.append("! --- Ramps ---")
        for ramp in self.ramps:
            lines.append(ramp.to_fds())

    # SPEC namelists (after RAMP, before REAC)
    if self.species:
        lines.append("! --- Species ---")
        for spec in self.species:
            lines.append(spec.to_fds())

    # REAC namelists (allow multiple REAC entries)
    if self.reactions:
        lines.append("! --- Reactions ---")
        for reac in self.reactions:
            lines.append(reac.to_fds())

    # COMB namelist (after REAC)
    if self._registry.combustion:
        lines.append("! --- Combustion Parameters ---")
        lines.append(self._registry.combustion.to_fds())

    # MATL namelists
    if self.materials:
        lines.append("! --- Materials ---")
        for material in self.materials:
            lines.append(material.to_fds())

    # SURF namelists
    if self.surfaces:
        lines.append("! --- Surfaces ---")
        for surface in self.surfaces:
            lines.append(surface.to_fds())

    # PART namelists (after SPEC, before PROP that references them)
    if self._registry.parts:
        lines.append("! --- Particles ---")
        for part in self._registry.parts:
            lines.append(part.to_fds())

    # OBST namelists
    if self.obstructions:
        lines.append("! --- Obstructions ---")
        for obst in self.obstructions:
            lines.append(obst.to_fds())

    # HOLE namelists (must come after OBST)
    if self.holes:
        lines.append("! --- Holes ---")
        for hole in self.holes:
            lines.append(hole.to_fds())

    # VENT namelists (after OBST and SURF)
    if self.vents:
        lines.append("! --- Vents ---")
        for vent in self.vents:
            lines.append(vent.to_fds())

    # HVAC namelists (after VENT, references VENT_ID)
    if self._registry.hvacs:
        lines.append("! --- HVAC System ---")
        for hvac in self._registry.hvacs:
            lines.append(hvac.to_fds())

    # PROP namelists
    if self.props:
        lines.append("! --- Device Properties ---")
        for prop in self.props:
            lines.append(prop.to_fds())

    # DEVC namelists
    if self.devices:
        lines.append("! --- Devices ---")
        for device in self.devices:
            lines.append(device.to_fds())

    # CTRL namelists
    if self.ctrls:
        lines.append("! --- Controls ---")
        for ctrl in self.ctrls:
            lines.append(ctrl.to_fds())

    # INIT namelists
    if self.inits:
        lines.append("! --- Initial Conditions ---")
        for init in self.inits:
            lines.append(init.to_fds())

    # TAIL namelist
    lines.append("\n&TAIL /")

    return "\n".join(lines)
write
write(filename)

Write the FDS input file to disk.

PARAMETER DESCRIPTION
filename

Path to output file (should have .fds extension)

TYPE: str or Path

RETURNS DESCRIPTION
Path

Path to the written file

Examples:

>>> sim = Simulation('test')
>>> sim.add(Time(t_end=100.0))
>>> sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
>>> sim.write('test.fds')
Source code in src/pyfds/core/simulation.py
def write(self, filename: str | Path) -> Path:
    """
    Write the FDS input file to disk.

    Parameters
    ----------
    filename : str or Path
        Path to output file (should have .fds extension)

    Returns
    -------
    Path
        Path to the written file

    Examples
    --------
    >>> sim = Simulation('test')
    >>> sim.add(Time(t_end=100.0))
    >>> sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
    >>> sim.write('test.fds')
    """
    # Run validation
    warnings = self.validate()
    if warnings:
        for warning in warnings:
            logger.warning(warning)

    content = self.to_fds()

    # Write to file
    path = Path(filename)
    if path.suffix != ".fds":
        path = path.with_suffix(".fds")
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(content, encoding="utf-8")

    logger.info(f"Wrote FDS input file: {path}")
    return path
run
run(config=None, **kwargs)

Write FDS file and execute simulation.

This is a convenience method that combines write() and execution in a single call. The FDS file is written to a temporary location or specified output directory, then executed.

PARAMETER DESCRIPTION
config

Execution configuration (default: RunConfig())

TYPE: RunConfig DEFAULT: None

**kwargs

Additional keyword arguments passed to RunConfig()

TYPE: Any DEFAULT: {}

RETURNS DESCRIPTION
Results or Job

Results object if wait=True, Job object if wait=False

RAISES DESCRIPTION
ValueError

If validation fails and strict=True

ExecutionError

If FDS execution fails

FDSNotFoundError

If FDS executable cannot be found

Examples:

>>> sim = Simulation('test')
>>> sim.add(Time(t_end=100.0))
>>> sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
>>> results = sim.run(n_threads=4)
>>> print(f"Peak HRR: {results.hrr['HRR'].max()}")
>>> # Non-blocking execution
>>> job = sim.run(wait=False, monitor=True)
>>> while job.is_running():
...     print(f"Progress: {job.progress}%")
...     time.sleep(5)
>>> results = job.get_results()
Source code in src/pyfds/core/simulation.py
def run(
    self,
    config: "RunConfig | None" = None,
    **kwargs: Any,
) -> "Results | Job":
    """
    Write FDS file and execute simulation.

    This is a convenience method that combines write() and execution
    in a single call. The FDS file is written to a temporary location
    or specified output directory, then executed.

    Parameters
    ----------
    config : RunConfig, optional
        Execution configuration (default: RunConfig())
    **kwargs
        Additional keyword arguments passed to RunConfig()

    Returns
    -------
    Results or Job
        Results object if wait=True, Job object if wait=False

    Raises
    ------
    ValueError
        If validation fails and strict=True
    ExecutionError
        If FDS execution fails
    FDSNotFoundError
        If FDS executable cannot be found

    Examples
    --------
    >>> sim = Simulation('test')
    >>> sim.add(Time(t_end=100.0))
    >>> sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
    >>> results = sim.run(n_threads=4)
    >>> print(f"Peak HRR: {results.hrr['HRR'].max()}")

    >>> # Non-blocking execution
    >>> job = sim.run(wait=False, monitor=True)
    >>> while job.is_running():
    ...     print(f"Progress: {job.progress}%")
    ...     time.sleep(5)
    >>> results = job.get_results()
    """
    # Import here to avoid circular import
    from pyfds.config import RunConfig
    from pyfds.execution import FDSRunner

    # Create config from kwargs if not provided
    if config is None:
        config = RunConfig(**kwargs)
    elif kwargs:
        raise ValueError("Cannot specify both 'config' and keyword arguments")

    # Validate if requested
    if config.validate:
        warnings = self.validate()
        if warnings:
            logger.warning(f"Simulation validation found {len(warnings)} warning(s)")
            for warning in warnings:
                logger.warning(f"  - {warning}")
            if config.strict:
                raise ValueError(
                    "Simulation validation failed:\n" + "\n".join(f"  - {w}" for w in warnings)
                )

    # Determine output directory
    output_dir = Path.cwd() if config.output_dir is None else Path(config.output_dir)

    output_dir.mkdir(parents=True, exist_ok=True)

    # Write FDS file
    fds_file = output_dir / f"{self.chid}.fds"
    self.write(fds_file)

    logger.info(f"Running simulation: {self.chid}")
    logger.debug(f"  Threads: {config.n_threads}, MPI processes: {config.n_mpi}")
    logger.debug(f"  Output directory: {output_dir}")

    # Create runner and execute
    runner = FDSRunner(fds_executable=config.fds_executable)
    return runner.run(
        fds_file=fds_file,
        n_threads=config.n_threads,
        n_mpi=config.n_mpi,
        mpiexec_path=config.mpiexec_path,
        output_dir=output_dir,
        monitor=config.monitor,
        wait=config.wait,
        timeout=config.timeout,
        simulation=self,  # Pass simulation for parallel validation
    )

Quick Reference

Creating a Simulation

# Basic creation
sim = Simulation(chid='test')

# With title
sim = Simulation(chid='test', title='My Test Simulation')

Time Configuration

# Simple
sim.add(Time(t_end=600.0))

# With start time and time step
sim.add(Time(t_end=600.0, t_begin=0.0, dt=0.1))

Domain Setup

# Single mesh
sim.add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)))

# Multiple meshes
sim.add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5), id='MESH1'))
sim.add(Mesh(ijk=Grid3D.of(30, 30, 15), xb=Bounds3D.of(5, 8, 0, 3, 0, 1.5), id='MESH2'))

Surfaces

# Fire surface
sim.add(Surface(id='BURNER', hrrpua=1000.0, color='RED'))

# Material surface
sim.add(Surface(id='WALL', matl_id='CONCRETE', thickness=0.2))

# Boundary condition
sim.add(Surface(id='INLET', vel=1.0, tmp_front=25.0))

Geometry

# Obstruction
sim.add(Obstruction(
    xb=Bounds3D.of(0, 0.2, 0, 5, 0, 2.5),
    surf_id='WALL'
)

# Vent
sim.add(Vent(
    xb=Bounds3D.of(4, 4, 0, 2, 0, 2),
    surf_id='OPEN'
)

# Circular vent
sim.add(Vent(
    xb=Bounds3D.of(-2, 2, -2, 2, 0, 0),
    surf_id='BURNER',
    xyz=Point3D.of(0, 0, 0),
    radius=1.0
)

Devices

# Point measurement
sim.add(Device(
    id='TEMP_1',
    quantity='TEMPERATURE',
    xyz=Point3D.of(2.5, 2.5, 2.4)
)

# Area measurement
sim.add(Device(
    id='HF_FLOOR',
    quantity='HEAT FLUX',
    xb=Bounds3D.of(0, 5, 0, 5, 0, 0)
)

Advanced Features

# Material definition
sim.add(Material(
    id='WOOD',
    conductivity=0.12,
    specific_heat=1.0,
    density=500.0
)

# Time-varying property
sim.add(Ramp(
    id='FIRE_GROWTH',
    t=[0, 100, 200, 300],
    f=[0, 0.5, 1.0, 1.0]
)

# Control logic
sim.add(Control(
    id='ACTIVATION',
    input_id='TEMP_1',
    setpoint=100.0
)

# Initial condition
sim.add(Init(
    xb=Bounds3D.of(0, 5, 0, 5, 2.0, 2.5),
    temperature=200.0
)

Validation and Output

# Validate before writing
warnings = sim.validate()
for w in warnings:
    print(f"Warning: {w}")

# Write FDS file
sim.write('simulation.fds')

# Get FDS content as string
fds_content = sim.to_fds()
print(fds_content)

Execution

# Blocking execution
results = sim.run(n_threads=4)

# Non-blocking execution
job = sim.run(wait=False, monitor=True)

# With MPI
results = sim.run(n_mpi=4, n_threads=2)

Method Chaining

All configuration methods return self for chaining:

sim = (Simulation(chid='test')
       .add(Time(t_end=600.0))
       .add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)))
       .add(Surface(id='FIRE', hrrpua=1000.0))
       .add(Obstruction(xb=Bounds3D.of(2, 3, 2, 3, 0, 0.1), surf_id='FIRE')))

sim.write('test.fds')

See Also