Spaces:
Sleeping
Sleeping
Update app/hvac_loads.py
Browse files- app/hvac_loads.py +25 -19
app/hvac_loads.py
CHANGED
@@ -273,7 +273,7 @@ class TFMCalculations:
|
|
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")
|
279 |
if not material_library:
|
@@ -281,7 +281,7 @@ class TFMCalculations:
|
|
281 |
material_library = MaterialLibrary()
|
282 |
st.session_state.material_library = material_library
|
283 |
logger.info(f"Created new MaterialLibrary for {component_name}")
|
284 |
-
|
285 |
project_materials = st.session_state.get("project_data", {}).get("materials", {}).get("project", {})
|
286 |
project_constructions = st.session_state.get("project_data", {}).get("constructions", {}).get("project", {})
|
287 |
project_glazing_materials = st.session_state.get("project_data", {}).get("fenestrations", {}).get("project", {})
|
@@ -290,7 +290,7 @@ class TFMCalculations:
|
|
290 |
longitude = climate_data.get("longitude", 0.0)
|
291 |
timezone = climate_data.get("time_zone", 0.0)
|
292 |
ground_reflectivity = climate_data.get("ground_reflectivity", 0.2)
|
293 |
-
|
294 |
if not -90 <= latitude <= 90:
|
295 |
logger.warning(f"Invalid latitude {latitude} for {component_name}. Using default 0.0.")
|
296 |
latitude = 0.0
|
@@ -303,17 +303,17 @@ class TFMCalculations:
|
|
303 |
if not 0 <= ground_reflectivity <= 1:
|
304 |
logger.warning(f"Invalid ground_reflectivity {ground_reflectivity} for {component_name}. Using default 0.2.")
|
305 |
ground_reflectivity = 0.2
|
306 |
-
|
307 |
required_fields = ["month", "day", "hour", "global_horizontal_radiation", "direct_normal_radiation",
|
308 |
"diffuse_horizontal_radiation", "dry_bulb"]
|
309 |
if not all(field in hourly_data for field in required_fields):
|
310 |
logger.warning(f"Missing required fields in hourly_data for hour {hour} for {component_name}: {hourly_data}")
|
311 |
return 0
|
312 |
-
|
313 |
if hourly_data["global_horizontal_radiation"] <= 0:
|
314 |
logger.info(f"No solar load for hour {hour} due to GHI={hourly_data['global_horizontal_radiation']} for {component_name}")
|
315 |
return 0
|
316 |
-
|
317 |
month = hourly_data["month"]
|
318 |
day = hourly_data["day"]
|
319 |
hour = hourly_data["hour"]
|
@@ -321,30 +321,30 @@ class TFMCalculations:
|
|
321 |
dni = hourly_data.get("direct_normal_radiation", ghi * 0.7)
|
322 |
dhi = hourly_data.get("diffuse_horizontal_radiation", ghi * 0.3)
|
323 |
outdoor_temp = hourly_data["dry_bulb"]
|
324 |
-
|
325 |
if ghi < 0 or dni < 0 or dhi < 0:
|
326 |
logger.error(f"Negative radiation values for {month}/{day}/{hour} for {component_name}")
|
327 |
raise ValueError(f"Negative radiation values for {month}/{day}/{hour}")
|
328 |
-
|
329 |
logger.info(f"Processing solar for {month}/{day}/{hour} with GHI={ghi}, DNI={dni}, DHI={dhi}, "
|
330 |
f"dry_bulb={outdoor_temp} for {component_name}")
|
331 |
-
|
332 |
year = 2025
|
333 |
n = TFMCalculations.day_of_year(month, day, year)
|
334 |
EOT = TFMCalculations.equation_of_time(n)
|
335 |
lambda_std = 15 * timezone
|
336 |
standard_time = hour - 1 + 0.5
|
337 |
LST = standard_time + (4 * (lambda_std - longitude) + EOT) / 60
|
338 |
-
|
339 |
delta = 23.45 * math.sin(math.radians(360 / 365 * (284 + n)))
|
340 |
phi = math.radians(latitude)
|
341 |
delta_rad = math.radians(delta)
|
342 |
hra = 15 * (LST - 12)
|
343 |
hra_rad = math.radians(hra)
|
344 |
-
|
345 |
sin_alpha = math.sin(phi) * math.sin(delta_rad) + math.cos(phi) * math.cos(delta_rad) * math.cos(hra_rad)
|
346 |
alpha = math.degrees(math.asin(sin_alpha))
|
347 |
-
|
348 |
if abs(math.cos(math.radians(alpha))) < 0.01:
|
349 |
azimuth = 0
|
350 |
else:
|
@@ -353,10 +353,10 @@ class TFMCalculations:
|
|
353 |
azimuth = math.degrees(math.atan2(sin_az, cos_az))
|
354 |
if hra > 0:
|
355 |
azimuth = 360 - azimuth if azimuth > 0 else -azimuth
|
356 |
-
|
357 |
logger.info(f"Solar angles for {month}/{day}/{hour}: declination={delta:.2f}, LST={LST:.2f}, "
|
358 |
f"HRA={hra:.2f}, altitude={alpha:.2f}, azimuth={azimuth:.2f} for {component_name}")
|
359 |
-
|
360 |
building_info = {"orientation_angle": building_orientation}
|
361 |
try:
|
362 |
surface_tilt, surface_azimuth, h_o, emissivity, absorptivity = \
|
@@ -385,7 +385,7 @@ class TFMCalculations:
|
|
385 |
else: # WINDOW, SKYLIGHT
|
386 |
absorptivity = 0.0
|
387 |
h_o = DEFAULT_WINDOW_PROPERTIES["h_o"]
|
388 |
-
|
389 |
alpha_rad = math.radians(alpha)
|
390 |
surface_tilt_rad = math.radians(surface_tilt)
|
391 |
azimuth_rad = math.radians(azimuth)
|
@@ -400,7 +400,7 @@ class TFMCalculations:
|
|
400 |
logger.info(f" Component {component_name} at {month}/{day}/{hour}: "
|
401 |
f"surface_tilt={surface_tilt:.2f}, surface_azimuth={surface_azimuth:.2f}, "
|
402 |
f"cos_theta={cos_theta:.4f}")
|
403 |
-
|
404 |
view_factor = (1 - math.cos(surface_tilt_rad)) / 2
|
405 |
ground_reflected = ground_reflectivity * ghi * view_factor
|
406 |
|
@@ -415,14 +415,20 @@ class TFMCalculations:
|
|
415 |
shgc = component.get('shgc', 0.7) # Use component-stored shgc
|
416 |
glazing_type = SolarCalculations.GLAZING_TYPE_MAPPING.get(component.get('fenestration', ''), "Single Clear")
|
417 |
shading_coeff = component.get('shading_coefficient', 1.0) # Aligned with components.py
|
418 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
419 |
shgc_dynamic = shgc * SolarCalculations.calculate_dynamic_shgc(glazing_type, cos_theta)
|
420 |
|
421 |
solar_heat_gain = component.get('area', 0.0) * shgc_dynamic * I_t * shading_coeff / 1000
|
422 |
|
423 |
logger.info(f"Fenestration solar heat gain for {component_name} at {month}/{day}/{hour}: "
|
424 |
f"{solar_heat_gain:.4f} kW (area={component.get('area', 0.0)}, shgc_dynamic={shgc_dynamic:.4f}, "
|
425 |
-
f"I_t={I_t:.2f}, shading_coeff={shading_coeff})")
|
426 |
|
427 |
elif component_type in [ComponentType.WALL, ComponentType.ROOF]:
|
428 |
surface_resistance = 1/h_o
|
@@ -434,7 +440,7 @@ class TFMCalculations:
|
|
434 |
f"I_t={I_t:.2f}, surface_resistance={surface_resistance:.4f})")
|
435 |
|
436 |
return solar_heat_gain
|
437 |
-
|
438 |
except Exception as e:
|
439 |
logger.error(f"Error calculating solar load for {component_name} at hour {hour}: {str(e)}")
|
440 |
return 0
|
|
|
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")
|
279 |
if not material_library:
|
|
|
281 |
material_library = MaterialLibrary()
|
282 |
st.session_state.material_library = material_library
|
283 |
logger.info(f"Created new MaterialLibrary for {component_name}")
|
284 |
+
|
285 |
project_materials = st.session_state.get("project_data", {}).get("materials", {}).get("project", {})
|
286 |
project_constructions = st.session_state.get("project_data", {}).get("constructions", {}).get("project", {})
|
287 |
project_glazing_materials = st.session_state.get("project_data", {}).get("fenestrations", {}).get("project", {})
|
|
|
290 |
longitude = climate_data.get("longitude", 0.0)
|
291 |
timezone = climate_data.get("time_zone", 0.0)
|
292 |
ground_reflectivity = climate_data.get("ground_reflectivity", 0.2)
|
293 |
+
|
294 |
if not -90 <= latitude <= 90:
|
295 |
logger.warning(f"Invalid latitude {latitude} for {component_name}. Using default 0.0.")
|
296 |
latitude = 0.0
|
|
|
303 |
if not 0 <= ground_reflectivity <= 1:
|
304 |
logger.warning(f"Invalid ground_reflectivity {ground_reflectivity} for {component_name}. Using default 0.2.")
|
305 |
ground_reflectivity = 0.2
|
306 |
+
|
307 |
required_fields = ["month", "day", "hour", "global_horizontal_radiation", "direct_normal_radiation",
|
308 |
"diffuse_horizontal_radiation", "dry_bulb"]
|
309 |
if not all(field in hourly_data for field in required_fields):
|
310 |
logger.warning(f"Missing required fields in hourly_data for hour {hour} for {component_name}: {hourly_data}")
|
311 |
return 0
|
312 |
+
|
313 |
if hourly_data["global_horizontal_radiation"] <= 0:
|
314 |
logger.info(f"No solar load for hour {hour} due to GHI={hourly_data['global_horizontal_radiation']} for {component_name}")
|
315 |
return 0
|
316 |
+
|
317 |
month = hourly_data["month"]
|
318 |
day = hourly_data["day"]
|
319 |
hour = hourly_data["hour"]
|
|
|
321 |
dni = hourly_data.get("direct_normal_radiation", ghi * 0.7)
|
322 |
dhi = hourly_data.get("diffuse_horizontal_radiation", ghi * 0.3)
|
323 |
outdoor_temp = hourly_data["dry_bulb"]
|
324 |
+
|
325 |
if ghi < 0 or dni < 0 or dhi < 0:
|
326 |
logger.error(f"Negative radiation values for {month}/{day}/{hour} for {component_name}")
|
327 |
raise ValueError(f"Negative radiation values for {month}/{day}/{hour}")
|
328 |
+
|
329 |
logger.info(f"Processing solar for {month}/{day}/{hour} with GHI={ghi}, DNI={dni}, DHI={dhi}, "
|
330 |
f"dry_bulb={outdoor_temp} for {component_name}")
|
331 |
+
|
332 |
year = 2025
|
333 |
n = TFMCalculations.day_of_year(month, day, year)
|
334 |
EOT = TFMCalculations.equation_of_time(n)
|
335 |
lambda_std = 15 * timezone
|
336 |
standard_time = hour - 1 + 0.5
|
337 |
LST = standard_time + (4 * (lambda_std - longitude) + EOT) / 60
|
338 |
+
|
339 |
delta = 23.45 * math.sin(math.radians(360 / 365 * (284 + n)))
|
340 |
phi = math.radians(latitude)
|
341 |
delta_rad = math.radians(delta)
|
342 |
hra = 15 * (LST - 12)
|
343 |
hra_rad = math.radians(hra)
|
344 |
+
|
345 |
sin_alpha = math.sin(phi) * math.sin(delta_rad) + math.cos(phi) * math.cos(delta_rad) * math.cos(hra_rad)
|
346 |
alpha = math.degrees(math.asin(sin_alpha))
|
347 |
+
|
348 |
if abs(math.cos(math.radians(alpha))) < 0.01:
|
349 |
azimuth = 0
|
350 |
else:
|
|
|
353 |
azimuth = math.degrees(math.atan2(sin_az, cos_az))
|
354 |
if hra > 0:
|
355 |
azimuth = 360 - azimuth if azimuth > 0 else -azimuth
|
356 |
+
|
357 |
logger.info(f"Solar angles for {month}/{day}/{hour}: declination={delta:.2f}, LST={LST:.2f}, "
|
358 |
f"HRA={hra:.2f}, altitude={alpha:.2f}, azimuth={azimuth:.2f} for {component_name}")
|
359 |
+
|
360 |
building_info = {"orientation_angle": building_orientation}
|
361 |
try:
|
362 |
surface_tilt, surface_azimuth, h_o, emissivity, absorptivity = \
|
|
|
385 |
else: # WINDOW, SKYLIGHT
|
386 |
absorptivity = 0.0
|
387 |
h_o = DEFAULT_WINDOW_PROPERTIES["h_o"]
|
388 |
+
|
389 |
alpha_rad = math.radians(alpha)
|
390 |
surface_tilt_rad = math.radians(surface_tilt)
|
391 |
azimuth_rad = math.radians(azimuth)
|
|
|
400 |
logger.info(f" Component {component_name} at {month}/{day}/{hour}: "
|
401 |
f"surface_tilt={surface_tilt:.2f}, surface_azimuth={surface_azimuth:.2f}, "
|
402 |
f"cos_theta={cos_theta:.4f}")
|
403 |
+
|
404 |
view_factor = (1 - math.cos(surface_tilt_rad)) / 2
|
405 |
ground_reflected = ground_reflectivity * ghi * view_factor
|
406 |
|
|
|
415 |
shgc = component.get('shgc', 0.7) # Use component-stored shgc
|
416 |
glazing_type = SolarCalculations.GLAZING_TYPE_MAPPING.get(component.get('fenestration', ''), "Single Clear")
|
417 |
shading_coeff = component.get('shading_coefficient', 1.0) # Aligned with components.py
|
418 |
+
# Adjust shading coefficient based on shading type
|
419 |
+
shading_type = component.get('shading_type', 'No shading')
|
420 |
+
if shading_type == "External Shading":
|
421 |
+
shading_coeff *= 0.6
|
422 |
+
elif shading_type == "Internal Shading":
|
423 |
+
shading_coeff *= 0.8
|
424 |
+
|
425 |
shgc_dynamic = shgc * SolarCalculations.calculate_dynamic_shgc(glazing_type, cos_theta)
|
426 |
|
427 |
solar_heat_gain = component.get('area', 0.0) * shgc_dynamic * I_t * shading_coeff / 1000
|
428 |
|
429 |
logger.info(f"Fenestration solar heat gain for {component_name} at {month}/{day}/{hour}: "
|
430 |
f"{solar_heat_gain:.4f} kW (area={component.get('area', 0.0)}, shgc_dynamic={shgc_dynamic:.4f}, "
|
431 |
+
f"I_t={I_t:.2f}, shading_coeff={shading_coeff}, shading_type={shading_type})")
|
432 |
|
433 |
elif component_type in [ComponentType.WALL, ComponentType.ROOF]:
|
434 |
surface_resistance = 1/h_o
|
|
|
440 |
f"I_t={I_t:.2f}, surface_resistance={surface_resistance:.4f})")
|
441 |
|
442 |
return solar_heat_gain
|
443 |
+
|
444 |
except Exception as e:
|
445 |
logger.error(f"Error calculating solar load for {component_name} at hour {hour}: {str(e)}")
|
446 |
return 0
|