mabuseif commited on
Commit
b9b64f6
·
verified ·
1 Parent(s): e2a2c43

Update utils/psychrometrics.py

Browse files
Files changed (1) hide show
  1. utils/psychrometrics.py +89 -10
utils/psychrometrics.py CHANGED
@@ -1,6 +1,7 @@
1
  """
2
  Psychrometric module for HVAC Load Calculator.
3
  This module implements psychrometric calculations for air properties.
 
4
  """
5
 
6
  from typing import Dict, List, Any, Optional, Tuple
@@ -19,10 +20,31 @@ GAS_CONSTANT_WATER_VAPOR = UNIVERSAL_GAS_CONSTANT / WATER_MOLECULAR_WEIGHT # J/
19
  class Psychrometrics:
20
  """Class for psychrometric calculations."""
21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  @staticmethod
23
  def saturation_pressure(t_db: float) -> float:
24
  """
25
  Calculate saturation pressure of water vapor.
 
26
 
27
  Args:
28
  t_db: Dry-bulb temperature in °C
@@ -30,6 +52,8 @@ class Psychrometrics:
30
  Returns:
31
  Saturation pressure in Pa
32
  """
 
 
33
  # Convert temperature to Kelvin
34
  t_k = t_db + 273.15
35
 
@@ -67,6 +91,7 @@ class Psychrometrics:
67
  def humidity_ratio(t_db: float, rh: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
68
  """
69
  Calculate humidity ratio (mass of water vapor per unit mass of dry air).
 
70
 
71
  Args:
72
  t_db: Dry-bulb temperature in °C
@@ -76,6 +101,8 @@ class Psychrometrics:
76
  Returns:
77
  Humidity ratio in kg water vapor / kg dry air
78
  """
 
 
79
  # Convert relative humidity to decimal
80
  rh_decimal = rh / 100.0
81
 
@@ -85,8 +112,10 @@ class Psychrometrics:
85
  # Calculate partial pressure of water vapor
86
  p_w = rh_decimal * p_ws
87
 
 
 
 
88
  # Calculate humidity ratio
89
- # ASHRAE Fundamentals 2017 Chapter 1, Equation 20
90
  w = 0.621945 * p_w / (p_atm - p_w)
91
 
92
  return w
@@ -95,6 +124,7 @@ class Psychrometrics:
95
  def relative_humidity(t_db: float, w: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
96
  """
97
  Calculate relative humidity from humidity ratio.
 
98
 
99
  Args:
100
  t_db: Dry-bulb temperature in °C
@@ -104,11 +134,14 @@ class Psychrometrics:
104
  Returns:
105
  Relative humidity (0-100)
106
  """
 
 
 
 
107
  # Calculate saturation pressure
108
  p_ws = Psychrometrics.saturation_pressure(t_db)
109
 
110
  # Calculate partial pressure of water vapor
111
- # Rearranged from ASHRAE Fundamentals 2017 Chapter 1, Equation 20
112
  p_w = p_atm * w / (0.621945 + w)
113
 
114
  # Calculate relative humidity
@@ -120,6 +153,7 @@ class Psychrometrics:
120
  def wet_bulb_temperature(t_db: float, rh: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
121
  """
122
  Calculate wet-bulb temperature using iterative method.
 
123
 
124
  Args:
125
  t_db: Dry-bulb temperature in °C
@@ -129,6 +163,8 @@ class Psychrometrics:
129
  Returns:
130
  Wet-bulb temperature in °C
131
  """
 
 
132
  # Calculate humidity ratio at given conditions
133
  w = Psychrometrics.humidity_ratio(t_db, rh, p_atm)
134
 
@@ -140,6 +176,9 @@ class Psychrometrics:
140
  tolerance = 0.001 # °C
141
 
142
  for i in range(max_iterations):
 
 
 
143
  # Calculate saturation pressure at wet-bulb temperature
144
  p_ws_wb = Psychrometrics.saturation_pressure(t_wb)
145
 
@@ -147,7 +186,6 @@ class Psychrometrics:
147
  w_s_wb = 0.621945 * p_ws_wb / (p_atm - p_ws_wb)
148
 
149
  # Calculate humidity ratio from wet-bulb temperature
150
- # ASHRAE Fundamentals 2017 Chapter 1, Equation 35
151
  h_fg = 2501000 + 1840 * t_wb # Latent heat of vaporization at t_wb in J/kg
152
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
153
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
@@ -170,6 +208,7 @@ class Psychrometrics:
170
  def dew_point_temperature(t_db: float, rh: float) -> float:
171
  """
172
  Calculate dew point temperature.
 
173
 
174
  Args:
175
  t_db: Dry-bulb temperature in °C
@@ -178,6 +217,8 @@ class Psychrometrics:
178
  Returns:
179
  Dew point temperature in °C
180
  """
 
 
181
  # Convert relative humidity to decimal
182
  rh_decimal = rh / 100.0
183
 
@@ -188,7 +229,6 @@ class Psychrometrics:
188
  p_w = rh_decimal * p_ws
189
 
190
  # Calculate dew point temperature
191
- # ASHRAE Fundamentals 2017 Chapter 1, Equation 39 and 40
192
  alpha = math.log(p_w / 1000.0) # Convert to kPa for the formula
193
 
194
  if t_db >= 0:
@@ -214,6 +254,7 @@ class Psychrometrics:
214
  def enthalpy(t_db: float, w: float) -> float:
215
  """
216
  Calculate specific enthalpy of moist air.
 
217
 
218
  Args:
219
  t_db: Dry-bulb temperature in °C
@@ -222,7 +263,10 @@ class Psychrometrics:
222
  Returns:
223
  Specific enthalpy in J/kg dry air
224
  """
225
- # ASHRAE Fundamentals 2017 Chapter 1, Equation 30
 
 
 
226
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
227
  h_fg = 2501000 # Latent heat of vaporization at 0°C in J/kg
228
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
@@ -235,6 +279,7 @@ class Psychrometrics:
235
  def specific_volume(t_db: float, w: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
236
  """
237
  Calculate specific volume of moist air.
 
238
 
239
  Args:
240
  t_db: Dry-bulb temperature in °C
@@ -244,10 +289,13 @@ class Psychrometrics:
244
  Returns:
245
  Specific volume in m³/kg dry air
246
  """
 
 
 
 
247
  # Convert temperature to Kelvin
248
  t_k = t_db + 273.15
249
 
250
- # ASHRAE Fundamentals 2017 Chapter 1, Equation 28
251
  r_da = GAS_CONSTANT_DRY_AIR # Gas constant for dry air in J/(kg·K)
252
 
253
  v = r_da * t_k * (1 + 1.607858 * w) / p_atm
@@ -258,6 +306,7 @@ class Psychrometrics:
258
  def density(t_db: float, w: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
259
  """
260
  Calculate density of moist air.
 
261
 
262
  Args:
263
  t_db: Dry-bulb temperature in °C
@@ -267,6 +316,10 @@ class Psychrometrics:
267
  Returns:
268
  Density in kg/m³
269
  """
 
 
 
 
270
  # Calculate specific volume
271
  v = Psychrometrics.specific_volume(t_db, w, p_atm)
272
 
@@ -279,6 +332,7 @@ class Psychrometrics:
279
  def moist_air_properties(t_db: float, rh: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> Dict[str, float]:
280
  """
281
  Calculate all psychrometric properties of moist air.
 
282
 
283
  Args:
284
  t_db: Dry-bulb temperature in °C
@@ -288,6 +342,8 @@ class Psychrometrics:
288
  Returns:
289
  Dictionary with all psychrometric properties
290
  """
 
 
291
  # Calculate humidity ratio
292
  w = Psychrometrics.humidity_ratio(t_db, rh, p_atm)
293
 
@@ -331,6 +387,7 @@ class Psychrometrics:
331
  def find_humidity_ratio_for_enthalpy(t_db: float, h: float) -> float:
332
  """
333
  Find humidity ratio for a given dry-bulb temperature and enthalpy.
 
334
 
335
  Args:
336
  t_db: Dry-bulb temperature in °C
@@ -339,19 +396,23 @@ class Psychrometrics:
339
  Returns:
340
  Humidity ratio in kg water vapor / kg dry air
341
  """
342
- # Rearrange ASHRAE Fundamentals 2017 Chapter 1, Equation 30
 
 
 
343
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
344
  h_fg = 2501000 # Latent heat of vaporization at 0°C in J/kg
345
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
346
 
347
  w = (h - c_pa * t_db) / (h_fg + c_pw * t_db)
348
 
349
- return max(0, w) # Ensure non-negative value
350
 
351
  @staticmethod
352
  def find_temperature_for_enthalpy(w: float, h: float) -> float:
353
  """
354
  Find dry-bulb temperature for a given humidity ratio and enthalpy.
 
355
 
356
  Args:
357
  w: Humidity ratio in kg water vapor / kg dry air
@@ -360,19 +421,25 @@ class Psychrometrics:
360
  Returns:
361
  Dry-bulb temperature in °C
362
  """
363
- # Rearrange ASHRAE Fundamentals 2017 Chapter 1, Equation 30
 
 
 
 
364
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
365
  h_fg = 2501000 # Latent heat of vaporization at 0°C in J/kg
366
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
367
 
368
  t_db = (h - w * h_fg) / (c_pa + w * c_pw)
369
 
 
370
  return t_db
371
 
372
  @staticmethod
373
  def sensible_heat_ratio(q_sensible: float, q_total: float) -> float:
374
  """
375
  Calculate sensible heat ratio.
 
376
 
377
  Args:
378
  q_sensible: Sensible heat load in W
@@ -383,6 +450,8 @@ class Psychrometrics:
383
  """
384
  if q_total == 0:
385
  return 1.0
 
 
386
 
387
  return q_sensible / q_total
388
 
@@ -391,6 +460,7 @@ class Psychrometrics:
391
  rh_return: float = 50.0, p_atm: float = ATMOSPHERIC_PRESSURE) -> Dict[str, float]:
392
  """
393
  Calculate required air flow rate for a given sensible load.
 
394
 
395
  Args:
396
  q_sensible: Sensible heat load in W
@@ -402,6 +472,9 @@ class Psychrometrics:
402
  Returns:
403
  Dictionary with air flow rate in different units
404
  """
 
 
 
405
  # Calculate return air properties
406
  w_return = Psychrometrics.humidity_ratio(t_return, rh_return, p_atm)
407
  rho_return = Psychrometrics.density(t_return, w_return, p_atm)
@@ -441,6 +514,7 @@ class Psychrometrics:
441
  p_atm: float = ATMOSPHERIC_PRESSURE) -> Dict[str, float]:
442
  """
443
  Calculate properties of mixed airstreams.
 
444
 
445
  Args:
446
  m1: Mass flow rate of airstream 1 in kg/s
@@ -454,6 +528,11 @@ class Psychrometrics:
454
  Returns:
455
  Dictionary with mixed air properties
456
  """
 
 
 
 
 
457
  # Calculate humidity ratios
458
  w1 = Psychrometrics.humidity_ratio(t_db1, rh1, p_atm)
459
  w2 = Psychrometrics.humidity_ratio(t_db2, rh2, p_atm)
@@ -499,4 +578,4 @@ if __name__ == "__main__":
499
  print(f"Specific volume: {properties['specific_volume']:.4f} m³/kg")
500
  print(f"Density: {properties['density']:.4f} kg/m³")
501
  print(f"Saturation pressure: {properties['saturation_pressure']/1000:.2f} kPa")
502
- print(f"Partial pressure: {properties['partial_pressure']/1000:.2f} kPa")
 
1
  """
2
  Psychrometric module for HVAC Load Calculator.
3
  This module implements psychrometric calculations for air properties.
4
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1.
5
  """
6
 
7
  from typing import Dict, List, Any, Optional, Tuple
 
20
  class Psychrometrics:
21
  """Class for psychrometric calculations."""
22
 
23
+ @staticmethod
24
+ def validate_inputs(t_db: float, rh: Optional[float] = None, p_atm: Optional[float] = None) -> None:
25
+ """
26
+ Validate input parameters for psychrometric calculations.
27
+
28
+ Args:
29
+ t_db: Dry-bulb temperature in °C
30
+ rh: Relative humidity in % (0-100), optional
31
+ p_atm: Atmospheric pressure in Pa, optional
32
+
33
+ Raises:
34
+ ValueError: If inputs are invalid
35
+ """
36
+ if not -50 <= t_db <= 60:
37
+ raise ValueError(f"Temperature {t_db}°C must be between -50°C and 60°C")
38
+ if rh is not None and not 0 <= rh <= 100:
39
+ raise ValueError(f"Relative humidity {rh}% must be between 0 and 100%")
40
+ if p_atm is not None and p_atm <= 0:
41
+ raise ValueError(f"Atmospheric pressure {p_atm} Pa must be positive")
42
+
43
  @staticmethod
44
  def saturation_pressure(t_db: float) -> float:
45
  """
46
  Calculate saturation pressure of water vapor.
47
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equations 5 and 6.
48
 
49
  Args:
50
  t_db: Dry-bulb temperature in °C
 
52
  Returns:
53
  Saturation pressure in Pa
54
  """
55
+ Psychrometrics.validate_inputs(t_db)
56
+
57
  # Convert temperature to Kelvin
58
  t_k = t_db + 273.15
59
 
 
91
  def humidity_ratio(t_db: float, rh: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
92
  """
93
  Calculate humidity ratio (mass of water vapor per unit mass of dry air).
94
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 20.
95
 
96
  Args:
97
  t_db: Dry-bulb temperature in °C
 
101
  Returns:
102
  Humidity ratio in kg water vapor / kg dry air
103
  """
104
+ Psychrometrics.validate_inputs(t_db, rh, p_atm)
105
+
106
  # Convert relative humidity to decimal
107
  rh_decimal = rh / 100.0
108
 
 
112
  # Calculate partial pressure of water vapor
113
  p_w = rh_decimal * p_ws
114
 
115
+ if p_w >= p_atm:
116
+ raise ValueError("Partial pressure of water vapor exceeds atmospheric pressure")
117
+
118
  # Calculate humidity ratio
 
119
  w = 0.621945 * p_w / (p_atm - p_w)
120
 
121
  return w
 
124
  def relative_humidity(t_db: float, w: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
125
  """
126
  Calculate relative humidity from humidity ratio.
127
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 20 (rearranged).
128
 
129
  Args:
130
  t_db: Dry-bulb temperature in °C
 
134
  Returns:
135
  Relative humidity (0-100)
136
  """
137
+ Psychrometrics.validate_inputs(t_db, p_atm=p_atm)
138
+ if w < 0:
139
+ raise ValueError("Humidity ratio cannot be negative")
140
+
141
  # Calculate saturation pressure
142
  p_ws = Psychrometrics.saturation_pressure(t_db)
143
 
144
  # Calculate partial pressure of water vapor
 
145
  p_w = p_atm * w / (0.621945 + w)
146
 
147
  # Calculate relative humidity
 
153
  def wet_bulb_temperature(t_db: float, rh: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
154
  """
155
  Calculate wet-bulb temperature using iterative method.
156
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 35.
157
 
158
  Args:
159
  t_db: Dry-bulb temperature in °C
 
163
  Returns:
164
  Wet-bulb temperature in °C
165
  """
166
+ Psychrometrics.validate_inputs(t_db, rh, p_atm)
167
+
168
  # Calculate humidity ratio at given conditions
169
  w = Psychrometrics.humidity_ratio(t_db, rh, p_atm)
170
 
 
176
  tolerance = 0.001 # °C
177
 
178
  for i in range(max_iterations):
179
+ # Validate wet-bulb temperature
180
+ Psychrometrics.validate_inputs(t_wb)
181
+
182
  # Calculate saturation pressure at wet-bulb temperature
183
  p_ws_wb = Psychrometrics.saturation_pressure(t_wb)
184
 
 
186
  w_s_wb = 0.621945 * p_ws_wb / (p_atm - p_ws_wb)
187
 
188
  # Calculate humidity ratio from wet-bulb temperature
 
189
  h_fg = 2501000 + 1840 * t_wb # Latent heat of vaporization at t_wb in J/kg
190
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
191
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
 
208
  def dew_point_temperature(t_db: float, rh: float) -> float:
209
  """
210
  Calculate dew point temperature.
211
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equations 39 and 40.
212
 
213
  Args:
214
  t_db: Dry-bulb temperature in °C
 
217
  Returns:
218
  Dew point temperature in °C
219
  """
220
+ Psychrometrics.validate_inputs(t_db, rh)
221
+
222
  # Convert relative humidity to decimal
223
  rh_decimal = rh / 100.0
224
 
 
229
  p_w = rh_decimal * p_ws
230
 
231
  # Calculate dew point temperature
 
232
  alpha = math.log(p_w / 1000.0) # Convert to kPa for the formula
233
 
234
  if t_db >= 0:
 
254
  def enthalpy(t_db: float, w: float) -> float:
255
  """
256
  Calculate specific enthalpy of moist air.
257
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 30.
258
 
259
  Args:
260
  t_db: Dry-bulb temperature in °C
 
263
  Returns:
264
  Specific enthalpy in J/kg dry air
265
  """
266
+ Psychrometrics.validate_inputs(t_db)
267
+ if w < 0:
268
+ raise ValueError("Humidity ratio cannot be negative")
269
+
270
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
271
  h_fg = 2501000 # Latent heat of vaporization at 0°C in J/kg
272
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
 
279
  def specific_volume(t_db: float, w: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
280
  """
281
  Calculate specific volume of moist air.
282
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 28.
283
 
284
  Args:
285
  t_db: Dry-bulb temperature in °C
 
289
  Returns:
290
  Specific volume in m³/kg dry air
291
  """
292
+ Psychrometrics.validate_inputs(t_db, p_atm=p_atm)
293
+ if w < 0:
294
+ raise ValueError("Humidity ratio cannot be negative")
295
+
296
  # Convert temperature to Kelvin
297
  t_k = t_db + 273.15
298
 
 
299
  r_da = GAS_CONSTANT_DRY_AIR # Gas constant for dry air in J/(kg·K)
300
 
301
  v = r_da * t_k * (1 + 1.607858 * w) / p_atm
 
306
  def density(t_db: float, w: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> float:
307
  """
308
  Calculate density of moist air.
309
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, derived from Equation 28.
310
 
311
  Args:
312
  t_db: Dry-bulb temperature in °C
 
316
  Returns:
317
  Density in kg/m³
318
  """
319
+ Psychrometrics.validate_inputs(t_db, p_atm=p_atm)
320
+ if w < 0:
321
+ raise ValueError("Humidity ratio cannot be negative")
322
+
323
  # Calculate specific volume
324
  v = Psychrometrics.specific_volume(t_db, w, p_atm)
325
 
 
332
  def moist_air_properties(t_db: float, rh: float, p_atm: float = ATMOSPHERIC_PRESSURE) -> Dict[str, float]:
333
  """
334
  Calculate all psychrometric properties of moist air.
335
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1.
336
 
337
  Args:
338
  t_db: Dry-bulb temperature in °C
 
342
  Returns:
343
  Dictionary with all psychrometric properties
344
  """
345
+ Psychrometrics.validate_inputs(t_db, rh, p_atm)
346
+
347
  # Calculate humidity ratio
348
  w = Psychrometrics.humidity_ratio(t_db, rh, p_atm)
349
 
 
387
  def find_humidity_ratio_for_enthalpy(t_db: float, h: float) -> float:
388
  """
389
  Find humidity ratio for a given dry-bulb temperature and enthalpy.
390
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 30 (rearranged).
391
 
392
  Args:
393
  t_db: Dry-bulb temperature in °C
 
396
  Returns:
397
  Humidity ratio in kg water vapor / kg dry air
398
  """
399
+ Psychrometrics.validate_inputs(t_db)
400
+ if h < 0:
401
+ raise ValueError("Enthalpy cannot be negative")
402
+
403
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
404
  h_fg = 2501000 # Latent heat of vaporization at 0°C in J/kg
405
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
406
 
407
  w = (h - c_pa * t_db) / (h_fg + c_pw * t_db)
408
 
409
+ return max(0, w)
410
 
411
  @staticmethod
412
  def find_temperature_for_enthalpy(w: float, h: float) -> float:
413
  """
414
  Find dry-bulb temperature for a given humidity ratio and enthalpy.
415
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Equation 30 (rearranged).
416
 
417
  Args:
418
  w: Humidity ratio in kg water vapor / kg dry air
 
421
  Returns:
422
  Dry-bulb temperature in °C
423
  """
424
+ if w < 0:
425
+ raise ValueError("Humidity ratio cannot be negative")
426
+ if h < 0:
427
+ raise ValueError("Enthalpy cannot be negative")
428
+
429
  c_pa = 1006 # Specific heat of dry air in J/(kg·K)
430
  h_fg = 2501000 # Latent heat of vaporization at 0°C in J/kg
431
  c_pw = 1860 # Specific heat of water vapor in J/(kg·K)
432
 
433
  t_db = (h - w * h_fg) / (c_pa + w * c_pw)
434
 
435
+ Psychrometrics.validate_inputs(t_db)
436
  return t_db
437
 
438
  @staticmethod
439
  def sensible_heat_ratio(q_sensible: float, q_total: float) -> float:
440
  """
441
  Calculate sensible heat ratio.
442
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Section 1.5.
443
 
444
  Args:
445
  q_sensible: Sensible heat load in W
 
450
  """
451
  if q_total == 0:
452
  return 1.0
453
+ if q_sensible < 0 or q_total < 0:
454
+ raise ValueError("Heat loads cannot be negative")
455
 
456
  return q_sensible / q_total
457
 
 
460
  rh_return: float = 50.0, p_atm: float = ATMOSPHERIC_PRESSURE) -> Dict[str, float]:
461
  """
462
  Calculate required air flow rate for a given sensible load.
463
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Section 1.6.
464
 
465
  Args:
466
  q_sensible: Sensible heat load in W
 
472
  Returns:
473
  Dictionary with air flow rate in different units
474
  """
475
+ Psychrometrics.validate_inputs(t_return, rh_return, p_atm)
476
+ Psychrometrics.validate_inputs(t_supply)
477
+
478
  # Calculate return air properties
479
  w_return = Psychrometrics.humidity_ratio(t_return, rh_return, p_atm)
480
  rho_return = Psychrometrics.density(t_return, w_return, p_atm)
 
514
  p_atm: float = ATMOSPHERIC_PRESSURE) -> Dict[str, float]:
515
  """
516
  Calculate properties of mixed airstreams.
517
+ Reference: ASHRAE Handbook—Fundamentals (2017), Chapter 1, Section 1.7.
518
 
519
  Args:
520
  m1: Mass flow rate of airstream 1 in kg/s
 
528
  Returns:
529
  Dictionary with mixed air properties
530
  """
531
+ Psychrometrics.validate_inputs(t_db1, rh1, p_atm)
532
+ Psychrometrics.validate_inputs(t_db2, rh2, p_atm)
533
+ if m1 < 0 or m2 < 0:
534
+ raise ValueError("Mass flow rates cannot be negative")
535
+
536
  # Calculate humidity ratios
537
  w1 = Psychrometrics.humidity_ratio(t_db1, rh1, p_atm)
538
  w2 = Psychrometrics.humidity_ratio(t_db2, rh2, p_atm)
 
578
  print(f"Specific volume: {properties['specific_volume']:.4f} m³/kg")
579
  print(f"Density: {properties['density']:.4f} kg/m³")
580
  print(f"Saturation pressure: {properties['saturation_pressure']/1000:.2f} kPa")
581
+ print(f"Partial pressure: {properties['partial_pressure']/1000:.2f} kPa")