Skip to content

Running Simulations

The simulate() function executes EnergyPlus as a subprocess and returns a structured SimulationResult with access to all output files.

Basic Usage

from idfkit import load_idf
from idfkit.simulation import simulate

model = load_idf("building.idf")
result = simulate(model, "weather.epw")

print(f"Success: {result.success}")
print(f"Runtime: {result.runtime_seconds:.1f}s")
print(f"Output directory: {result.run_dir}")

Simulation Modes

Design-Day Only

Fast simulation using only design day conditions:

result = simulate(model, weather, design_day=True)

Annual Simulation

Full-year simulation:

result = simulate(model, weather, annual=True)

Default Mode

Without flags, EnergyPlus uses whatever run periods are defined in the model:

result = simulate(model, weather)  # Uses model's RunPeriod objects

Preprocessing

Some EnergyPlus models contain high-level template objects that must be expanded into their low-level equivalents before simulation. idfkit provides standalone preprocessing functions for this.

Expanding HVAC Templates

HVACTemplate:* objects are shorthand for complex HVAC systems. expand_objects() converts them into their fully specified equivalents:

from idfkit.simulation import expand_objects

expanded = expand_objects(model)
# HVACTemplate:Zone:IdealLoadsAirSystem → ZoneHVAC:IdealLoadsAirSystem + ...

Note

simulate() runs ExpandObjects automatically when expand_objects=True (the default). Call expand_objects() directly only when you need to inspect or modify the expanded model before simulation.

Ground Heat Transfer (Slab & Basement)

Models with GroundHeatTransfer:Slab:* or GroundHeatTransfer:Basement:* objects need the Slab or Basement preprocessor to compute ground temperatures.

Note

simulate() automatically runs the Slab and/or Basement preprocessors when expand_objects=True (the default) and the model contains the corresponding ground heat-transfer objects. In most cases you do not need to call these functions yourself.

Preprocessor Timeout

Each preprocessor stage (ExpandObjects, Slab, Basement) gets its own subprocess timeout, separate from the EnergyPlus timeout. Override it per call with preprocessor_timeout:

simulate(model, weather, preprocessor_timeout=600.0)  # 10 min per stage

Or set a process-wide default with the IDFKIT_PREPROCESSOR_TIMEOUT environment variable — useful for slow shared hardware (raise it) or fast CI where you want to catch hangs quickly (lower it):

export IDFKIT_PREPROCESSOR_TIMEOUT=600   # slow shared hardware
export IDFKIT_PREPROCESSOR_TIMEOUT=30    # fail fast in CI

The default is 120 seconds per subprocess. timeout and preprocessor_timeout are independent budgets — there is no shared wall-clock cap across the pipeline, so a long preprocessor_timeout will not trip the simulation's timeout and vice versa.

For cases where you need to inspect or modify the preprocessed model before simulation, standalone functions are available:

from idfkit.simulation import run_slab_preprocessor, run_basement_preprocessor

# Slab-on-grade foundation
expanded = run_slab_preprocessor(model, weather="weather.epw")

# Basement walls and floors
expanded = run_basement_preprocessor(model, weather="weather.epw")

Each function runs ExpandObjects first (to extract the ground heat-transfer input), then the Fortran solver, and returns a new IDFDocument with the computed temperature schedules appended.

All preprocessing functions raise ExpandObjectsError on failure, with structured preprocessor, exit_code, and stderr fields for programmatic error handling.

See the Preprocessing API reference for full details.

Function Signature

def simulate(
    model: IDFDocument,
    weather: str | Path,
    *,
    output_dir: str | Path | None = None,
    energyplus: EnergyPlusConfig | None = None,
    expand_objects: bool = True,
    annual: bool = False,
    design_day: bool = False,
    output_prefix: str = "eplus",
    output_suffix: Literal["C", "L", "D"] = "C",
    readvars: bool = False,
    timeout: float = 3600.0,
    preprocessor_timeout: float | None = None,
    extra_args: list[str] | None = None,
    cache: SimulationCache | None = None,
    fs: FileSystem | None = None,
    on_progress: Callable[[SimulationProgress], Any] | Literal["tqdm"] | None = None,
    auto_migrate: bool = False,
) -> SimulationResult:

Parameters

Required

Parameter Description
model The EnergyPlus model to simulate
weather Path to the weather file (.epw)

Optional

Parameter Default Description
output_dir Auto temp Directory for output files
energyplus Auto-detect Pre-configured EnergyPlus installation
expand_objects True Run ExpandObjects (and Slab/Basement if needed) before simulation
annual False Run annual simulation (-a flag)
design_day False Run design-day-only (-D flag)
output_prefix "eplus" Prefix for output files
output_suffix "C" Output naming style (C/L/D)
readvars False Run ReadVarsESO after simulation
timeout 3600.0 Maximum runtime in seconds (main EnergyPlus subprocess only)
preprocessor_timeout None Per-subprocess timeout for ExpandObjects / Slab / Basement. None reads IDFKIT_PREPROCESSOR_TIMEOUT, defaulting to 120 s
extra_args None Additional command-line arguments
cache None Simulation cache for result reuse
fs None File system backend for cloud storage
on_progress None Callback or "tqdm" for real-time progress updates
auto_migrate False Forward-migrate the model when its version differs from the installed EnergyPlus (see Migrating Versions)

EnergyPlus Discovery

By default, simulate() auto-discovers EnergyPlus:

# Auto-discovery
result = simulate(model, weather)

# Explicit path
from idfkit.simulation import find_energyplus

config = find_energyplus("/custom/path/EnergyPlus-24-1-0")
result = simulate(model, weather, energyplus=config)

# Environment variable
# Set ENERGYPLUS_DIR=/path/to/EnergyPlus before running
result = simulate(model, weather)

Discovery priority:

  1. Explicit energyplus parameter
  2. ENERGYPLUS_DIR environment variable
  3. System PATH
  4. Platform default directories

Output Directory

Automatic Temporary Directory

By default, outputs go to an auto-generated temp directory:

result = simulate(model, weather)
print(result.run_dir)  # e.g., /tmp/idfkit_sim_abc123/

Explicit Directory

Specify where to store outputs:

result = simulate(model, weather, output_dir="./sim_output")

The directory is created if it doesn't exist.

Error Handling

Simulation Errors

from idfkit.exceptions import SimulationError

try:
    result = simulate(model, weather)
except SimulationError as e:
    print(f"Simulation failed: {e}")
    print(f"Exit code: {e.exit_code}")
    print(f"Stderr: {e.stderr}")

Timeout

try:
    result = simulate(model, weather, timeout=60.0)  # 1 minute max
except SimulationError as e:
    if e.exit_code is None:
        print("Simulation timed out")

Checking Success

result = simulate(model, weather)

if not result.success:
    print(f"Exit code: {result.exit_code}")
    print(f"Stderr: {result.stderr}")
    for err in result.errors.fatal:
        print(f"Error: {err.message}")

Model Safety

simulate() copies your model before running — the original is never modified:

model = load_idf("building.idf")
original_count = len(model)

result = simulate(model, weather)

# Model unchanged
assert len(model) == original_count
assert "Output:SQLite" not in model

Command-Line Options

Output Suffix Modes

Value Description
"C" Combined table files (default)
"L" Legacy separate table files
"D" Timestamped separate files
result = simulate(model, weather, output_suffix="L")

Extra Arguments

Pass additional EnergyPlus flags:

result = simulate(
    model,
    weather,
    extra_args=["--convert-only"],  # Just convert, don't simulate
)

Cloud Storage

For remote storage backends (S3, etc.):

from idfkit.simulation import S3FileSystem

fs = S3FileSystem(bucket="my-bucket", prefix="runs/")
result = simulate(
    model,
    weather,
    output_dir="run-001",  # Required with fs
    fs=fs,
)

See Cloud & Remote Storage for details.

Caching

Enable content-addressed caching to avoid redundant simulations:

from idfkit.simulation import SimulationCache

cache = SimulationCache()

# First run: executes simulation
result1 = simulate(model, weather, cache=cache)

# Second run: instant cache hit
result2 = simulate(model, weather, cache=cache)

See Caching for details.

Version Migration

When model.version differs from the installed EnergyPlus, simulate() raises VersionMismatchError by default. Pass auto_migrate=True to forward-migrate the model transparently before the run; the resulting MigrationReport is attached to SimulationResult.migration_report. See Migrating Versions for the full workflow.

See Also