Skip to content

Registry System

registry

Unified registry for all FDS simulation components.

Classes

Registry

Registry(type_name)

Bases: Generic[T]

Type-safe registry for FDS namelist objects.

Unlike WeakValueDictionary, this uses strong references to ensure objects are not garbage collected unexpectedly.

Source code in src/pyfds/core/registry.py
def __init__(self, type_name: str) -> None:
    self._type_name = type_name
    self._items: dict[str, T] = {}
    self._items_without_id: list[T] = []
Functions
add
add(item)

Add an item to the registry.

Source code in src/pyfds/core/registry.py
def add(self, item: T) -> None:
    """Add an item to the registry."""
    if not hasattr(item, "id"):
        raise AttributeError(f"{type(item).__name__} must have 'id' attribute")

    item_id = item.id
    if item_id is not None:
        if item_id in self._items:
            raise DuplicateIdError(item_id, self._type_name)
        self._items[item_id] = item
    else:
        self._items_without_id.append(item)
get
get(item_id)

Get an item by ID.

Source code in src/pyfds/core/registry.py
def get(self, item_id: str) -> T:
    """Get an item by ID."""
    if item_id not in self._items:
        raise UnknownIdError(item_id, self._type_name, list(self._items.keys()))
    return self._items[item_id]
remove
remove(item_id)

Remove an item by ID.

Source code in src/pyfds/core/registry.py
def remove(self, item_id: str) -> None:
    """Remove an item by ID."""
    self._items.pop(item_id, None)
contains
contains(item_id)

Check if ID exists.

Source code in src/pyfds/core/registry.py
def contains(self, item_id: str) -> bool:
    """Check if ID exists."""
    return item_id in self._items
list_ids
list_ids()

Get all registered IDs.

Source code in src/pyfds/core/registry.py
def list_ids(self) -> list[str]:
    """Get all registered IDs."""
    return list(self._items.keys())
list_items
list_items()

Get all registered items.

Source code in src/pyfds/core/registry.py
def list_items(self) -> list[T]:
    """Get all registered items."""
    return list(self._items.values()) + self._items_without_id
clear
clear()

Remove all items.

Source code in src/pyfds/core/registry.py
def clear(self) -> None:
    """Remove all items."""
    self._items.clear()
    self._items_without_id.clear()

SimulationRegistry

SimulationRegistry()

Centralized registry for all simulation components.

All IDs must be unique across ALL namelist types (FDS requirement). This mirrors FDS behavior where IDs like 'INERT' could refer to a SURF, MATL, or other component depending on context.

Source code in src/pyfds/core/registry.py
def __init__(self) -> None:
    # Import here to avoid circular imports

    # Typed registries - organized by FDS namelist group
    self.meshes: Registry[Mesh] = Registry("MESH")
    self.surfaces: Registry[Surface] = Registry("SURF")
    self.materials: Registry[Material] = Registry("MATL")
    self.ramps: Registry[Ramp] = Registry("RAMP")
    self.holes: Registry[Hole] = Registry("HOLE")
    self.species: Registry[Species] = Registry("SPEC")
    self.reactions: Registry[Reaction] = Registry("REAC")
    self.devices: Registry[Device] = Registry("DEVC")
    self.props: Registry[Property] = Registry("PROP")
    self.ctrls: Registry[Control] = Registry("CTRL")
    self.mults: Registry[Multiplier] = Registry("MULT")
    self.obstructions: Registry[Obstruction] = Registry("OBST")
    self.vents: Registry[Vent] = Registry("VENT")
    self.inits: Registry[Initialization] = Registry("INIT")
    self.geoms: Registry[Geometry] = Registry("GEOM")
    self.parts: Registry[Particle] = Registry("PART")
    self.moves: Registry[Move] = Registry("MOVE")
    self.hvacs: Registry[Hvac] = Registry("HVAC")

    # Singleton namelists (only one allowed per simulation)
    # These mirror FDS behavior where only one HEAD, TIME, MISC, COMB is allowed.
    self.time: Time | None = None
    self.combustion: Combustion | None = None
    self.misc: Misc | None = None
    self.head: Head | None = None

    # Track all IDs for global uniqueness
    self._all_ids: set[str] = set()
Functions
register
register(item)

Register any item to appropriate registry.

Source code in src/pyfds/core/registry.py
def register(self, item: object) -> None:
    """Register any item to appropriate registry."""
    # Import here to avoid circular imports
    from pyfds.core.namelists import (
        Combustion,
        Control,
        Device,
        Geometry,
        Head,
        Hole,
        Hvac,
        Initialization,
        Material,
        Mesh,
        Misc,
        Move,
        Multiplier,
        Obstruction,
        Particle,
        Property,
        Ramp,
        Reaction,
        Species,
        Surface,
        Time,
        Vent,
    )

    # Check global uniqueness for ID-based items
    if hasattr(item, "id") and getattr(item, "id", None) is not None:
        item_id = item.id
        self._check_global_uniqueness(item_id)
        self._all_ids.add(item_id)

    # Route to appropriate registry or singleton
    match item:
        case Mesh():
            self.meshes.add(item)
        case Surface():
            self.surfaces.add(item)
        case Material():
            self.materials.add(item)
        case Ramp():
            self.ramps.add(item)
        case Hole():
            self.holes.add(item)
        case Species():
            self.species.add(item)
        case Reaction():
            # Allow multiple REAC entries; store in reactions registry
            self.reactions.add(item)
        case Device():
            self.devices.add(item)
        case Property():
            self.props.add(item)
        case Control():
            self.ctrls.add(item)
        case Multiplier():
            self.mults.add(item)
        case Obstruction():
            self.obstructions.add(item)
        case Vent():
            self.vents.add(item)
        case Initialization():
            self.inits.add(item)
        case Geometry():
            self.geoms.add(item)
        case Particle():
            self.parts.add(item)
        case Move():
            self.moves.add(item)
        case Hvac():
            self.hvacs.add(item)
        case Time():
            if self.time is not None:
                raise ValueError("TIME already set")
            self.time = item
        case Combustion():
            if self.combustion is not None:
                raise ValueError("COMB already set")
            self.combustion = item
        case Misc():
            if self.misc is not None:
                raise ValueError("MISC already set")
            self.misc = item
        case Head():
            if self.head is not None:
                raise ValueError("HEAD already set")
            self.head = item
        case _:
            raise TypeError(f"Unknown item type: {type(item).__name__}")
get_by_id
get_by_id(item_id)

Get any item by ID from any registry.

Source code in src/pyfds/core/registry.py
def get_by_id(self, item_id: str) -> object:
    """Get any item by ID from any registry."""
    registries: list[Registry] = [
        self.meshes,
        self.surfaces,
        self.materials,
        self.ramps,
        self.holes,
        self.species,
        self.reactions,
        self.devices,
        self.props,
        self.ctrls,
        self.mults,
        self.obstructions,
        self.vents,
        self.inits,
        self.geoms,
        self.parts,
        self.moves,
        self.hvacs,
    ]

    for registry in registries:
        if item_id in registry:
            return registry.get(item_id)

    raise UnknownIdError(item_id, "any", list(self._all_ids))
clear
clear()

Clear all registries and singletons.

Source code in src/pyfds/core/registry.py
def clear(self) -> None:
    """Clear all registries and singletons."""
    self.meshes.clear()
    self.surfaces.clear()
    self.materials.clear()
    self.ramps.clear()
    self.holes.clear()
    self.species.clear()
    self.reactions.clear()
    self.devices.clear()
    self.props.clear()
    self.ctrls.clear()
    self.mults.clear()
    self.obstructions.clear()
    self.vents.clear()
    self.inits.clear()
    self.geoms.clear()
    self.parts.clear()
    self.moves.clear()

    self.time = None
    self.combustion = None
    self.misc = None
    self.head = None
    self._all_ids.clear()
all_namelists
all_namelists()

Get all namelists in FDS output order.

Source code in src/pyfds/core/registry.py
def all_namelists(self) -> list[object]:
    """Get all namelists in FDS output order."""
    from typing import Any

    namelists: list[Any] = []

    # Order matches FDS User Guide Table convention:
    # HEAD, TIME, MISC, MESH, MULT, RAMP, SPEC, REAC, COMB, MATL, SURF,
    # OBST, HOLE, VENT, PROP, DEVC, CTRL, INIT, TAIL

    # Singleton namelists (handled in specific order in Simulation.to_fds())
    # TIME and MISC are handled separately in Simulation.to_fds()

    # Registry-based namelists in FDS order
    namelists.extend(self.meshes.list_items())  # MESH
    namelists.extend(self.mults.list_items())  # MULT
    namelists.extend(self.ramps.list_items())  # RAMP (before SPEC/REAC/MATL)
    namelists.extend(self.species.list_items())  # SPEC
    namelists.extend(self.reactions.list_items())  # REAC

    # COMB (after REAC, before MATL)
    if self.combustion:
        namelists.append(self.combustion)

    namelists.extend(self.materials.list_items())  # MATL (after COMB, before SURF)
    namelists.extend(self.surfaces.list_items())  # SURF
    namelists.extend(self.obstructions.list_items())  # OBST
    namelists.extend(self.holes.list_items())  # HOLE
    namelists.extend(self.vents.list_items())  # VENT
    namelists.extend(self.props.list_items())  # PROP (before DEVC)
    namelists.extend(self.devices.list_items())  # DEVC
    namelists.extend(self.ctrls.list_items())  # CTRL
    namelists.extend(self.inits.list_items())  # INIT
    namelists.extend(self.geoms.list_items())  # GEOM
    namelists.extend(self.parts.list_items())  # PART
    namelists.extend(self.moves.list_items())  # MOVE

    return namelists

registry_view

Read-only views into Registry collections.

This module provides RegistryView, a typed read-only interface for accessing registry contents without exposing mutation operations.

Classes

RegistryView

RegistryView(registry)

Bases: Generic[T]

Read-only typed view into a Registry.

Provides dictionary-like read access to registry contents without exposing mutation operations. This ensures that namelists can only be added through the Simulation.add() method.

PARAMETER DESCRIPTION
registry

The underlying registry to wrap

TYPE: Registry[T]

Examples:

>>> sim = Simulation(chid="test")
>>> sim.add(Mesh(id="mesh1", ...))
>>>
>>> # Access via view
>>> mesh = sim.meshes["mesh1"]  # Get by ID
>>> len(sim.meshes)             # Count items
>>> "mesh1" in sim.meshes       # Check existence
>>> for m in sim.meshes:        # Iterate
...     print(m.id)
Source code in src/pyfds/core/registry_view.py
def __init__(self, registry: "Registry[T]") -> None:
    self._registry = registry
Functions
__getitem__
__getitem__(key: str) -> T
__getitem__(key: int) -> T
__getitem__(key)

Get an item by its ID or index.

PARAMETER DESCRIPTION
key

The unique identifier of the item, or an integer index

TYPE: str or int

RETURNS DESCRIPTION
T

The item with the given ID or at the given index

RAISES DESCRIPTION
UnknownIdError

If a string ID does not exist in the registry

IndexError

If an integer index is out of range

Source code in src/pyfds/core/registry_view.py
def __getitem__(self, key: str | int) -> T:
    """
    Get an item by its ID or index.

    Parameters
    ----------
    key : str or int
        The unique identifier of the item, or an integer index

    Returns
    -------
    T
        The item with the given ID or at the given index

    Raises
    ------
    UnknownIdError
        If a string ID does not exist in the registry
    IndexError
        If an integer index is out of range
    """
    if isinstance(key, int):
        items = self._registry.list_items()
        return items[key]
    return self._registry.get(key)
__contains__
__contains__(item)

Check if an ID or item exists in the registry.

PARAMETER DESCRIPTION
item

The ID to check, or an item to check for membership

TYPE: str or T

RETURNS DESCRIPTION
bool

True if the ID/item exists

Source code in src/pyfds/core/registry_view.py
def __contains__(self, item: str | T) -> bool:
    """
    Check if an ID or item exists in the registry.

    Parameters
    ----------
    item : str or T
        The ID to check, or an item to check for membership

    Returns
    -------
    bool
        True if the ID/item exists
    """
    if isinstance(item, str):
        return self._registry.contains(item)
    # Check if the item itself exists
    return item in self._registry.list_items()
__iter__
__iter__()

Iterate over all items in the registry.

YIELDS DESCRIPTION
T

Each item in the registry

Source code in src/pyfds/core/registry_view.py
def __iter__(self) -> Iterator[T]:
    """
    Iterate over all items in the registry.

    Yields
    ------
    T
        Each item in the registry
    """
    return iter(self._registry)
__len__
__len__()

Return the number of items in the registry.

RETURNS DESCRIPTION
int

Count of registered items

Source code in src/pyfds/core/registry_view.py
def __len__(self) -> int:
    """
    Return the number of items in the registry.

    Returns
    -------
    int
        Count of registered items
    """
    return len(self._registry)
__bool__
__bool__()

Return True if registry contains items.

RETURNS DESCRIPTION
bool

True if not empty

Source code in src/pyfds/core/registry_view.py
def __bool__(self) -> bool:
    """
    Return True if registry contains items.

    Returns
    -------
    bool
        True if not empty
    """
    return len(self._registry) > 0
ids
ids()

Get all registered IDs.

RETURNS DESCRIPTION
list[str]

List of all IDs in the registry

Source code in src/pyfds/core/registry_view.py
def ids(self) -> list[str]:
    """
    Get all registered IDs.

    Returns
    -------
    list[str]
        List of all IDs in the registry
    """
    return self._registry.list_ids()
all
all()

Get all registered items.

RETURNS DESCRIPTION
list[T]

List of all items in the registry

Source code in src/pyfds/core/registry_view.py
def all(self) -> list[T]:
    """
    Get all registered items.

    Returns
    -------
    list[T]
        List of all items in the registry
    """
    return self._registry.list_items()
get
get(item_id, default=None)

Get an item by ID with optional default.

PARAMETER DESCRIPTION
item_id

The ID to look up

TYPE: str

default

Value to return if ID not found

TYPE: T DEFAULT: None

RETURNS DESCRIPTION
T or None

The item if found, otherwise default

Source code in src/pyfds/core/registry_view.py
def get(self, item_id: str, default: T | None = None) -> T | None:
    """
    Get an item by ID with optional default.

    Parameters
    ----------
    item_id : str
        The ID to look up
    default : T, optional
        Value to return if ID not found

    Returns
    -------
    T or None
        The item if found, otherwise default
    """
    if item_id in self._registry:
        return self._registry.get(item_id)
    return default

Overview

The registry system manages namelist objects and their IDs within a simulation. It provides centralized object storage, ID validation, and type-safe retrieval.

Core Components

Registry

Generic registry for managing objects of a specific type with ID-based lookup.

from pyfds.core.registry import Registry
from pyfds import Surface

# Create a registry for surfaces
registry = Registry[Surface]()

# Add objects
surface = Surface(id="FIRE", hrrpua=1000)
registry.add(surface)

# Retrieve by ID
fire_surface = registry.get("FIRE")

# Check existence
if registry.contains("FIRE"):
    print("Surface exists")

# Get all objects
all_surfaces = registry.get_all()

Type Parameters:

  • T: The type of object stored in the registry

Key Methods:

  • add(obj): Add an object to the registry
  • get(id): Retrieve an object by ID
  • contains(id): Check if an ID exists
  • get_all(): Get all registered objects
  • clear(): Remove all objects

SimulationRegistry

Main registry system for a simulation, managing all namelist types.

from pyfds import Simulation

sim = Simulation(chid="test")

# Registry is accessed internally
# Add objects to simulation
sim.add(Surface(id="FIRE", hrrpua=1000))
sim.add(Mesh(id="MAIN", ijk=(50, 50, 25), xb=(0, 10, 0, 10, 0, 5)))

# The registry manages these objects and enforces ID uniqueness

The SimulationRegistry maintains separate registries for each namelist type:

  • Surfaces
  • Materials
  • Meshes
  • Devices
  • Reactions
  • Species
  • And all other namelist types

RegistryView

Read-only view of a registry, providing safe access without modification.

from pyfds import Simulation

sim = Simulation(chid="test")
sim.add(Surface(id="FIRE", hrrpua=1000))
sim.add(Surface(id="WALL", color="GRAY"))

# Get read-only view of surfaces
surfaces_view = sim.surfaces  # Returns RegistryView

# Read operations work
fire = surfaces_view.get("FIRE")
all_surfaces = surfaces_view.get_all()
exists = surfaces_view.contains("FIRE")

# But cannot modify through view
# surfaces_view.add(...)  # Not available

ID Management

Duplicate ID Detection

from pyfds import Simulation, Surface
from pyfds.exceptions import DuplicateIdError

sim = Simulation(chid="test")
sim.add(Surface(id="FIRE", hrrpua=1000))

try:
    # This will raise an error
    sim.add(Surface(id="FIRE", hrrpua=2000))
except DuplicateIdError as e:
    print(f"Cannot add duplicate: {e}")

ID Resolution

from pyfds import Simulation, Obstruction
from pyfds.exceptions import UnknownIdError

sim = Simulation(chid="test")

try:
    # Reference to undefined surface
    sim.add(Obstruction(xb=(0, 1, 0, 1, 0, 1), surf_id="UNKNOWN"))
    sim.write("test.fds")  # Validation catches this
except UnknownIdError as e:
    print(f"Unknown surface ID: {e}")

Type-Safe Access

The registry system is fully typed for IDE support:

from pyfds import Simulation, Surface

sim = Simulation(chid="test")
sim.add(Surface(id="FIRE", hrrpua=1000))

# Type-safe retrieval
surface: Surface = sim.surfaces.get("FIRE")

# IDE knows the type and provides autocomplete
print(surface.hrrpua)  # IDE suggests 'hrrpua' attribute

Common Patterns

Checking for Existence

sim = Simulation(chid="test")

# Check before adding
if not sim.surfaces.contains("FIRE"):
    sim.add(Surface(id="FIRE", hrrpua=1000))

Retrieving All Objects

sim = Simulation(chid="test")
# ... add multiple surfaces ...

# Get all surfaces
for surface in sim.surfaces.get_all():
    print(f"Surface {surface.id}: {surface.hrrpua} kW/m²")

Conditional Object Creation

sim = Simulation(chid="test")

# Add default surface if not present
if not sim.surfaces.contains("INERT"):
    sim.add(Surface(id="INERT", color="GRAY"))

Getting Object Count

sim = Simulation(chid="test")
# ... add objects ...

# Check registry sizes
surface_count = len(sim.surfaces.get_all())
mesh_count = len(sim.meshes.get_all())

print(f"Simulation has {surface_count} surfaces and {mesh_count} meshes")

Advanced Usage

Programmatic Object Creation

from pyfds import Simulation, Device
from pyfds.core.geometry import Point3D

sim = Simulation(chid="test")

# Create devices programmatically
for i in range(10):
    for j in range(10):
        device = Device(
            id=f"TEMP_{i}_{j}",
            quantity="TEMPERATURE",
            xyz=Point3D.of(i * 1.0, j * 1.0, 2.4)
        )
        sim.add(device)

# Registry ensures all IDs are unique
print(f"Created {len(sim.devices.get_all())} devices")

Bulk Operations

sim = Simulation(chid="test")
# ... add multiple materials ...

# Process all materials
for material in sim.materials.get_all():
    if material.density and material.density < 100:
        print(f"Warning: {material.id} has low density")

Cross-Reference Validation

sim = Simulation(chid="test")

# Add surfaces and obstructions
sim.add(Surface(id="FIRE", hrrpua=1000))
sim.add(Obstruction(xb=(2, 3, 2, 3, 0, 0.1), surf_id="FIRE"))

# Registry enables validation
# Can check if all surf_id references are valid
for obst in sim.obstructions.get_all():
    if obst.surf_id and not sim.surfaces.contains(obst.surf_id):
        print(f"Warning: {obst.id} references unknown surface {obst.surf_id}")

Implementation Details

Registry Properties

Each namelist type has a corresponding registry accessed via properties on the Simulation class:

sim.surfaces      # Registry[Surface]
sim.materials     # Registry[Material]
sim.meshes        # Registry[Mesh]
sim.obstructions  # Registry[Obstruction]
sim.vents         # Registry[Vent]
sim.devices       # Registry[Device]
sim.reactions     # Registry[Reaction]
sim.species       # Registry[Species]
# ... and more

ID Uniqueness Scope

IDs must be unique within their type:

sim = Simulation(chid="test")

# This is OK - different types can have same ID
sim.add(Surface(id="FIRE", hrrpua=1000))
sim.add(Device(id="FIRE", quantity="TEMPERATURE", xyz=(5, 5, 2)))

# This is NOT OK - same type, duplicate ID
sim.add(Surface(id="FIRE", color="RED"))  # Raises DuplicateIdError

See Also