Spaces:
Sleeping
Sleeping
Update utils/solar.py
Browse files- utils/solar.py +53 -39
utils/solar.py
CHANGED
@@ -119,7 +119,22 @@ class SolarCalculations:
|
|
119 |
h_o (W/m虏路K), emissivity, absorptivity.
|
120 |
"""
|
121 |
component_id = component.get('id', 'unknown_component')
|
122 |
-
component_type = component.get('
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
# Default parameters
|
125 |
if component_type == ComponentType.ROOF:
|
@@ -131,31 +146,31 @@ class SolarCalculations:
|
|
131 |
elif component_type == ComponentType.FLOOR:
|
132 |
surface_tilt = component.get('surface_tilt', 180.0)
|
133 |
h_o = 17.0
|
134 |
-
else: # WALL,
|
135 |
surface_tilt = component.get('surface_tilt', 90.0)
|
136 |
h_o = 17.0
|
137 |
|
138 |
surface_azimuth = component.get('surface_azimuth', 0.0)
|
139 |
-
emissivity = component.get('emissivity', 0.9 if component_type in [ComponentType.WALL, ComponentType.ROOF
|
140 |
-
absorptivity = component.get('absorptivity', 0.6 if component_type in [ComponentType.WALL, ComponentType.ROOF
|
141 |
|
142 |
try:
|
143 |
# For windows and skylights, use shgc instead of absorptivity and adjust h_o
|
144 |
if component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
|
145 |
-
|
146 |
-
if not
|
147 |
logger.warning(f"No fenestration defined for {component_id}. Using default SHGC=0.7, h_o={h_o}.")
|
148 |
absorptivity = component.get('shgc', 0.7)
|
149 |
else:
|
150 |
-
glazing_material_obj = (self.project_glazing_materials.get(
|
151 |
-
self.material_library.library_glazing_materials.get(
|
152 |
if not glazing_material_obj:
|
153 |
-
logger.warning(f"Fenestration '{
|
154 |
absorptivity = component.get('shgc', 0.7)
|
155 |
else:
|
156 |
absorptivity = glazing_material_obj.get('shgc', component.get('shgc', 0.7))
|
157 |
h_o = glazing_material_obj.get('h_o', h_o)
|
158 |
-
logger.debug(f"Using fenestration '{
|
159 |
emissivity = None
|
160 |
|
161 |
logger.debug(f"Surface parameters for {component_id}: tilt={surface_tilt:.2f}, azimuth={surface_azimuth:.2f}, "
|
@@ -178,12 +193,12 @@ class SolarCalculations:
|
|
178 |
surface_tilt = 180.0
|
179 |
h_o = 17.0
|
180 |
surface_azimuth = 0.0
|
181 |
-
else: # WALL,
|
182 |
surface_tilt = 90.0
|
183 |
h_o = 17.0
|
184 |
surface_azimuth = 0.0
|
185 |
|
186 |
-
if component_type in [ComponentType.WALL, ComponentType.ROOF
|
187 |
absorptivity = 0.6
|
188 |
emissivity = 0.9
|
189 |
else: # WINDOW, SKYLIGHT
|
@@ -235,7 +250,7 @@ class SolarCalculations:
|
|
235 |
timezone (float): Timezone offset in hours.
|
236 |
ground_reflectivity (float): Ground reflectivity (albedo, typically 0.2).
|
237 |
components (Dict[str, List]): Dictionary of component lists (e.g., walls, windows) with id, area,
|
238 |
-
|
239 |
|
240 |
Returns:
|
241 |
List[Dict]: List of results for each hour with GHI > 0, containing solar angles and per-component results.
|
@@ -268,7 +283,7 @@ class SolarCalculations:
|
|
268 |
|
269 |
lambda_std = 15 * timezone # Standard meridian longitude (掳)
|
270 |
|
271 |
-
# Cache facade azimuths (used only for walls,
|
272 |
building_info = components.get("_building_info", {})
|
273 |
facade_cache = {
|
274 |
"A": building_info.get("orientation_angle", 0.0),
|
@@ -345,17 +360,16 @@ class SolarCalculations:
|
|
345 |
surface_tilt, surface_azimuth, h_o, emissivity, absorptivity = \
|
346 |
self.get_surface_parameters(comp, building_info)
|
347 |
|
348 |
-
# For windows/skylights, get SHGC from
|
349 |
-
shgc = 0.7 #
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
logger.warning(f"Glazing material '{glazing_material.name}' not found for {comp.id}. Using default SHGC=0.7.")
|
359 |
|
360 |
# Calculate angle of incidence (胃)
|
361 |
cos_theta = (math.sin(math.radians(alpha)) * math.cos(math.radians(surface_tilt)) +
|
@@ -363,9 +377,9 @@ class SolarCalculations:
|
|
363 |
math.cos(math.radians(azimuth - surface_azimuth)))
|
364 |
cos_theta = max(min(cos_theta, 1.0), 0.0) # Clamp to [0, 1]
|
365 |
|
366 |
-
logger.info(f" Component {
|
367 |
-
|
368 |
-
|
369 |
|
370 |
# Calculate total incident radiation (I_t)
|
371 |
view_factor = (1 - math.cos(math.radians(surface_tilt))) / 2
|
@@ -374,36 +388,36 @@ class SolarCalculations:
|
|
374 |
|
375 |
# Initialize result
|
376 |
comp_result = {
|
377 |
-
"component_id":
|
378 |
"total_incident_radiation": round(I_t, 2),
|
379 |
"absorptivity": round(absorptivity, 2),
|
380 |
"emissivity": round(emissivity, 2) if emissivity is not None else None
|
381 |
}
|
382 |
|
383 |
# Calculate sol-air temperature for opaque surfaces
|
384 |
-
if comp.
|
385 |
-
delta_R = 63.0 if comp.
|
386 |
sol_air_temp = outdoor_temp + (absorptivity * I_t - (emissivity or 0.9) *
|
387 |
-
|
388 |
comp_result["sol_air_temp"] = round(sol_air_temp, 2)
|
389 |
logger.info(f"Sol-air temp for {comp_result['component_id']} at {month}/{day}/{hour}: {sol_air_temp:.2f}掳C")
|
390 |
|
391 |
# Calculate solar heat gain for fenestration
|
392 |
-
elif comp.
|
393 |
-
glazing_type = self.GLAZING_TYPE_MAPPING.get(comp.name, 'Single Clear')
|
394 |
-
iac =
|
395 |
shgc_dynamic = shgc * self.calculate_dynamic_shgc(glazing_type, cos_theta)
|
396 |
-
solar_heat_gain = comp.area * shgc_dynamic * I_t * iac / 1000 # kW
|
397 |
comp_result["solar_heat_gain"] = round(solar_heat_gain, 2)
|
398 |
comp_result["shgc_dynamic"] = round(shgc_dynamic, 2)
|
399 |
logger.info(f"Solar heat gain for {comp_result['component_id']} at {month}/{day}/{hour}: "
|
400 |
-
|
401 |
-
|
402 |
|
403 |
component_results.append(comp_result)
|
404 |
|
405 |
except Exception as e:
|
406 |
-
component_id =
|
407 |
logger.error(f"Error processing component {component_id} at {month}/{day}/{hour}: {str(e)}")
|
408 |
continue
|
409 |
|
|
|
119 |
h_o (W/m虏路K), emissivity, absorptivity.
|
120 |
"""
|
121 |
component_id = component.get('id', 'unknown_component')
|
122 |
+
component_type = component.get('type', 'wall').lower() # Changed to dictionary access
|
123 |
+
|
124 |
+
# Map string type to ComponentType enum
|
125 |
+
type_map = {
|
126 |
+
'walls': ComponentType.WALL,
|
127 |
+
'roofs': ComponentType.ROOF,
|
128 |
+
'floors': ComponentType.FLOOR,
|
129 |
+
'windows': ComponentType.WINDOW,
|
130 |
+
'skylights': ComponentType.SKYLIGHT,
|
131 |
+
'wall': ComponentType.WALL,
|
132 |
+
'roof': ComponentType.ROOF,
|
133 |
+
'floor': ComponentType.FLOOR,
|
134 |
+
'window': ComponentType.WINDOW,
|
135 |
+
'skylight': ComponentType.SKYLIGHT
|
136 |
+
}
|
137 |
+
component_type = type_map.get(component_type, ComponentType.WALL)
|
138 |
|
139 |
# Default parameters
|
140 |
if component_type == ComponentType.ROOF:
|
|
|
146 |
elif component_type == ComponentType.FLOOR:
|
147 |
surface_tilt = component.get('surface_tilt', 180.0)
|
148 |
h_o = 17.0
|
149 |
+
else: # WALL, WINDOW
|
150 |
surface_tilt = component.get('surface_tilt', 90.0)
|
151 |
h_o = 17.0
|
152 |
|
153 |
surface_azimuth = component.get('surface_azimuth', 0.0)
|
154 |
+
emissivity = component.get('emissivity', 0.9 if component_type in [ComponentType.WALL, ComponentType.ROOF] else None)
|
155 |
+
absorptivity = component.get('absorptivity', 0.6 if component_type in [ComponentType.WALL, ComponentType.ROOF] else 0.0)
|
156 |
|
157 |
try:
|
158 |
# For windows and skylights, use shgc instead of absorptivity and adjust h_o
|
159 |
if component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
|
160 |
+
fenestration_name = component.get('fenestration', None) # Standardized to 'fenestration'
|
161 |
+
if not fenestration_name:
|
162 |
logger.warning(f"No fenestration defined for {component_id}. Using default SHGC=0.7, h_o={h_o}.")
|
163 |
absorptivity = component.get('shgc', 0.7)
|
164 |
else:
|
165 |
+
glazing_material_obj = (self.project_glazing_materials.get(fenestration_name) or
|
166 |
+
self.material_library.library_glazing_materials.get(fenestration_name))
|
167 |
if not glazing_material_obj:
|
168 |
+
logger.warning(f"Fenestration '{fenestration_name}' not found for {component_id}. Using default SHGC=0.7, h_o={h_o}.")
|
169 |
absorptivity = component.get('shgc', 0.7)
|
170 |
else:
|
171 |
absorptivity = glazing_material_obj.get('shgc', component.get('shgc', 0.7))
|
172 |
h_o = glazing_material_obj.get('h_o', h_o)
|
173 |
+
logger.debug(f"Using fenestration '{fenestration_name}' for {component_id}: shgc={absorptivity}, h_o={h_o}")
|
174 |
emissivity = None
|
175 |
|
176 |
logger.debug(f"Surface parameters for {component_id}: tilt={surface_tilt:.2f}, azimuth={surface_azimuth:.2f}, "
|
|
|
193 |
surface_tilt = 180.0
|
194 |
h_o = 17.0
|
195 |
surface_azimuth = 0.0
|
196 |
+
else: # WALL, WINDOW
|
197 |
surface_tilt = 90.0
|
198 |
h_o = 17.0
|
199 |
surface_azimuth = 0.0
|
200 |
|
201 |
+
if component_type in [ComponentType.WALL, ComponentType.ROOF]:
|
202 |
absorptivity = 0.6
|
203 |
emissivity = 0.9
|
204 |
else: # WINDOW, SKYLIGHT
|
|
|
250 |
timezone (float): Timezone offset in hours.
|
251 |
ground_reflectivity (float): Ground reflectivity (albedo, typically 0.2).
|
252 |
components (Dict[str, List]): Dictionary of component lists (e.g., walls, windows) with id, area,
|
253 |
+
type, facade, construction, fenestration, or door_material.
|
254 |
|
255 |
Returns:
|
256 |
List[Dict]: List of results for each hour with GHI > 0, containing solar angles and per-component results.
|
|
|
283 |
|
284 |
lambda_std = 15 * timezone # Standard meridian longitude (掳)
|
285 |
|
286 |
+
# Cache facade azimuths (used only for walls, windows)
|
287 |
building_info = components.get("_building_info", {})
|
288 |
facade_cache = {
|
289 |
"A": building_info.get("orientation_angle", 0.0),
|
|
|
360 |
surface_tilt, surface_azimuth, h_o, emissivity, absorptivity = \
|
361 |
self.get_surface_parameters(comp, building_info)
|
362 |
|
363 |
+
# For windows/skylights, get SHGC from component
|
364 |
+
shgc = comp.get('shgc', 0.7) # Changed to dictionary access
|
365 |
+
fenestration_name = comp.get('fenestration', None) # Standardized to 'fenestration'
|
366 |
+
if fenestration_name:
|
367 |
+
glazing_material_obj = (self.project_glazing_materials.get(fenestration_name) or
|
368 |
+
self.material_library.library_glazing_materials.get(fenestration_name))
|
369 |
+
if glazing_material_obj:
|
370 |
+
shgc = glazing_material_obj.get('shgc', shgc)
|
371 |
+
else:
|
372 |
+
logger.warning(f"Fenestration '{fenestration_name}' not found for {comp.get('id', 'unknown_component')}. Using default SHGC={shgc}.")
|
|
|
373 |
|
374 |
# Calculate angle of incidence (胃)
|
375 |
cos_theta = (math.sin(math.radians(alpha)) * math.cos(math.radians(surface_tilt)) +
|
|
|
377 |
math.cos(math.radians(azimuth - surface_azimuth)))
|
378 |
cos_theta = max(min(cos_theta, 1.0), 0.0) # Clamp to [0, 1]
|
379 |
|
380 |
+
logger.info(f" Component {comp.get('id', 'unknown_component')} at {month}/{day}/{hour}: "
|
381 |
+
f"surface_tilt={surface_tilt:.2f}, surface_azimuth={surface_azimuth:.2f}, " # Fixed typo
|
382 |
+
f"cos_theta={cos_theta:.2f}")
|
383 |
|
384 |
# Calculate total incident radiation (I_t)
|
385 |
view_factor = (1 - math.cos(math.radians(surface_tilt))) / 2
|
|
|
388 |
|
389 |
# Initialize result
|
390 |
comp_result = {
|
391 |
+
"component_id": comp.get('id', 'unknown_component'),
|
392 |
"total_incident_radiation": round(I_t, 2),
|
393 |
"absorptivity": round(absorptivity, 2),
|
394 |
"emissivity": round(emissivity, 2) if emissivity is not None else None
|
395 |
}
|
396 |
|
397 |
# Calculate sol-air temperature for opaque surfaces
|
398 |
+
if comp.get('type', '').lower() in ['walls', 'roofs']:
|
399 |
+
delta_R = 63.0 if comp.get('type', '').lower() == 'roofs' else 0.0
|
400 |
sol_air_temp = outdoor_temp + (absorptivity * I_t - (emissivity or 0.9) *
|
401 |
+
delta_R) / h_o
|
402 |
comp_result["sol_air_temp"] = round(sol_air_temp, 2)
|
403 |
logger.info(f"Sol-air temp for {comp_result['component_id']} at {month}/{day}/{hour}: {sol_air_temp:.2f}掳C")
|
404 |
|
405 |
# Calculate solar heat gain for fenestration
|
406 |
+
elif comp.get('type', '').lower() in ['windows', 'skylights']:
|
407 |
+
glazing_type = self.GLAZING_TYPE_MAPPING.get(comp.get('name', ''), 'Single Clear')
|
408 |
+
iac = comp.get('shading_coefficient', 1.0) # Changed to dictionary access, aligned with components.py
|
409 |
shgc_dynamic = shgc * self.calculate_dynamic_shgc(glazing_type, cos_theta)
|
410 |
+
solar_heat_gain = comp.get('area', 0.0) * shgc_dynamic * I_t * iac / 1000 # kW
|
411 |
comp_result["solar_heat_gain"] = round(solar_heat_gain, 2)
|
412 |
comp_result["shgc_dynamic"] = round(shgc_dynamic, 2)
|
413 |
logger.info(f"Solar heat gain for {comp_result['component_id']} at {month}/{day}/{hour}: "
|
414 |
+
f"{solar_heat_gain:.2f} kW (area={comp.get('area', 0.0)}, shgc_dynamic={shgc_dynamic:.2f}, "
|
415 |
+
f"I_t={I_t:.2f}, iac={iac})")
|
416 |
|
417 |
component_results.append(comp_result)
|
418 |
|
419 |
except Exception as e:
|
420 |
+
component_id = comp.get('id', 'unknown_component')
|
421 |
logger.error(f"Error processing component {component_id} at {month}/{day}/{hour}: {str(e)}")
|
422 |
continue
|
423 |
|