File size: 13,655 Bytes
60cca7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1d7129
 
 
 
60cca7c
 
 
 
 
 
 
 
 
 
 
 
 
 
a1d7129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60cca7c
 
 
 
 
 
 
 
 
 
 
 
 
 
a1d7129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60cca7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a1d7129
60cca7c
 
 
 
a1d7129
60cca7c
a1d7129
60cca7c
 
 
 
 
 
 
a1d7129
60cca7c
 
 
 
 
 
a1d7129
 
 
 
 
 
 
 
 
 
 
 
 
60cca7c
 
 
 
 
 
a1d7129
 
 
 
 
 
 
 
 
 
 
60cca7c
 
 
 
 
a1d7129
 
60cca7c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
"""
ASHRAE Heating Load Calculation Module

This module implements the ASHRAE method for calculating heating loads in residential buildings.
It calculates the heat loss from the building envelope and unwanted ventilation/infiltration.
"""

import numpy as np
import pandas as pd


class HeatingLoadCalculator:
    """
    A class to calculate heating loads using the ASHRAE method.
    """

    def __init__(self):
        """Initialize the heating load calculator with default values."""
        # Specific heat capacity of air × density of air
        self.air_heat_factor = 0.33
        
        # Default values for internal heat gains (W)
        self.heat_gain_per_person = 75
        self.heat_gain_kitchen = 1000

    def calculate_conduction_heat_loss(self, area, u_value, temp_diff):
        """
        Calculate conduction heat loss through building components.
        
        Args:
            area (float): Area of the building component in m²
            u_value (float): U-value of the component in W/m²°C
            temp_diff (float): Temperature difference (inside - outside) in °C
            
        Returns:
            float: Heat loss in Watts
        """
        return area * u_value * temp_diff
        
    def calculate_wall_solar_heat_gain(self, area, u_value, orientation, daily_range='medium', latitude='medium'):
        """
        Calculate solar heat gain through walls based on orientation.
        
        Args:
            area (float): Area of the wall in m²
            u_value (float): U-value of the wall in W/m²°C
            orientation (str): Wall orientation ('north', 'east', 'south', 'west')
            daily_range (str): Daily temperature range ('low', 'medium', 'high')
            latitude (str): Latitude category ('low', 'medium', 'high')
            
        Returns:
            float: Heat gain in Watts
        """
        # Solar intensity factors based on orientation - for heating, south-facing walls (northern hemisphere)
        # or north-facing walls (southern hemisphere) receive more solar gain in winter
        # These are simplified factors for demonstration
        orientation_factors = {
            'north': 0.6,  # Higher in southern hemisphere during winter
            'east': 0.4,
            'south': 0.2,  # Lower in southern hemisphere during winter
            'west': 0.4,
            'horizontal': 0.3
        }
        
        # Adjustments for latitude
        latitude_factors = {
            'low': 0.9,    # Closer to equator - less winter sun angle
            'medium': 1.0,  # Mid latitudes
            'high': 1.1     # Closer to poles - more winter sun angle variation
        }
        
        # Adjustments for daily temperature range
        range_factors = {
            'low': 0.95,    # Less than 8.5°C
            'medium': 1.0,  # Between 8.5°C and 14°C
            'high': 1.05    # Over 14°C
        }
        
        # Base solar heat gain through walls (W/m²) - lower in winter
        base_solar_gain = 10.0
        
        # Get factors
        orientation_factor = orientation_factors.get(orientation.lower(), 0.5)  # Default to south if not found
        latitude_factor = latitude_factors.get(latitude.lower(), 1.0)
        range_factor = range_factors.get(daily_range.lower(), 1.0)
        
        # Calculate solar heat gain
        solar_gain = area * base_solar_gain * orientation_factor * latitude_factor * range_factor
        
        # Factor in the U-value (walls with higher U-values transmit more solar heat)
        u_value_factor = min(u_value / 0.5, 2.0)  # Normalize against a typical U-value of 0.5
        
        return solar_gain * u_value_factor

    def calculate_infiltration_heat_loss(self, volume, air_changes, temp_diff):
        """
        Calculate heat loss due to infiltration and ventilation.
        
        Args:
            volume (float): Volume of the space in m³
            air_changes (float): Number of air changes per hour
            temp_diff (float): Temperature difference (inside - outside) in °C
            
        Returns:
            float: Heat loss in Watts
        """
        return self.air_heat_factor * volume * air_changes * temp_diff
        
    def calculate_internal_heat_gain(self, num_people, has_kitchen=False, equipment_watts=0):
        """
        Calculate internal heat gain from people, kitchen, and equipment.
        
        Args:
            num_people (int): Number of occupants
            has_kitchen (bool): Whether the space includes a kitchen
            equipment_watts (float): Additional equipment heat gain in Watts
            
        Returns:
            float: Heat gain in Watts
        """
        people_gain = num_people * self.heat_gain_per_person
        kitchen_gain = self.heat_gain_kitchen if has_kitchen else 0
        return people_gain + kitchen_gain + equipment_watts

    def calculate_annual_heating_energy(self, total_heat_loss, heating_degree_days, correction_factor=1.0):
        """
        Calculate annual heating energy requirement using heating degree days.
        
        Args:
            total_heat_loss (float): Total heat loss in Watts
            heating_degree_days (float): Number of heating degree days
            correction_factor (float): Correction factor for occupancy
            
        Returns:
            float: Annual heating energy in kWh
        """
        # Convert W to kW
        heat_loss_kw = total_heat_loss / 1000
        
        # Calculate annual heating energy (kWh)
        # 24 hours in a day
        annual_energy = heat_loss_kw * 24 * heating_degree_days * correction_factor
        
        return annual_energy

    def get_outdoor_design_temperature(self, location):
        """
        Get the outdoor design temperature for a location.
        
        Args:
            location (str): Location name
            
        Returns:
            float: Outdoor design temperature in °C
        """
        # This is a simplified version - in a real implementation, this would use lookup tables
        # based on the AIRAH Design Data Manual
        
        # Example data for Australian locations
        temperatures = {
            'sydney': 7.0,
            'melbourne': 4.0,
            'brisbane': 9.0,
            'perth': 7.0,
            'adelaide': 5.0,
            'hobart': 2.0,
            'darwin': 15.0,
            'canberra': -1.0,
            'mildura': 4.5
        }
        
        return temperatures.get(location.lower(), 5.0)  # Default to 5°C if location not found

    def get_heating_degree_days(self, location, base_temp=18):
        """
        Get the heating degree days for a location.
        
        Args:
            location (str): Location name
            base_temp (int): Base temperature for HDD calculation (default: 18°C)
            
        Returns:
            float: Heating degree days
        """
        # This is a simplified version - in a real implementation, this would use lookup tables
        # or API data from Bureau of Meteorology
        
        # Example data for Australian locations with base temperature of 18°C
        hdd_data = {
            'sydney': 740,
            'melbourne': 1400,
            'brisbane': 320,
            'perth': 760,
            'adelaide': 1100,
            'hobart': 1800,
            'darwin': 0,
            'canberra': 2000,
            'mildura': 1200
        }
        
        return hdd_data.get(location.lower(), 1000)  # Default to 1000 if location not found

    def get_occupancy_correction_factor(self, occupancy_type):
        """
        Get the correction factor for occupancy type.
        
        Args:
            occupancy_type (str): Type of occupancy
            
        Returns:
            float: Correction factor
        """
        # Correction factors based on occupancy patterns
        factors = {
            'continuous': 1.0,      # Continuously heated
            'intermittent': 0.8,    # Heated during occupied hours
            'night_setback': 0.9,   # Temperature setback at night
            'weekend_off': 0.85,    # Heating off during weekends
            'vacation_home': 0.6    # Occasionally occupied
        }
        
        return factors.get(occupancy_type.lower(), 1.0)  # Default to continuous if not found

    def calculate_total_heating_load(self, building_components, infiltration, internal_gains=None):
        """
        Calculate the total peak heating load.
        
        Args:
            building_components (list): List of dicts with 'area', 'u_value', 'temp_diff', and 'orientation' for each component
            infiltration (dict): Dict with 'volume', 'air_changes', and 'temp_diff'
            internal_gains (dict): Dict with 'num_people', 'has_kitchen', and 'equipment_watts'
            
        Returns:
            dict: Dictionary with component heat losses and total heating load in Watts
        """
        # Calculate conduction heat loss through building components
        component_losses = {}
        total_conduction_loss = 0
        wall_solar_gain = 0
        
        for comp in building_components:
            name = comp.get('name', f"Component {len(component_losses) + 1}")
            loss = self.calculate_conduction_heat_loss(comp['area'], comp['u_value'], comp['temp_diff'])
            component_losses[name] = loss
            total_conduction_loss += loss
            
            # Calculate solar gain for walls based on orientation
            if 'orientation' in comp:
                daily_range = comp.get('daily_range', 'medium')
                latitude = comp.get('latitude', 'medium')
                solar_gain = self.calculate_wall_solar_heat_gain(
                    comp['area'], 
                    comp['u_value'], 
                    comp['orientation'],
                    daily_range,
                    latitude
                )
                wall_solar_gain += solar_gain
        
        # Calculate infiltration heat loss
        infiltration_loss = self.calculate_infiltration_heat_loss(
            infiltration['volume'], infiltration['air_changes'], infiltration['temp_diff']
        )
        
        # Calculate internal heat gain if provided
        internal_gain = 0
        if internal_gains:
            internal_gain = self.calculate_internal_heat_gain(
                internal_gains.get('num_people', 0),
                internal_gains.get('has_kitchen', False),
                internal_gains.get('equipment_watts', 0)
            )
        
        # Calculate total heating load (subtract solar gain and internal gains as they reduce heating load)
        total_load = total_conduction_loss + infiltration_loss - wall_solar_gain - internal_gain
        
        return {
            'component_losses': component_losses,
            'total_conduction_loss': total_conduction_loss,
            'infiltration_loss': infiltration_loss,
            'wall_solar_gain': wall_solar_gain,
            'internal_gain': internal_gain,
            'total_load': total_load
        }

    def calculate_annual_heating_requirement(self, total_load, location, occupancy_type='continuous', base_temp=18):
        """
        Calculate the annual heating energy requirement.
        
        Args:
            total_load (float): Total heating load in Watts
            location (str): Location name
            occupancy_type (str): Type of occupancy
            base_temp (int): Base temperature for HDD calculation
            
        Returns:
            dict: Dictionary with annual heating energy in kWh and related factors
        """
        # Get heating degree days for the location
        hdd = self.get_heating_degree_days(location, base_temp)
        
        # Get correction factor for occupancy
        correction_factor = self.get_occupancy_correction_factor(occupancy_type)
        
        # Calculate annual heating energy
        annual_energy = self.calculate_annual_heating_energy(total_load, hdd, correction_factor)
        
        return {
            'heating_degree_days': hdd,
            'correction_factor': correction_factor,
            'annual_energy_kwh': annual_energy,
            'annual_energy_mj': annual_energy * 3.6  # Convert kWh to MJ
        }


# Example usage
if __name__ == "__main__":
    calculator = HeatingLoadCalculator()
    
    # Example data for a simple room in Mildura
    building_components = [
        {'name': 'Floor', 'area': 50, 'u_value': 1.47, 'temp_diff': 16.5},  # Concrete slab
        {'name': 'Walls', 'area': 80, 'u_value': 1.5, 'temp_diff': 16.5},   # External walls
        {'name': 'Ceiling', 'area': 50, 'u_value': 0.9, 'temp_diff': 16.5}, # Ceiling
        {'name': 'Windows', 'area': 8, 'u_value': 5.8, 'temp_diff': 16.5}   # Windows
    ]
    
    infiltration = {'volume': 125, 'air_changes': 0.5, 'temp_diff': 16.5}
    
    # Calculate peak heating load
    result = calculator.calculate_total_heating_load(building_components, infiltration)
    
    print("Heating Load Calculation Results:")
    for key, value in result.items():
        if key == 'component_losses':
            print("Component Losses:")
            for comp, loss in value.items():
                print(f"  {comp}: {loss:.2f} W")
        else:
            print(f"{key}: {value:.2f} W")
    
    # Calculate annual heating requirement
    annual_result = calculator.calculate_annual_heating_requirement(result['total_load'], 'mildura')
    
    print("\nAnnual Heating Requirement:")
    for key, value in annual_result.items():
        print(f"{key}: {value:.2f}")