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."
)
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.