mabuseif commited on
Commit
0b53b9e
·
verified ·
1 Parent(s): 5712989

Update utils/cooling_load.py

Browse files
Files changed (1) hide show
  1. utils/cooling_load.py +569 -763
utils/cooling_load.py CHANGED
@@ -1,906 +1,712 @@
1
  """
2
  Cooling load calculation module for HVAC Load Calculator.
3
- This module implements an enhanced CLTD/CLF method with dynamic heat transfer,
4
- detailed solar calculations, and pressure-driven infiltration.
 
 
 
5
  """
6
 
7
  from typing import Dict, List, Any, Optional, Tuple
8
- import math
9
  import numpy as np
10
- import pandas as pd
11
- import os
12
- from datetime import datetime, timedelta
13
- from enum import Enum
14
-
15
- # Import data models and utilities
16
- from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType
17
  from data.ashrae_tables import ASHRAETables
18
- from utils.psychrometrics import Psychrometrics
19
  from utils.heat_transfer import HeatTransferCalculations
20
-
21
- # Define paths
22
- DATA_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
23
 
24
 
25
  class CoolingLoadCalculator:
26
- """Class for calculating cooling loads using an enhanced CLTD/CLF method."""
27
 
28
  def __init__(self):
29
- """Initialize cooling load calculator with heat transfer and psychrometrics utilities."""
30
- self.heat_transfer = HeatTransferCalculations()
31
- self.psychrometrics = Psychrometrics()
32
  self.ashrae_tables = ASHRAETables()
 
 
33
 
34
- def validate_inputs(self, temp: float, rh: float, area: float, u_value: float) -> None:
35
- """
36
- Validate input parameters for calculations.
37
-
38
- Args:
39
- temp: Temperature in °C
40
- rh: Relative humidity in %
41
- area: Area in
42
- u_value: U-value in W/(m²·K)
43
-
44
- Raises:
45
- ValueError: If inputs are out of acceptable ranges
46
- """
47
- if not -50 <= temp <= 60:
48
- raise ValueError(f"Temperature {temp}°C is outside valid range (-50 to 60°C)")
49
- if not 0 <= rh <= 100:
50
- raise ValueError(f"Relative humidity {rh}% is outside valid range (0 to 100%)")
51
- if area < 0:
52
- raise ValueError(f"Area {area}m² cannot be negative")
53
- if u_value < 0:
54
- raise ValueError(f"U-value {u_value} W/(m²·K) cannot be negative")
55
-
56
- def calculate_wall_cooling_load(self, wall: Wall, outdoor_temp: float, indoor_temp: float,
57
- month: str, hour: int, latitude: str = "40N",
58
- color: str = "Dark", day_of_year: int = 204,
59
- wind_speed: float = 4.0) -> float:
60
  """
61
- Calculate cooling load through a wall using enhanced CLTD method with thermal mass.
62
 
63
  Args:
64
- wall: Wall object
65
- outdoor_temp: Outdoor temperature in °C
66
- indoor_temp: Indoor temperature in °C
67
- month: Month (Jan, Feb, ..., Dec)
68
- hour: Hour of the day (0-23)
69
- latitude: Latitude (24N, 32N, 40N, 48N, 56N)
70
- color: Surface color (Dark, Medium, Light)
71
- day_of_year: Day of year (1-365, default: 204 for mid-July)
72
- wind_speed: Wind speed in m/s (default: 4.0 m/s)
73
 
74
  Returns:
75
- Cooling load in W
76
  """
77
- self.validate_inputs(outdoor_temp, 50.0, wall.area, wall.u_value)
78
-
79
- # Get wall properties
80
- u_value = wall.u_value
81
- area = wall.area
82
- orientation = wall.orientation.value
83
- wall_group = wall.wall_group
84
- surface_absorptivity = {"Dark": 0.9, "Medium": 0.6, "Light": 0.3}.get(color, 0.9)
 
 
 
 
 
 
 
 
85
 
86
- # Calculate sol-air temperature
87
- latitude_deg = float(latitude[:-1]) if latitude.endswith("N") else -float(latitude[:-1])
88
- solar_altitude = self.heat_transfer.solar_altitude(
89
- latitude=latitude_deg,
90
- declination=self.heat_transfer.solar_declination(day_of_year),
91
- hour_angle=self.heat_transfer.solar_hour_angle(hour)
92
- )
93
- dni = self.heat_transfer.direct_normal_irradiance(solar_altitude)
94
- dhi = self.heat_transfer.diffuse_horizontal_irradiance(dni, solar_altitude)
95
- irradiance = self.heat_transfer.irradiance_on_surface(
96
- dni, dhi,
97
- incident_angle=self.heat_transfer.incident_angle(
98
- surface_tilt=90, # Vertical wall
99
- surface_azimuth={"NORTH": 0, "EAST": 90, "SOUTH": 180, "WEST": 270}.get(orientation, 180),
100
- solar_altitude=solar_altitude,
101
- solar_azimuth=self.heat_transfer.solar_azimuth(
102
- latitude_deg,
103
- self.heat_transfer.solar_declination(day_of_year),
104
- self.heat_transfer.solar_hour_angle(hour),
105
- solar_altitude
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  )
107
- ),
108
- surface_tilt=90
109
- )
110
- sol_air_temp = self.heat_transfer.sol_air_temperature(
111
- outdoor_temp=outdoor_temp,
112
- solar_irradiance=irradiance,
113
- surface_absorptivity=surface_absorptivity,
114
- surface_resistance=0.04 # Typical for wind-exposed surface
115
- )
116
-
117
- # Calculate corrected CLTD with sol-air temperature
118
- cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
119
- wall_group=wall_group,
120
- orientation=orientation,
121
- hour=hour,
122
- color=color,
123
- month=month,
124
- latitude=latitude,
125
- indoor_temp=indoor_temp,
126
- outdoor_temp=sol_air_temp # Use sol-air temperature
127
- )
128
-
129
- # Apply thermal mass effect
130
- thermal_mass = getattr(wall, "thermal_mass", 100000) # J/K, default value
131
- time_constant = getattr(wall, "time_constant", 2.0) # hours, default
132
- lag_factor = self.heat_transfer.thermal_lag_factor(thermal_mass, time_constant, 1.0)
133
- cltd_adjusted = cltd * lag_factor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
- # Calculate cooling load
136
- cooling_load = u_value * area * cltd_adjusted
137
 
138
- return max(0, cooling_load)
 
139
 
140
- def calculate_roof_cooling_load(self, roof: Roof, outdoor_temp: float, indoor_temp: float,
141
- month: str, hour: int, latitude: str = "40N",
142
- color: str = "Dark", day_of_year: int = 204,
143
- wind_speed: float = 4.0) -> float:
144
  """
145
- Calculate cooling load through a roof using enhanced CLTD method with thermal mass.
146
 
147
  Args:
148
- roof: Roof object
149
- outdoor_temp: Outdoor temperature in °C
150
- indoor_temp: Indoor temperature in °C
151
- month: Month (Jan, Feb, ..., Dec)
152
- hour: Hour of the day (0-23)
153
- latitude: Latitude (24N, 32N, 40N, 48N, 56N)
154
- color: Surface color (Dark, Medium, Light)
155
- day_of_year: Day of year (1-365, default: 204 for mid-July)
156
- wind_speed: Wind speed in m/s (default: 4.0 m/s)
157
 
158
  Returns:
159
- Cooling load in W
160
  """
161
- self.validate_inputs(outdoor_temp, 50.0, roof.area, roof.u_value)
162
-
163
- # Get roof properties
164
- u_value = roof.u_value
165
- area = roof.area
166
- roof_group = roof.roof_group
167
- surface_absorptivity = {"Dark": 0.9, "Medium": 0.6, "Light": 0.3}.get(color, 0.9)
168
-
169
- # Calculate sol-air temperature
170
- latitude_deg = float(latitude[:-1]) if latitude.endswith("N") else -float(latitude[:-1])
171
- solar_altitude = self.heat_transfer.solar_altitude(
172
- latitude=latitude_deg,
173
- declination=self.heat_transfer.solar_declination(day_of_year),
174
- hour_angle=self.heat_transfer.solar_hour_angle(hour)
175
- )
176
- dni = self.heat_transfer.direct_normal_irradiance(solar_altitude)
177
- dhi = self.heat_transfer.diffuse_horizontal_irradiance(dni, solar_altitude)
178
- irradiance = self.heat_transfer.irradiance_on_surface(
179
- dni, dhi,
180
- incident_angle=self.heat_transfer.incident_angle(
181
- surface_tilt=0, # Horizontal roof
182
- surface_azimuth=0,
183
- solar_altitude=solar_altitude,
184
- solar_azimuth=self.heat_transfer.solar_azimuth(
185
- latitude_deg,
186
- self.heat_transfer.solar_declination(day_of_year),
187
- self.heat_transfer.solar_hour_angle(hour),
188
- solar_altitude
189
- )
190
- ),
191
- surface_tilt=0
192
- )
193
- sol_air_temp = self.heat_transfer.sol_air_temperature(
194
- outdoor_temp=outdoor_temp,
195
- solar_irradiance=irradiance,
196
- surface_absorptivity=surface_absorptivity,
197
- surface_resistance=0.04
198
- )
199
-
200
- # Calculate corrected CLTD
201
- cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
202
- roof_group=roof_group,
203
- hour=hour,
204
- color=color,
205
- month=month,
206
- latitude=latitude,
207
- indoor_temp=indoor_temp,
208
- outdoor_temp=sol_air_temp
209
- )
210
-
211
- # Apply thermal mass effect
212
- thermal_mass = getattr(roof, "thermal_mass", 200000) # J/K, default
213
- time_constant = getattr(roof, "time_constant", 3.0) # hours, default
214
- lag_factor = self.heat_transfer.thermal_lag_factor(thermal_mass, time_constant, 1.0)
215
- cltd_adjusted = cltd * lag_factor
216
-
217
- # Calculate cooling load
218
- cooling_load = u_value * area * cltd_adjusted
219
 
220
- return max(0, cooling_load)
 
221
 
222
- def calculate_window_cooling_load(self, window: Window, outdoor_temp: float, indoor_temp: float,
223
- month: str, hour: int, latitude: str = "40N",
224
- shading_coefficient: float = 1.0, day_of_year: int = 204) -> Dict[str, float]:
225
  """
226
- Calculate cooling load through a window using dynamic solar calculations.
227
 
228
  Args:
229
- window: Window object
230
- outdoor_temp: Outdoor temperature in °C
231
- indoor_temp: Indoor temperature in °C
232
- month: Month (Jan, Feb, ..., Dec)
233
- hour: Hour of the day (0-23)
234
- latitude: Latitude (24N, 32N, 40N, 48N, 56N)
235
- shading_coefficient: Shading coefficient (0-1)
236
- day_of_year: Day of year (1-365, default: 204 for mid-July)
237
 
238
  Returns:
239
- Dictionary with conduction, solar, and total cooling loads in W
240
  """
241
- self.validate_inputs(outdoor_temp, 50.0, window.area, window.u_value)
242
-
243
- # Get window properties
244
- u_value = window.u_value
245
- area = window.area
246
- orientation = window.orientation.value
247
- shgc = window.shgc
248
-
249
- # Calculate conduction cooling load
250
- delta_t = outdoor_temp - indoor_temp
251
- conduction_load = u_value * area * delta_t
252
-
253
- # Calculate solar cooling load dynamically
254
- latitude_deg = float(latitude[:-1]) if latitude.endswith("N") else -float(latitude[:-1])
255
- solar_altitude = self.heat_transfer.solar_altitude(
256
- latitude=latitude_deg,
257
- declination=self.heat_transfer.solar_declination(day_of_year),
258
- hour_angle=self.heat_transfer.solar_hour_angle(hour)
259
- )
260
- solar_azimuth = self.heat_transfer.solar_azimuth(
261
- latitude=latitude_deg,
262
- declination=self.heat_transfer.solar_declination(day_of_year),
263
- hour_angle=self.heat_transfer.solar_hour_angle(hour),
264
- altitude=solar_altitude
265
- )
266
- incident_angle = self.heat_transfer.incident_angle(
267
- surface_tilt=90, # Vertical window
268
- surface_azimuth={"NORTH": 0, "EAST": 90, "SOUTH": 180, "WEST": 270}.get(orientation, 180),
269
- solar_altitude=solar_altitude,
270
- solar_azimuth=solar_azimuth
271
- )
272
- dni = self.heat_transfer.direct_normal_irradiance(solar_altitude)
273
- dhi = self.heat_transfer.diffuse_horizontal_irradiance(dni, solar_altitude)
274
- irradiance = self.heat_transfer.irradiance_on_surface(dni, dhi, incident_angle, surface_tilt=90)
275
- solar_load = self.heat_transfer.solar_heat_gain(
276
- irradiance=irradiance,
277
- area=area,
278
- shgc=shgc,
279
- shading_coefficient=shading_coefficient
280
- )
281
-
282
- # Calculate total cooling load
283
- total_load = conduction_load + solar_load
284
 
285
- return {
286
- "conduction": max(0, conduction_load),
287
- "solar": max(0, solar_load),
288
- "total": max(0, total_load)
289
- }
290
 
291
- def calculate_door_cooling_load(self, door: Door, outdoor_temp: float, indoor_temp: float) -> float:
 
 
 
 
 
 
 
 
292
  """
293
- Calculate cooling load through a door using simple conduction.
294
 
295
  Args:
296
- door: Door object
297
- outdoor_temp: Outdoor temperature in °C
298
- indoor_temp: Indoor temperature in °C
 
 
 
299
 
300
  Returns:
301
- Cooling load in W
302
  """
303
- self.validate_inputs(outdoor_temp, 50.0, door.area, door.u_value)
304
-
305
- u_value = door.u_value
306
- area = door.area
307
- delta_t = outdoor_temp - indoor_temp
308
- cooling_load = u_value * area * delta_t
 
 
 
 
 
 
 
 
309
 
310
- return max(0, cooling_load)
 
311
 
312
- def calculate_floor_cooling_load(self, floor: Floor, ground_temp: float, indoor_temp: float) -> float:
 
 
 
 
 
 
 
 
313
  """
314
- Calculate cooling load through a floor.
315
 
316
  Args:
317
- floor: Floor object
318
- ground_temp: Ground or adjacent space temperature in °C
319
- indoor_temp: Indoor temperature in °C
 
 
 
320
 
321
  Returns:
322
- Cooling load in W
323
  """
324
- self.validate_inputs(ground_temp, 50.0, floor.area, floor.u_value)
325
-
326
- u_value = floor.u_value
327
- area = floor.area
328
- delta_t = ground_temp - indoor_temp
329
- cooling_load = u_value * area * delta_t
 
 
 
 
 
 
 
330
 
331
- return max(0, cooling_load)
 
332
 
333
- def calculate_infiltration_cooling_load(self, building_volume: float, outdoor_temp: float,
334
- indoor_temp: float, outdoor_rh: float, indoor_rh: float,
335
- wind_speed: float = 4.0, height: float = 3.0,
336
- crack_length: float = 10.0) -> Dict[str, float]:
 
 
 
 
 
 
337
  """
338
- Calculate sensible and latent cooling loads due to pressure-driven infiltration.
339
 
340
  Args:
341
- building_volume: Building volume in m³
342
- outdoor_temp: Outdoor temperature in °C
343
- indoor_temp: Indoor temperature in °C
344
- outdoor_rh: Outdoor relative humidity in %
345
- indoor_rh: Indoor relative humidity in %
346
- wind_speed: Wind speed in m/s (default: 4.0 m/s)
347
- height: Building height in m (default: 3.0 m)
348
- crack_length: Total crack length in m (default: 10.0 m)
349
 
350
  Returns:
351
- Dictionary with sensible, latent, and total cooling loads in W
352
  """
353
- self.validate_inputs(outdoor_temp, outdoor_rh, building_volume, 0.0)
354
-
355
- # Calculate infiltration flow rate
356
- wind_pd = self.heat_transfer.wind_pressure_difference(wind_speed, wind_coefficient=0.4)
357
- stack_pd = self.heat_transfer.stack_pressure_difference(
358
- height=height,
359
- indoor_temp=indoor_temp + 273.15,
360
- outdoor_temp=outdoor_temp + 273.15
361
- )
362
- total_pd = self.heat_transfer.combined_pressure_difference(wind_pd, stack_pd)
363
- flow_rate = self.heat_transfer.crack_method_infiltration(
364
- crack_length=crack_length,
365
- coefficient=0.0001,
366
- pressure_difference=total_pd
367
- )
368
-
369
- # Calculate sensible cooling load
370
- sensible_load = self.heat_transfer.infiltration_heat_transfer(
371
- flow_rate=flow_rate,
372
- delta_t=outdoor_temp - indoor_temp
373
- )
374
-
375
- # Calculate humidity ratios
376
- w_outdoor = self.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
377
- w_indoor = self.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
378
-
379
- # Calculate latent cooling load
380
- latent_load = self.heat_transfer.infiltration_latent_heat_transfer(
381
- flow_rate=flow_rate,
382
- delta_w=w_outdoor - w_indoor
383
- )
384
-
385
- total_load = sensible_load + latent_load
386
 
387
- return {
388
- "sensible": max(0, sensible_load),
389
- "latent": max(0, latent_load),
390
- "total": max(0, total_load)
391
- }
392
 
393
- def calculate_ventilation_cooling_load(self, flow_rate: float, outdoor_temp: float, indoor_temp: float,
394
- outdoor_rh: float, indoor_rh: float) -> Dict[str, float]:
 
 
 
 
395
  """
396
- Calculate sensible and latent cooling loads due to ventilation.
397
 
398
  Args:
399
- flow_rate: Ventilation flow rate in m³/s
400
- outdoor_temp: Outdoor temperature in °C
401
- indoor_temp: Indoor temperature in °C
402
- outdoor_rh: Outdoor relative humidity in %
403
- indoor_rh: Indoor relative humidity in %
404
 
405
  Returns:
406
- Dictionary with sensible, latent, and total cooling loads in W
407
  """
408
- self.validate_inputs(outdoor_temp, outdoor_rh, 0.0, 0.0)
409
-
410
- sensible_load = self.heat_transfer.infiltration_heat_transfer(
411
- flow_rate=flow_rate,
412
- delta_t=outdoor_temp - indoor_temp
413
- )
414
- w_outdoor = self.psychrometrics.humidity_ratio(outdoor_temp, outdoor_rh)
415
- w_indoor = self.psychrometrics.humidity_ratio(indoor_temp, indoor_rh)
416
- latent_load = self.heat_transfer.infiltration_latent_heat_transfer(
417
- flow_rate=flow_rate,
418
- delta_w=w_outdoor - w_indoor
419
- )
420
- total_load = sensible_load + latent_load
421
 
422
- return {
423
- "sensible": max(0, sensible_load),
424
- "latent": max(0, latent_load),
425
- "total": max(0, total_load)
426
- }
427
 
428
- def calculate_people_cooling_load(self, num_people: int, activity_level: str,
429
- hour: int, occupancy_schedule: Optional[List[float]] = None) -> Dict[str, float]:
 
 
 
 
430
  """
431
- Calculate sensible and latent cooling loads due to people with schedule.
432
 
433
  Args:
434
  num_people: Number of people
435
- activity_level: Activity level (Seated/Resting, Light work, Medium work, Heavy work)
436
- hour: Hour of the day (0-23)
437
- occupancy_schedule: List of 24 hourly occupancy factors (0-1, default: None)
438
 
439
  Returns:
440
- Dictionary with sensible, latent, and total cooling loads in W
441
  """
442
- activity_gains = {
443
- "Seated/Resting": {"sensible": 70, "latent": 45},
444
- "Light work": {"sensible": 75, "latent": 55},
445
- "Medium work": {"sensible": 85, "latent": 80},
446
- "Heavy work": {"sensible": 95, "latent": 145}
447
- }
448
-
449
- if activity_level not in activity_gains:
450
- raise ValueError(f"Invalid activity level: {activity_level}")
451
-
452
- sensible_gain = activity_gains[activity_level]["sensible"]
453
- latent_gain = activity_gains[activity_level]["latent"]
454
-
455
- # Apply occupancy schedule
456
- occupancy_factor = 1.0
457
- if occupancy_schedule and len(occupancy_schedule) == 24:
458
- occupancy_factor = occupancy_schedule[hour]
459
-
460
- sensible_load = num_people * sensible_gain * occupancy_factor
461
- latent_load = num_people * latent_gain * occupancy_factor
462
- total_load = sensible_load + latent_load
 
 
 
 
463
 
464
- return {
465
- "sensible": max(0, sensible_load),
466
- "latent": max(0, latent_load),
467
- "total": max(0, total_load)
468
- }
469
 
470
- def calculate_lights_cooling_load(self, power: float, use_factor: float,
471
- special_allowance: float, hour: int,
472
- lighting_schedule: Optional[List[float]] = None) -> float:
 
 
 
 
473
  """
474
- Calculate cooling load due to lights with schedule.
475
 
476
  Args:
477
- power: Installed lighting power in W
478
- use_factor: Usage factor (0-1)
479
- special_allowance: Special allowance factor for fixtures (0-1)
480
- hour: Hour of the day (0-23)
481
- lighting_schedule: List of 24 hourly lighting factors (0-1, default: None)
482
 
483
  Returns:
484
- Cooling load in W
485
  """
486
- schedule_factor = 1.0
487
- if lighting_schedule and len(lighting_schedule) == 24:
488
- schedule_factor = lighting_schedule[hour]
 
489
 
490
- cooling_load = power * use_factor * (1 + special_allowance) * schedule_factor
491
-
492
- return max(0, cooling_load)
493
 
494
- def calculate_equipment_cooling_load(self, power: float, use_factor: float,
495
- radiation_factor: float, hour: int,
496
- equipment_schedule: Optional[List[float]] = None) -> Dict[str, float]:
 
 
 
 
497
  """
498
- Calculate sensible and latent cooling loads due to equipment with schedule.
499
 
500
  Args:
501
- power: Equipment power in W
502
- use_factor: Usage factor (0-1)
503
- radiation_factor: Radiation factor (0-1)
504
- hour: Hour of the day (0-23)
505
- equipment_schedule: List of 24 hourly equipment factors (0-1, default: None)
506
 
507
  Returns:
508
- Dictionary with sensible, latent, and total cooling loads in W
509
  """
510
- schedule_factor = 1.0
511
- if equipment_schedule and len(equipment_schedule) == 24:
512
- schedule_factor = equipment_schedule[hour]
513
-
514
- sensible_load = power * use_factor * radiation_factor * schedule_factor
515
- latent_load = power * use_factor * (1 - radiation_factor) * schedule_factor
516
- total_load = sensible_load + latent_load
 
 
 
517
 
518
- return {
519
- "sensible": max(0, sensible_load),
520
- "latent": max(0, latent_load),
521
- "total": max(0, total_load)
522
- }
523
 
524
- def calculate_hourly_cooling_loads(self, building_components: Dict[str, List[Any]],
525
- outdoor_conditions: Dict[str, Any],
526
- indoor_conditions: Dict[str, Any],
527
- internal_loads: Dict[str, Any],
528
- building_volume: float = 300.0) -> Dict[int, Dict[str, float]]:
 
 
 
 
529
  """
530
- Calculate hourly cooling loads for a building with enhanced calculations.
531
 
532
  Args:
533
- building_components: Dictionary with lists of building components
534
- outdoor_conditions: Dictionary with outdoor conditions
535
- indoor_conditions: Dictionary with indoor conditions
536
- internal_loads: Dictionary with internal loads
537
- building_volume: Building volume in m³ (default: 300 m³)
 
538
 
539
  Returns:
540
- Dictionary with hourly cooling loads
541
  """
542
- # Extract inputs
543
- walls = building_components.get("walls", [])
544
- roofs = building_components.get("roofs", [])
545
- floors = building_components.get("floors", [])
546
- windows = building_components.get("windows", [])
547
- doors = building_components.get("doors", [])
548
-
549
- outdoor_temp = outdoor_conditions.get("temperature", 35.0)
550
- outdoor_rh = outdoor_conditions.get("relative_humidity", 50.0)
551
- ground_temp = outdoor_conditions.get("ground_temperature", 20.0)
552
- month = outdoor_conditions.get("month", "Jul")
553
- latitude = outdoor_conditions.get("latitude", "40N")
554
- wind_speed = outdoor_conditions.get("wind_speed", 4.0)
555
- day_of_year = outdoor_conditions.get("day_of_year", 204)
556
-
557
- indoor_temp = indoor_conditions.get("temperature", 24.0)
558
- indoor_rh = indoor_conditions.get("relative_humidity", 50.0)
559
-
560
- people = internal_loads.get("people", {})
561
- lights = internal_loads.get("lights", {})
562
- equipment = internal_loads.get("equipment", {})
563
- infiltration = internal_loads.get("infiltration", {})
564
- ventilation = internal_loads.get("ventilation", {})
565
-
566
- # Initialize hourly loads
567
- hourly_loads = {}
568
-
569
- for hour in range(24):
570
- loads = {
571
- "walls": 0,
572
- "roofs": 0,
573
- "floors": 0,
574
- "windows_conduction": 0,
575
- "windows_solar": 0,
576
- "doors": 0,
577
- "infiltration_sensible": 0,
578
- "infiltration_latent": 0,
579
- "ventilation_sensible": 0,
580
- "ventilation_latent": 0,
581
- "people_sensible": 0,
582
- "people_latent": 0,
583
- "lights": 0,
584
- "equipment_sensible": 0,
585
- "equipment_latent": 0,
586
- "total_sensible": 0,
587
- "total_latent": 0,
588
- "total": 0
589
- }
590
-
591
- # Wall loads
592
- for wall in walls:
593
- loads["walls"] += self.calculate_wall_cooling_load(
594
- wall=wall,
595
- outdoor_temp=outdoor_temp,
596
- indoor_temp=indoor_temp,
597
- month=month,
598
- hour=hour,
599
- latitude=latitude,
600
- color=wall.color if hasattr(wall, "color") else "Dark",
601
- day_of_year=day_of_year,
602
- wind_speed=wind_speed
603
- )
604
-
605
- # Roof loads
606
- for roof in roofs:
607
- loads["roofs"] += self.calculate_roof_cooling_load(
608
- roof=roof,
609
- outdoor_temp=outdoor_temp,
610
- indoor_temp=indoor_temp,
611
- month=month,
612
- hour=hour,
613
- latitude=latitude,
614
- color=roof.color if hasattr(roof, "color") else "Dark",
615
- day_of_year=day_of_year,
616
- wind_speed=wind_speed
617
- )
618
-
619
- # Floor loads
620
- for floor in floors:
621
- loads["floors"] += self.calculate_floor_cooling_load(
622
- floor=floor,
623
- ground_temp=ground_temp,
624
- indoor_temp=indoor_temp
625
- )
626
 
627
- # Window loads
628
- for window in windows:
629
- window_loads = self.calculate_window_cooling_load(
630
- window=window,
631
- outdoor_temp=outdoor_temp,
632
- indoor_temp=indoor_temp,
633
- month=month,
634
- hour=hour,
635
- latitude=latitude,
636
- shading_coefficient=window.shading_coefficient if hasattr(window, "shading_coefficient") else 1.0,
637
- day_of_year=day_of_year
638
- )
639
- loads["windows_conduction"] += window_loads["conduction"]
640
- loads["windows_solar"] += window_loads["solar"]
641
-
642
- # Door loads
643
- for door in doors:
644
- loads["doors"] += self.calculate_door_cooling_load(
645
- door=door,
646
- outdoor_temp=outdoor_temp,
647
- indoor_temp=indoor_temp
648
- )
649
-
650
- # Infiltration loads
651
- if infiltration:
652
- infiltration_loads = self.calculate_infiltration_cooling_load(
653
- building_volume=building_volume,
654
- outdoor_temp=outdoor_temp,
655
- indoor_temp=indoor_temp,
656
- outdoor_rh=outdoor_rh,
657
- indoor_rh=indoor_rh,
658
- wind_speed=wind_speed,
659
- height=infiltration.get("height", 3.0),
660
- crack_length=infiltration.get("crack_length", 10.0)
661
- )
662
- loads["infiltration_sensible"] = infiltration_loads["sensible"]
663
- loads["infiltration_latent"] = infiltration_loads["latent"]
664
-
665
- # Ventilation loads
666
- if ventilation:
667
- flow_rate = ventilation.get("flow_rate", 0.0)
668
- ventilation_loads = self.calculate_ventilation_cooling_load(
669
- flow_rate=flow_rate,
670
- outdoor_temp=outdoor_temp,
671
- indoor_temp=indoor_temp,
672
- outdoor_rh=outdoor_rh,
673
- indoor_rh=indoor_rh
674
- )
675
- loads["ventilation_sensible"] = ventilation_loads["sensible"]
676
- loads["ventilation_latent"] = ventilation_loads["latent"]
677
-
678
- # People loads
679
- if people:
680
- loads["people_sensible"], loads["people_latent"] = self.calculate_people_cooling_load(
681
- num_people=people.get("number", 0),
682
- activity_level=people.get("activity_level", "Seated/Resting"),
683
- hour=hour,
684
- occupancy_schedule=people.get("occupancy_schedule", None)
685
- )["sensible"], self.calculate_people_cooling_load(
686
- num_people=people.get("number", 0),
687
- activity_level=people.get("activity_level", "Seated/Resting"),
688
- hour=hour,
689
- occupancy_schedule=people.get("occupancy_schedule", None)
690
- )["latent"]
691
-
692
- # Lights loads
693
- if lights:
694
- loads["lights"] = self.calculate_lights_cooling_load(
695
- power=lights.get("power", 0.0),
696
- use_factor=lights.get("use_factor", 1.0),
697
- special_allowance=lights.get("special_allowance", 0.0),
698
- hour=hour,
699
- lighting_schedule=lights.get("lighting_schedule", None)
700
- )
701
 
702
- # Equipment loads
703
- if equipment:
704
- equipment_loads = self.calculate_equipment_cooling_load(
705
- power=equipment.get("power", 0.0),
706
- use_factor=equipment.get("use_factor", 1.0),
707
- radiation_factor=equipment.get("radiation_factor", 0.7),
708
- hour=hour,
709
- equipment_schedule=equipment.get("equipment_schedule", None)
710
- )
711
- loads["equipment_sensible"] = equipment_loads["sensible"]
712
- loads["equipment_latent"] = equipment_loads["latent"]
713
-
714
- # Calculate totals
715
- loads["total_sensible"] = sum([
716
- loads["walls"], loads["roofs"], loads["floors"],
717
- loads["windows_conduction"], loads["windows_solar"],
718
- loads["doors"], loads["infiltration_sensible"],
719
- loads["ventilation_sensible"], loads["people_sensible"],
720
- loads["lights"], loads["equipment_sensible"]
721
- ])
722
- loads["total_latent"] = sum([
723
- loads["infiltration_latent"], loads["ventilation_latent"],
724
- loads["people_latent"], loads["equipment_latent"]
725
- ])
726
- loads["total"] = loads["total_sensible"] + loads["total_latent"]
727
-
728
- hourly_loads[hour] = loads
729
 
730
- return hourly_loads
 
731
 
732
- def calculate_design_cooling_load(self, hourly_loads: Dict[int, Dict[str, float]]) -> Dict[str, float]:
 
 
 
 
 
 
 
733
  """
734
- Calculate design cooling load based on hourly loads.
735
 
736
  Args:
737
- hourly_loads: Dictionary with hourly cooling loads
 
 
 
 
738
 
739
  Returns:
740
- Dictionary with design cooling loads
741
- """
742
- max_hour = max(hourly_loads.keys(), key=lambda h: hourly_loads[h]["total"])
743
- design_loads = hourly_loads[max_hour].copy()
744
- design_loads["design_hour"] = max_hour
745
- return design_loads
746
-
747
- def calculate_cooling_load_summary(self, design_loads: Dict[str, float]) -> Dict[str, float]:
748
  """
749
- Calculate cooling load summary.
750
-
751
- Args:
752
- design_loads: Dictionary with design cooling loads
753
 
754
- Returns:
755
- Dictionary with cooling load summary
756
- """
757
- envelope_loads = sum([
758
- design_loads["walls"], design_loads["roofs"], design_loads["floors"],
759
- design_loads["windows_conduction"], design_loads["windows_solar"],
760
- design_loads["doors"]
761
- ])
762
- ventilation_loads = design_loads["ventilation_sensible"] + design_loads["ventilation_latent"]
763
- infiltration_loads = design_loads["infiltration_sensible"] + design_loads["infiltration_latent"]
764
- internal_loads = sum([
765
- design_loads["people_sensible"], design_loads["people_latent"],
766
- design_loads["lights"], design_loads["equipment_sensible"],
767
- design_loads["equipment_latent"]
768
- ])
769
- shr = design_loads["total_sensible"] / design_loads["total"] if design_loads["total"] > 0 else 1.0
770
 
771
- return {
772
- "envelope_loads": envelope_loads,
773
- "ventilation_loads": ventilation_loads,
774
- "infiltration_loads": infiltration_loads,
775
- "internal_loads": internal_loads,
776
- "total_sensible": design_loads["total_sensible"],
777
- "total_latent": design_loads["total_latent"],
778
- "total": design_loads["total"],
779
- "sensible_heat_ratio": shr,
780
- "design_hour": design_loads["design_hour"]
781
- }
782
-
783
 
784
- # Create a singleton instance
785
- cooling_load_calculator = CoolingLoadCalculator()
786
 
787
- # Example usage
788
  if __name__ == "__main__":
789
- from data.building_components import Wall, Roof, Window, Door, Orientation, ComponentType
790
-
791
- # Sample building components
792
- wall = Wall(
793
- id="wall1",
794
- name="Exterior Wall",
795
- component_type=ComponentType.WALL,
796
- u_value=0.5,
797
- area=20.0,
798
- orientation=Orientation.SOUTH,
799
- wall_type="Brick",
800
- wall_group="B",
801
- thermal_mass=100000,
802
- time_constant=2.0,
803
- color="Dark"
804
- )
805
- roof = Roof(
806
- id="roof1",
807
- name="Flat Roof",
808
- component_type=ComponentType.ROOF,
809
- u_value=0.3,
810
- area=50.0,
811
- orientation=Orientation.HORIZONTAL,
812
- roof_type="Concrete",
813
- roof_group="C",
814
- thermal_mass=200000,
815
- time_constant=3.0,
816
- color="Dark"
817
- )
818
- window = Window(
819
- id="window1",
820
- name="South Window",
821
- component_type=ComponentType.WINDOW,
822
- u_value=2.8,
823
- area=5.0,
824
- orientation=Orientation.SOUTH,
825
- shgc=0.7,
826
- vt=0.8,
827
- window_type="Double Glazed",
828
- glazing_layers=2,
829
- gas_fill="Air",
830
- low_e_coating=False,
831
- shading_coefficient=1.0
832
- )
833
 
 
834
  building_components = {
835
- "walls": [wall],
836
- "roofs": [roof],
837
- "windows": [window],
838
- "doors": [],
839
- "floors": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
  }
841
 
842
- # Define conditions
843
  outdoor_conditions = {
844
- "temperature": 35.0,
845
- "relative_humidity": 50.0,
846
- "ground_temperature": 20.0,
847
- "month": "Jul",
848
- "latitude": "40N",
849
- "wind_speed": 4.0,
850
- "day_of_year": 204
851
  }
 
852
  indoor_conditions = {
853
- "temperature": 24.0,
854
- "relative_humidity": 50.0
855
  }
856
 
857
- # Define internal loads with schedules
858
- occupancy_schedule = [0.0] * 8 + [1.0] * 8 + [0.5] * 8 # 8h off, 8h full, 8h half
859
- lighting_schedule = [0.1] * 8 + [0.9] * 8 + [0.5] * 8
860
- equipment_schedule = [0.2] * 8 + [0.8] * 8 + [0.4] * 8
861
-
862
  internal_loads = {
863
- "people": {
864
- "number": 3,
865
- "activity_level": "Seated/Resting",
866
- "occupancy_schedule": occupancy_schedule
867
  },
868
- "lights": {
869
- "power": 500.0,
870
- "use_factor": 0.9,
871
- "special_allowance": 0.1,
872
- "lighting_schedule": lighting_schedule
873
  },
874
- "equipment": {
875
- "power": 1000.0,
876
- "use_factor": 0.7,
877
- "radiation_factor": 0.7,
878
- "equipment_schedule": equipment_schedule
879
  },
880
- "infiltration": {
881
- "height": 3.0,
882
- "crack_length": 10.0
 
883
  },
884
- "ventilation": {
885
- "flow_rate": 0.1
886
- }
 
887
  }
888
 
 
 
889
  # Calculate loads
890
- hourly_loads = cooling_load_calculator.calculate_hourly_cooling_loads(
891
  building_components=building_components,
892
  outdoor_conditions=outdoor_conditions,
893
  indoor_conditions=indoor_conditions,
894
  internal_loads=internal_loads,
895
- building_volume=300.0
896
  )
897
- design_loads = cooling_load_calculator.calculate_design_cooling_load(hourly_loads)
898
- summary = cooling_load_calculator.calculate_cooling_load_summary(design_loads)
899
 
900
- # Print results
901
- print("Cooling Load Summary:")
902
- for key, value in summary.items():
903
- if isinstance(value, float):
904
- print(f"{key.replace('_', ' ').title()}: {value:.2f} W")
905
- else:
906
- print(f"{key.replace('_', ' ').title()}: {value}")
 
1
  """
2
  Cooling load calculation module for HVAC Load Calculator.
3
+ Based on ASHRAE steady-state calculation methods.
4
+
5
+ Author: Dr Majed Abuseif
6
+ Date: March 2025
7
+ Version: 1.0.0
8
  """
9
 
10
  from typing import Dict, List, Any, Optional, Tuple
 
11
  import numpy as np
12
+ from datetime import datetime
 
 
 
 
 
 
13
  from data.ashrae_tables import ASHRAETables
 
14
  from utils.heat_transfer import HeatTransferCalculations
15
+ from app.component_selection import Wall, Roof, Window, Door, Orientation
 
 
16
 
17
 
18
  class CoolingLoadCalculator:
19
+ """Class for cooling load calculations."""
20
 
21
  def __init__(self):
22
+ """Initialize cooling load calculator."""
 
 
23
  self.ashrae_tables = ASHRAETables()
24
+ self.heat_transfer = HeatTransferCalculations()
25
+ self.hours = list(range(1, 25))
26
 
27
+ def calculate_hourly_cooling_loads(
28
+ self,
29
+ building_components: Dict[str, List[Any]],
30
+ outdoor_conditions: Dict[str, Any],
31
+ indoor_conditions: Dict[str, Any],
32
+ internal_loads: Dict[str, Any],
33
+ building_volume: float
34
+ ) -> Dict[str, Any]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  """
36
+ Calculate hourly cooling loads for all components.
37
 
38
  Args:
39
+ building_components: Dictionary of building components
40
+ outdoor_conditions: Outdoor weather conditions
41
+ indoor_conditions: Indoor design conditions
42
+ internal_loads: Internal heat gains
43
+ building_volume: Building volume in cubic meters
 
 
 
 
44
 
45
  Returns:
46
+ Dictionary containing hourly cooling loads
47
  """
48
+ hourly_loads = {
49
+ 'walls': {h: 0.0 for h in self.hours},
50
+ 'roofs': {h: 0.0 for h in self.hours},
51
+ 'windows_conduction': {h: 0.0 for h in self.hours},
52
+ 'windows_solar': {h: 0.0 for h in self.hours},
53
+ 'doors': {h: 0.0 for h in self.hours},
54
+ 'people_sensible': {h: 0.0 for h in self.hours},
55
+ 'people_latent': {h: 0.0 for h in self.hours},
56
+ 'lights': {h: 0.0 for h in self.hours},
57
+ 'equipment_sensible': {h: 0.0 for h in self.hours},
58
+ 'equipment_latent': {h: 0.0 for h in self.hours},
59
+ 'infiltration_sensible': {h: 0.0 for h in self.hours},
60
+ 'infiltration_latent': {h: 0.0 for h in self.hours},
61
+ 'ventilation_sensible': {h: 0.0 for h in self.hours},
62
+ 'ventilation_latent': {h: 0.0 for h in self.hours}
63
+ }
64
 
65
+ try:
66
+ # Calculate loads for walls
67
+ for wall in building_components.get('walls', []):
68
+ for hour in self.hours:
69
+ load = self.calculate_wall_cooling_load(
70
+ wall=wall,
71
+ outdoor_temp=outdoor_conditions['temperature'],
72
+ indoor_temp=indoor_conditions['temperature'],
73
+ month=outdoor_conditions['month'],
74
+ hour=hour,
75
+ latitude=outdoor_conditions['latitude']
76
+ )
77
+ hourly_loads['walls'][hour] += load
78
+
79
+ # Calculate loads for roofs
80
+ for roof in building_components.get('roofs', []):
81
+ for hour in self.hours:
82
+ load = self.calculate_roof_cooling_load(
83
+ roof=roof,
84
+ outdoor_temp=outdoor_conditions['temperature'],
85
+ indoor_temp=indoor_conditions['temperature'],
86
+ month=outdoor_conditions['month'],
87
+ hour=hour,
88
+ latitude=outdoor_conditions['latitude']
89
+ )
90
+ hourly_loads['roofs'][hour] += load
91
+
92
+ # Calculate loads for windows
93
+ for window in building_components.get('windows', []):
94
+ for hour in self.hours:
95
+ load_dict = self.calculate_window_cooling_load(
96
+ window=window,
97
+ outdoor_temp=outdoor_conditions['temperature'],
98
+ indoor_temp=indoor_conditions['temperature'],
99
+ month=outdoor_conditions['month'],
100
+ hour=hour,
101
+ latitude=outdoor_conditions['latitude'],
102
+ shading_coefficient=window.shading_coefficient
103
+ )
104
+ hourly_loads['windows_conduction'][hour] += load_dict['conduction']
105
+ hourly_loads['windows_solar'][hour] += load_dict['solar']
106
+
107
+ # Calculate loads for doors
108
+ for door in building_components.get('doors', []):
109
+ for hour in self.hours:
110
+ load = self.calculate_door_cooling_load(
111
+ door=door,
112
+ outdoor_temp=outdoor_conditions['temperature'],
113
+ indoor_temp=indoor_conditions['temperature']
114
+ )
115
+ hourly_loads['doors'][hour] += load
116
+
117
+ # Calculate internal loads
118
+ for hour in self.hours:
119
+ # People loads
120
+ people_load = self.calculate_people_cooling_load(
121
+ num_people=internal_loads['people']['number'],
122
+ activity_level=internal_loads['people']['activity_level'],
123
+ hour=hour
124
  )
125
+ hourly_loads['people_sensible'][hour] += people_load['sensible']
126
+ hourly_loads['people_latent'][hour] += people_load['latent']
127
+
128
+ # Lighting loads
129
+ lights_load = self.calculate_lights_cooling_load(
130
+ power=internal_loads['lights']['power'],
131
+ use_factor=internal_loads['lights']['use_factor'],
132
+ special_allowance=internal_loads['lights']['special_allowance'],
133
+ hour=hour
134
+ )
135
+ hourly_loads['lights'][hour] += lights_load
136
+
137
+ # Equipment loads
138
+ equipment_load = self.calculate_equipment_cooling_load(
139
+ power=internal_loads['equipment']['power'],
140
+ use_factor=internal_loads['equipment']['use_factor'],
141
+ radiation_factor=internal_loads['equipment']['radiation_factor'],
142
+ hour=hour
143
+ )
144
+ hourly_loads['equipment_sensible'][hour] += equipment_load['sensible']
145
+ hourly_loads['equipment_latent'][hour] += equipment_load['latent']
146
+
147
+ # Infiltration loads
148
+ infiltration_load = self.calculate_infiltration_cooling_load(
149
+ flow_rate=internal_loads['infiltration']['flow_rate'],
150
+ building_volume=building_volume,
151
+ outdoor_temp=outdoor_conditions['temperature'],
152
+ outdoor_rh=outdoor_conditions['relative_humidity'],
153
+ indoor_temp=indoor_conditions['temperature'],
154
+ indoor_rh=indoor_conditions['relative_humidity']
155
+ )
156
+ hourly_loads['infiltration_sensible'][hour] += infiltration_load['sensible']
157
+ hourly_loads['infiltration_latent'][hour] += infiltration_load['latent']
158
+
159
+ # Ventilation loads
160
+ ventilation_load = self.calculate_ventilation_cooling_load(
161
+ flow_rate=internal_loads['ventilation']['flow_rate'],
162
+ outdoor_temp=outdoor_conditions['temperature'],
163
+ outdoor_rh=outdoor_conditions['relative_humidity'],
164
+ indoor_temp=indoor_conditions['temperature'],
165
+ indoor_rh=indoor_conditions['relative_humidity']
166
+ )
167
+ hourly_loads['ventilation_sensible'][hour] += ventilation_load['sensible']
168
+ hourly_loads['ventilation_latent'][hour] += ventilation_load['latent']
169
 
170
+ return hourly_loads
 
171
 
172
+ except Exception as e:
173
+ raise Exception(f"Error in calculate_hourly_cooling_loads: {str(e)}")
174
 
175
+ def calculate_design_cooling_load(self, hourly_loads: Dict[str, Any]) -> Dict[str, Any]:
 
 
 
176
  """
177
+ Calculate design cooling load based on peak hourly loads.
178
 
179
  Args:
180
+ hourly_loads: Dictionary of hourly cooling loads
 
 
 
 
 
 
 
 
181
 
182
  Returns:
183
+ Dictionary containing design cooling loads
184
  """
185
+ try:
186
+ design_loads = {}
187
+ total_loads = []
188
+
189
+ for hour in self.hours:
190
+ total_load = sum([
191
+ hourly_loads['walls'][hour],
192
+ hourly_loads['roofs'][hour],
193
+ hourly_loads['windows_conduction'][hour],
194
+ hourly_loads['windows_solar'][hour],
195
+ hourly_loads['doors'][hour],
196
+ hourly_loads['people_sensible'][hour],
197
+ hourly_loads['people_latent'][hour],
198
+ hourly_loads['lights'][hour],
199
+ hourly_loads['equipment_sensible'][hour],
200
+ hourly_loads['equipment_latent'][hour],
201
+ hourly_loads['infiltration_sensible'][hour],
202
+ hourly_loads['infiltration_latent'][hour],
203
+ hourly_loads['ventilation_sensible'][hour],
204
+ hourly_loads['ventilation_latent'][hour]
205
+ ])
206
+ total_loads.append(total_load)
207
+
208
+ design_hour = self.hours[np.argmax(total_loads)]
209
+
210
+ design_loads = {
211
+ 'design_hour': design_hour,
212
+ 'walls': hourly_loads['walls'][design_hour],
213
+ 'roofs': hourly_loads['roofs'][design_hour],
214
+ 'windows_conduction': hourly_loads['windows_conduction'][design_hour],
215
+ 'windows_solar': hourly_loads['windows_solar'][design_hour],
216
+ 'doors': hourly_loads['doors'][design_hour],
217
+ 'people_sensible': hourly_loads['people_sensible'][design_hour],
218
+ 'people_latent': hourly_loads['people_latent'][design_hour],
219
+ 'lights': hourly_loads['lights'][design_hour],
220
+ 'equipment_sensible': hourly_loads['equipment_sensible'][design_hour],
221
+ 'equipment_latent': hourly_loads['equipment_latent'][design_hour],
222
+ 'infiltration_sensible': hourly_loads['infiltration_sensible'][design_hour],
223
+ 'infiltration_latent': hourly_loads['infiltration_latent'][design_hour],
224
+ 'ventilation_sensible': hourly_loads['ventilation_sensible'][design_hour],
225
+ 'ventilation_latent': hourly_loads['ventilation_latent'][design_hour]
226
+ }
227
+
228
+ return design_loads
 
 
 
 
 
 
 
 
 
 
 
 
 
 
229
 
230
+ except Exception as e:
231
+ raise Exception(f"Error in calculate_design_cooling_load: {str(e)}")
232
 
233
+ def calculate_cooling_load_summary(self, design_loads: Dict[str, Any]) -> Dict[str, float]:
 
 
234
  """
235
+ Calculate summary of cooling loads.
236
 
237
  Args:
238
+ design_loads: Dictionary of design cooling loads
 
 
 
 
 
 
 
239
 
240
  Returns:
241
+ Dictionary containing cooling load summary
242
  """
243
+ try:
244
+ total_sensible = (
245
+ design_loads['walls'] +
246
+ design_loads['roofs'] +
247
+ design_loads['windows_conduction'] +
248
+ design_loads['windows_solar'] +
249
+ design_loads['doors'] +
250
+ design_loads['people_sensible'] +
251
+ design_loads['lights'] +
252
+ design_loads['equipment_sensible'] +
253
+ design_loads['infiltration_sensible'] +
254
+ design_loads['ventilation_sensible']
255
+ )
256
+
257
+ total_latent = (
258
+ design_loads['people_latent'] +
259
+ design_loads['equipment_latent'] +
260
+ design_loads['infiltration_latent'] +
261
+ design_loads['ventilation_latent']
262
+ )
263
+
264
+ total = total_sensible + total_latent
265
+
266
+ return {
267
+ 'total_sensible': total_sensible,
268
+ 'total_latent': total_latent,
269
+ 'total': total
270
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
+ except Exception as e:
273
+ raise Exception(f"Error in calculate_cooling_load_summary: {str(e)}")
 
 
 
274
 
275
+ def calculate_wall_cooling_load(
276
+ self,
277
+ wall: Wall,
278
+ outdoor_temp: float,
279
+ indoor_temp: float,
280
+ month: str,
281
+ hour: int,
282
+ latitude: str
283
+ ) -> float:
284
  """
285
+ Calculate cooling load for a wall.
286
 
287
  Args:
288
+ wall: Wall component
289
+ outdoor_temp: Outdoor temperature (°C)
290
+ indoor_temp: Indoor temperature (°C)
291
+ month: Design month
292
+ hour: Hour of the day
293
+ latitude: Latitude (e.g., '40N')
294
 
295
  Returns:
296
+ Cooling load in Watts
297
  """
298
+ try:
299
+ cltd = self.ashrae_tables.calculate_corrected_cltd_wall(
300
+ wall_group=wall.wall_group,
301
+ orientation=wall.orientation.value,
302
+ hour=hour,
303
+ color='Dark',
304
+ month=month,
305
+ latitude=latitude,
306
+ indoor_temp=indoor_temp,
307
+ outdoor_temp=outdoor_temp
308
+ )
309
+
310
+ load = wall.u_value * wall.area * cltd
311
+ return max(load, 0.0)
312
 
313
+ except Exception as e:
314
+ raise Exception(f"Error in calculate_wall_cooling_load: {str(e)}")
315
 
316
+ def calculate_roof_cooling_load(
317
+ self,
318
+ roof: Roof,
319
+ outdoor_temp: float,
320
+ indoor_temp: float,
321
+ month: str,
322
+ hour: int,
323
+ latitude: str
324
+ ) -> float:
325
  """
326
+ Calculate cooling load for a roof.
327
 
328
  Args:
329
+ roof: Roof component
330
+ outdoor_temp: Outdoor temperature (°C)
331
+ indoor_temp: Indoor temperature (°C)
332
+ month: Design month
333
+ hour: Hour of the day
334
+ latitude: Latitude (e.g., '40N')
335
 
336
  Returns:
337
+ Cooling load in Watts
338
  """
339
+ try:
340
+ cltd = self.ashrae_tables.calculate_corrected_cltd_roof(
341
+ roof_group=roof.roof_group,
342
+ hour=hour,
343
+ color='Dark',
344
+ month=month,
345
+ latitude=latitude,
346
+ indoor_temp=indoor_temp,
347
+ outdoor_temp=outdoor_temp
348
+ )
349
+
350
+ load = roof.u_value * roof.area * cltd
351
+ return max(load, 0.0)
352
 
353
+ except Exception as e:
354
+ raise Exception(f"Error in calculate_roof_cooling_load: {str(e)}")
355
 
356
+ def calculate_window_cooling_load(
357
+ self,
358
+ window: Window,
359
+ outdoor_temp: float,
360
+ indoor_temp: float,
361
+ month: str,
362
+ hour: int,
363
+ latitude: str,
364
+ shading_coefficient: float
365
+ ) -> Dict[str, float]:
366
  """
367
+ Calculate cooling load for a window (conduction and solar).
368
 
369
  Args:
370
+ window: Window component
371
+ outdoor_temp: Outdoor temperature (°C)
372
+ indoor_temp: Indoor temperature (°C)
373
+ month: Design month
374
+ hour: Hour of the day
375
+ latitude: Latitude (e.g., '40N')
376
+ shading_coefficient: Shading coefficient for drapery
 
377
 
378
  Returns:
379
+ Dictionary with conduction and solar loads in Watts
380
  """
381
+ try:
382
+ # Conduction load
383
+ delta_t = outdoor_temp - indoor_temp
384
+ conduction_load = window.u_value * window.area * delta_t
385
+
386
+ # Solar load
387
+ solar_altitude = self.heat_transfer.solar.solar_altitude
388
+ scl_latitude = f"{float(latitude[:-1])}_{month.upper()}"
389
+ scl = self.ashrae_tables.get_scl(
390
+ orientation=window.orientation.value,
391
+ hour=hour,
392
+ latitude=scl_latitude
393
+ )
394
+
395
+ solar_load = window.area * window.shgc * scl * shading_coefficient
396
+
397
+ return {
398
+ 'conduction': max(conduction_load, 0.0),
399
+ 'solar': max(solar_load, 0.0),
400
+ 'total': max(conduction_load + solar_load, 0.0)
401
+ }
 
 
 
 
 
 
 
 
 
 
 
 
402
 
403
+ except Exception as e:
404
+ raise Exception(f"Error in calculate_window_cooling_load: {str(e)}")
 
 
 
405
 
406
+ def calculate_door_cooling_load(
407
+ self,
408
+ door: Door,
409
+ outdoor_temp: float,
410
+ indoor_temp: float
411
+ ) -> float:
412
  """
413
+ Calculate cooling load for a door.
414
 
415
  Args:
416
+ door: Door component
417
+ outdoor_temp: Outdoor temperature (°C)
418
+ indoor_temp: Indoor temperature (°C)
 
 
419
 
420
  Returns:
421
+ Cooling load in Watts
422
  """
423
+ try:
424
+ delta_t = outdoor_temp - indoor_temp
425
+ load = door.u_value * door.area * delta_t
426
+ return max(load, 0.0)
 
 
 
 
 
 
 
 
 
427
 
428
+ except Exception as e:
429
+ raise Exception(f"Error in calculate_door_cooling_load: {str(e)}")
 
 
 
430
 
431
+ def calculate_people_cooling_load(
432
+ self,
433
+ num_people: int,
434
+ activity_level: str,
435
+ hour: int
436
+ ) -> Dict[str, float]:
437
  """
438
+ Calculate cooling load from people.
439
 
440
  Args:
441
  num_people: Number of people
442
+ activity_level: Activity level
443
+ hour: Hour of the day
 
444
 
445
  Returns:
446
+ Dictionary with sensible and latent loads in Watts
447
  """
448
+ try:
449
+ sensible_gain = {
450
+ 'Seated/Resting': 70,
451
+ 'Light Work': 100,
452
+ 'Moderate Work': 150,
453
+ 'Heavy Work': 200
454
+ }.get(activity_level, 70)
455
+
456
+ latent_gain = {
457
+ 'Seated/Resting': 45,
458
+ 'Light Work': 75,
459
+ 'Moderate Work': 120,
460
+ 'Heavy Work': 180
461
+ }.get(activity_level, 45)
462
+
463
+ clf = self.ashrae_tables.get_clf_people(hour, '8h')
464
+
465
+ sensible_load = num_people * sensible_gain * clf
466
+ latent_load = num_people * latent_gain
467
+
468
+ return {
469
+ 'sensible': max(sensible_load, 0.0),
470
+ 'latent': max(latent_load, 0.0),
471
+ 'total': max(sensible_load + latent_load, 0.0)
472
+ }
473
 
474
+ except Exception as e:
475
+ raise Exception(f"Error in calculate_people_cooling_load: {str(e)}")
 
 
 
476
 
477
+ def calculate_lights_cooling_load(
478
+ self,
479
+ power: float,
480
+ use_factor: float,
481
+ special_allowance: float,
482
+ hour: int
483
+ ) -> float:
484
  """
485
+ Calculate cooling load from lighting.
486
 
487
  Args:
488
+ power: Lighting power (W)
489
+ use_factor: Usage factor
490
+ special_allowance: Special allowance factor
491
+ hour: Hour of the day
 
492
 
493
  Returns:
494
+ Cooling load in Watts
495
  """
496
+ try:
497
+ clf = self.ashrae_tables.get_clf_lights(hour, '8h')
498
+ load = power * use_factor * special_allowance * clf
499
+ return max(load, 0.0)
500
 
501
+ except Exception as e:
502
+ raise Exception(f"Error in calculate_lights_cooling_load: {str(e)}")
 
503
 
504
+ def calculate_equipment_cooling_load(
505
+ self,
506
+ power: float,
507
+ use_factor: float,
508
+ radiation_factor: float,
509
+ hour: int
510
+ ) -> Dict[str, float]:
511
  """
512
+ Calculate cooling load from equipment.
513
 
514
  Args:
515
+ power: Equipment power (W)
516
+ use_factor: Usage factor
517
+ radiation_factor: Radiation factor
518
+ hour: Hour of the day
 
519
 
520
  Returns:
521
+ Dictionary with sensible and latent loads in Watts
522
  """
523
+ try:
524
+ clf = self.ashrae_tables.get_clf_equipment(hour, '8h')
525
+ sensible_load = power * use_factor * radiation_factor * clf
526
+ latent_load = power * use_factor * (1 - radiation_factor)
527
+
528
+ return {
529
+ 'sensible': max(sensible_load, 0.0),
530
+ 'latent': max(latent_load, 0.0),
531
+ 'total': max(sensible_load + latent_load, 0.0)
532
+ }
533
 
534
+ except Exception as e:
535
+ raise Exception(f"Error in calculate_equipment_cooling_load: {str(e)}")
 
 
 
536
 
537
+ def calculate_infiltration_cooling_load(
538
+ self,
539
+ flow_rate: float,
540
+ building_volume: float,
541
+ outdoor_temp: float,
542
+ outdoor_rh: float,
543
+ indoor_temp: float,
544
+ indoor_rh: float
545
+ ) -> Dict[str, float]:
546
  """
547
+ Calculate cooling load from infiltration.
548
 
549
  Args:
550
+ flow_rate: Infiltration flow rate (m³/s)
551
+ building_volume: Building volume (m³)
552
+ outdoor_temp: Outdoor temperature (°C)
553
+ outdoor_rh: Outdoor relative humidity (%)
554
+ indoor_temp: Indoor temperature (°C)
555
+ indoor_rh: Indoor relative humidity (%)
556
 
557
  Returns:
558
+ Dictionary with sensible and latent loads in Watts
559
  """
560
+ try:
561
+ air_changes_per_hour = (flow_rate * 3600) / building_volume
562
+ sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
 
564
+ # Calculate humidity ratio difference
565
+ outdoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(outdoor_temp, outdoor_rh)
566
+ indoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(indoor_temp, indoor_rh)
567
+ latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
568
 
569
+ return {
570
+ 'sensible': max(sensible_load, 0.0),
571
+ 'latent': max(latent_load, 0.0),
572
+ 'total': max(sensible_load + latent_load, 0.0)
573
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
574
 
575
+ except Exception as e:
576
+ raise Exception(f"Error in calculate_infiltration_cooling_load: {str(e)}")
577
 
578
+ def calculate_ventilation_cooling_load(
579
+ self,
580
+ flow_rate: float,
581
+ outdoor_temp: float,
582
+ outdoor_rh: float,
583
+ indoor_temp: float,
584
+ indoor_rh: float
585
+ ) -> Dict[str, float]:
586
  """
587
+ Calculate cooling load from ventilation.
588
 
589
  Args:
590
+ flow_rate: Ventilation flow rate (m³/s)
591
+ outdoor_temp: Outdoor temperature (°C)
592
+ outdoor_rh: Outdoor relative humidity (%)
593
+ indoor_temp: Indoor temperature (°C)
594
+ indoor_rh: Indoor relative humidity (%)
595
 
596
  Returns:
597
+ Dictionary with sensible and latent loads in Watts
 
 
 
 
 
 
 
598
  """
599
+ try:
600
+ sensible_load = 1.2 * flow_rate * 1000 * (outdoor_temp - indoor_temp)
 
 
601
 
602
+ # Calculate humidity ratio difference
603
+ outdoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(outdoor_temp, outdoor_rh)
604
+ indoor_w = self.heat_transfer.psychrometrics.calculate_humidity_ratio(indoor_temp, indoor_rh)
605
+ latent_load = 2501 * flow_rate * 1000 * (outdoor_w - indoor_w)
606
+
607
+ return {
608
+ 'sensible': max(sensible_load, 0.0),
609
+ 'latent': max(latent_load, 0.0),
610
+ 'total': max(sensible_load + latent_load, 0.0)
611
+ }
 
 
 
 
 
 
612
 
613
+ except Exception as e:
614
+ raise Exception(f"Error in calculate_ventilation_cooling_load: {str(e)}")
 
 
 
 
 
 
 
 
 
 
615
 
 
 
616
 
 
617
  if __name__ == "__main__":
618
+ # Example usage for testing
619
+ calculator = CoolingLoadCalculator()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
 
621
+ # Dummy inputs
622
  building_components = {
623
+ 'walls': [Wall(
624
+ name="North Wall",
625
+ orientation=Orientation.NORTH,
626
+ area=20.0,
627
+ u_value=0.5,
628
+ wall_group="A"
629
+ )],
630
+ 'roofs': [Roof(
631
+ name="Main Roof",
632
+ orientation=Orientation.HORIZONTAL,
633
+ area=100.0,
634
+ u_value=0.3,
635
+ roof_group="1"
636
+ )],
637
+ 'windows': [Window(
638
+ name="South Window",
639
+ orientation=Orientation.SOUTH,
640
+ area=10.0,
641
+ u_value=2.8,
642
+ shgc=0.7,
643
+ shading_device="Medium drapery",
644
+ shading_coefficient=0.8
645
+ )],
646
+ 'doors': [Door(
647
+ name="Main Door",
648
+ orientation=Orientation.NORTH,
649
+ area=2.0,
650
+ u_value=2.0
651
+ )]
652
  }
653
 
 
654
  outdoor_conditions = {
655
+ 'temperature': 35.0,
656
+ 'relative_humidity': 50.0,
657
+ 'ground_temperature': 20.0,
658
+ 'month': 'Jul',
659
+ 'latitude': '31.973N',
660
+ 'wind_speed': 4.0,
661
+ 'day_of_year': 204
662
  }
663
+
664
  indoor_conditions = {
665
+ 'temperature': 24.0,
666
+ 'relative_humidity': 50.0
667
  }
668
 
 
 
 
 
 
669
  internal_loads = {
670
+ 'people': {
671
+ 'number': 10,
672
+ 'activity_level': 'Seated/Resting',
673
+ 'operating_hours': '8:00-18:00'
674
  },
675
+ 'lights': {
676
+ 'power': 1000.0,
677
+ 'use_factor': 0.8,
678
+ 'special_allowance': 0.1,
679
+ 'hours_operation': '8h'
680
  },
681
+ 'equipment': {
682
+ 'power': 500.0,
683
+ 'use_factor': 0.7,
684
+ 'radiation_factor': 0.3,
685
+ 'hours_operation': '8h'
686
  },
687
+ 'infiltration': {
688
+ 'flow_rate': 0.05,
689
+ 'height': 3.0,
690
+ 'crack_length': 10.0
691
  },
692
+ 'ventilation': {
693
+ 'flow_rate': 0.1
694
+ },
695
+ 'operating_hours': '8:00-18:00'
696
  }
697
 
698
+ building_volume = 300.0
699
+
700
  # Calculate loads
701
+ hourly_loads = calculator.calculate_hourly_cooling_loads(
702
  building_components=building_components,
703
  outdoor_conditions=outdoor_conditions,
704
  indoor_conditions=indoor_conditions,
705
  internal_loads=internal_loads,
706
+ building_volume=building_volume
707
  )
 
 
708
 
709
+ design_loads = calculator.calculate_design_cooling_load(hourly_loads)
710
+ summary = calculator.calculate_cooling_load_summary(design_loads)
711
+
712
+ print("Cooling Load Summary:", summary)