mabuseif commited on
Commit
21f1b35
·
verified ·
1 Parent(s): 4950b18

Update app/hvac_loads.py

Browse files
Files changed (1) hide show
  1. app/hvac_loads.py +100 -10
app/hvac_loads.py CHANGED
@@ -115,17 +115,48 @@ class TFMCalculations:
115
  return component_type
116
 
117
  @staticmethod
118
- def calculate_conduction_load(component: Dict[str, Any], outdoor_temp: float, indoor_temp: float, hour: int, mode: str = "none") -> tuple[float, float]:
119
  """Calculate conduction load for heating and cooling in kW based on mode."""
120
  if mode == "none":
121
  return 0, 0
 
122
  component_name = component.get('name', 'unnamed_component')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  delta_t = outdoor_temp - indoor_temp
124
  if mode == "cooling" and delta_t <= 0:
125
  return 0, 0
126
  if mode == "heating" and delta_t >= 0:
127
  return 0, 0
128
-
129
  try:
130
  # Get CTF coefficients, preferring stored value
131
  ctf = component.get('ctf')
@@ -143,7 +174,7 @@ class TFMCalculations:
143
  heating_load = -load / 1000 if mode == "heating" else 0
144
  logger.info(f"Conduction load for {component_name} at hour {hour}: cooling={cooling_load:.3f} kW, heating={heating_load:.3f} kW")
145
  return cooling_load, heating_load
146
-
147
  except Exception as e:
148
  logger.error(f"Error calculating conduction load for {component_name} at hour {hour}: {str(e)}")
149
  return 0, 0
@@ -234,12 +265,14 @@ class TFMCalculations:
234
  """Calculate solar load in kW (cooling only) using ASHRAE-compliant solar calculations."""
235
  if mode != "cooling":
236
  return 0
237
-
238
  component_type = TFMCalculations.get_component_type(component)
239
- if component_type == ComponentType.FLOOR:
240
- return 0
241
-
242
  component_name = component.get('name', 'unnamed_component')
 
 
 
 
 
243
 
244
  try:
245
  material_library = st.session_state.get("material_library")
@@ -862,7 +895,7 @@ class TFMCalculations:
862
  if is_operating and mode == "cooling":
863
  for comp_list in components.values():
864
  for comp in comp_list:
865
- cool_load, _ = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, mode="cooling")
866
  component_solar_load = TFMCalculations.calculate_solar_load(comp, hour_data, hour, building_orientation, mode="cooling")
867
  orientation = classify_azimuth(comp.get("surface_azimuth", 0))
868
  element = TFMCalculations.get_component_type(comp).name
@@ -872,14 +905,14 @@ class TFMCalculations:
872
  conduction_cooling += cool_load
873
  solar += component_solar_load
874
  logger.info(f"Component {comp.get('name', 'Unknown')} ({TFMCalculations.get_component_type(comp).value}) solar load: {component_solar_load:.3f} kW, accumulated solar: {solar:.3f} kW")
875
-
876
  internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
877
  ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, hour, mode="cooling")
878
  infiltration_cooling, _ = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, hour, mode="cooling")
879
  elif is_operating and mode == "heating":
880
  for comp_list in components.values():
881
  for comp in comp_list:
882
- _, heat_load = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, mode="heating")
883
  orientation = classify_azimuth(comp.get("surface_azimuth", 0))
884
  element = TFMCalculations.get_component_type(comp).name
885
  key = orientation if element in ["WALL", "WINDOW"] else element
@@ -1421,6 +1454,63 @@ def display_hvac_loads_page():
1421
  st.success("Internal loads conditions saved successfully.")
1422
  logger.info("Internal loads conditions updated in session state.")
1423
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1424
  # Calculate HVAC Loads
1425
  if st.button("Calculate HVAC Loads"):
1426
  try:
 
115
  return component_type
116
 
117
  @staticmethod
118
+ def calculate_conduction_load(component: Dict[str, Any], outdoor_temp: float, indoor_temp: float, hour: int, month: int, mode: str = "none") -> tuple[float, float]:
119
  """Calculate conduction load for heating and cooling in kW based on mode."""
120
  if mode == "none":
121
  return 0, 0
122
+
123
  component_name = component.get('name', 'unnamed_component')
124
+ component_type = TFMCalculations.get_component_type(component)
125
+
126
+ # Validate adiabatic and ground_contact mutual exclusivity
127
+ if component.get('adiabatic', False) and component.get('ground_contact', False):
128
+ logger.warning(f"Component {component_name} has both adiabatic and ground_contact set to True. Treating as adiabatic, setting ground_contact to False.")
129
+ component['ground_contact'] = False
130
+
131
+ # Skip for adiabatic components
132
+ if component.get('adiabatic', False):
133
+ logger.info(f"Skipping conduction load calculation for adiabatic component {component_name} at hour {hour}")
134
+ return 0, 0
135
+
136
+ # Handle ground-contact surfaces
137
+ if component.get('ground_contact', False):
138
+ valid_types = [ComponentType.WALL, ComponentType.ROOF, ComponentType.FLOOR]
139
+ if component_type not in valid_types:
140
+ logger.warning(f"Invalid ground-contact component type '{component_type.value}' for {component_name}. Using outdoor temperature {outdoor_temp:.2f}°C.")
141
+ else:
142
+ # Retrieve ground temperature
143
+ climate_data = st.session_state.project_data.get("climate_data", {})
144
+ ground_temperatures = climate_data.get("ground_temperatures", {})
145
+ depth = "2" # Default depth
146
+ default_temps = {"0.5": 20.0, "2": 18.0, "4": 16.0}
147
+ if depth not in ground_temperatures or not ground_temperatures[depth]:
148
+ logger.warning(f"No ground temperature data for depth {depth} m for month {month}. Using default {default_temps[depth]}°C.")
149
+ outdoor_temp = default_temps[depth]
150
+ else:
151
+ outdoor_temp = ground_temperatures[depth][month-1] if len(ground_temperatures[depth]) >= month else default_temps[depth]
152
+ logger.info(f"Ground-contact component {component_name} at hour {hour}, month {month}: using ground temperature {outdoor_temp:.2f}°C")
153
+
154
  delta_t = outdoor_temp - indoor_temp
155
  if mode == "cooling" and delta_t <= 0:
156
  return 0, 0
157
  if mode == "heating" and delta_t >= 0:
158
  return 0, 0
159
+
160
  try:
161
  # Get CTF coefficients, preferring stored value
162
  ctf = component.get('ctf')
 
174
  heating_load = -load / 1000 if mode == "heating" else 0
175
  logger.info(f"Conduction load for {component_name} at hour {hour}: cooling={cooling_load:.3f} kW, heating={heating_load:.3f} kW")
176
  return cooling_load, heating_load
177
+
178
  except Exception as e:
179
  logger.error(f"Error calculating conduction load for {component_name} at hour {hour}: {str(e)}")
180
  return 0, 0
 
265
  """Calculate solar load in kW (cooling only) using ASHRAE-compliant solar calculations."""
266
  if mode != "cooling":
267
  return 0
268
+
269
  component_type = TFMCalculations.get_component_type(component)
 
 
 
270
  component_name = component.get('name', 'unnamed_component')
271
+
272
+ # Skip for floors, adiabatic, or ground-contact components
273
+ if component_type == ComponentType.FLOOR or component.get('adiabatic', False) or component.get('ground_contact', False):
274
+ logger.info(f"Skipping solar load calculation for {component_name} at hour {hour} (type={component_type.value}, adiabatic={component.get('adiabatic', False)}, ground_contact={component.get('ground_contact', False)})")
275
+ return 0
276
 
277
  try:
278
  material_library = st.session_state.get("material_library")
 
895
  if is_operating and mode == "cooling":
896
  for comp_list in components.values():
897
  for comp in comp_list:
898
+ cool_load, _ = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, month, mode="cooling")
899
  component_solar_load = TFMCalculations.calculate_solar_load(comp, hour_data, hour, building_orientation, mode="cooling")
900
  orientation = classify_azimuth(comp.get("surface_azimuth", 0))
901
  element = TFMCalculations.get_component_type(comp).name
 
905
  conduction_cooling += cool_load
906
  solar += component_solar_load
907
  logger.info(f"Component {comp.get('name', 'Unknown')} ({TFMCalculations.get_component_type(comp).value}) solar load: {component_solar_load:.3f} kW, accumulated solar: {solar:.3f} kW")
908
+
909
  internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
910
  ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, hour, mode="cooling")
911
  infiltration_cooling, _ = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, hour, mode="cooling")
912
  elif is_operating and mode == "heating":
913
  for comp_list in components.values():
914
  for comp in comp_list:
915
+ _, heat_load = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, month, mode="heating")
916
  orientation = classify_azimuth(comp.get("surface_azimuth", 0))
917
  element = TFMCalculations.get_component_type(comp).name
918
  key = orientation if element in ["WALL", "WINDOW"] else element
 
1454
  st.success("Internal loads conditions saved successfully.")
1455
  logger.info("Internal loads conditions updated in session state.")
1456
 
1457
+ # Ground Temperature Configuration
1458
+ st.subheader("Ground Temperature Configuration")
1459
+ has_ground_contact = any(
1460
+ comp.get('ground_contact', False)
1461
+ for comp_list in st.session_state.project_data["components"].values()
1462
+ for comp in comp_list
1463
+ )
1464
+ if has_ground_contact:
1465
+ st.markdown("Configure monthly ground temperatures for components in contact with the ground (e.g., floors, walls, roofs). Typical ranges are 10–20°C at 2 m depth (ASHRAE Fundamentals, Chapter 18).")
1466
+
1467
+ depth_options = ["0.5", "2", "4"]
1468
+ default_depth = "2"
1469
+ selected_depth = st.selectbox(
1470
+ "Ground Temperature Depth (m)",
1471
+ options=depth_options,
1472
+ index=depth_options.index(default_depth),
1473
+ key="ground_temp_depth"
1474
+ )
1475
+
1476
+ climate_data = st.session_state.project_data.get("climate_data", {})
1477
+ ground_temperatures = climate_data.get("ground_temperatures", {})
1478
+ default_temps = {"0.5": [20.0]*12, "2": [18.0]*12, "4": [16.0]*12}
1479
+
1480
+ if selected_depth not in ground_temperatures or not ground_temperatures[selected_depth]:
1481
+ st.warning(f"No ground temperature data available for depth {selected_depth} m. Using default temperatures: {default_temps[selected_depth][0]}°C.")
1482
+ monthly_temps = default_temps[selected_depth]
1483
+ else:
1484
+ monthly_temps = ground_temperatures[selected_depth]
1485
+
1486
+ st.write("Enter monthly ground temperatures (°C):")
1487
+ months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
1488
+ temp_inputs = []
1489
+ cols = st.columns(12)
1490
+ for i, month in enumerate(months):
1491
+ with cols[i]:
1492
+ temp = st.number_input(
1493
+ month,
1494
+ min_value=-20.0,
1495
+ max_value=40.0,
1496
+ value=monthly_temps[i],
1497
+ step=0.1,
1498
+ key=f"ground_temp_{selected_depth}_{month}"
1499
+ )
1500
+ temp_inputs.append(temp)
1501
+
1502
+ if st.button("Save Ground Temperatures"):
1503
+ if len(temp_inputs) != 12:
1504
+ st.error("Please provide temperatures for all 12 months.")
1505
+ elif any(not -20.0 <= t <= 40.0 for t in temp_inputs):
1506
+ st.error("All temperatures must be between -20°C and 40°C.")
1507
+ else:
1508
+ st.session_state.project_data["climate_data"]["ground_temperatures"][selected_depth] = temp_inputs
1509
+ st.success(f"Ground temperatures for depth {selected_depth} m saved successfully.")
1510
+ logger.info(f"Ground temperatures for depth {selected_depth} m updated in session state")
1511
+ else:
1512
+ st.info("No ground-contact components detected. Ground temperature configuration is not required.")
1513
+
1514
  # Calculate HVAC Loads
1515
  if st.button("Calculate HVAC Loads"):
1516
  try: