CTF-TFM / test.py
mabuseif's picture
Update test.py
a7d4c0c verified
"""
Main HVAC Calculator Script with Streamlit Interface
Developed by: Dr Majed Abuseif, Deakin University
© 2025
"""
import streamlit as st
import pandas as pd
import plotly.graph_objects as go
import logging
from enum import Enum
from typing import Dict, List
from data.material_library import GlazingMaterial
from data.calculation import TFMCalculations
from utils.solar import SolarCalculations
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class ComponentType(Enum):
WALL = "Wall"
ROOF = "Roof"
FLOOR = "Floor"
WINDOW = "Window"
DOOR = "Door"
SKYLIGHT = "Skylight"
class HVACCalculatorApp:
def __init__(self):
self.tfm = TFMCalculations()
# Initialize session state
if 'window_action' not in st.session_state:
st.session_state.glazing_action = {"action": None, "id": None}
if 'components' not in st.session_state:
st.session_state.components = {
"windows": [
GlazingMaterial(
id="window_1",
name="Single Clear 6mm",
component_type=ComponentType.WINDOW,
area=10.0, # m²
u_value=5.8, # W/m²·K
shgc=0.7,
facade="A"
)
],
"skylights": [],
"walls": [],
"roofs": [],
"floors": [],
"doors": [],
"_building_info": {
"A": {"azimuth": 0.0, "tilt_angle": 0.0}
}
}
if 'climate_data' not in st.session_state:
st.session_state.climate_data = {
"hourly_data": [
{
"month": 7,
"day": 1,
"hour": h,
"dry_bulb": 30.0,
"global_horizontal_radiation": 800.0 if 8 <= h <= 16 else 0.0, # W/m²
"direct_normal_radiation": 560.0 if 8 <= h <= 16 else 0.0,
"diffuse_horizontal_radiation": 240.0 if 8 <= h <= 16 else 0.0
} for h in range(24)
],
"typical_extreme_periods": {
"summer_extreme": {"start": {"month": 7, "day": 1}, "end": {"month": 7, "day": 7}},
"summer_typical": {"start": {"month": 7, "day": 1}, "end": {"month": 7, "day": 7}},
"winter_extreme": {"start": {"month": 1, "day": 1}, "end": {"month": 1, "day": 7}},
"winter_typical": {"start": {"month": 1, "day": 1}, "end": {"month": 1, "day": 7}}
}
}
if 'building_info' not in st.session_state:
st.session_state.building_info = {
"latitude": 37.8,
"longitude": -122.4,
"timezone": -8.0,
"ground_reflectivity": 0.2,
"floor_area": 100.0,
"indoor_design_temp": 24.0,
"indoor_design_rh": 50.0
}
if 'internal_loads' not in st.session_state:
st.session_state.internal_loads = {
"people": [{"num_people": 10, "activity_data": {"sensible_min_w": 70, "sensible_max_w": 100, "latent_min_w": 40, "latent_max_w": 60}, "diversity_factor": 1.0}],
"lighting": [{"lpd": 10.0, "operating_hours": 10}],
"equipment": {"total_power_density": 5.0},
"ventilation": {"space_rate": 0.3, "people_rate": 2.5},
"infiltration": {"method": "ACH", "settings": {"rate": 0.5}}
}
if 'calculation_results' not in st.session_state:
st.session_state.calculation_results = {"cooling": [], "heating": []}
def display_calculation_results(self):
st.title("Calculation Results")
st.write("Configure simulation settings and view cooling and heating load calculations based on ASHRAE CTF/TFM methods.")
if not st.session_state.get("climate_data"):
st.error("Please upload climate data in the Climate Data section.")
st.button("Go to Climate Data", key="results_to_climate",
on_click=lambda: setattr(st.session_state, "page", "Climate Data and Design Requirements"))
return
if not any(st.session_state.components.values()):
st.error("Please define building components in the Building Components section.")
st.button("Go to Building Components", key="results_to_components",
on_click=lambda: setattr(st.session_state, "page", "Building Components"))
return
# Debug SolarCalculations for 4 hours
st.subheader("Debugging Solar Calculations")
debug_hours = [8, 9, 10, 11] # July 1, 8 AM–12 PM
debug_data = [d for d in st.session_state.climate_data["hourly_data"] if d["month"] == 7 and d["day"] == 1 and d["hour"] in debug_hours]
if not debug_data:
st.warning("No climate data for debug hours (July 1, 8 AM–12 PM).")
else:
logger.info("Debugging SolarCalculations Inputs:")
logger.info(f"Components (windows): {[{'id': c.id, 'area': c.area, 'shgc': c.shgc, 'facade': c.facade} for c in st.session_state.components.get('windows', [])]}")
logger.info(f"Components (skylights): {[{'id': c.id, 'area': c.area, 'shgc': c.shgc, 'facade': c.facade} for c in st.session_state.components.get('skylights', [])]}")
logger.info(f"Hourly Data: {[{'month': d['month'], 'day': d['day'], 'hour': d['hour'], 'GHI': d['global_horizontal_radiation'], 'DNI': d.get('direct_normal_radiation', 0), 'DHI': d.get('diffuse_horizontal_radiation', 0), 'dry_bulb': d['dry_bulb']} for d in debug_data]}")
logger.info(f"Building Info: {{'latitude': {st.session_state.building_info.get('latitude', 0.0)}, 'longitude': {st.session_state.building_info.get('longitude', 0.0)}, 'timezone': {st.session_state.building_info.get('timezone', 0.0)}, 'ground_reflectivity': {st.session_state.building_info.get('ground_reflectivity', 0.2)}}}")
try:
solar_results = SolarCalculations.calculate_solar_parameters(
hourly_data=debug_data,
latitude=st.session_state.building_info.get("latitude", 0.0),
longitude=st.session_state.building_info.get("longitude", 0.0),
timezone=st.session_state.building_info.get("timezone", 0.0),
ground_reflectivity=st.session_state.building_info.get("ground_reflectivity", 0.2),
components=st.session_state.components
)
st.write("Solar Calculations Debug Output:")
for result in solar_results:
month, day, hour = result["month"], result["day"], result["hour"]
logger.info(f"Solar Results for {month}/{day}/{hour}:")
logger.info(f" Solar Altitude: {result['altitude']:.2f}°, Azimuth: {result['azimuth']:.2f}°")
for comp_result in result["component_results"]:
if "solar_heat_gain" in comp_result:
logger.info(f" Component {comp_result['component_id']}: Solar Heat Gain = {comp_result['solar_heat_gain']:.2f} kW")
st.write(f"Month {month}, Day {day}, Hour {hour}, Component {comp_result['component_id']}: Solar Heat Gain = {comp_result['solar_heat_gain']:.2f} kW")
else:
logger.info(f" Component {comp_result['component_id']}: No solar heat gain (not a window/skylight)")
if not any("solar_heat_gain" in cr for cr in result["component_results"]):
st.write(f"Month {month}, Day {day}, Hour {hour}: No solar heat gains calculated.")
except Exception as e:
logger.error(f"Error in SolarCalculations: {str(e)}")
st.error(f"Error in Solar Calculations: {str(e)}")
# Initialize session state for simulation settings
if 'sim_period' not in st.session_state:
st.session_state.sim_period = {"type": "Full Year"}
if 'indoor_conditions' not in st.session_state:
st.session_state.indoor_conditions = {
"type": "Fixed",
"cooling_setpoint": {
"temperature": st.session_state.building_info["indoor_design_temp"],
"rh": st.session_state.building_info["indoor_design_rh"]
},
"heating_setpoint": {
"temperature": st.session_state.building_info["indoor_design_temp"] - 2.0,
"rh": st.session_state.building_info["indoor_design_rh"]
}
}
if 'hvac_settings' not in st.session_state:
st.session_state.hvac_settings = {
"cop": 3.5,
"operating_hours": [{"start": 8, "end": 18}]
}
# Simulation Period Controls
st.subheader("Simulation Period")
typical_extreme_periods = st.session_state.climate_data.get("typical_extreme_periods", {})
sim_type_options = [
"Full Year",
"From Date to Date",
"Heating Only",
"Cooling Only",
"Summer Extreme",
"Summer Typical",
"Winter Extreme",
"Winter Typical"
]
sim_type = st.selectbox(
"Simulation Type",
sim_type_options,
index=sim_type_options.index(st.session_state.sim_period["type"])
if st.session_state.sim_period["type"] in sim_type_options else 0,
key="sim_type"
)
sim_period = {"type": sim_type}
if sim_type == "From Date to Date":
col1, col2 = st.columns(2)
with col1:
start_date = st.date_input("Start Date", value=pd.to_datetime("2025-01-01"), key="start_date")
with col2:
end_date = st.date_input("End Date", value=pd.to_datetime("2025-12-31"), key="end_date")
sim_period["start_date"] = start_date
sim_period["end_date"] = end_date
elif sim_type in ["Summer Extreme", "Summer Typical", "Winter Extreme", "Winter Typical"]:
period_key = sim_type.lower().replace(" ", "_")
if period_key in typical_extreme_periods:
period = typical_extreme_periods[period_key]
start_date = pd.to_datetime(f"2025-{period['start']['month']}-{period['start']['day']}")
end_date = pd.to_datetime(f"2025-{period['end']['month']}-{period['end']['day']}")
st.write(f"Period: {start_date.strftime('%Y-%m-%d')} to {end_date.strftime('%Y-%m-%d')}")
sim_period["start_date"] = start_date
sim_period["end_date"] = end_date
else:
st.warning(f"No data available for {sim_type}. Please check climate data.")
sim_period["start_date"] = pd.to_datetime("2025-01-01")
sim_period["end_date"] = pd.to_datetime("2025-12-31")
# Indoor Conditions Controls
st.subheader("Indoor Conditions")
indoor_type = st.selectbox(
"Indoor Conditions Type",
["Fixed", "Time-varying", "Adaptive"],
index=["Fixed", "Time-varying", "Adaptive"].index(st.session_state.indoor_conditions["type"])
if st.session_state.indoor_conditions["type"] in ["Fixed", "Time-varying", "Adaptive"] else 0,
key="indoor_type"
)
indoor_conditions = {"type": indoor_type}
if indoor_type == "Fixed":
st.write("Cooling Setpoint")
col1, col2 = st.columns(2)
with col1:
cooling_temp = st.number_input(
"Cooling Indoor Temperature (°C)",
min_value=15.0,
max_value=30.0,
value=st.session_state.indoor_conditions.get("cooling_setpoint", {}).get("temperature", 24.0),
key="cooling_fixed_temp"
)
with col2:
cooling_rh = st.number_input(
"Cooling Indoor Relative Humidity (%)",
min_value=0.0,
max_value=100.0,
value=st.session_state.indoor_conditions.get("cooling_setpoint", {}).get("rh", 50.0),
key="cooling_fixed_rh"
)
st.write("Heating Setpoint")
col3, col4 = st.columns(2)
with col3:
heating_temp = st.number_input(
"Heating Indoor Temperature (°C)",
min_value=15.0,
max_value=30.0,
value=st.session_state.indoor_conditions.get("heating_setpoint", {}).get("temperature", 22.0),
key="heating_fixed_temp"
)
with col4:
heating_rh = st.number_input(
"Heating Indoor Relative Humidity (%)",
min_value=0.0,
max_value=100.0,
value=st.session_state.indoor_conditions.get("heating_setpoint", {}).get("rh", 50.0),
key="heating_fixed_rh"
)
indoor_conditions["cooling_setpoint"] = {"temperature": cooling_temp, "rh": cooling_rh}
indoor_conditions["heating_setpoint"] = {"temperature": heating_temp, "rh": heating_rh}
elif indoor_type == "Time-varying":
st.write("Define hourly schedule (0-23 hours) for Cooling and Heating Setpoints")
cooling_schedule = []
heating_schedule = []
for hour in range(24):
with st.expander(f"Hour {hour}", expanded=False):
st.write("Cooling Setpoint")
col1, col2 = st.columns(2)
with col1:
cooling_temp = st.number_input(
f"Cooling Temperature (°C) at Hour {hour}",
min_value=15.0,
max_value=30.0,
value=24.0,
key=f"cooling_schedule_temp_{hour}"
)
with col2:
cooling_rh = st.number_input(
f"Cooling RH (%) at Hour {hour}",
min_value=0.0,
max_value=100.0,
value=50.0,
key=f"cooling_schedule_rh_{hour}"
)
st.write("Heating Setpoint")
col3, col4 = st.columns(2)
with col3:
heating_temp = st.number_input(
f"Heating Temperature (°C) at Hour {hour}",
min_value=15.0,
max_value=30.0,
value=22.0,
key=f"heating_schedule_temp_{hour}"
)
with col4:
heating_rh = st.number_input(
f"Heating RH (%) at Hour {hour}",
min_value=0.0,
max_value=100.0,
value=50.0,
key=f"heating_schedule_rh_{hour}"
)
cooling_schedule.append({"hour": hour, "temperature": cooling_temp, "rh": cooling_rh})
heating_schedule.append({"hour": hour, "temperature": heating_temp, "rh": heating_rh})
indoor_conditions["cooling_schedule"] = cooling_schedule
indoor_conditions["heating_schedule"] = heating_schedule
else: # Adaptive
st.write("Adaptive comfort model (ASHRAE 55) will be used, adjusting temperature based on outdoor conditions.")
indoor_conditions["rh"] = 50.0
# HVAC System Controls
st.subheader("HVAC System Settings")
col1, col2 = st.columns(2)
with col1:
cop = st.number_input(
"Coefficient of Performance (COP)",
min_value=1.0,
max_value=6.0,
value=st.session_state.hvac_settings.get("cop", 3.5),
step=0.1,
key="hvac_cop"
)
with col2:
num_periods = st.number_input(
"Number of Operating Periods",
min_value=1,
max_value=5,
value=len(st.session_state.hvac_settings.get("operating_hours", [{"start": 8, "end": 18}])),
step=1,
key="num_operating_periods"
)
operating_hours = []
for i in range(int(num_periods)):
st.write(f"Operating Period {i+1}")
col1, col2 = st.columns(2)
with col1:
start_hour = st.slider(
f"Start Hour (Period {i+1})",
min_value=0,
max_value=23,
value=st.session_state.hvac_settings["operating_hours"][i]["start"] if i < len(st.session_state.hvac_settings["operating_hours"]) else 8,
key=f"start_hour_{i}"
)
with col2:
end_hour = st.slider(
f"End Hour (Period {i+1})",
min_value=start_hour,
max_value=23,
value=st.session_state.hvac_settings["operating_hours"][i]["end"] if i < len(st.session_state.hvac_settings["operating_hours"]) else 18,
key=f"end_hour_{i}"
)
operating_hours.append({"start": start_hour, "end": end_hour})
# Save settings to session state
st.session_state.sim_period = sim_period
st.session_state.indoor_conditions = indoor_conditions
st.session_state.hvac_settings = {
"cop": cop,
"operating_hours": operating_hours
}
# Run Simulation Button
if st.button("Run Simulation", key="run_simulation"):
climate_data = st.session_state.climate_data.get("hourly_data", [])
if not climate_data:
st.error("No valid climate data available.")
return
with st.spinner("Running simulation..."):
loads = self.tfm.calculate_tfm_loads(
st.session_state.components,
climate_data,
st.session_state.indoor_conditions,
st.session_state.internal_loads,
st.session_state.building_info,
st.session_state.sim_period,
st.session_state.hvac_settings
)
st.session_state.calculation_results["cooling"] = loads
st.session_state.calculation_results["heating"] = loads
# Debug TFMCalculations solar loads for the same hours
st.write("TFMCalculations Solar Load Debug Output:")
for load in loads:
if load["month"] == 7 and load["day"] == 1 and load["hour"] in debug_hours:
logger.info(f"TFM Solar Load for {load['month']}/{load['day']}/{load['hour']}: {load['solar']:.2f} kW")
st.write(f"Month {load['month']}, Day {load['day']}, Hour {load['hour']}: Solar Load = {load['solar']:.2f} kW")
st.success("Simulation completed!")
# Display Results
if st.session_state.calculation_results.get("cooling") and st.session_state.calculation_results.get("heating"):
df = pd.DataFrame(st.session_state.calculation_results["cooling"])
if df.empty:
st.error("No load calculations available.")
return
# Equipment Sizing
st.subheader("Equipment Sizing")
peak_cooling_load = df["total_cooling"].max() if "total_cooling" in df else 0.0
peak_heating_load = df["total_heating"].max() if "total_heating" in df else 0.0
col1, col2 = st.columns(2)
with col1:
st.metric("Cooling Equipment Size", f"{peak_cooling_load:.2f} kW", help="Peak hourly cooling load")
with col2:
st.metric("Heating Equipment Size", f"{peak_heating_load:.2f} kW", help="Peak hourly heating load")
# Pie Charts for Load Breakdown
st.subheader("Load Breakdown")
cooling_totals = {
"Conduction": df["conduction_cooling"].sum(),
"Solar": df["solar"].sum(),
"Internal": df["internal"].sum(),
"Ventilation": df["ventilation_cooling"].sum(),
"Infiltration": df["infiltration_cooling"].sum()
}
heating_totals = {
"Conduction": df["conduction_heating"].sum(),
"Ventilation": df["ventilation_heating"].sum(),
"Infiltration": df["infiltration_heating"].sum()
}
col1, col2 = st.columns(2)
with col1:
fig_cooling = go.Figure(data=[
go.Pie(labels=list(cooling_totals.keys()), values=list(cooling_totals.values()))
])
fig_cooling.update_layout(title="Cooling Load Breakdown (kWh)", width=400, height=400)
st.plotly_chart(fig_cooling, use_container_width=True)
with col2:
fig_heating = go.Figure(data=[
go.Pie(labels=list(heating_totals.keys()), values=list(heating_totals.values()))
])
fig_heating.update_layout(title="Heating Load Breakdown (kWh)", width=400, height=400)
st.plotly_chart(fig_heating, use_container_width=True)
# Monthly Loads Bar Chart
st.subheader("Monthly Heating and Cooling Loads")
df["month_name"] = df["month"].map({
1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun",
7: "Jul", 8: "Aug", 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec"
})
monthly_loads = df.groupby("month_name")[["total_cooling", "total_heating"]].sum().reindex(
["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
)
fig_monthly = go.Figure(data=[
go.Bar(name="Cooling Load (kWh)", x=monthly_loads.index, y=monthly_loads["total_cooling"]),
go.Bar(name="Heating Load (kWh)", x=monthly_loads.index, y=monthly_loads["total_heating"])
])
fig_monthly.update_layout(
title="Monthly Heating and Cooling Loads",
xaxis_title="Month",
yaxis_title="Load (kWh)",
barmode="group",
width=800,
height=400
)
st.plotly_chart(fig_monthly, use_container_width=True)
# Detailed Load Summary Table
st.subheader("Load Summary")
summary_row = {
"hour": "Total",
"month_name": "",
"conduction_cooling": df["conduction_cooling"].sum(),
"conduction_heating": df["conduction_heating"].sum(),
"solar": df["solar"].sum(),
"internal": df["internal"].sum(),
"ventilation_cooling": df["ventilation_cooling"].sum(),
"ventilation_heating": df["ventilation_heating"].sum(),
"infiltration_cooling": df["infiltration_cooling"].sum(),
"infiltration_heating": df["infiltration_heating"].sum(),
"total_cooling": df["total_cooling"].sum(),
"total_heating": df["total_heating"].sum()
}
display_df = df[["hour", "month_name", "conduction_cooling", "conduction_heating", "solar",
"internal", "ventilation_cooling", "ventilation_heating",
"infiltration_cooling", "infiltration_heating", "total_cooling", "total_heating"]]
display_df = pd.concat([display_df, pd.DataFrame([summary_row])], ignore_index=True)
st.dataframe(display_df.rename(columns={"month_name": "Month"}), use_container_width=True)
col1, col2 = st.columns(2)
with col1:
st.button("Back to Internal Loads", key="results_back_to_internal",
on_click=lambda: setattr(st.session_state, "page", "Internal Loads"))
with col2:
st.button("Continue to Export Data", key="results_to_export",
on_click=lambda: setattr(st.session_state, "page", "Export Data"))
def main():
app = HVACCalculatorApp()
app.display_calculation_results()
if __name__ == "__main__":
main()