|
|
""" |
|
|
Cooling load calculation module for HVAC calculator. |
|
|
|
|
|
This module provides the core functionality for calculating cooling loads |
|
|
based on building characteristics, climate data, and internal heat sources. |
|
|
""" |
|
|
|
|
|
class CoolingLoadCalculator: |
|
|
""" |
|
|
Calculator for determining cooling loads in buildings. |
|
|
|
|
|
This class implements the ASHRAE method for calculating cooling loads, |
|
|
taking into account transmission gains, solar gains, internal gains, |
|
|
and ventilation/infiltration. |
|
|
""" |
|
|
|
|
|
def __init__(self, ref_data): |
|
|
""" |
|
|
Initialize the cooling load calculator. |
|
|
|
|
|
Args: |
|
|
ref_data: Reference data object containing climate data and material properties |
|
|
""" |
|
|
self.ref_data = ref_data |
|
|
self.building_info = {} |
|
|
self.components = [] |
|
|
self.windows = [] |
|
|
self.internal_sources = { |
|
|
'people': 0, |
|
|
'lighting': {'type': '', 'area': 0}, |
|
|
'equipment': [] |
|
|
} |
|
|
self.custom_heat_sources = [] |
|
|
self.infiltration = { |
|
|
'volume': 0, |
|
|
'air_changes': 0, |
|
|
'temp_diff': 0 |
|
|
} |
|
|
|
|
|
def set_building_info(self, location, indoor_temp, building_volume, air_changes): |
|
|
""" |
|
|
Set basic building information. |
|
|
|
|
|
Args: |
|
|
location (str): Location ID for climate data |
|
|
indoor_temp (float): Indoor design temperature in °C |
|
|
building_volume (float): Building volume in m³ |
|
|
air_changes (float): Air changes per hour |
|
|
""" |
|
|
self.building_info = { |
|
|
'location': location, |
|
|
'indoor_temp': indoor_temp, |
|
|
'volume': building_volume, |
|
|
'air_changes': air_changes |
|
|
} |
|
|
|
|
|
|
|
|
location_data = self.ref_data.get_location(location) |
|
|
if location_data: |
|
|
outdoor_temp = location_data['summer_design_temp'] |
|
|
self.infiltration = { |
|
|
'volume': building_volume, |
|
|
'air_changes': air_changes, |
|
|
'temp_diff': outdoor_temp - indoor_temp |
|
|
} |
|
|
|
|
|
def add_component(self, component_type, area, u_value, orientation=None): |
|
|
""" |
|
|
Add a building component (wall, roof, floor). |
|
|
|
|
|
Args: |
|
|
component_type (str): Type of component ('wall', 'roof', 'floor') |
|
|
area (float): Area of the component in m² |
|
|
u_value (float): U-value of the component in W/m²°C |
|
|
orientation (str, optional): Orientation for walls ('north', 'south', 'east', 'west') |
|
|
""" |
|
|
component = { |
|
|
'type': component_type, |
|
|
'area': area, |
|
|
'u_value': u_value |
|
|
} |
|
|
|
|
|
if orientation and component_type == 'wall': |
|
|
component['orientation'] = orientation |
|
|
|
|
|
self.components.append(component) |
|
|
|
|
|
def add_window(self, orientation, area, u_value, glass_type, shading): |
|
|
""" |
|
|
Add a window. |
|
|
|
|
|
Args: |
|
|
orientation (str): Orientation ('north', 'south', 'east', 'west') |
|
|
area (float): Area of the window in m² |
|
|
u_value (float): U-value of the window in W/m²°C |
|
|
glass_type (str): Type of glass |
|
|
shading (str): Type of shading |
|
|
""" |
|
|
window = { |
|
|
'orientation': orientation, |
|
|
'area': area, |
|
|
'u_value': u_value, |
|
|
'glass_type': glass_type, |
|
|
'shading': shading |
|
|
} |
|
|
|
|
|
self.windows.append(window) |
|
|
|
|
|
def set_internal_sources(self, people, lighting_type, lighting_area, equipment, custom_heat_sources=None): |
|
|
""" |
|
|
Set internal heat sources. |
|
|
|
|
|
Args: |
|
|
people (int): Number of occupants |
|
|
lighting_type (str): Type of lighting |
|
|
lighting_area (float): Area with lighting in m² |
|
|
equipment (list): List of equipment items with type and quantity |
|
|
custom_heat_sources (list, optional): List of custom heat sources with name and watts |
|
|
""" |
|
|
self.internal_sources = { |
|
|
'people': people, |
|
|
'lighting': { |
|
|
'type': lighting_type, |
|
|
'area': lighting_area |
|
|
}, |
|
|
'equipment': equipment |
|
|
} |
|
|
|
|
|
if custom_heat_sources: |
|
|
self.custom_heat_sources = custom_heat_sources |
|
|
|
|
|
def calculate_transmission_heat_gain(self, area, u_value, temp_diff): |
|
|
""" |
|
|
Calculate heat gain through building components. |
|
|
|
|
|
Args: |
|
|
area (float): Area of the component in m² |
|
|
u_value (float): U-value of the component in W/m²°C |
|
|
temp_diff (float): Temperature difference (outdoor - indoor) in °C |
|
|
|
|
|
Returns: |
|
|
float: Heat gain in Watts |
|
|
""" |
|
|
return area * u_value * temp_diff |
|
|
|
|
|
def calculate_solar_transmission_gain(self, area, u_value, orientation, location): |
|
|
""" |
|
|
Calculate solar heat gain through opaque surfaces. |
|
|
|
|
|
Args: |
|
|
area (float): Area of the component in m² |
|
|
u_value (float): U-value of the component in W/m²°C |
|
|
orientation (str): Orientation of the component |
|
|
location (str): Location ID for climate data |
|
|
|
|
|
Returns: |
|
|
float: Heat gain in Watts |
|
|
""" |
|
|
|
|
|
location_data = self.ref_data.get_location(location) |
|
|
if not location_data or 'solar_intensity' not in location_data: |
|
|
return 0 |
|
|
|
|
|
solar_intensity = location_data['solar_intensity'].get(orientation, 0) |
|
|
|
|
|
|
|
|
solar_gain = area * solar_intensity * 0.1 |
|
|
|
|
|
|
|
|
u_value_factor = min(u_value / 0.5, 2.0) |
|
|
|
|
|
return solar_gain * u_value_factor |
|
|
|
|
|
def calculate_solar_heat_gain(self, area, shgf, shade_factor=1.0): |
|
|
""" |
|
|
Calculate solar heat gain through glazing. |
|
|
|
|
|
Args: |
|
|
area (float): Area of the glazing in m² |
|
|
shgf (float): Solar Heat Gain Factor based on orientation and climate |
|
|
shade_factor (float): Factor to account for shading (1.0 = no shade, 0.0 = full shade) |
|
|
|
|
|
Returns: |
|
|
float: Heat gain in Watts |
|
|
""" |
|
|
return area * shgf * shade_factor |
|
|
|
|
|
def calculate_infiltration_heat_gain(self, volume, air_changes, temp_diff): |
|
|
""" |
|
|
Calculate heat gain due to infiltration and ventilation. |
|
|
|
|
|
Args: |
|
|
volume (float): Volume of the space in m³ |
|
|
air_changes (float): Air changes per hour |
|
|
temp_diff (float): Temperature difference (outdoor - indoor) in °C |
|
|
|
|
|
Returns: |
|
|
float: Heat gain in Watts |
|
|
""" |
|
|
|
|
|
air_density = 1.2 |
|
|
specific_heat = 1000 |
|
|
|
|
|
|
|
|
air_flow = (volume * air_changes) / 3600 |
|
|
|
|
|
|
|
|
heat_gain = air_density * specific_heat * air_flow * temp_diff |
|
|
|
|
|
return heat_gain |
|
|
|
|
|
def calculate_internal_heat_gain(self, people, lighting, equipment, custom_sources=None): |
|
|
""" |
|
|
Calculate heat gain from internal sources. |
|
|
|
|
|
Args: |
|
|
people (int): Number of occupants |
|
|
lighting (dict): Lighting information (type and area) |
|
|
equipment (list): List of equipment items |
|
|
custom_sources (list, optional): List of custom heat sources |
|
|
|
|
|
Returns: |
|
|
dict: Heat gain breakdown in Watts |
|
|
""" |
|
|
|
|
|
people_gain = people * 100 |
|
|
|
|
|
|
|
|
lighting_gain = 0 |
|
|
if lighting['type'] and lighting['area'] > 0: |
|
|
lighting_factors = { |
|
|
'led': 5, |
|
|
'fluorescent': 12, |
|
|
'incandescent': 20 |
|
|
} |
|
|
lighting_factor = lighting_factors.get(lighting['type'], 0) |
|
|
lighting_gain = lighting['area'] * lighting_factor |
|
|
|
|
|
|
|
|
equipment_gain = 0 |
|
|
equipment_breakdown = {} |
|
|
|
|
|
for item in equipment: |
|
|
item_type = item['type'] |
|
|
quantity = item['quantity'] |
|
|
|
|
|
|
|
|
equipment_data = self.ref_data.get_internal_load('appliances', item_type) |
|
|
if equipment_data: |
|
|
item_gain = equipment_data['heat_gain'] * quantity |
|
|
equipment_gain += item_gain |
|
|
equipment_breakdown[item_type] = item_gain |
|
|
|
|
|
|
|
|
custom_gain = 0 |
|
|
if custom_sources: |
|
|
for source in custom_sources: |
|
|
custom_gain += source.get('watts', 0) |
|
|
|
|
|
|
|
|
total_gain = people_gain + lighting_gain + equipment_gain + custom_gain |
|
|
|
|
|
return { |
|
|
'total': total_gain, |
|
|
'people': people_gain, |
|
|
'lighting': lighting_gain, |
|
|
'equipment': equipment_gain, |
|
|
'custom': custom_gain, |
|
|
'equipment_breakdown': equipment_breakdown |
|
|
} |
|
|
|
|
|
def calculate_total_cooling_load(self): |
|
|
""" |
|
|
Calculate the total cooling load. |
|
|
|
|
|
Returns: |
|
|
dict: Cooling load results |
|
|
""" |
|
|
|
|
|
location_data = self.ref_data.get_location(self.building_info['location']) |
|
|
if not location_data: |
|
|
return {'error': 'Invalid location'} |
|
|
|
|
|
outdoor_temp = location_data['summer_design_temp'] |
|
|
temp_diff = outdoor_temp - self.building_info['indoor_temp'] |
|
|
|
|
|
|
|
|
transmission_gain = 0 |
|
|
component_gains = {} |
|
|
|
|
|
for component in self.components: |
|
|
component_gain = self.calculate_transmission_heat_gain( |
|
|
component['area'], component['u_value'], temp_diff |
|
|
) |
|
|
|
|
|
|
|
|
if component['type'] in ['wall', 'roof'] and 'orientation' in component: |
|
|
component_gain += self.calculate_solar_transmission_gain( |
|
|
component['area'], component['u_value'], |
|
|
component['orientation'], self.building_info['location'] |
|
|
) |
|
|
|
|
|
transmission_gain += component_gain |
|
|
component_gains[f"{component['type']}_{len(component_gains)}"] = component_gain |
|
|
|
|
|
|
|
|
window_transmission_gain = 0 |
|
|
window_solar_gain = 0 |
|
|
window_gains = {} |
|
|
window_solar_gains = {} |
|
|
|
|
|
for window in self.windows: |
|
|
|
|
|
window_gain = self.calculate_transmission_heat_gain( |
|
|
window['area'], window['u_value'], temp_diff |
|
|
) |
|
|
window_transmission_gain += window_gain |
|
|
window_gains[f"window_{len(window_gains)}"] = window_gain |
|
|
|
|
|
|
|
|
orientation = window['orientation'] |
|
|
glass_type = window['glass_type'] |
|
|
|
|
|
|
|
|
shgf = 0 |
|
|
glass_data = self.ref_data.get_glass_type(glass_type) |
|
|
if glass_data and 'shgf' in glass_data: |
|
|
shgf = glass_data['shgf'].get(orientation, 0) |
|
|
|
|
|
|
|
|
shading_id = window.get('shading', 'none') |
|
|
shade_factor = 1.0 |
|
|
|
|
|
|
|
|
shading_data = self.ref_data.get_shading_factor(shading_id) |
|
|
if shading_data and 'factor' in shading_data: |
|
|
|
|
|
|
|
|
shade_factor = 1.0 - shading_data['factor'] |
|
|
|
|
|
window_solar_gain += self.calculate_solar_heat_gain(window['area'], shgf, shade_factor) |
|
|
window_solar_gains[f"window_{orientation}_{len(window_solar_gains)}"] = self.calculate_solar_heat_gain(window['area'], shgf, shade_factor) |
|
|
|
|
|
|
|
|
infiltration_gain = self.calculate_infiltration_heat_gain( |
|
|
self.infiltration['volume'], self.infiltration['air_changes'], self.infiltration['temp_diff'] |
|
|
) |
|
|
|
|
|
|
|
|
internal_gain = self.calculate_internal_heat_gain( |
|
|
self.internal_sources['people'], |
|
|
self.internal_sources['lighting'], |
|
|
self.internal_sources['equipment'], |
|
|
self.custom_heat_sources |
|
|
) |
|
|
|
|
|
|
|
|
total_load_w = transmission_gain + window_transmission_gain + window_solar_gain + infiltration_gain + internal_gain['total'] |
|
|
|
|
|
|
|
|
total_load_kw = total_load_w / 1000 |
|
|
recommended_size_kw = total_load_kw * 1.15 |
|
|
|
|
|
|
|
|
transmission_percentage = ((transmission_gain + window_transmission_gain) / total_load_w) * 100 if total_load_w > 0 else 0 |
|
|
solar_percentage = (window_solar_gain / total_load_w) * 100 if total_load_w > 0 else 0 |
|
|
infiltration_percentage = (infiltration_gain / total_load_w) * 100 if total_load_w > 0 else 0 |
|
|
internal_percentage = (internal_gain['total'] / total_load_w) * 100 if total_load_w > 0 else 0 |
|
|
|
|
|
|
|
|
results = { |
|
|
'total_load_w': total_load_w, |
|
|
'total_load_kw': total_load_kw, |
|
|
'recommended_size_kw': recommended_size_kw, |
|
|
'transmission_gain': { |
|
|
'total': transmission_gain + window_transmission_gain, |
|
|
'components': component_gains, |
|
|
'windows': window_gains |
|
|
}, |
|
|
'solar_gain': { |
|
|
'total': window_solar_gain, |
|
|
'windows': window_solar_gains |
|
|
}, |
|
|
'ventilation_gain': infiltration_gain, |
|
|
'internal_gain': internal_gain, |
|
|
'breakdown_percentage': { |
|
|
'transmission': transmission_percentage, |
|
|
'solar': solar_percentage, |
|
|
'ventilation': infiltration_percentage, |
|
|
'internal': internal_percentage |
|
|
} |
|
|
} |
|
|
|
|
|
return results |
|
|
|