Spaces:
Sleeping
Sleeping
Update utils/solar.py
Browse files- utils/solar.py +44 -10
utils/solar.py
CHANGED
@@ -354,6 +354,11 @@ class SolarCalculations:
|
|
354 |
continue
|
355 |
for comp in comp_list:
|
356 |
try:
|
|
|
|
|
|
|
|
|
|
|
357 |
# Get surface parameters
|
358 |
surface_tilt, surface_azimuth, h_o, emissivity, absorptivity = \
|
359 |
self.get_surface_parameters(comp, building_info)
|
@@ -361,22 +366,22 @@ class SolarCalculations:
|
|
361 |
# For windows/skylights, get SHGC from component
|
362 |
shgc = comp.get('shgc', 0.7)
|
363 |
fenestration_name = comp.get('fenestration', None)
|
364 |
-
|
365 |
# Calculate angle of incidence (θ)
|
366 |
cos_theta = (math.sin(math.radians(alpha)) * math.cos(math.radians(surface_tilt)) +
|
367 |
math.cos(math.radians(alpha)) * math.sin(math.radians(surface_tilt)) *
|
368 |
math.cos(math.radians(azimuth - surface_azimuth)))
|
369 |
cos_theta = max(min(cos_theta, 1.0), 0.0) # Clamp to [0, 1]
|
370 |
-
|
371 |
logger.info(f" Component {comp.get('name', 'unknown_component')} at {month}/{day}/{hour}: "
|
372 |
f"surface_tilt={surface_tilt:.2f}, surface_azimuth={surface_azimuth:.2f}, "
|
373 |
f"cos_theta={cos_theta:.2f}, h_o={h_o:.2f}")
|
374 |
-
|
375 |
# Calculate total incident radiation (I_t)
|
376 |
view_factor = (1 - math.cos(math.radians(surface_tilt))) / 2
|
377 |
ground_reflected = ground_reflectivity * ghi * view_factor
|
378 |
I_t = dni * cos_theta + dhi + ground_reflected
|
379 |
-
|
380 |
# Initialize result
|
381 |
comp_result = {
|
382 |
"component_id": comp.get('name', 'unknown_component'),
|
@@ -384,17 +389,46 @@ class SolarCalculations:
|
|
384 |
"absorptivity": round(absorptivity, 2),
|
385 |
"emissivity": round(emissivity, 2) if emissivity is not None else None
|
386 |
}
|
387 |
-
|
388 |
-
#
|
389 |
-
|
390 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
391 |
T_sol_air = CTFCalculator.calculate_sol_air_temperature(
|
392 |
outdoor_temp, I_t, absorptivity, emissivity or 0.9,
|
393 |
h_o, dew_point, total_sky_cover
|
394 |
)
|
395 |
comp_result["sol_air_temp"] = round(T_sol_air, 2)
|
396 |
logger.info(f"Sol-air temp for {comp_result['component_id']} at {month}/{day}/{hour}: {T_sol_air:.2f}°C")
|
397 |
-
|
398 |
# Calculate solar heat gain for fenestration
|
399 |
elif comp.get('type', '').lower() in ['windows', 'skylights']:
|
400 |
glazing_type = self.GLAZING_TYPE_MAPPING.get(comp.get('fenestration', ''), 'Single Clear')
|
@@ -408,7 +442,7 @@ class SolarCalculations:
|
|
408 |
f"I_t={I_t:.2f}, iac={iac})")
|
409 |
|
410 |
component_results.append(comp_result)
|
411 |
-
|
412 |
except Exception as e:
|
413 |
component_name = comp.get('name', 'unknown_component')
|
414 |
logger.error(f"Error processing component {component_name} at {month}/{day}/{hour}: {str(e)}")
|
|
|
354 |
continue
|
355 |
for comp in comp_list:
|
356 |
try:
|
357 |
+
# Validate adiabatic and ground_contact mutual exclusivity
|
358 |
+
if comp.get('adiabatic', False) and comp.get('ground_contact', False):
|
359 |
+
logger.warning(f"Component {comp.get('name', 'unknown_component')} has both adiabatic and ground_contact set to True. Treating as adiabatic, setting ground_contact to False.")
|
360 |
+
comp['ground_contact'] = False
|
361 |
+
|
362 |
# Get surface parameters
|
363 |
surface_tilt, surface_azimuth, h_o, emissivity, absorptivity = \
|
364 |
self.get_surface_parameters(comp, building_info)
|
|
|
366 |
# For windows/skylights, get SHGC from component
|
367 |
shgc = comp.get('shgc', 0.7)
|
368 |
fenestration_name = comp.get('fenestration', None)
|
369 |
+
|
370 |
# Calculate angle of incidence (θ)
|
371 |
cos_theta = (math.sin(math.radians(alpha)) * math.cos(math.radians(surface_tilt)) +
|
372 |
math.cos(math.radians(alpha)) * math.sin(math.radians(surface_tilt)) *
|
373 |
math.cos(math.radians(azimuth - surface_azimuth)))
|
374 |
cos_theta = max(min(cos_theta, 1.0), 0.0) # Clamp to [0, 1]
|
375 |
+
|
376 |
logger.info(f" Component {comp.get('name', 'unknown_component')} at {month}/{day}/{hour}: "
|
377 |
f"surface_tilt={surface_tilt:.2f}, surface_azimuth={surface_azimuth:.2f}, "
|
378 |
f"cos_theta={cos_theta:.2f}, h_o={h_o:.2f}")
|
379 |
+
|
380 |
# Calculate total incident radiation (I_t)
|
381 |
view_factor = (1 - math.cos(math.radians(surface_tilt))) / 2
|
382 |
ground_reflected = ground_reflectivity * ghi * view_factor
|
383 |
I_t = dni * cos_theta + dhi + ground_reflected
|
384 |
+
|
385 |
# Initialize result
|
386 |
comp_result = {
|
387 |
"component_id": comp.get('name', 'unknown_component'),
|
|
|
389 |
"absorptivity": round(absorptivity, 2),
|
390 |
"emissivity": round(emissivity, 2) if emissivity is not None else None
|
391 |
}
|
392 |
+
|
393 |
+
# Skip calculations for adiabatic surfaces
|
394 |
+
if comp.get('adiabatic', False):
|
395 |
+
logger.info(f"Skipping solar calculations for adiabatic component {comp_result['component_id']} at {month}/{day}/{hour}")
|
396 |
+
component_results.append(comp_result)
|
397 |
+
continue
|
398 |
+
|
399 |
+
# Handle ground-contact surfaces
|
400 |
+
if comp.get('ground_contact', False):
|
401 |
+
# Validate component type
|
402 |
+
component_type = comp.get('type', '').lower()
|
403 |
+
valid_types = ['walls', 'roofs', 'floors']
|
404 |
+
if component_type not in valid_types:
|
405 |
+
logger.warning(f"Invalid ground-contact component type '{component_type}' for {comp_result['component_id']}. Skipping ground temperature assignment.")
|
406 |
+
component_results.append(comp_result)
|
407 |
+
continue
|
408 |
+
|
409 |
+
# Retrieve ground temperature
|
410 |
+
climate_data = st.session_state.project_data.get("climate_data", {})
|
411 |
+
ground_temperatures = climate_data.get("ground_temperatures", {})
|
412 |
+
depth = "2" # Default depth
|
413 |
+
if depth not in ground_temperatures or not ground_temperatures[depth]:
|
414 |
+
logger.warning(f"No ground temperature data for depth {depth} m for {month}/{day}/{hour}. Using default 18°C.")
|
415 |
+
ground_temp = 18.0
|
416 |
+
else:
|
417 |
+
ground_temp = ground_temperatures[depth][month-1] if len(ground_temperatures[depth]) >= month else 18.0
|
418 |
+
comp_result["ground_temp"] = round(ground_temp, 2)
|
419 |
+
logger.info(f"Ground-contact component {comp_result['component_id']} at {month}/{day}/{hour}: ground_temp={ground_temp:.2f}°C")
|
420 |
+
component_results.append(comp_result)
|
421 |
+
continue
|
422 |
+
|
423 |
+
# Calculate sol-air temperature for opaque surfaces (non-ground-contact)
|
424 |
+
if comp.get('type', '').lower() in ['walls', 'roofs'] and not comp.get('ground_contact', False):
|
425 |
T_sol_air = CTFCalculator.calculate_sol_air_temperature(
|
426 |
outdoor_temp, I_t, absorptivity, emissivity or 0.9,
|
427 |
h_o, dew_point, total_sky_cover
|
428 |
)
|
429 |
comp_result["sol_air_temp"] = round(T_sol_air, 2)
|
430 |
logger.info(f"Sol-air temp for {comp_result['component_id']} at {month}/{day}/{hour}: {T_sol_air:.2f}°C")
|
431 |
+
|
432 |
# Calculate solar heat gain for fenestration
|
433 |
elif comp.get('type', '').lower() in ['windows', 'skylights']:
|
434 |
glazing_type = self.GLAZING_TYPE_MAPPING.get(comp.get('fenestration', ''), 'Single Clear')
|
|
|
442 |
f"I_t={I_t:.2f}, iac={iac})")
|
443 |
|
444 |
component_results.append(comp_result)
|
445 |
+
|
446 |
except Exception as e:
|
447 |
component_name = comp.get('name', 'unknown_component')
|
448 |
logger.error(f"Error processing component {component_name} at {month}/{day}/{hour}: {str(e)}")
|