Skip to content

How to access simulation results

The SimulationResult class provides structured access to all EnergyPlus output files with lazy loading for efficient memory usage.

Python only, permanently

Running a locally installed EnergyPlus and reading its results belongs to Python alone. JavaScript is not waiting on a port and will not gain a counterpart: this is a permanent boundary, so no issue tracks it, and moving the capability out of that state takes a constitutional amendment rather than a ledger edit.

Requires an EnergyPlus installation on the machine and a subprocess to drive it, then reads the eplusout files that run leaves on disk. Neither the installation nor the subprocess is available in a browser, which is the runtime the JavaScript library targets. JavaScript reaches EnergyPlus by the other mechanism instead: see browser-simulation, which is not a workaround for this entry but a different capability.

The full entry, including the vocabulary this capability owns, is on the capability parity page.

SimulationResult Overview

from idfkit.simulation import simulate

result = simulate(model, weather)

# Basic info
print(f"Success: {result.success}")
print(f"Exit code: {result.exit_code}")
print(f"Runtime: {result.runtime_seconds:.1f}s")
print(f"Output dir: {result.run_dir}")

# Parsed outputs (lazy-loaded)
result.errors  # ErrorReport from .err file
result.sql  # SQLResult from .sql database
result.variables  # OutputVariableIndex from .rdd/.mdd
result.csv  # CSVResult from .csv file
result.html  # HTMLResult from HTML tabular output

Output File Paths

Access paths to specific output files:

result.sql_path  # Path to .sql database
result.err_path  # Path to .err file
result.eso_path  # Path to .eso file
result.csv_path  # Path to .csv file
result.html_path  # Path to HTML table file
result.rdd_path  # Path to .rdd file
result.mdd_path  # Path to .mdd file

Each returns None if the file wasn't produced.

Error Report

Parse warnings and errors from the .err file:

errors = result.errors

# Summary
print(errors.summary())

# Check for fatal errors
if errors.has_fatal:
    for err in errors.fatal:
        print(f"FATAL: {err.message}")

# Check for severe errors
if errors.has_severe:
    for err in errors.severe:
        print(f"SEVERE: {err.message}")

# All warnings
for warn in errors.warnings:
    print(f"Warning: {warn.message}")

# Counts
print(f"Fatal: {errors.fatal_count}")
print(f"Severe: {errors.severe_count}")
print(f"Warnings: {errors.warning_count}")

See how to handle simulation errors for detailed error parsing.

SQL Database

Query time-series and tabular data from the SQLite output:

sql = result.sql
if sql is not None:
    # Time-series data
    ts = sql.get_timeseries(
        variable_name="Zone Mean Air Temperature",
        key_value="THERMAL ZONE 1",
    )
    print(f"Max: {max(ts.values):.1f}°C")

    # Tabular reports
    rows = sql.get_tabular_data(report_name="AnnualBuildingUtilityPerformanceSummary")

See how to query simulation SQL output for detailed SQL parsing.

Output Variables

Discover available output variables from .rdd/.mdd files:

variables = result.variables
if variables is not None:
    # Search for variables
    matches = variables.search("Temperature")
    for var in matches:
        print(f"{var.name} [{var.units}]")

    # Add outputs to model for next run
    variables.add_all_to_model(model, filter_pattern="Zone.*Temperature")

See how to discover output variables for variable discovery.

CSV Output

Parse CSV time-series output:

csv_result = result.csv
if csv_result is not None:
    # List all columns
    for col in csv_result.columns:
        print(f"{col.variable_name} ({col.key_value}) [{col.units}]")

    # Get data for a specific column
    column = csv_result.get_column("Zone Mean Air Temperature", "THERMAL ZONE 1")
    if column is not None:
        values = column.values

HTML Tabular Output

Parse the HTML tabular summary file (eplustbl.htm) that EnergyPlus produces alongside every simulation:

html = result.html
if html is not None:
    # Iterate all tables
    for table in html:
        print(f"{table.title}: {len(table.rows)} rows")

    # eppy-compatible (title, rows) pairs
    for title, rows in html.titletable():
        print(title)

    # Look up a table by title (case-insensitive substring match)
    table = html.tablebyname("Site and Source Energy")
    if table:
        data = table.to_dict()  # {row_key: {col_header: value}}
        print(data)

    # Get all tables from a specific report
    annual = html.tablesbyreport("Annual Building Utility Performance Summary")

    # Access by index
    first = html.tablebyindex(0)

For the attributes and helper methods of each HTMLTable, see the HTMLTable reference.

You can also parse a standalone HTML file without a full simulation:

from idfkit.simulation.parsers.html import HTMLResult

html = HTMLResult.from_file("eplustbl.htm")
html = HTMLResult.from_string(html_string)

This replaces eppy's readhtml module.

Lazy Loading

Output files are parsed only when accessed:

result = simulate(model, weather)

# Nothing parsed yet - only metadata stored

result.errors  # NOW parses .err file
result.sql  # NOW opens SQLite database
result.variables  # NOW parses .rdd/.mdd files
result.html  # NOW parses HTML tabular output

This keeps memory usage low, especially for batch simulations where you might only need specific outputs.

Releasing File Handles

Accessing result.sql opens a SQLite connection on first use and caches it. That connection holds an OS-level file handle on eplus.sql. On Windows, the handle locks the file, so deleting the run directory while the result is alive fails with PermissionError [WinError 32] — typically when the run directory lives inside a tempfile.TemporaryDirectory or is removed with shutil.rmtree. POSIX systems don't lock on open, so this only affects Windows.

SimulationResult is a context manager: exiting the with block calls close(), which releases the connection. Close the result before the run directory is removed:

# The result context exits first - closing the SQLite connection - then
# TemporaryDirectory cleans up, so rmtree succeeds even on Windows.
with tempfile.TemporaryDirectory() as tmp, simulate(model, weather, output_dir=Path(tmp) / "run") as result:
    temps = result.sql.get_timeseries("Zone Mean Air Temperature", "ZONE 1")

# Equivalent without a context manager:
result = simulate(model, weather)
try:
    temps = result.sql.get_timeseries("Zone Mean Air Temperature", "ZONE 1")
finally:
    result.close()  # idempotent; reopens lazily if you touch result.sql again

close() is idempotent and resets the cached connection, so touching result.sql afterwards transparently reopens it. The other accessors (errors, csv, eso, html, variables) read their files eagerly and hold no handles, so they need no cleanup.

Reconstructing from Directory

Inspect results from a previous simulation:

from idfkit.simulation import SimulationResult

# From a local directory
result = SimulationResult.from_directory("/path/to/sim_output")

# From a cloud storage location
from idfkit.simulation import S3FileSystem

fs = S3FileSystem(bucket="my-bucket")
result = SimulationResult.from_directory("runs/run-001", fs=fs)

# Query data
ts = result.sql.get_timeseries("Zone Mean Air Temperature", "ZONE 1")

Full attribute and property reference

SimulationResult exposes many more attributes, lazy properties, and output file paths than the recipes above use. For the complete, always-current list — generated from the source — see the SimulationResult reference.

See Also