mabuseif commited on
Commit
dfc6c05
verified
1 Parent(s): 75cb0a1

Update utils/solar.py

Browse files
Files changed (1) hide show
  1. 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, solar_absorption) for a component.
110
- Uses MaterialLibrary to fetch properties from first layer for walls/roofs, DoorMaterial for doors,
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 object with component_type, facade, rotation, orientation, tilt,
117
- construction, glazing_material, or door_material.
118
- building_info (Dict): Building information containing orientation_angle for facade mapping.
119
 
120
  Returns:
121
  Tuple[float, float, float, Optional[float], float]: Surface tilt (掳), surface azimuth (掳),
122
- h_o (W/m虏路K), emissivity, solar_absorption.
123
-
124
- Raises:
125
- ValueError: If facade is missing or invalid for walls, doors, or windows.
126
  """
 
 
 
127
  # Default parameters
128
- if component.component_type == ComponentType.ROOF:
129
- surface_tilt = getattr(component, 'tilt', 180.0) # Horizontal, downward if tilt absent
130
- h_o = 23.0 # W/m虏路K for roofs
131
- elif component.component_type == ComponentType.SKYLIGHT:
132
- surface_tilt = getattr(component, 'tilt', 180.0) # Horizontal, downward if tilt absent
133
- h_o = 23.0 # W/m虏路K for skylights
134
- elif component.component_type == ComponentType.FLOOR:
135
- surface_tilt = 0.0 # Horizontal, upward
136
- h_o = 17.0 # W/m虏路K
137
  else: # WALL, DOOR, WINDOW
138
- surface_tilt = 90.0 # Vertical
139
- h_o = 17.0 # W/m虏路K
140
 
141
- emissivity = 0.9 # Default for opaque components
142
- solar_absorption = 0.6 # Default
143
- shgc = None # Only for windows/skylights
144
 
145
  try:
146
- # Determine surface azimuth
147
- if component.component_type in [ComponentType.ROOF, ComponentType.SKYLIGHT]:
148
- # Use component's orientation attribute directly, ignoring facade
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 glazing material defined for {component.id}. "
223
- f"Using default SHGC=0.7, h_o={h_o}.")
224
- shgc = 0.7
225
  else:
226
- # Get glazing material from library or project
227
- glazing_material_obj = (self.project_glazing_materials.get(glazing_material.name) or
228
- self.material_library.library_glazing_materials.get(glazing_material.name))
229
  if not glazing_material_obj:
230
- logger.error(f"Glazing material '{glazing_material.name}' not found for {component.id}.")
231
- shgc = 0.7
232
  else:
233
- shgc = glazing_material_obj.shgc
234
- h_o = glazing_material_obj.h_o
235
- logger.debug(f"Using glazing material '{glazing_material_obj.name}' for {component.id}: "
236
- f"shgc={shgc}, h_o={h_o}")
237
- emissivity = None # Not used for glazing
 
 
 
 
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 component.component_type in [ComponentType.WALL, ComponentType.ROOF, ComponentType.DOOR]:
244
- solar_absorption = 0.6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  emissivity = 0.9
246
  else: # WINDOW, SKYLIGHT
247
- shgc = 0.7
248
- # h_o retains default from component type
249
 
250
- return surface_tilt, surface_azimuth, h_o, emissivity, solar_absorption
 
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, solar_absorption = \
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
- "solar_absorption": round(solar_absorption, 2),
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 + (solar_absorption * I_t - (emissivity or 0.9) *
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")