mabuseif commited on
Commit
6e4c279
·
verified ·
1 Parent(s): a4353e4

Update data/calculation.py

Browse files
Files changed (1) hide show
  1. data/calculation.py +131 -94
data/calculation.py CHANGED
@@ -14,8 +14,8 @@ from data.internal_loads import PEOPLE_ACTIVITY_LEVELS, DIVERSITY_FACTORS, LIGHT
14
  from datetime import datetime
15
  from collections import defaultdict
16
  import logging
17
- from utils.ctf_calculations import CTFCalculator, ComponentType, CTFCoefficients
18
- from utils.solar import SolarCalculations
19
 
20
  # Configure logging
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -34,16 +34,7 @@ class TFMCalculations:
34
  if mode == "heating" and delta_t >= 0:
35
  return 0, 0
36
 
37
- # For windows and skylights, use U-value directly (CTF coefficients are zero)
38
- if component.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
39
- load = component.u_value * component.area * delta_t
40
- cooling_load = load / 1000 if mode == "cooling" else 0
41
- heating_load = -load / 1000 if mode == "heating" else 0
42
- logger.info(f"Conduction load for {component.component_type} {getattr(component, 'id', 'unknown_component')} "
43
- f"at hour {hour}: cooling={cooling_load:.2f} kW, heating={heating_load:.2f} kW (U={component.u_value}, A={component.area}, ΔT={delta_t:.2f})")
44
- return cooling_load, heating_load
45
-
46
- # For other components, use CTFCalculator
47
  ctf = CTFCalculator.calculate_ctf_coefficients(component)
48
 
49
  # Initialize history terms (simplified: assume steady-state history for demonstration)
@@ -55,8 +46,6 @@ class TFMCalculations:
55
  # Note: F terms require flux history, omitted here for simplicity
56
  cooling_load = load / 1000 if mode == "cooling" else 0
57
  heating_load = -load / 1000 if mode == "heating" else 0
58
- logger.info(f"Conduction load for {component.component_type} {getattr(component, 'id', 'unknown_component')} "
59
- f"at hour {hour}: cooling={cooling_load:.2f} kW, heating={heating_load:.2f} kW (CTF Y[0]={ctf.Y[0] if ctf.Y else 0})")
60
  return cooling_load, heating_load
61
 
62
  @staticmethod
@@ -210,70 +199,30 @@ class TFMCalculations:
210
  operating_periods = hvac_settings.get("operating_hours", [{"start": 8, "end": 18}])
211
  area = building_info.get("floor_area", 100.0)
212
 
213
- # Validate inputs
214
- if not filtered_data:
215
- logger.warning("No filtered hourly data available. Returning empty loads.")
216
- return []
217
-
218
- required_fields = ["global_horizontal_radiation", "dry_bulb", "month", "day", "hour"]
219
- for data in filtered_data:
220
- missing = [field for field in required_fields if field not in data]
221
- if missing:
222
- logger.warning(f"Missing fields {missing} in hourly data for {data.get('month')}/{data.get('day')}/{data.get('hour')}")
223
-
224
- # Validate fenestration components
225
- fenestration_count = 0
226
- for comp_type in ['windows', 'skylights']:
227
- comp_list = components.get(comp_type, [])
228
- fenestration_count += len(comp_list)
229
- for comp in comp_list:
230
- comp_id = getattr(comp, 'id', 'unknown_component')
231
- if not hasattr(comp, 'area') or comp.area <= 0:
232
- logger.warning(f"Component {comp_id} ({comp_type}) has invalid area: {getattr(comp, 'area', 'None')}")
233
- if not hasattr(comp, 'shgc') or comp.shgc <= 0:
234
- logger.warning(f"Component {comp_id} ({comp_type}) has invalid SHGC: {getattr(comp, 'shgc', 'None')}")
235
- if not hasattr(comp, 'u_value') or comp.u_value <= 0:
236
- logger.warning(f"Component {comp_id} ({comp_type}) has invalid U-value: {getattr(comp, 'u_value', 'None')}")
237
- if not hasattr(comp, 'facade') or comp.facade not in ['A', 'B', 'C', 'D']:
238
- logger.warning(f"Component {comp_id} ({comp_type}) has invalid facade: {getattr(comp, 'facade', 'None')}")
239
- if not hasattr(comp, 'id') or not comp.id:
240
- logger.warning(f"Component {comp_type} missing id. Assigning temporary id.")
241
- comp.id = f"{comp_type}_{id(comp)}"
242
- logger.info(f"Number of fenestration components (windows/skylights): {fenestration_count}")
243
- if fenestration_count == 0:
244
- logger.warning("No windows or skylights defined. Solar load will be 0.")
245
-
246
- # Validate building_info
247
- climate_data = building_info.get("climate_data", {})
248
- for field in ["latitude", "longitude", "timezone"]:
249
- if field not in climate_data:
250
- logger.warning(f"Missing {field} in building_info.climate_data. Using default 0.0.")
251
- climate_data[field] = 0.0
252
- if "ground_reflectivity" not in building_info:
253
- logger.warning("Missing ground_reflectivity in building_info. Using default 0.2.")
254
- building_info["ground_reflectivity"] = 0.2
255
-
256
- # Pre-calculate CTF coefficients for non-fenestration components
257
- for comp_type, comp_list in components.items():
258
- if comp_type in ['windows', 'skylights']:
259
- continue
260
  for comp in comp_list:
261
  comp.ctf = CTFCalculator.calculate_ctf_coefficients(comp)
262
 
263
  # Calculate solar parameters
 
 
264
  solar_results = SolarCalculations.calculate_solar_parameters(
265
  hourly_data=filtered_data,
266
  latitude=climate_data.get("latitude", 0.0),
267
  longitude=climate_data.get("longitude", 0.0),
268
  timezone=climate_data.get("timezone", 0.0),
269
- ground_reflectivity=building_info.get("ground_reflectivity", 0.2),
270
  components=components
271
  )
272
 
273
  # Create a dictionary for quick lookup of solar results by hour
274
  solar_results_by_hour = {(res["month"], res["day"], res["hour"]): res for res in solar_results}
275
- if not solar_results:
276
- logger.warning("No solar results returned. Check GHI > 0 and component data.")
 
 
 
277
 
278
  for hour_data in filtered_data:
279
  hour = hour_data["hour"]
@@ -292,43 +241,40 @@ class TFMCalculations:
292
  break
293
  # Determine mode based on temperature threshold (18°C)
294
  mode = "none" if abs(outdoor_temp - 18) < 0.01 else "cooling" if outdoor_temp > 18 else "heating"
295
- solar_key = (hour_data["month"], hour_data["day"], hour)
296
- solar_result_hour = solar_results_by_hour.get(solar_key, {})
297
- if is_operating:
298
  for comp_type, comp_list in components.items():
299
  for comp in comp_list:
300
  # Get solar result for this component and hour
301
- comp_id = getattr(comp, 'id', 'unknown_component')
302
- solar_result = next((res for res in solar_result_hour.get("component_results", [])
303
- if res["component_id"] == comp_id), None)
304
  sol_air_temp = solar_result.get("sol_air_temp") if solar_result else None
305
- # Calculate conduction load
306
- if mode == "cooling":
307
- cool_load, _ = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="cooling")
308
- conduction_cooling += cool_load
309
- elif mode == "heating":
310
- _, heat_load = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="heating")
311
- conduction_heating += heat_load
312
- # Add solar heat gain for windows and skylights if available
313
  if solar_result and comp.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
314
  solar_heat_gain = solar_result.get("solar_heat_gain", 0)
315
- if solar_heat_gain > 0:
316
- solar += solar_heat_gain
317
- logger.info(f"Adding solar heat gain for component {comp_id} "
318
- f"at {hour_data['month']}/{hour_data['day']}/{hour}: {solar_heat_gain:.2f} kW")
319
- else:
320
- logger.info(f"No solar heat gain for component {comp_id} "
321
- f"at {hour_data['month']}/{hour_data['day']}/{hour}")
322
  logger.info(f"Total solar load for {hour_data['month']}/{hour_data['day']}/{hour}: {solar:.2f} kW")
323
  internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
324
- if mode == "cooling":
325
- ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
326
- infiltration_cooling, _ = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
327
- elif mode == "heating":
328
- _, ventilation_heating = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="heating")
329
- _, infiltration_heating = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="heating")
330
- else: # not is_operating
331
- internal = 0 # No internal loads when outside operating hours
 
 
 
 
 
 
 
 
 
332
  # Calculate total loads, subtracting internal load for heating
333
  total_cooling = conduction_cooling + solar + internal + ventilation_cooling + infiltration_cooling
334
  total_heating = max(conduction_heating + ventilation_heating + infiltration_heating - internal, 0)
@@ -372,4 +318,95 @@ class TFMCalculations:
372
  load["total_cooling"] = 0
373
  load["total_heating"] = 0 # Zero both totals, keep components
374
  final_loads.append(load)
375
- return final_loads
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  from datetime import datetime
15
  from collections import defaultdict
16
  import logging
17
+ import streamlit as st
18
+ import plotly.graph_objects as go
19
 
20
  # Configure logging
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
34
  if mode == "heating" and delta_t >= 0:
35
  return 0, 0
36
 
37
+ # Get CTF coefficients using CTFCalculator
 
 
 
 
 
 
 
 
 
38
  ctf = CTFCalculator.calculate_ctf_coefficients(component)
39
 
40
  # Initialize history terms (simplified: assume steady-state history for demonstration)
 
46
  # Note: F terms require flux history, omitted here for simplicity
47
  cooling_load = load / 1000 if mode == "cooling" else 0
48
  heating_load = -load / 1000 if mode == "heating" else 0
 
 
49
  return cooling_load, heating_load
50
 
51
  @staticmethod
 
199
  operating_periods = hvac_settings.get("operating_hours", [{"start": 8, "end": 18}])
200
  area = building_info.get("floor_area", 100.0)
201
 
202
+ # Pre-calculate CTF coefficients for all components using CTFCalculator
203
+ for comp_list in components.values():
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  for comp in comp_list:
205
  comp.ctf = CTFCalculator.calculate_ctf_coefficients(comp)
206
 
207
  # Calculate solar parameters
208
+ climate_data = building_info.get("climate_data", {})
209
+ ground_reflectivity = building_info.get("ground_reflectivity", 0.2)
210
  solar_results = SolarCalculations.calculate_solar_parameters(
211
  hourly_data=filtered_data,
212
  latitude=climate_data.get("latitude", 0.0),
213
  longitude=climate_data.get("longitude", 0.0),
214
  timezone=climate_data.get("timezone", 0.0),
215
+ ground_reflectivity=ground_reflectivity,
216
  components=components
217
  )
218
 
219
  # Create a dictionary for quick lookup of solar results by hour
220
  solar_results_by_hour = {(res["month"], res["day"], res["hour"]): res for res in solar_results}
221
+
222
+ # Log presence of fenestration components
223
+ fenestration_count = sum(len(comp_list) for comp_type, comp_list in components.items()
224
+ if comp_type in ['windows', 'skylights'])
225
+ logger.info(f"Number of fenestration components (windows/skylights): {fenestration_count}")
226
 
227
  for hour_data in filtered_data:
228
  hour = hour_data["hour"]
 
241
  break
242
  # Determine mode based on temperature threshold (18°C)
243
  mode = "none" if abs(outdoor_temp - 18) < 0.01 else "cooling" if outdoor_temp > 18 else "heating"
244
+ if is_operating and mode == "cooling":
 
 
245
  for comp_type, comp_list in components.items():
246
  for comp in comp_list:
247
  # Get solar result for this component and hour
248
+ solar_key = (hour_data["month"], hour_data["day"], hour)
249
+ solar_result = next((res for res in solar_results_by_hour.get(solar_key, {}).get("component_results", [])
250
+ if res["component_id"] == getattr(comp, 'id', 'unknown_component')), None)
251
  sol_air_temp = solar_result.get("sol_air_temp") if solar_result else None
252
+ cool_load, _ = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="cooling")
253
+ conduction_cooling += cool_load
 
 
 
 
 
 
254
  if solar_result and comp.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
255
  solar_heat_gain = solar_result.get("solar_heat_gain", 0)
256
+ solar += solar_heat_gain
257
+ logger.info(f"Adding solar heat gain for component {getattr(comp, 'id', 'unknown_component')} "
258
+ f"at {hour_data['month']}/{hour_data['day']}/{hour}: {solar_heat_gain:.2f} kW")
 
 
 
 
259
  logger.info(f"Total solar load for {hour_data['month']}/{hour_data['day']}/{hour}: {solar:.2f} kW")
260
  internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
261
+ ventilation_cooling, _ = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
262
+ infiltration_cooling, _ = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="cooling")
263
+ elif is_operating and mode == "heating":
264
+ for comp_type, comp_list in components.items():
265
+ for comp in comp_list:
266
+ # Get solar result for this component and hour
267
+ solar_key = (hour_data["month"], hour_data["day"], hour)
268
+ solar_result = next((res for res in solar_results_by_hour.get(solar_key, {}).get("component_results", [])
269
+ if res["component_id"] == getattr(comp, 'id', 'unknown_component')), None)
270
+ sol_air_temp = solar_result.get("sol_air_temp") if solar_result else None
271
+ _, heat_load = TFMCalculations.calculate_conduction_load(comp, outdoor_temp, indoor_temp, hour, sol_air_temp, mode="heating")
272
+ conduction_heating += heat_load
273
+ internal = TFMCalculations.calculate_internal_load(internal_loads, hour, max([p["end"] - p["start"] for p in operating_periods]), area)
274
+ _, ventilation_heating = TFMCalculations.calculate_ventilation_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="heating")
275
+ _, infiltration_heating = TFMCalculations.calculate_infiltration_load(internal_loads, outdoor_temp, indoor_temp, area, building_info, mode="heating")
276
+ else: # mode == "none" or not is_operating
277
+ internal = 0 # No internal loads when no heating or cooling is needed or outside operating hours
278
  # Calculate total loads, subtracting internal load for heating
279
  total_cooling = conduction_cooling + solar + internal + ventilation_cooling + infiltration_cooling
280
  total_heating = max(conduction_heating + ventilation_heating + infiltration_heating - internal, 0)
 
318
  load["total_cooling"] = 0
319
  load["total_heating"] = 0 # Zero both totals, keep components
320
  final_loads.append(load)
321
+ return final_loads
322
+
323
+ @staticmethod
324
+ def display_results(loads: List[Dict]):
325
+ """Display simulation results including equipment sizing, load breakdown, monthly loads, and summary table."""
326
+ df = pd.DataFrame(loads)
327
+ if df.empty:
328
+ st.error("No load calculations available.")
329
+ return
330
+
331
+ # Equipment Sizing
332
+ st.subheader("Equipment Sizing")
333
+ peak_cooling_load = df["total_cooling"].max() if "total_cooling" in df else 0.0
334
+ peak_heating_load = df["total_heating"].max() if "total_heating" in df else 0.0
335
+ col1, col2 = st.columns(2)
336
+ with col1:
337
+ st.metric("Cooling Equipment Size", f"{peak_cooling_load:.2f} kW", help="Peak hourly cooling load")
338
+ with col2:
339
+ st.metric("Heating Equipment Size", f"{peak_heating_load:.2f} kW", help="Peak hourly heating load")
340
+
341
+ # Pie Charts for Load Breakdown
342
+ st.subheader("Load Breakdown")
343
+ cooling_totals = {
344
+ "Conduction": df["conduction_cooling"].sum(),
345
+ "Solar": df["solar"].sum(),
346
+ "Internal": df["internal"].sum(),
347
+ "Ventilation": df["ventilation_cooling"].sum(),
348
+ "Infiltration": df["infiltration_cooling"].sum()
349
+ }
350
+ heating_totals = {
351
+ "Conduction": df["conduction_heating"].sum(),
352
+ "Ventilation": df["ventilation_heating"].sum(),
353
+ "Infiltration": df["infiltration_heating"].sum()
354
+ }
355
+ col1, col2 = st.columns(2)
356
+ with col1:
357
+ fig_cooling = go.Figure(data=[
358
+ go.Pie(labels=list(cooling_totals.keys()), values=list(cooling_totals.values()))
359
+ ])
360
+ fig_cooling.update_layout(title="Cooling Load Breakdown (kWh)", width=400, height=400)
361
+ st.plotly_chart(fig_cooling, use_container_width=True)
362
+ with col2:
363
+ fig_heating = go.Figure(data=[
364
+ go.Pie(labels=list(heating_totals.keys()), values=list(heating_totals.values()))
365
+ ])
366
+ fig_heating.update_layout(title="Heating Load Breakdown (kWh)", width=400, height=400)
367
+ st.plotly_chart(fig_heating, use_container_width=True)
368
+
369
+ # Monthly Loads Bar Chart
370
+ st.subheader("Monthly Heating and Cooling Loads")
371
+ df["month_name"] = df["month"].map({
372
+ 1: "Jan", 2: "Feb", 3: "Mar", 4: "Apr", 5: "May", 6: "Jun",
373
+ 7: "Jul", 8: "Aug", 9: "Sep", 10: "Oct", 11: "Nov", 12: "Dec"
374
+ })
375
+ monthly_loads = df.groupby("month_name")[["total_cooling", "total_heating"]].sum().reindex(
376
+ ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
377
+ )
378
+ fig_monthly = go.Figure(data=[
379
+ go.Bar(name="Cooling Load (kWh)", x=monthly_loads.index, y=monthly_loads["total_cooling"]),
380
+ go.Bar(name="Heating Load (kWh)", x=monthly_loads.index, y=monthly_loads["total_heating"])
381
+ ])
382
+ fig_monthly.update_layout(
383
+ title="Monthly Heating and Cooling Loads",
384
+ xaxis_title="Month",
385
+ yaxis_title="Load (kWh)",
386
+ barmode="group",
387
+ width=800,
388
+ height=400
389
+ )
390
+ st.plotly_chart(fig_monthly, use_container_width=True)
391
+
392
+ # Detailed Load Summary Table
393
+ st.subheader("Load Summary")
394
+ summary_row = {
395
+ "hour": "Total",
396
+ "month_name": "",
397
+ "conduction_cooling": df["conduction_cooling"].sum(),
398
+ "conduction_heating": df["conduction_heating"].sum(),
399
+ "solar": df["solar"].sum(),
400
+ "internal": df["internal"].sum(),
401
+ "ventilation_cooling": df["ventilation_cooling"].sum(),
402
+ "ventilation_heating": df["ventilation_heating"].sum(),
403
+ "infiltration_cooling": df["infiltration_cooling"].sum(),
404
+ "infiltration_heating": df["infiltration_heating"].sum(),
405
+ "total_cooling": df["total_cooling"].sum(),
406
+ "total_heating": df["total_heating"].sum()
407
+ }
408
+ display_df = df[["hour", "month_name", "conduction_cooling", "conduction_heating", "solar",
409
+ "internal", "ventilation_cooling", "ventilation_heating",
410
+ "infiltration_cooling", "infiltration_heating", "total_cooling", "total_heating"]]
411
+ display_df = pd.concat([display_df, pd.DataFrame([summary_row])], ignore_index=True)
412
+ st.dataframe(display_df.rename(columns={"month_name": "Month"}), use_container_width=True)