Skip to content

ControlBuilder

ControlBuilder

ControlBuilder(id=None)

Bases: Builder[Control]

Builder for creating CTRL namelists.

Provides convenient methods for creating control logic including logic gates (ANY, ALL), time delays, and other control functions.

PARAMETER DESCRIPTION
id

Unique identifier for the control. Can also be set via with_id().

TYPE: str DEFAULT: None

Examples:

>>> # ANY logic (OR) - activate if any input is true
>>> ctrl = ControlBuilder('SMOKE_ALARM') \
...     .any(['SD_1', 'SD_2', 'SD_3']) \
...     .build()
>>> # ALL logic (AND) - activate if all inputs are true
>>> ctrl = ControlBuilder('DUAL_CONDITION') \
...     .all(['TEMP_HIGH', 'SMOKE_DETECTED']) \
...     .build()
>>> # Time delay
>>> ctrl = ControlBuilder('DELAYED_SPRINKLER') \
...     .time_delay('HEAT_DETECTOR', delay=10.0) \
...     .build()
>>> # Custom control with latch and initial state
>>> ctrl = ControlBuilder('ALARM') \
...     .any(['SD_1', 'SD_2']) \
...     .with_latch(True) \
...     .with_initial_state(False) \
...     .build()
>>> # Using with_id() method
>>> ctrl = ControlBuilder() \
...     .with_id('ALARM') \
...     .any(['SD_1', 'SD_2']) \
...     .build()

Initialize the ControlBuilder.

PARAMETER DESCRIPTION
id

Unique identifier for the control

TYPE: str DEFAULT: None

Source code in src/pyfds/builders/control.py
def __init__(self, id: str | None = None):
    """
    Initialize the ControlBuilder.

    Parameters
    ----------
    id : str, optional
        Unique identifier for the control
    """
    super().__init__()
    self._id = id
    self._function_type: ControlFunction | None = None
    self._input_id: str | list[str] | None = None
    self._delay: float = 0.0
    self._initial_state: bool = False
    self._latch: bool = True

Functions

with_id

with_id(id)

Set the control identifier.

PARAMETER DESCRIPTION
id

Unique identifier for the control

TYPE: str

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Source code in src/pyfds/builders/control.py
def with_id(self, id: str) -> "ControlBuilder":
    """
    Set the control identifier.

    Parameters
    ----------
    id : str
        Unique identifier for the control

    Returns
    -------
    ControlBuilder
        Self for method chaining
    """
    self._id = id
    return self

any

any(input_ids)

OR logic - activate if ANY input is true.

PARAMETER DESCRIPTION
input_ids

List of device or control IDs to monitor

TYPE: list[str]

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('ALARM').any(['SD_1', 'SD_2', 'SD_3']).build()
Source code in src/pyfds/builders/control.py
def any(self, input_ids: list[str]) -> "ControlBuilder":
    """
    OR logic - activate if ANY input is true.

    Parameters
    ----------
    input_ids : list[str]
        List of device or control IDs to monitor

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('ALARM').any(['SD_1', 'SD_2', 'SD_3']).build()
    """
    self._function_type = ControlFunction.ANY
    self._input_id = input_ids
    return self

all

all(input_ids)

AND logic - activate if ALL inputs are true.

PARAMETER DESCRIPTION
input_ids

List of device or control IDs to monitor

TYPE: list[str]

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('DUAL_COND').all(['TEMP_HIGH', 'SMOKE']).build()
Source code in src/pyfds/builders/control.py
def all(self, input_ids: list[str]) -> "ControlBuilder":
    """
    AND logic - activate if ALL inputs are true.

    Parameters
    ----------
    input_ids : list[str]
        List of device or control IDs to monitor

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('DUAL_COND').all(['TEMP_HIGH', 'SMOKE']).build()
    """
    self._function_type = ControlFunction.ALL
    self._input_id = input_ids
    return self

only

only(input_id)

Direct pass-through of single input.

PARAMETER DESCRIPTION
input_id

Device or control ID to pass through

TYPE: str

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('PASSTHROUGH').only('DETECTOR_1').build()
Source code in src/pyfds/builders/control.py
def only(self, input_id: str) -> "ControlBuilder":
    """
    Direct pass-through of single input.

    Parameters
    ----------
    input_id : str
        Device or control ID to pass through

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('PASSTHROUGH').only('DETECTOR_1').build()
    """
    self._function_type = ControlFunction.ONLY
    self._input_id = input_id
    return self

time_delay

time_delay(input_id, delay)

Time-delayed activation.

PARAMETER DESCRIPTION
input_id

Device or control ID to monitor

TYPE: str

delay

Time delay in seconds

TYPE: float

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('DELAYED') \
...     .time_delay('DETECTOR', delay=10.0) \
...     .build()
Source code in src/pyfds/builders/control.py
def time_delay(self, input_id: str, delay: float) -> "ControlBuilder":
    """
    Time-delayed activation.

    Parameters
    ----------
    input_id : str
        Device or control ID to monitor
    delay : float
        Time delay in seconds

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('DELAYED') \\
    ...     .time_delay('DETECTOR', delay=10.0) \\
    ...     .build()
    """
    self._function_type = ControlFunction.TIME_DELAY
    self._input_id = input_id
    self._delay = delay
    return self

custom

custom(input_id)

Custom control function.

PARAMETER DESCRIPTION
input_id

Device or control ID(s) to monitor

TYPE: str or list[str]

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Source code in src/pyfds/builders/control.py
def custom(self, input_id: str | list[str]) -> "ControlBuilder":
    """
    Custom control function.

    Parameters
    ----------
    input_id : str or list[str]
        Device or control ID(s) to monitor

    Returns
    -------
    ControlBuilder
        Self for method chaining
    """
    self._function_type = ControlFunction.CUSTOM
    self._input_id = input_id
    return self

kill

kill()

Kill function - stops the simulation.

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('KILL_AT_TEMP') \
...     .kill() \
...     .build()
Source code in src/pyfds/builders/control.py
def kill(self) -> "ControlBuilder":
    """
    Kill function - stops the simulation.

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('KILL_AT_TEMP') \\
    ...     .kill() \\
    ...     .build()
    """
    self._function_type = ControlFunction.KILL
    return self

restart

restart()

Restart function - triggers simulation restart.

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Source code in src/pyfds/builders/control.py
def restart(self) -> "ControlBuilder":
    """
    Restart function - triggers simulation restart.

    Returns
    -------
    ControlBuilder
        Self for method chaining
    """
    self._function_type = ControlFunction.RESTART
    return self

with_delay

with_delay(delay)

Add time delay to current control function.

PARAMETER DESCRIPTION
delay

Time delay in seconds

TYPE: float

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('ALARM') \
...     .any(['SD_1', 'SD_2']) \
...     .with_delay(5.0) \
...     .build()
Source code in src/pyfds/builders/control.py
def with_delay(self, delay: float) -> "ControlBuilder":
    """
    Add time delay to current control function.

    Parameters
    ----------
    delay : float
        Time delay in seconds

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('ALARM') \\
    ...     .any(['SD_1', 'SD_2']) \\
    ...     .with_delay(5.0) \\
    ...     .build()
    """
    self._delay = delay
    return self

with_initial_state

with_initial_state(state)

Set initial state of the control.

PARAMETER DESCRIPTION
state

Initial state (True=on, False=off)

TYPE: bool

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> ctrl = ControlBuilder('CTRL') \
...     .any(['A', 'B']) \
...     .with_initial_state(True) \
...     .build()
Source code in src/pyfds/builders/control.py
def with_initial_state(self, state: bool) -> "ControlBuilder":
    """
    Set initial state of the control.

    Parameters
    ----------
    state : bool
        Initial state (True=on, False=off)

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> ctrl = ControlBuilder('CTRL') \\
    ...     .any(['A', 'B']) \\
    ...     .with_initial_state(True) \\
    ...     .build()
    """
    self._initial_state = state
    return self

with_latch

with_latch(latch)

Set whether control latches on activation.

When latched, the control stays active once triggered. When unlatched, it can toggle on and off.

PARAMETER DESCRIPTION
latch

Whether to latch on activation

TYPE: bool

RETURNS DESCRIPTION
ControlBuilder

Self for method chaining

Examples:

>>> # Latch stays on once activated
>>> ctrl = ControlBuilder('LATCH').any(['A', 'B']).with_latch(True).build()
>>> # Can toggle on/off
>>> ctrl = ControlBuilder('TOGGLE').any(['A', 'B']).with_latch(False).build()
Source code in src/pyfds/builders/control.py
def with_latch(self, latch: bool) -> "ControlBuilder":
    """
    Set whether control latches on activation.

    When latched, the control stays active once triggered.
    When unlatched, it can toggle on and off.

    Parameters
    ----------
    latch : bool
        Whether to latch on activation

    Returns
    -------
    ControlBuilder
        Self for method chaining

    Examples
    --------
    >>> # Latch stays on once activated
    >>> ctrl = ControlBuilder('LATCH').any(['A', 'B']).with_latch(True).build()

    >>> # Can toggle on/off
    >>> ctrl = ControlBuilder('TOGGLE').any(['A', 'B']).with_latch(False).build()
    """
    self._latch = latch
    return self

Overview

ControlBuilder creates control logic (&CTRL namelists) for device interactions and system automation.

Key Features

  • Logic Gates: ANY (OR), ALL (AND), ONLY (passthrough)
  • Time Delays: Delayed activation
  • State Management: Initial state, latch behavior
  • Special Functions: KILL, RESTART, CUSTOM

Quick Examples

ANY Logic (OR)

from pyfds.builders import ControlBuilder

# Activates if ANY detector triggers
alarm = (
    ControlBuilder('SMOKE_ALARM')
    .any(['SMOKE_DET_1', 'SMOKE_DET_2', 'SMOKE_DET_3'])
    .build()
)

ALL Logic (AND)

# Activates only if ALL detectors trigger
multi_zone = (
    ControlBuilder('MULTI_ZONE')
    .all(['ZONE_1_DET', 'ZONE_2_DET', 'ZONE_3_DET'])
    .build()
)

ONLY Logic (Passthrough)

# Simple passthrough of single device
passthrough = (
    ControlBuilder('PASSTHROUGH')
    .only('HEAT_DET_1')
    .build()
)

Time Delay

# Delayed sprinkler activation (10 second delay)
delayed_sprinkler = (
    ControlBuilder('DELAYED_SPRINKLER')
    .time_delay('HEAT_DETECTOR', delay=10.0)
    .build()
)

With Modifiers

# Complex control with modifiers
ctrl = (
    ControlBuilder('COMPLEX_CTRL')
    .any(['DET_1', 'DET_2', 'DET_3'])
    .with_delay(3.0)              # 3 second delay
    .with_initial_state(False)    # Start deactivated
    .with_latch(True)             # Stay on once activated
    .build()
)

Logic Functions

ANY (OR Gate)

Activates when any input device activates:

# Fire alarm if any smoke detector activates
alarm = ControlBuilder('ALARM').any(['SD_1', 'SD_2', 'SD_3']).build()

# Door opens if any occupant detector triggers
door = ControlBuilder('AUTO_DOOR').any(['OCCUPANT_1', 'OCCUPANT_2']).build()

ALL (AND Gate)

Activates only when all input devices are active:

# Requires all zones to be clear
safe = ControlBuilder('ALL_CLEAR').all(['ZONE_1_OK', 'ZONE_2_OK', 'ZONE_3_OK']).build()

# Multi-factor activation
secure = ControlBuilder('SECURE').all(['CARD_READER', 'PIN_OK', 'BIOMETRIC']).build()

ONLY (Passthrough)

Simple one-to-one mapping:

# Direct passthrough
ctrl = ControlBuilder('DIRECT').only('HEAT_DET').build()

Modifiers

Time Delay

Add delay to activation:

# 5 second delay
ctrl = (
    ControlBuilder('DELAYED')
    .any(['DET_1', 'DET_2'])
    .with_delay(5.0)
    .build()
)

# Time delay function (alternative)
ctrl = (
    ControlBuilder('DELAYED')
    .time_delay('HEAT_DET', delay=10.0)
    .build()
)

Initial State

Set the initial state (default is False):

# Start in activated state
ctrl = (
    ControlBuilder('INITIALLY_ON')
    .any(['DET_1', 'DET_2'])
    .with_initial_state(True)
    .build()
)

# Start deactivated (default)
ctrl = (
    ControlBuilder('INITIALLY_OFF')
    .any(['DET_1', 'DET_2'])
    .with_initial_state(False)
    .build()
)

Latch

Once activated, stay activated (ignore deactivation):

# Latching alarm (stays on)
alarm = (
    ControlBuilder('LATCH_ALARM')
    .any(['SMOKE_DET_1', 'SMOKE_DET_2'])
    .with_latch(True)
    .build()
)

# Non-latching (default - can turn off)
ctrl = (
    ControlBuilder('TOGGLE')
    .any(['DET_1', 'DET_2'])
    .with_latch(False)
    .build()
)

Special Functions

KILL

Stop the simulation when activated:

# Stop simulation at t=600s
kill = ControlBuilder('KILL').kill(on_t=600).build()

# Kill on high temperature
kill = ControlBuilder('KILL_ON_TEMP').kill(on_device='TEMP_SENSOR').build()

RESTART

Restart simulation from t=0:

# Restart at t=300s
restart = ControlBuilder('RESTART').restart(on_t=300).build()

# Restart on device trigger
restart = ControlBuilder('RESTART_CTRL').restart(on_device='TRIGGER').build()

CUSTOM

Use custom ramp function:

# Custom control based on ramp
custom = (
    ControlBuilder('CUSTOM_CTRL')
    .custom(ramp_id='MY_RAMP')
    .build()
)

Usage in Simulations

Smoke Alarm System

from pyfds import Simulation
from pyfds.builders import ControlBuilder, PropBuilder
from pyfds.core.geometry import Point3D

sim = Simulation('alarm_system')

# Add smoke detectors
for i in range(1, 4):
    sim.add(Device(
        id=f'SMOKE_DET_{i}',
        prop_id='SMOKE_DETECTOR',
        xyz=Point3D.of(i*2, 5, 2.5)
    )

# Add detector properties
smoke_prop = PropBuilder.smoke_detector(id='SMOKE_DETECTOR')
sim.add_prop(smoke_prop)

# ANY logic alarm (activates if any detector triggers)
alarm = (
    ControlBuilder('BUILDING_ALARM')
    .any(['SMOKE_DET_1', 'SMOKE_DET_2', 'SMOKE_DET_3'])
    .with_latch(True)  # Stay on once activated
    .build()
)
sim.add_ctrl(alarm)

Delayed Sprinkler Activation

# Heat detector
sim.add(Device(id='HEAT_DET', prop_id='HEAT_DETECTOR', xyz=Point3D.of(5, 5, 2.5)))

heat_prop = PropBuilder.heat_detector(id='HEAT_DETECTOR', activation_temp=74)
sim.add_prop(heat_prop)

# Delayed sprinkler activation (10s delay)
sprinkler_ctrl = (
    ControlBuilder('DELAYED_SPRINKLER')
    .time_delay('HEAT_DET', delay=10.0)
    .with_latch(True)
    .build()
)
sim.add_ctrl(sprinkler_ctrl)

# Sprinkler device controlled by ctrl
sim.add(Device(
    id='SPRINKLER',
    prop_id='SPRINKLER_QR',
    xyz=Point3D.of(5, 5, 3),
    ctrl_id='DELAYED_SPRINKLER'
)

Multi-Zone System

# Detectors in each zone
for zone in range(1, 4):
    sim.add(Device(
        id=f'ZONE_{zone}_DET',
        prop_id='SMOKE_DETECTOR',
        xyz=Point3D.of(zone*3, 5, 2.5)
    )

# ALL logic - requires all zones
all_zones = (
    ControlBuilder('ALL_ZONES')
    .all(['ZONE_1_DET', 'ZONE_2_DET', 'ZONE_3_DET'])
    .build()
)
sim.add_ctrl(all_zones)

# ANY logic - any zone triggers alarm
any_zone = (
    ControlBuilder('ANY_ZONE')
    .any(['ZONE_1_DET', 'ZONE_2_DET', 'ZONE_3_DET'])
    .with_latch(True)
    .build()
)
sim.add_ctrl(any_zone)

HVAC Control

# Temperature-based HVAC control
sim.add(Device(id='TEMP_SENSOR', quantity='TEMPERATURE', xyz=Point3D.of(5, 5, 2)))

# Turn on HVAC when temp > threshold
hvac_on = (
    ControlBuilder('HVAC_ON')
    .only('TEMP_SENSOR')
    .build()
)
sim.add_ctrl(hvac_on)

# HVAC vent controlled by temperature
supply = VentBuilder.hvac_supply(
    xb=Bounds3D.of(5, 6, 5, 6, 3, 3),
    volume_flow=0.5,
    id='SUPPLY'
)
supply.ctrl_id = 'HVAC_ON'
sim.add_vent(supply)

Safety Shutdown

# Kill simulation on dangerous conditions
kill_high_temp = (
    ControlBuilder('KILL_HIGH_TEMP')
    .kill(on_device='MAX_TEMP_SENSOR')
    .build()
)
sim.add_ctrl(kill_high_temp)

# Kill at specific time
kill_timeout = (
    ControlBuilder('KILL_TIMEOUT')
    .kill(on_t=600)
    .build()
)
sim.add_ctrl(kill_timeout)

Control Logic Truth Tables

ANY (OR)

Input 1 Input 2 Output
False False False
False True True
True False True
True True True

ALL (AND)

Input 1 Input 2 Output
False False False
False True False
True False False
True True True

Best Practices

Use Descriptive IDs

# Good
alarm = ControlBuilder('SMOKE_ALARM_ZONE_1').any(['SD_1', 'SD_2']).build()

# Avoid
alarm = ControlBuilder('CTRL_1').any(['SD_1', 'SD_2']).build()

Latch Critical Alarms

# Good: Latch fire alarms
alarm = (
    ControlBuilder('FIRE_ALARM')
    .any(['SMOKE_DET_1', 'SMOKE_DET_2'])
    .with_latch(True)  # Stay on
    .build()
)

# Avoid: Non-latching for safety-critical
alarm = ControlBuilder('FIRE_ALARM').any(['SD_1', 'SD_2']).build()  # Can turn off

Add Appropriate Delays

# Good: Delay to prevent false alarms
ctrl = (
    ControlBuilder('DELAYED_ALARM')
    .any(['SMOKE_DET_1', 'SMOKE_DET_2'])
    .with_delay(3.0)  # 3s to confirm
    .build()
)

# Avoid: No delay for fluctuating signals
ctrl = ControlBuilder('INSTANT').any(['NOISY_SENSOR']).build()

Use ALL for Safety Interlocks

# Good: Require multiple conditions for safety
safe_to_start = (
    ControlBuilder('SAFE_START')
    .all(['DOOR_CLOSED', 'POWER_OK', 'COOLANT_OK'])
    .build()
)

Validation

The builder validates:

  • Function specified: Must call one of: .any(), .all(), .only(), .time_delay(), .kill(), .restart(), .custom()
  • Valid device IDs: Device IDs should exist (warning only)
  • No conflicting options: Can't combine incompatible options

See Also