Spaces:
Sleeping
Sleeping
Update utils/solar.py
Browse files- utils/solar.py +67 -123
utils/solar.py
CHANGED
@@ -106,148 +106,92 @@ class SolarCalculations:
|
|
106 |
|
107 |
def get_surface_parameters(self, component: Any, building_info: Dict) -> Tuple[float, float, float, Optional[float], float]:
|
108 |
"""
|
109 |
-
Determine surface parameters (tilt, azimuth, h_o, emissivity,
|
110 |
-
Uses
|
111 |
-
and GlazingMaterial for windows/skylights. Handles orientation and tilt based on component type:
|
112 |
-
- Walls, Doors, Windows: Azimuth = facade base azimuth + component.rotation; Tilt = 90掳.
|
113 |
-
- Roofs, Skylights: Azimuth = component.orientation; Tilt = component.tilt (default 180掳).
|
114 |
|
115 |
Args:
|
116 |
-
component: Component
|
117 |
-
|
118 |
-
building_info (Dict): Building information
|
119 |
|
120 |
Returns:
|
121 |
Tuple[float, float, float, Optional[float], float]: Surface tilt (掳), surface azimuth (掳),
|
122 |
-
h_o (W/m虏路K), emissivity,
|
123 |
-
|
124 |
-
Raises:
|
125 |
-
ValueError: If facade is missing or invalid for walls, doors, or windows.
|
126 |
"""
|
|
|
|
|
|
|
127 |
# Default parameters
|
128 |
-
if
|
129 |
-
surface_tilt =
|
130 |
-
h_o = 23.0
|
131 |
-
elif
|
132 |
-
surface_tilt =
|
133 |
-
h_o = 23.0
|
134 |
-
elif
|
135 |
-
surface_tilt =
|
136 |
-
h_o = 17.0
|
137 |
else: # WALL, DOOR, WINDOW
|
138 |
-
surface_tilt = 90.0
|
139 |
-
h_o = 17.0
|
140 |
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
|
145 |
try:
|
146 |
-
#
|
147 |
-
if
|
148 |
-
|
149 |
-
surface_azimuth = getattr(component, 'orientation', 0.0)
|
150 |
-
logger.debug(f"Using component orientation for {component.id}: "
|
151 |
-
f"azimuth={surface_azimuth}, tilt={surface_tilt}")
|
152 |
-
else: # WALL, DOOR, WINDOW
|
153 |
-
# Check for facade attribute
|
154 |
-
facade = getattr(component, 'facade', None)
|
155 |
-
if not facade:
|
156 |
-
component_id = getattr(component, 'id', 'unknown_component')
|
157 |
-
raise ValueError(f"Component {component_id} is missing 'facade' field")
|
158 |
-
|
159 |
-
# Define facade azimuths based on building orientation_angle
|
160 |
-
base_azimuth = building_info.get("orientation_angle", 0.0)
|
161 |
-
facade_angles = {
|
162 |
-
"A": base_azimuth,
|
163 |
-
"B": (base_azimuth + 90.0) % 360,
|
164 |
-
"C": (base_azimuth + 180.0) % 360,
|
165 |
-
"D": (base_azimuth + 270.0) % 360
|
166 |
-
}
|
167 |
-
|
168 |
-
if facade not in facade_angles:
|
169 |
-
component_id = getattr(component, 'id', 'unknown_component')
|
170 |
-
raise ValueError(f"Invalid facade '{facade}' for component {component_id}. "
|
171 |
-
f"Expected one of {list(facade_angles.keys())}")
|
172 |
-
|
173 |
-
# Add component rotation to facade azimuth
|
174 |
-
surface_azimuth = (facade_angles[facade] + getattr(component, 'rotation', 0.0)) % 360
|
175 |
-
logger.debug(f"Component {component.id}: facade={facade}, "
|
176 |
-
f"base_azimuth={facade_angles[facade]}, rotation={getattr(component, 'rotation', 0.0)}, "
|
177 |
-
f"total_azimuth={surface_azimuth}, tilt={surface_tilt}")
|
178 |
-
|
179 |
-
# Fetch material properties
|
180 |
-
if component.component_type in [ComponentType.WALL, ComponentType.ROOF]:
|
181 |
-
construction = getattr(component, 'construction', None)
|
182 |
-
if not construction:
|
183 |
-
logger.warning(f"No construction defined for {component.id}. "
|
184 |
-
f"Using defaults: solar_absorption=0.6, emissivity=0.9.")
|
185 |
-
else:
|
186 |
-
# Get construction from library or project
|
187 |
-
construction_obj = (self.project_constructions.get(construction.name) or
|
188 |
-
self.material_library.library_constructions.get(construction.name))
|
189 |
-
if not construction_obj:
|
190 |
-
logger.error(f"Construction '{construction.name}' not found for {component.id}.")
|
191 |
-
elif not construction_obj.layers:
|
192 |
-
logger.warning(f"No layers in construction '{construction.name}' for {component.id}.")
|
193 |
-
else:
|
194 |
-
# Use first (outermost) layer's properties
|
195 |
-
first_layer = construction_obj.layers[0]
|
196 |
-
material = first_layer["material"]
|
197 |
-
solar_absorption = material.solar_absorption
|
198 |
-
emissivity = material.emissivity
|
199 |
-
logger.debug(f"Using first layer material '{material.name}' for {component.id}: "
|
200 |
-
f"solar_absorption={solar_absorption}, emissivity={emissivity}")
|
201 |
-
|
202 |
-
elif component.component_type == ComponentType.DOOR:
|
203 |
-
door_material = getattr(component, 'door_material', None)
|
204 |
-
if not door_material:
|
205 |
-
logger.warning(f"No door material defined for {component.id}. "
|
206 |
-
f"Using defaults: solar_absorption=0.6, emissivity=0.9.")
|
207 |
-
else:
|
208 |
-
# Get door material from library or project
|
209 |
-
door_material_obj = (self.project_door_materials.get(door_material.name) or
|
210 |
-
self.material_library.library_door_materials.get(door_material.name))
|
211 |
-
if not door_material_obj:
|
212 |
-
logger.error(f"Door material '{door_material.name}' not found for {component.id}.")
|
213 |
-
else:
|
214 |
-
solar_absorption = door_material_obj.solar_absorption
|
215 |
-
emissivity = door_material_obj.emissivity
|
216 |
-
logger.debug(f"Using door material '{door_material_obj.name}' for {component.id}: "
|
217 |
-
f"solar_absorption={solar_absorption}, emissivity={emissivity}")
|
218 |
-
|
219 |
-
elif component.component_type in [ComponentType.WINDOW, ComponentType.SKYLIGHT]:
|
220 |
-
glazing_material = getattr(component, 'glazing_material', None)
|
221 |
if not glazing_material:
|
222 |
-
logger.warning(f"No
|
223 |
-
|
224 |
-
shgc = 0.7
|
225 |
else:
|
226 |
-
|
227 |
-
|
228 |
-
self.material_library.library_glazing_materials.get(glazing_material.name))
|
229 |
if not glazing_material_obj:
|
230 |
-
logger.
|
231 |
-
|
232 |
else:
|
233 |
-
|
234 |
-
h_o = glazing_material_obj.h_o
|
235 |
-
logger.debug(f"Using
|
236 |
-
|
237 |
-
|
|
|
|
|
|
|
|
|
238 |
|
239 |
except Exception as e:
|
240 |
-
component_id = getattr(component, 'id', 'unknown_component')
|
241 |
logger.error(f"Error retrieving surface parameters for {component_id}: {str(e)}")
|
242 |
# Apply defaults
|
243 |
-
if
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
emissivity = 0.9
|
246 |
else: # WINDOW, SKYLIGHT
|
247 |
-
|
248 |
-
|
249 |
|
250 |
-
|
|
|
251 |
|
252 |
@staticmethod
|
253 |
def calculate_dynamic_shgc(glazing_type: str, cos_theta: float) -> float:
|
@@ -398,7 +342,7 @@ class SolarCalculations:
|
|
398 |
for comp in comp_list:
|
399 |
try:
|
400 |
# Get surface parameters
|
401 |
-
surface_tilt, surface_azimuth, h_o, emissivity,
|
402 |
self.get_surface_parameters(comp, building_info)
|
403 |
|
404 |
# For windows/skylights, get SHGC from material
|
@@ -432,14 +376,14 @@ class SolarCalculations:
|
|
432 |
comp_result = {
|
433 |
"component_id": getattr(comp, 'id', 'unknown_component'),
|
434 |
"total_incident_radiation": round(I_t, 2),
|
435 |
-
"
|
436 |
"emissivity": round(emissivity, 2) if emissivity is not None else None
|
437 |
}
|
438 |
|
439 |
# Calculate sol-air temperature for opaque surfaces
|
440 |
if comp.component_type in [ComponentType.WALL, ComponentType.ROOF, ComponentType.DOOR]:
|
441 |
delta_R = 63.0 if comp.component_type == ComponentType.ROOF else 0.0
|
442 |
-
sol_air_temp = outdoor_temp + (
|
443 |
delta_R) / h_o
|
444 |
comp_result["sol_air_temp"] = round(sol_air_temp, 2)
|
445 |
logger.info(f"Sol-air temp for {comp_result['component_id']} at {month}/{day}/{hour}: {sol_air_temp:.2f}掳C")
|
|
|
106 |
|
107 |
def get_surface_parameters(self, component: Any, building_info: Dict) -> Tuple[float, float, float, Optional[float], float]:
|
108 |
"""
|
109 |
+
Determine surface parameters (tilt, azimuth, h_o, emissivity, absorptivity) for a component.
|
110 |
+
Uses pre-calculated values stored in the component dictionary from components.py.
|
|
|
|
|
|
|
111 |
|
112 |
Args:
|
113 |
+
component: Component dictionary with surface_tilt, surface_azimuth, absorptivity/emissivity or shgc,
|
114 |
+
component_type, and optionally fenestration.
|
115 |
+
building_info (Dict): Building information (not used since parameters are pre-calculated).
|
116 |
|
117 |
Returns:
|
118 |
Tuple[float, float, float, Optional[float], float]: Surface tilt (掳), surface azimuth (掳),
|
119 |
+
h_o (W/m虏路K), emissivity, absorptivity.
|
|
|
|
|
|
|
120 |
"""
|
121 |
+
component_id = component.get('id', 'unknown_component')
|
122 |
+
component_type = component.get('component_type', ComponentType.WALL)
|
123 |
+
|
124 |
# Default parameters
|
125 |
+
if component_type == ComponentType.ROOF:
|
126 |
+
surface_tilt = component.get('surface_tilt', 0.0)
|
127 |
+
h_o = 23.0
|
128 |
+
elif component_type == ComponentType.SKYLIGHT:
|
129 |
+
surface_tilt = component.get('surface_tilt', 0.0)
|
130 |
+
h_o = 23.0
|
131 |
+
elif component_type == ComponentType.FLOOR:
|
132 |
+
surface_tilt = component.get('surface_tilt', 180.0)
|
133 |
+
h_o = 17.0
|
134 |
else: # WALL, DOOR, WINDOW
|
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, ComponentType.DOOR] else None)
|
140 |
+
absorptivity = component.get('absorptivity', 0.6 if component_type in [ComponentType.WALL, ComponentType.ROOF, ComponentType.DOOR] else 0.0)
|
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 |
+
glazing_material = component.get('fenestration', None)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
if not glazing_material:
|
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(glazing_material) or
|
151 |
+
self.material_library.library_glazing_materials.get(glazing_material))
|
|
|
152 |
if not glazing_material_obj:
|
153 |
+
logger.warning(f"Fenestration '{glazing_material}' not found for {component_id}. Using default SHGC=0.7, h_o={h_o}.")
|
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 '{glazing_material}' for {component_id}: shgc={absorptivity}, h_o={h_o}")
|
159 |
+
emissivity = None
|
160 |
+
|
161 |
+
logger.debug(f"Surface parameters for {component_id}: tilt={surface_tilt:.2f}, azimuth={surface_azimuth:.2f}, "
|
162 |
+
f"h_o={h_o:.2f}, emissivity={emissivity}, absorptivity={absorptivity}")
|
163 |
+
|
164 |
+
return surface_tilt, surface_azimuth, h_o, emissivity, absorptivity
|
165 |
|
166 |
except Exception as e:
|
|
|
167 |
logger.error(f"Error retrieving surface parameters for {component_id}: {str(e)}")
|
168 |
# Apply defaults
|
169 |
+
if component_type == ComponentType.ROOF:
|
170 |
+
surface_tilt = 0.0
|
171 |
+
h_o = 23.0
|
172 |
+
surface_azimuth = 0.0
|
173 |
+
elif component_type == ComponentType.SKYLIGHT:
|
174 |
+
surface_tilt = 0.0
|
175 |
+
h_o = 23.0
|
176 |
+
surface_azimuth = 0.0
|
177 |
+
elif component_type == ComponentType.FLOOR:
|
178 |
+
surface_tilt = 180.0
|
179 |
+
h_o = 17.0
|
180 |
+
surface_azimuth = 0.0
|
181 |
+
else: # WALL, DOOR, WINDOW
|
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, ComponentType.DOOR]:
|
187 |
+
absorptivity = 0.6
|
188 |
emissivity = 0.9
|
189 |
else: # WINDOW, SKYLIGHT
|
190 |
+
absorptivity = 0.7
|
191 |
+
emissivity = None
|
192 |
|
193 |
+
logger.info(f"Default surface parameters for {component_id}: tilt={surface_tilt:.1f}, azimuth={surface_azimuth:.1f}, h_o={h_o:.1f}")
|
194 |
+
return surface_tilt, surface_azimuth, h_o, emissivity, absorptivity
|
195 |
|
196 |
@staticmethod
|
197 |
def calculate_dynamic_shgc(glazing_type: str, cos_theta: float) -> float:
|
|
|
342 |
for comp in comp_list:
|
343 |
try:
|
344 |
# Get surface parameters
|
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 material
|
|
|
376 |
comp_result = {
|
377 |
"component_id": getattr(comp, 'id', 'unknown_component'),
|
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.component_type in [ComponentType.WALL, ComponentType.ROOF, ComponentType.DOOR]:
|
385 |
delta_R = 63.0 if comp.component_type == ComponentType.ROOF else 0.0
|
386 |
+
sol_air_temp = outdoor_temp + (absorptivity * I_t - (emissivity or 0.9) *
|
387 |
delta_R) / h_o
|
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")
|