HVAC: 1R1C
This notebook demonstrates how to use a simple Resistance-Capacitance (RC) thermal model to simulate indoor temperatures and HVAC loads for multiple buildings.
Imports
Import required libraries and set visualization defaults.
import os
import matplotlib.pyplot as plt
import pandas as pd
from entise.constants import Columns as Cols
from entise.core.generator import Generator as TSGen
%matplotlib inline
Load Data
We load building parameters from objects.csv and simulation data from the data folder.
cwd = '.' # Current working directory: change if your kernel is not running in the same folder
objects = pd.read_csv(os.path.join(cwd, 'objects.csv'))
data = {}
common_data_folder = "../common_data"
for file in os.listdir(os.path.join(cwd, common_data_folder)):
if file.endswith(".csv"):
name = file.split(".")[0]
data[name] = pd.read_csv(os.path.join(os.path.join(cwd, common_data_folder, file)), parse_dates=True)
data_folder = 'data'
for file in os.listdir(os.path.join(cwd, data_folder)):
if file.endswith('.csv'):
name = file.split('.')[0]
data[name] = pd.read_csv(os.path.join(os.path.join(cwd, data_folder, file)), parse_dates=True)
print('Loaded data keys:', list(data.keys()))
print(objects)
Loaded data keys: ['weather', 'internal_gains', 'ventilation', 'windows']
Instantiate and Configure Model
Initialize the time series generator and configure it with building objects.
gen = TSGen()
gen.add_objects(objects)
Run the Simulation
Generate sequential HVAC load and indoor temperature time series for each building.
summary, df = gen.generate(data, workers=1)
100%|██████████| 9/9 [00:04<00:00, 1.95it/s]
Results Summary
Below is a summary of the annual heating and cooling demands (in kWh/a) and peak loads (kW).
print("Summary:")
summary_kwh = (summary / 1000).round(0).astype(int)
summary_kwh.rename(columns=lambda x: x.replace("[W]", "[kW]").replace("[Wh]", "[kWh]"), inplace=True)
print(summary_kwh.to_string())
Summary:
heating:demand[kWh] heating:load_max[kW] cooling:demand[kWh] cooling:load_max[kW]
367791 279630 113 90335 129
31991680 451645 170 20046 103
31991682 23989 10 8615 12
31991685 144780 61 15980 46
31991686 34961 14 6268 12
31991687 61066 22 4169 14
31991688 81598 37 3688 23
31991690 35605 17 4568 16
31991691 30866 11 563 6
Visualization of Results
Visualize indoor temperature, heating, and cooling loads for a selected building.
# Select building ID to visualize
building_id = summary.index[0] # Change index to visualize different buildings
building_data = df[building_id]['hvac']
# Figure 1: Indoor & Outdoor Temperature and Solar Radiation (GHI)
fig, ax1 = plt.subplots(figsize=(15, 6))
# Solar radiation plot (GHI) with separate y-axis
ax2 = ax1.twinx()
ax2.plot(
building_data.index, data["weather"][Cols.SOLAR_GHI],
label="Solar Radiation (GHI)", color="tab:orange", alpha=0.3
)
ax2.set_ylabel("Solar Radiation (W/m²)")
ax2.legend(loc="upper right")
ax2.set_ylim(-250, 1000)
# Temperature plot
ax1.plot(building_data.index, data["weather"][f"{Cols.TEMP_AIR}@2m"], label="Outdoor Temp", color="tab:cyan", alpha=0.7)
ax1.plot(building_data.index, building_data[Cols.TEMP_IN], label="Indoor Temp", color="tab:blue")
ax1.set_ylabel("Temperature (°C)")
ax1.set_title(f"Building ID: {building_id} - Temperatures and Solar Radiation")
ax1.legend(loc="upper left")
ax1.grid(True)
ax1.set_ylim(-10, 40)
ax1.set_zorder(ax2.get_zorder() + 1)
ax1.patch.set_visible(False) # required to see through ax1 to ax2
plt.tight_layout()
plt.show()
# Figure 2: Heating and Cooling Loads
fig, ax = plt.subplots(figsize=(14, 5))
heating_MWh = summary.loc[building_id, "heating:demand[Wh]"] / 1e6
cooling_MWh = summary.loc[building_id, "cooling:demand[Wh]"] / 1e6
(line1,) = ax.plot(
building_data.index,
building_data["heating:load[W]"],
label=f"Heating: {heating_MWh:.1f} MWh",
color="tab:red",
alpha=0.8,
)
(line2,) = ax.plot(
building_data.index,
building_data["cooling:load[W]"],
label=f"Cooling: {cooling_MWh:.1f} MWh",
color="tab:cyan",
alpha=0.8,
)
# Create the combined legend in the upper left corner
ax.set_ylabel("Load (W)")
ax.set_title(f"Building ID: {building_id} - HVAC Loads")
ax.legend()
ax.grid(True)
plt.tight_layout()
plt.show()
# Figure 3: Outdoor Temperature with Heating & Cooling Loads
fig, ax1 = plt.subplots(figsize=(15, 6))
# Plot outdoor temperature on left y-axis
air_temp = data["weather"][f"{Cols.TEMP_AIR}@2m"]
ax1.plot(building_data.index, air_temp
, label="Outdoor Temp", color="tab:cyan", alpha=0.7)
ax1.set_ylabel("Outdoor Temp (°C)")
ax1.set_ylim(air_temp.min().round() - 2, air_temp.max().round() + 2)
# Create second y-axis for loads
ax2 = ax1.twinx()
ax2.plot(building_data.index, building_data["heating:load[W]"], label="Heating Load", color="tab:red", alpha=0.8)
ax2.plot(building_data.index, building_data["cooling:load[W]"], label="Cooling Load", color="tab:blue", alpha=0.8)
ax2.set_ylabel("HVAC Load (W)")
ax2.set_ylim(
min(building_data["heating:load[W]"].min(), building_data["cooling:load[W]"].min()) * 1.1,
max(building_data["heating:load[W]"].max(), building_data["cooling:load[W]"].max()) * 1.1,
)
# Combine legends from both axes
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc="upper left")
ax1.set_title(f"Building ID: {building_id} - Outdoor Temp & HVAC Loads")
ax1.grid(True)
fig.tight_layout()
plt.show()
Next Steps
You can further explore:
Adjusting building parameters in
objects.csvIncorporating or excluding additional data (e.g., internal gains, solar gains)
Investigate how different ventilation strategies impact a buildings energy demand (ventilation)
Automating analysis for larger building datasets