Skip to content

Building Simulations

Learn how to create and configure FDS simulations using PyFDS.

Overview

The Simulation class is the main entry point for creating FDS simulations. It provides a high-level API for adding components and generating FDS input files.

from pyfds import Simulation

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

Creating a Simulation

Basic Creation

Every simulation requires a Case ID (CHID):

sim = Simulation(chid='room_fire')

The CHID is used as the filename prefix for all output files:

  • room_fire.fds - Input file
  • room_fire.out - Output log
  • room_fire_devc.csv - Device data
  • room_fire_hrr.csv - Heat release rate data

Adding a Title

Add a descriptive title for documentation:

sim = Simulation(
    chid='room_fire',
    title='5m x 5m Room Fire with 1kW/m² Burner'
)

The title appears in the FDS output and helps identify simulations.

CHID Naming Rules

CHID Best Practices

  • Use lowercase with underscores: room_fire, corridor_test
  • Keep it short but descriptive: office_fire_2kw
  • No spaces or special characters
  • Avoid starting with numbers
# Good
sim = Simulation(chid='warehouse_fire_1000kw')

# Bad
sim = Simulation(chid='Warehouse Fire 1000kW')  # Spaces and capitals
sim = Simulation(chid='123_test')  # Starts with number

Building a Simulation

Step-by-Step Approach

Build simulations by adding components in logical order:

from pyfds import Simulation
from pyfds.core.namelists import Time, Mesh, Surface, Obstruction, Device
from pyfds.core.geometry import Bounds3D, Grid3D, Point3D

# 1. Create simulation
sim = Simulation(chid='example', title='Example Fire')

# 2. Set time parameters
sim.add(Time(t_end=600.0))

# 3. Define computational domain
sim.add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)))

# 4. Create surfaces
sim.add(Surface(id='FIRE', hrrpua=1000.0, color='RED'))
sim.add(Surface(id='WALL', matl_id='CONCRETE', thickness=0.2))

# 5. Add geometry
sim.add(Obstruction(xb=Bounds3D.of(0, 0.2, 0, 5, 0, 2.5), surf_ids=('WALL', 'INERT', 'INERT')))
sim.add(Obstruction(xb=Bounds3D.of(2, 3, 2, 3, 0, 0.1), surf_ids=('FIRE', 'INERT', 'INERT')))

# 6. Add measurement devices
sim.add(Device(id='TEMP_CEILING', quantity='TEMPERATURE', xyz=Point3D.of(2.5, 2.5, 2.4)))

# 7. Write FDS file
sim.write('example.fds')

Method Chaining

For more concise code, use method chaining:

from pyfds import Simulation
from pyfds.core.namelists import Time, Mesh, Surface, Obstruction
from pyfds.core.geometry import Bounds3D, Grid3D

sim = (Simulation(chid='fire_test', title='Fire Test')
       .add(Time(t_end=300.0))
       .add(Mesh(ijk=Grid3D.of(30, 30, 15), xb=Bounds3D.of(0, 3, 0, 3, 0, 1.5)))
       .add(Surface(id='FIRE', hrrpua=500.0))
       .add(Obstruction(xb=Bounds3D.of(1, 2, 1, 2, 0, 0.1), surf_ids=('FIRE', 'INERT', 'INERT'))))

sim.write('fire_test.fds')

Method Chaining

All configuration methods return self, allowing you to chain method calls. This is optional - use whichever style you prefer.

Component Organization

Organize your simulation code for clarity:

from pyfds import Simulation
from pyfds.core.namelists import Time, Mesh, Surface, Obstruction, Vent, Device, Material
from pyfds.core.geometry import Bounds3D, Grid3D, Point3D

# ============================================================
# SIMULATION SETUP
# ============================================================
sim = Simulation(chid='organized_fire', title='Well Organized Simulation')

# ============================================================
# TIME AND DOMAIN
# ============================================================
sim.add(Time(t_end=600.0, dt=0.1))
sim.set_misc(tmpa=20.0, radiation=True)

# ============================================================
# COMPUTATIONAL MESH
# ============================================================
sim.add(Mesh(ijk=Grid3D.of(60, 40, 30), xb=Bounds3D.of(0, 6, 0, 4, 0, 3), id='MAIN_MESH'))

# ============================================================
# MATERIALS AND SURFACES
# ============================================================
# Materials
sim.add(Material(id='CONCRETE', conductivity=1.8, specific_heat=0.88, density=2400.0))
sim.add(Material(id='STEEL', conductivity=45.8, specific_heat=0.46, density=7850.0))

# Surfaces
sim.add(Surface(id='FIRE', hrrpua=1000.0, color='RED'))
sim.add(Surface(id='CONCRETE_WALL', matl_id='CONCRETE', thickness=0.2))
sim.add(Surface(id='STEEL_DOOR', matl_id='STEEL', thickness=0.05))

# ============================================================
# GEOMETRY
# ============================================================
# Walls
sim.add(Obstruction(xb=Bounds3D.of(0, 0.2, 0, 4, 0, 3), surf_ids=('CONCRETE_WALL', 'INERT', 'INERT')))  # West wall
sim.add(Obstruction(xb=Bounds3D.of(5.8, 6, 0, 4, 0, 3), surf_ids=('CONCRETE_WALL', 'INERT', 'INERT')))  # East wall
sim.add(Obstruction(xb=Bounds3D.of(0, 6, 0, 0.2, 0, 3), surf_ids=('CONCRETE_WALL', 'INERT', 'INERT')))  # South wall
sim.add(Obstruction(xb=Bounds3D.of(0, 6, 3.8, 4, 0, 3), surf_ids=('CONCRETE_WALL', 'INERT', 'INERT')))  # North wall

# Door
sim.add(Vent(xb=Bounds3D.of(5.8, 5.8, 1, 2, 0, 2.1), surf_id='OPEN'))  # Door opening

# Fire source
sim.add(Obstruction(xb=Bounds3D.of(2, 3, 1.5, 2.5, 0, 0.1), surf_ids=('FIRE', 'INERT', 'INERT')))

# ============================================================
# MEASUREMENT DEVICES
# ============================================================
# Temperature measurements
for i, x in enumerate([1.5, 3.0, 4.5]):
    sim.add(Device(
        id=f'TEMP_{i+1}',
        quantity='TEMPERATURE',
        xyz=Point3D.of(x, 2.0, 2.7)
    ))

# Heat flux at floor
sim.add(Device(
    id='HF_FLOOR',
    quantity='HEAT FLUX',
    xb=Bounds3D.of(0, 6, 0, 4, 0, 0)
))

# ============================================================
# WRITE OUTPUT
# ============================================================
sim.write('organized_fire.fds')

Using Functions for Reusability

Create helper functions for repeated patterns:

from pyfds import Simulation
from pyfds.core.namelists import Mesh, Surface, Obstruction
from pyfds.core.geometry import Bounds3D, Grid3D

def add_room(sim, origin, size, wall_surf='WALL'):
    """Add a rectangular room to the simulation."""
    x0, y0, z0 = origin
    lx, ly, lz = size

    # Four walls
    sim.add(Obstruction(xb=Bounds3D.of(x0, x0+0.2, y0, y0+ly, z0, z0+lz), surf_ids=(wall_surf, 'INERT', 'INERT')))
    sim.add(Obstruction(xb=Bounds3D.of(x0+lx-0.2, x0+lx, y0, y0+ly, z0, z0+lz), surf_ids=(wall_surf, 'INERT', 'INERT')))
    sim.add(Obstruction(xb=Bounds3D.of(x0, x0+lx, y0, y0+0.2, z0, z0+lz), surf_ids=(wall_surf, 'INERT', 'INERT')))
    sim.add(Obstruction(xb=Bounds3D.of(x0, x0+lx, y0+ly-0.2, y0+ly, z0, z0+lz), surf_ids=(wall_surf, 'INERT', 'INERT')))

    return sim

# Use the function
sim = Simulation(chid='multi_room')
sim.add(Mesh(ijk=Grid3D.of(100, 50, 30), xb=Bounds3D.of(0, 10, 0, 5, 0, 3)))
sim.add(Surface(id='WALL', matl_id='CONCRETE', thickness=0.2))

add_room(sim, origin=(0, 0, 0), size=(5, 5, 3))
add_room(sim, origin=(5, 0, 0), size=(5, 5, 3))

Validation

Automatic Validation

PyFDS automatically validates when you write files:

sim.write('test.fds')  # Validates automatically

If there are errors, you'll see informative messages:

ValidationError: Obstruction (2, 3, 2, 3, 0, 0.1) is outside mesh bounds (0, 5, 0, 5, 0, 2.5)

Explicit Validation

Validate before writing to catch issues early:

warnings = sim.validate()

if warnings:
    print(f"Found {len(warnings)} warnings:")
    for warning in warnings:
        print(f"  - {warning}")
else:
    print("No validation warnings!")
    sim.write('test.fds')

Common Validation Checks

PyFDS validates:

  • ✅ Geometry is within mesh bounds
  • ✅ Surface IDs exist before use
  • ✅ Coordinate bounds are properly ordered
  • ✅ Required parameters are present
  • ✅ Parameter values are physically reasonable
  • ✅ Material properties are consistent

Generating FDS Files

Writing to File

sim.write('simulation.fds')

This creates a properly formatted FDS input file.

Getting FDS Content

Get the FDS file content as a string:

fds_content = sim.to_fds()
print(fds_content)

Output:

&HEAD CHID='simulation', TITLE='My Simulation' /
&TIME T_END=600.0 /
&MESH IJK=50,50,25, XB=0,5,0,5,0,2.5 /
...

Custom Output Locations

Specify a directory for output files:

from pathlib import Path

# Create output directory
output_dir = Path('simulations/room_fires')
output_dir.mkdir(parents=True, exist_ok=True)

# Write to that directory
sim.write(output_dir / 'fire_1000kw.fds')

Complete Examples

Minimal Simulation

The absolute minimum for a valid FDS simulation:

from pyfds import Simulation
from pyfds.core.namelists import Time, Mesh
from pyfds.core.geometry import Bounds3D, Grid3D

sim = Simulation(chid='minimal')
sim.add(Time(t_end=10.0))
sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 1, 0, 1, 0, 1)))
sim.write('minimal.fds')

Simple Room Fire

A complete, runnable room fire:

from pyfds import Simulation
from pyfds.core.namelists import Time, Mesh, Surface, Obstruction, Device
from pyfds.core.geometry import Bounds3D, Grid3D, Point3D

# Create simulation
sim = Simulation(chid='room_fire', title='5x5x2.5m Room Fire')

# Time: 10 minutes
sim.add(Time(t_end=600.0))

# Domain: 5m x 5m x 2.5m room, 0.1m cells
sim.add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)))

# Fire: 1m x 1m burner, 1000 kW/m²
sim.add(Surface(id='BURNER', hrrpua=1000.0, color='RED'))
sim.add(Obstruction(xb=Bounds3D.of(2, 3, 2, 3, 0, 0.1), surf_ids=('BURNER', 'INERT', 'INERT')))

# Measurements
sim.add(Device(id='TEMP_CEILING', quantity='TEMPERATURE', xyz=Point3D.of(2.5, 2.5, 2.4)))
sim.add(Device(id='TEMP_FLOOR', quantity='TEMPERATURE', xyz=Point3D.of(2.5, 2.5, 0.1)))
sim.add(Device(id='VEL_CEILING', quantity='VELOCITY', xyz=Point3D.of(2.5, 2.5, 2.4)))

# Write
sim.write('room_fire.fds')
print("Simulation created successfully!")

Room with Ventilation

Room with door opening:

from pyfds import Simulation
from pyfds.core.namelists import Time, Mesh, Surface, Obstruction, Vent, Device
from pyfds.core.geometry import Bounds3D, Grid3D, Point3D

sim = Simulation(chid='room_vent', title='Room with Door')

sim.add(Time(t_end=300.0))
sim.add(Mesh(ijk=Grid3D.of(60, 40, 30), xb=Bounds3D.of(0, 6, 0, 4, 0, 3)))

# Ambient conditions
sim.set_misc(tmpa=20.0)

# Surfaces
sim.add(Surface(id='FIRE', hrrpua=500.0))
sim.add(Surface(id='WALL', matl_id='GYPSUM', thickness=0.013))

# Walls (with door opening)
sim.add(Obstruction(xb=Bounds3D.of(0, 0.2, 0, 4, 0, 3), surf_ids=('WALL', 'INERT', 'INERT')))
sim.add(Obstruction(xb=Bounds3D.of(5.8, 6, 0, 1.5, 0, 3), surf_ids=('WALL', 'INERT', 'INERT')))  # Wall beside door
sim.add(Obstruction(xb=Bounds3D.of(5.8, 6, 2.5, 4, 0, 3), surf_ids=('WALL', 'INERT', 'INERT')))  # Wall above door
sim.add(Obstruction(xb=Bounds3D.of(5.8, 6, 1.5, 2.5, 2.1, 3), surf_ids=('WALL', 'INERT', 'INERT')))  # Wall above door

# Door opening
sim.add(Vent(xb=Bounds3D.of(5.8, 5.8, 1.5, 2.5, 0, 2.1), surf_id='OPEN'))

# Fire
sim.add(Obstruction(xb=Bounds3D.of(2, 3, 1.5, 2.5, 0, 0.1), surf_ids=('FIRE', 'INERT', 'INERT')))

# Devices
sim.add(Device(id='TEMP_DOOR', quantity='TEMPERATURE', xyz=Point3D.of(5.8, 2.0, 1.0)))
sim.add(Device(id='VEL_DOOR', quantity='VELOCITY', xyz=Point3D.of(5.8, 2.0, 1.0)))

sim.write('room_vent.fds')

Best Practices

1. Start Simple

Begin with coarse meshes and short times for testing:

# Quick test (runs in seconds)
sim.add(Mesh(ijk=Grid3D.of(10, 10, 10), xb=Bounds3D.of(0, 2, 0, 2, 0, 1)))
sim.add(Time(t_end=10.0))

Then refine for production:

# Production run
sim.add(Mesh(ijk=Grid3D.of(40, 40, 20), xb=Bounds3D.of(0, 2, 0, 2, 0, 1)))
sim.add(Time(t_end=300.0))

2. Use Descriptive IDs

# Good - clear and specific
sim.add(Surface(id='WOOD_WALL_12MM', ...))
sim.add(Device(id='TEMP_CEILING_CENTER', ...))

# Bad - unclear
sim.add(Surface(id='S1', ...))
sim.add(Device(id='D1', ...))

3. Comment Your Code

# Heat release rate per unit area for medium intensity fire
sim.add(Surface(id='FIRE', hrrpua=1000.0))  # 1 MW/m²

# Cell size: 0.1m x 0.1m x 0.1m
# Total cells: 50 * 50 * 25 = 62,500
sim.add(Mesh(ijk=Grid3D.of(50, 50, 25), xb=Bounds3D.of(0, 5, 0, 5, 0, 2.5)))
# Material and its surface together
sim.add(Material(id='CONCRETE', conductivity=1.8, density=2400.0))
sim.add(Surface(id='CONCRETE_WALL', matl_id='CONCRETE', thickness=0.2))

# Fire source and measurement together
sim.add(Obstruction(xb=Bounds3D.of(2, 3, 2, 3, 0, 0.1), surf_ids=('FIRE', 'INERT', 'INERT')))
sim.add(Device(id='TEMP_ABOVE_FIRE', quantity='TEMPERATURE', xyz=Point3D.of(2.5, 2.5, 0.5)))

5. Validate Early and Often

# Validate after each major addition
sim.add(Mesh(...))
assert len(sim.validate()) == 0, "Mesh validation failed"

sim.add(Obstruction(...))
assert len(sim.validate()) == 0, "Geometry validation failed"

Troubleshooting

Common Issues

Simulation won't write

Cause: Validation errors

Solution: Check validation output

warnings = sim.validate()
for w in warnings:
    print(w)

Missing required parameters

Cause: Forgot TIME or MESH

Solution: Every simulation needs time and mesh

sim.add(Time(t_end=100.0))  # Required
sim.add(Mesh(...))  # Required

Geometry outside mesh

Cause: Obstruction coordinates outside mesh bounds

Solution: Check your coordinates

# Mesh: 0 to 5 in X
sim.add(Mesh(xb=(0, 5, ...)))

# Bad: X goes to 6
sim.add(Obstruction(xb=(4, 6, ...))  # ERROR

# Good: X stays within 0-5
sim.add(Obstruction(xb=(4, 5, ...))  # OK

Next Steps

Now that you know how to build simulations, learn about:


Computational Domain → View Examples →