09 — Run an AMY in EnergyPlus

Learning objectives: hand a generated AMY to a standalone simulation engine, retain its receipt, inspect strict engine diagnostics, and plot temperatures and ideal heating/cooling loads.

Prerequisites: run notebook 00 or supply a local EPW with SMHI2EPW_EPW_PATH; install the tutorial dependencies and EnergyPlus 24.2.0 build 94a887817b. Set ENERGYPLUS_EXE when the executable is outside PATH. Network: none during this lesson. Expected runtime: about one to three minutes for the small annual model. Outputs go to examples/output/energyplus/ when launched from the repository or output/energyplus/ from another working directory.

The shared model and runner live in examples/support/. Open this notebook from the checkout; set SMHI2EPW_EXAMPLES_DIR to that checkout’s examples directory when running elsewhere. This lesson does not require epsm_core.

Install the pinned engine once

Use the official EnergyPlus 24.2.0 bug-fix release for your platform, including the adjacent IDD and libraries. Both original and corrected builds report version 24.2.0; this lesson requires build 94a887817b. The repository’s scripts/install_energyplus.sh verifies a fixed SHA-256 for the Ubuntu 24.04 x86_64 archive. Other platforms should use the matching official archive and checksum list. There is no automatic engine download in this notebook.

[ ]:
import hashlib
import json
import os
import sys
from datetime import datetime, timezone
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd

from smhi2epw import read_epw

candidates = [
    Path(os.environ["SMHI2EPW_EXAMPLES_DIR"])
    if os.environ.get("SMHI2EPW_EXAMPLES_DIR")
    else None,
    Path("examples"),
    Path("."),
]
EXAMPLES_DIR = next(
    (
        candidate.resolve()
        for candidate in candidates
        if candidate is not None and (candidate / "support/energyplus.py").is_file()
    ),
    None,
)
if EXAMPLES_DIR is None:
    raise FileNotFoundError(
        "Locate the checkout with SMHI2EPW_EXAMPLES_DIR pointing to its examples directory."
    )
sys.path.insert(0, str(EXAMPLES_DIR / "support"))
from energyplus import find_energyplus, run_energyplus  # noqa: E402

OUTPUT_DIR = Path("examples/output") if Path("examples").is_dir() else Path("output")
EPW_PATH = Path(os.environ.get("SMHI2EPW_EPW_PATH", OUTPUT_DIR / "gothenburg_2023.epw"))
if not EPW_PATH.is_file():
    raise FileNotFoundError("Run notebook 00 or set SMHI2EPW_EPW_PATH to a local EPW.")
ENGINE = find_energyplus()
print("EnergyPlus executable:", ENGINE)
print("Weather:", EPW_PATH.resolve())

Review weather provenance before simulating

A complete EPW can contain reconstructed weather. Examine source shares and warnings before attributing a simulated peak to a measured event. The adjacent receipt checksum must describe this EPW; a custom receipt path may need to be checked separately.

[ ]:
weather = read_epw(EPW_PATH)
print(weather.attrs["location"])
receipt_path = Path(str(EPW_PATH) + ".json")
if receipt_path.is_file():
    receipt = json.loads(receipt_path.read_text(encoding="utf-8"))
    if receipt["epw_sha256"] != hashlib.sha256(EPW_PATH.read_bytes()).hexdigest():
        raise ValueError("Weather receipt checksum does not match the selected EPW.")
    report = receipt["result"]["report"]
    display(
        pd.Series(
            {
                "weather_classification": report["weather_classification"],
                "required_reconstructed_fraction": report[
                    "required_reconstructed_fraction"
                ],
                "warning_count": len(report["warnings"]),
            }
        )
    )
    display(pd.DataFrame(report["source_fractions"]).fillna(0))
else:
    print(
        "No adjacent receipt; review the weather policy and configured provenance path."
    )

Run the shared single-zone model

The model is a teaching fixture with explicit materials, a south-facing window, fixed thermostat schedules, and ideal loads. It has no detailed HVAC plant or sizing calculation. The runner adjusts the actual-year run period to the EPW, disables daylight saving, and requests hourly SQLite results. It requires a successful complete calendar and rejects severe errors, fatal errors, and every unaccepted warning; exceptions must be narrowly documented and bounded.

Each run uses its own output directory. Preserve the model, EPW, weather receipt, engine version, and error/SQL files alongside any analysis.

[ ]:
simulation = run_energyplus(
    EPW_PATH,
    OUTPUT_DIR / "energyplus" / datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ"),
    executable=ENGINE,
)
hourly = simulation["hourly"]
summary = simulation["summary"]
print("Engine:", simulation["engine_version"])
print("Run directory:", simulation["output_dir"])
display(pd.Series(summary))
hourly.head()

Inspect the response

Hourly timestamps mark interval ends in Local Standard Time. Assign monthly energy by the interval start, so a month’s final hour stays in that month. The engine interpolates weather internally, so its reported temperatures need not equal every raw EPW sample. Monthly ideal-load energy is delivered thermal energy in kWh; it is not electricity consumption and does not include real equipment efficiencies or distribution losses.

[ ]:
fig, axes = plt.subplots(2, 1, figsize=(11, 7))
week = hourly.iloc[: 24 * 7]
axes[0].plot(week.index, week.dry_bulb, label="Outdoor air")
axes[0].plot(week.index, week.zone_temperature, label="Zone air")
axes[0].set(ylabel="Temperature (°C)", xlabel="Hour-ending Local Standard Time")
axes[0].legend()
interval_start = hourly.index - pd.Timedelta(hours=1)
monthly = hourly[["heating_kwh", "cooling_kwh"]].groupby(interval_start.month).sum()
monthly.plot.bar(ax=axes[1], color=["#D55E00", "#0072B2"])
axes[1].set(ylabel="Delivered ideal-load energy (kWh)", xlabel="Month")
fig.tight_layout()

Key takeaways and exercise

Engine acceptance checks that the weather can drive this model through the intended calendar. It does not independently validate local meteorology, reconstructed event intensity, a building archetype, or calibration accuracy. Missing precipitation, snow, and illuminance constrain other applications. This fixture intentionally avoids relying on those fields.

Try it: run the same model with another AMY from notebook 03, keeping the model and engine fixed. Compare monthly loads and source shares. Explain why a difference in ideal cooling energy alone cannot establish a change in purchased electricity or equipment size.