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¶
- How to query simulation SQL output — Detailed SQL database access
- How to discover output variables — Finding available variables
- How to handle simulation errors — Parsing error reports