Library of psychrometric functions to calculate thermodynamic properties of air for Python, C, C#, Fortran, JavaScript and VBA/Excel
Overview
Psychrometrics are the study of physical and thermodynamic properties of moist air. These properties include, for example, the air’s dew point temperature, its wet bulb temperature, relative humidity, humidity ratio, enthalpy.
The estimation of these properties is critical in several engineering and scientific applications such as heating, ventilation, and air conditioning (HVAC) and meteorology. Although formulae to calculate the psychrometric properties of air are widely available in the literature (@Stull2011; @Wexler1983; @Stoecker1982; @Dilley1968; @Humphreys1920), their implementation in computer programs or spreadsheets can be challenging and time consuming.
PsychroLib is a library of functions to enable calculating psychrometric properties of moist and dry air. The library is available for Python, C, C#, Fortran, JavaScript, Microsoft Excel Visual Basic for Applications (VBA). It works in both metric (SI) and imperial (IP) systems of units. The functions are based of formulae from the 2017 ASHRAE Handbook — Fundamentals, Chapter 1, SI and IP editions. Functions can be grouped into two categories:
- Functions for the calculation of dew point temperature, wet-bulb temperature, partial vapour pressure of water, humidity ratio or relative humidity, knowing any other of these and dry bulb temperature and atmospheric pressure.
- Functions for the calculation of other moist air properties. All these use the humidity ratio as input.
Python
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 | # PsychroLib (version 2.3.0) (https://github.com/psychrometrics/psychrolib) # Copyright (c) 2018 D. Thevenard and D. Meyer for the current library implementation # Copyright (c) 2017 ASHRAE Handbook — Fundamentals for ASHRAE equations and coefficients # Licensed under the MIT License. """ psychrolib.py Contains functions for calculating thermodynamic properties of gas-vapor mixtures and standard atmosphere suitable for most engineering, physical and meteorological applications. Most of the functions are an implementation of the formulae found in the 2017 ASHRAE Handbook - Fundamentals, in both International System (SI), and Imperial (IP) units. Please refer to the information included in each function for their respective reference. Example >>> import psychrolib >>> # Set the unit system, for example to SI (can be either psychrolib.SI or psychrolib.IP) >>> psychrolib.SetUnitSystem(psychrolib.SI) >>> # Calculate the dew point temperature for a dry bulb temperature of 25 C and a relative humidity of 80% >>> TDewPoint = psychrolib.GetTDewPointFromRelHum(25.0, 0.80) >>> print(TDewPoint) 21.309397163661785 Copyright - For the current library implementation Copyright (c) 2018 D. Thevenard and D. Meyer. - For equations and coefficients published ASHRAE Handbook — Fundamentals, Chapter 1 Copyright (c) 2017 ASHRAE Handbook — Fundamentals (https://www.ashrae.org) License MIT (https://github.com/psychrometrics/psychrolib/LICENSE.txt) Note from the Authors We have made every effort to ensure that the code is adequate, however, we make no representation with respect to its accuracy. Use at your own risk. Should you notice an error, or if you have a suggestion, please notify us through GitHub at https://github.com/psychrometrics/psychrolib/issues. """ import math from enum import Enum, auto from typing import Optional ####################################################################################################### # Global constants ####################################################################################################### ZERO_FAHRENHEIT_AS_RANKINE = 459.67 """float: Zero degree Fahrenheit (°F) expressed as degree Rankine (°R) Units: °R Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39 """ ZERO_CELSIUS_AS_KELVIN = 273.15 """float: Zero degree Celsius (°C) expressed as Kelvin (K) Units: K Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39 """ R_DA_IP = 53.350 """float: Universal gas constant for dry air (IP version) Units: ft lb_Force lb_DryAir⁻¹ R⁻¹ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ R_DA_SI = 287.042 """float: Universal gas constant for dry air (SI version) Units: J kg_DryAir⁻¹ K⁻¹ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ MAX_ITER_COUNT = 100 """int: Maximum number of iterations before exiting while loops. """ MIN_HUM_RATIO = 1e-7 """float: Minimum acceptable humidity ratio used/returned by any functions. Any value above 0 or below the MIN_HUM_RATIO will be reset to this value. """ FREEZING_POINT_WATER_IP = 32.0 """float: Freezing point of water in Fahrenheit. """ FREEZING_POINT_WATER_SI = 0.0 """float: Freezing point of water in Celsius. """ TRIPLE_POINT_WATER_IP = 32.018 """float: Triple point of water in Fahrenheit. """ TRIPLE_POINT_WATER_SI = 0.01 """float: Triple point of water in Celsius. """ ####################################################################################################### # Helper functions ####################################################################################################### # Unit system to use. class UnitSystem(Enum): """ Private class not exposed used to set automatic enumeration values. """ IP = auto() SI = auto() IP = UnitSystem.IP SI = UnitSystem.SI PSYCHROLIB_UNITS = None PSYCHROLIB_TOLERANCE = 1.0 # Tolerance of temperature calculations def SetUnitSystem(Units: UnitSystem) -> None: """ Set the system of units to use (SI or IP). Args: Units: string indicating the system of units chosen (SI or IP) Notes: This function *HAS TO BE CALLED* before the library can be used """ global PSYCHROLIB_UNITS global PSYCHROLIB_TOLERANCE if not isinstance(Units, UnitSystem): raise ValueError("The system of units has to be either SI or IP.") PSYCHROLIB_UNITS = Units # Define tolerance on temperature calculations # The tolerance is the same in IP and SI if Units == IP: PSYCHROLIB_TOLERANCE = 0.001 * 9. / 5. else: PSYCHROLIB_TOLERANCE = 0.001 def GetUnitSystem() -> Optional[UnitSystem]: """ Return system of units in use. """ return PSYCHROLIB_UNITS def isIP() -> bool: """ Check whether the system in use is IP or SI. """ if PSYCHROLIB_UNITS == IP: return True elif PSYCHROLIB_UNITS == SI: return False else: raise ValueError('The system of units has not been defined.') ####################################################################################################### # Conversion between temperature units ####################################################################################################### def GetTRankineFromTFahrenheit(TFahrenheit: float) -> float: """ Utility function to convert temperature to degree Rankine (°R) given temperature in degree Fahrenheit (°F). Args: TRankine: Temperature in degree Fahrenheit (°F) Returns: Temperature in degree Rankine (°R) Reference: Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 Notes: Exact conversion. """ TRankine = TFahrenheit + ZERO_FAHRENHEIT_AS_RANKINE return TRankine def GetTFahrenheitFromTRankine(TRankine: float) -> float: """ Utility function to convert temperature to degree Fahrenheit (°F) given temperature in degree Rankine (°R). Args: TRankine: Temperature in degree Rankine (°R) Returns: Temperature in degree Fahrenheit (°F) Reference: Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 Notes: Exact conversion. """ return TRankine - ZERO_FAHRENHEIT_AS_RANKINE def GetTKelvinFromTCelsius(TCelsius: float) -> float: """ Utility function to convert temperature to Kelvin (K) given temperature in degree Celsius (°C). Args: TCelsius: Temperature in degree Celsius (°C) Returns: Temperature in Kelvin (K) Reference: Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 Notes: Exact conversion. """ TKelvin = TCelsius + ZERO_CELSIUS_AS_KELVIN return TKelvin def GetTCelsiusFromTKelvin(TKelvin: float) -> float: """ Utility function to convert temperature to degree Celsius (°C) given temperature in Kelvin (K). Args: TKelvin: Temperature in Kelvin (K) Returns: Temperature in degree Celsius (°C) Reference: Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 Notes: Exact conversion. """ return TKelvin - ZERO_CELSIUS_AS_KELVIN ####################################################################################################### # Conversions between dew point, wet bulb, and relative humidity ####################################################################################################### def GetTWetBulbFromTDewPoint(TDryBulb: float, TDewPoint: float, Pressure: float) -> float: """ Return wet-bulb temperature given dry-bulb temperature, dew-point temperature, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TDewPoint : Dew-point temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Wet-bulb temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if TDewPoint > TDryBulb: raise ValueError("Dew point temperature is above dry bulb temperature") HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) return TWetBulb def GetTWetBulbFromRelHum(TDryBulb: float, RelHum: float, Pressure: float) -> float: """ Return wet-bulb temperature given dry-bulb temperature, relative humidity, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] RelHum : Relative humidity in range [0, 1] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Wet-bulb temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if RelHum < 0 or RelHum > 1: raise ValueError("Relative humidity is outside range [0, 1]") HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) return TWetBulb def GetRelHumFromTDewPoint(TDryBulb: float, TDewPoint: float) -> float: """ Return relative humidity given dry-bulb temperature and dew-point temperature. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TDewPoint : Dew-point temperature in °F [IP] or °C [SI] Returns: Relative humidity in range [0, 1] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 22 """ if TDewPoint > TDryBulb: raise ValueError("Dew point temperature is above dry bulb temperature") VapPres = GetSatVapPres(TDewPoint) SatVapPres = GetSatVapPres(TDryBulb) RelHum = VapPres / SatVapPres return RelHum def GetRelHumFromTWetBulb(TDryBulb: float, TWetBulb: float, Pressure: float) -> float: """ Return relative humidity given dry-bulb temperature, wet bulb temperature and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Relative humidity in range [0, 1] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if TWetBulb > TDryBulb: raise ValueError("Wet bulb temperature is above dry bulb temperature") HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) return RelHum def GetTDewPointFromRelHum(TDryBulb: float, RelHum: float) -> float: """ Return dew-point temperature given dry-bulb temperature and relative humidity. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] RelHum: Relative humidity in range [0, 1] Returns: Dew-point temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if RelHum < 0 or RelHum > 1: raise ValueError("Relative humidity is outside range [0, 1]") VapPres = GetVapPresFromRelHum(TDryBulb, RelHum) TDewPoint = GetTDewPointFromVapPres(TDryBulb, VapPres) return TDewPoint def GetTDewPointFromTWetBulb(TDryBulb: float, TWetBulb: float, Pressure: float) -> float: """ Return dew-point temperature given dry-bulb temperature, wet-bulb temperature, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Dew-point temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if TWetBulb > TDryBulb: raise ValueError("Wet bulb temperature is above dry bulb temperature") HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) return TDewPoint ####################################################################################################### # Conversions between dew point, or relative humidity and vapor pressure ####################################################################################################### def GetVapPresFromRelHum(TDryBulb: float, RelHum: float) -> float: """ Return partial pressure of water vapor as a function of relative humidity and temperature. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] RelHum : Relative humidity in range [0, 1] Returns: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 """ if RelHum < 0 or RelHum > 1: raise ValueError("Relative humidity is outside range [0, 1]") VapPres = RelHum * GetSatVapPres(TDryBulb) return VapPres def GetRelHumFromVapPres(TDryBulb: float, VapPres: float) -> float: """ Return relative humidity given dry-bulb temperature and vapor pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] VapPres: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Returns: Relative humidity in range [0, 1] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 """ if VapPres < 0: raise ValueError("Partial pressure of water vapor in moist air cannot be negative") RelHum = VapPres / GetSatVapPres(TDryBulb) return RelHum def dLnPws_(TDryBulb: float) -> float: """ Helper function returning the derivative of the natural log of the saturation vapor pressure as a function of dry-bulb temperature. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Returns: Derivative of natural log of vapor pressure of saturated air in Psi [IP] or Pa [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 5 & 6 """ if isIP(): T = GetTRankineFromTFahrenheit(TDryBulb) if TDryBulb <= TRIPLE_POINT_WATER_IP: dLnPws = 1.0214165E+04 / math.pow(T, 2) - 5.3765794E-03 + 2 * 1.9202377E-07 * T \ + 3 * 3.5575832E-10 * math.pow(T, 2) - 4 * 9.0344688E-14 * math.pow(T, 3) + 4.1635019 / T else: dLnPws = 1.0440397E+04 / math.pow(T, 2) - 2.7022355E-02 + 2 * 1.2890360E-05 * T \ - 3 * 2.4780681E-09 * math.pow(T, 2) + 6.5459673 / T else: T = GetTKelvinFromTCelsius(TDryBulb) if TDryBulb <= TRIPLE_POINT_WATER_SI: dLnPws = 5.6745359E+03 / math.pow(T, 2) - 9.677843E-03 + 2 * 6.2215701E-07 * T \ + 3 * 2.0747825E-09 * math.pow(T, 2) - 4 * 9.484024E-13 * math.pow(T, 3) + 4.1635019 / T else: dLnPws = 5.8002206E+03 / math.pow(T, 2) - 4.8640239E-02 + 2 * 4.1764768E-05 * T \ - 3 * 1.4452093E-08 * math.pow(T, 2) + 6.5459673 / T return dLnPws def GetTDewPointFromVapPres(TDryBulb: float, VapPres: float) -> float: """ Return dew-point temperature given dry-bulb temperature and vapor pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] VapPres: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Returns: Dew-point temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 and 6 Notes: The dew point temperature is solved by inverting the equation giving water vapor pressure at saturation from temperature rather than using the regressions provided by ASHRAE (eqn. 37 and 38) which are much less accurate and have a narrower range of validity. The Newton-Raphson (NR) method is used on the logarithm of water vapour pressure as a function of temperature, which is a very smooth function Convergence is usually achieved in 3 to 5 iterations. TDryBulb is not really needed here, just used for convenience. """ if isIP(): BOUNDS = [-148, 392] else: BOUNDS = [-100, 200] # Validity check -- bounds outside which a solution cannot be found if VapPres < GetSatVapPres(BOUNDS[0]) or VapPres > GetSatVapPres(BOUNDS[1]): raise ValueError("Partial pressure of water vapor is outside range of validity of equations") # We use NR to approximate the solution. # First guess TDewPoint = TDryBulb # Calculated value of dew point temperatures, solved for iteratively lnVP = math.log(VapPres) # Partial pressure of water vapor in moist air index = 1 while True: TDewPoint_iter = TDewPoint # TDewPoint used in NR calculation lnVP_iter = math.log(GetSatVapPres(TDewPoint_iter)) # Derivative of function, calculated analytically d_lnVP = dLnPws_(TDewPoint_iter) # New estimate, bounded by the search domain defined above TDewPoint = TDewPoint_iter - (lnVP_iter - lnVP) / d_lnVP TDewPoint = max(TDewPoint, BOUNDS[0]) TDewPoint = min(TDewPoint, BOUNDS[1]) if ((math.fabs(TDewPoint - TDewPoint_iter) <= PSYCHROLIB_TOLERANCE)): break if (index > MAX_ITER_COUNT): raise ValueError("Convergence not reached in GetTDewPointFromVapPres. Stopping.") index = index + 1 TDewPoint = min(TDewPoint, TDryBulb) return TDewPoint def GetVapPresFromTDewPoint(TDewPoint: float) -> float: """ Return vapor pressure given dew point temperature. Args: TDewPoint : Dew-point temperature in °F [IP] or °C [SI] Returns: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36 """ VapPres = GetSatVapPres(TDewPoint) return VapPres ####################################################################################################### # Conversions from wet-bulb temperature, dew-point temperature, or relative humidity to humidity ratio ####################################################################################################### def GetTWetBulbFromHumRatio(TDryBulb: float, HumRatio: float, Pressure: float) -> float: """ Return wet-bulb temperature given dry-bulb temperature, humidity ratio, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Wet-bulb temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 solved for Tstar """ if HumRatio < 0: raise ValueError("Humidity ratio cannot be negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, BoundedHumRatio, Pressure) # Initial guesses TWetBulbSup = TDryBulb TWetBulbInf = TDewPoint TWetBulb = (TWetBulbInf + TWetBulbSup) / 2 index = 1 # Bisection loop while ((TWetBulbSup - TWetBulbInf) > PSYCHROLIB_TOLERANCE): # Compute humidity ratio at temperature Tstar Wstar = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) # Get new bounds if Wstar > BoundedHumRatio: TWetBulbSup = TWetBulb else: TWetBulbInf = TWetBulb # New guess of wet bulb temperature TWetBulb = (TWetBulbSup + TWetBulbInf) / 2 if (index >= MAX_ITER_COUNT): raise ValueError("Convergence not reached in GetTWetBulbFromHumRatio. Stopping.") index = index + 1 return TWetBulb def GetHumRatioFromTWetBulb(TDryBulb: float, TWetBulb: float, Pressure: float) -> float: """ Return humidity ratio given dry-bulb temperature, wet-bulb temperature, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 """ if TWetBulb > TDryBulb: raise ValueError("Wet bulb temperature is above dry bulb temperature") Wsstar = GetSatHumRatio(TWetBulb, Pressure) if isIP(): if TWetBulb >= FREEZING_POINT_WATER_IP: HumRatio = ((1093 - 0.556 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) \ / (1093 + 0.444 * TDryBulb - TWetBulb) else: HumRatio = ((1220 - 0.04 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) \ / (1220 + 0.444 * TDryBulb - 0.48*TWetBulb) else: if TWetBulb >= FREEZING_POINT_WATER_SI: HumRatio = ((2501. - 2.326 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) \ / (2501. + 1.86 * TDryBulb - 4.186 * TWetBulb) else: HumRatio = ((2830. - 0.24 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) \ / (2830. + 1.86 * TDryBulb - 2.1 * TWetBulb) # Validity check. return max(HumRatio, MIN_HUM_RATIO) def GetHumRatioFromRelHum(TDryBulb: float, RelHum: float, Pressure: float) -> float: """ Return humidity ratio given dry-bulb temperature, relative humidity, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] RelHum : Relative humidity in range [0, 1] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if RelHum < 0 or RelHum > 1: raise ValueError("Relative humidity is outside range [0, 1]") VapPres = GetVapPresFromRelHum(TDryBulb, RelHum) HumRatio = GetHumRatioFromVapPres(VapPres, Pressure) return HumRatio def GetRelHumFromHumRatio(TDryBulb: float, HumRatio: float, Pressure: float) -> float: """ Return relative humidity given dry-bulb temperature, humidity ratio, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Relative humidity in range [0, 1] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if HumRatio < 0: raise ValueError("Humidity ratio cannot be negative") VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) RelHum = GetRelHumFromVapPres(TDryBulb, VapPres) return RelHum def GetHumRatioFromTDewPoint(TDewPoint: float, Pressure: float) -> float: """ Return humidity ratio given dew-point temperature and pressure. Args: TDewPoint : Dew-point temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 13 """ VapPres = GetSatVapPres(TDewPoint) HumRatio = GetHumRatioFromVapPres(VapPres, Pressure) return HumRatio def GetTDewPointFromHumRatio(TDryBulb: float, HumRatio: float, Pressure: float) -> float: """ Return dew-point temperature given dry-bulb temperature, humidity ratio, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Dew-point temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ if HumRatio < 0: raise ValueError("Humidity ratio cannot be negative") VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) TDewPoint = GetTDewPointFromVapPres(TDryBulb, VapPres) return TDewPoint ####################################################################################################### # Conversions between humidity ratio and vapor pressure ####################################################################################################### def GetHumRatioFromVapPres(VapPres: float, Pressure: float) -> float: """ Return humidity ratio given water vapor pressure and atmospheric pressure. Args: VapPres : Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 """ if VapPres < 0: raise ValueError("Partial pressure of water vapor in moist air cannot be negative") HumRatio = 0.621945 * VapPres / (Pressure - VapPres) # Validity check. return max(HumRatio, MIN_HUM_RATIO) def GetVapPresFromHumRatio(HumRatio: float, Pressure: float) -> float: """ Return vapor pressure given humidity ratio and pressure. Args: HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 solved for pw """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) VapPres = Pressure * BoundedHumRatio / (0.621945 + BoundedHumRatio) return VapPres ####################################################################################################### # Conversions between humidity ratio and specific humidity ####################################################################################################### def GetSpecificHumFromHumRatio(HumRatio: float) -> float: """ Return the specific humidity from humidity ratio (aka mixing ratio). Args: HumRatio : Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] Returns: Specific humidity in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b """ if HumRatio < 0: raise ValueError("Humidity ratio cannot be negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) SpecificHum = BoundedHumRatio / (1.0 + BoundedHumRatio) return SpecificHum def GetHumRatioFromSpecificHum(SpecificHum: float) -> float: """ Return the humidity ratio (aka mixing ratio) from specific humidity. Args: SpecificHum : Specific humidity in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Returns: Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b (solved for humidity ratio) """ if SpecificHum < 0.0 or SpecificHum >= 1.0: raise ValueError("Specific humidity is outside range [0, 1[") HumRatio = SpecificHum / (1.0 - SpecificHum) # Validity check. return max(HumRatio, MIN_HUM_RATIO) ####################################################################################################### # Dry Air Calculations ####################################################################################################### def GetDryAirEnthalpy(TDryBulb: float) -> float: """ Return dry-air enthalpy given dry-bulb temperature. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Returns: Dry air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 28 """ if isIP(): DryAirEnthalpy = 0.240 * TDryBulb else: DryAirEnthalpy = 1006 * TDryBulb return DryAirEnthalpy def GetDryAirDensity(TDryBulb: float, Pressure: float) -> float: """ Return dry-air density given dry-bulb temperature and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Dry air density in lb ft⁻³ [IP] or kg m⁻³ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 Notes: Eqn 14 for the perfect gas relationship for dry air. Eqn 1 for the universal gas constant. The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². """ if isIP(): DryAirDensity = (144 * Pressure) / R_DA_IP / GetTRankineFromTFahrenheit(TDryBulb) else: DryAirDensity = Pressure / R_DA_SI / GetTKelvinFromTCelsius(TDryBulb) return DryAirDensity def GetDryAirVolume(TDryBulb: float, Pressure: float) -> float: """ Return dry-air volume given dry-bulb temperature and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Dry air volume in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 Notes: Eqn 14 for the perfect gas relationship for dry air. Eqn 1 for the universal gas constant. The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². """ if isIP(): DryAirVolume = R_DA_IP * GetTRankineFromTFahrenheit(TDryBulb) / (144 * Pressure) else: DryAirVolume = R_DA_SI * GetTKelvinFromTCelsius(TDryBulb) / Pressure return DryAirVolume def GetTDryBulbFromEnthalpyAndHumRatio(MoistAirEnthalpy: float, HumRatio: float) -> float: """ Return dry bulb temperature from enthalpy and humidity ratio. Args: MoistAirEnthalpy : Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Returns: Dry-bulb temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 Notes: Based on the `GetMoistAirEnthalpy` function, rearranged for temperature. """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if isIP(): TDryBulb = (MoistAirEnthalpy - 1061.0 * BoundedHumRatio) / (0.240 + 0.444 * BoundedHumRatio) else: TDryBulb = (MoistAirEnthalpy / 1000.0 - 2501.0 * BoundedHumRatio) / (1.006 + 1.86 * BoundedHumRatio) return TDryBulb def GetHumRatioFromEnthalpyAndTDryBulb(MoistAirEnthalpy: float, TDryBulb: float) -> float: """ Return humidity ratio from enthalpy and dry-bulb temperature. Args: MoistAirEnthalpy : Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. Notes: Based on the `GetMoistAirEnthalpy` function, rearranged for humidity ratio. """ if isIP(): HumRatio = (MoistAirEnthalpy - 0.240 * TDryBulb) / (1061.0 + 0.444 * TDryBulb) else: HumRatio = (MoistAirEnthalpy / 1000.0 - 1.006 * TDryBulb) / (2501.0 + 1.86 * TDryBulb) # Validity check. return max(HumRatio, MIN_HUM_RATIO) ####################################################################################################### # Saturated Air Calculations ####################################################################################################### def GetSatVapPres(TDryBulb: float) -> float: """ Return saturation vapor pressure given dry-bulb temperature. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Returns: Vapor pressure of saturated air in Psi [IP] or Pa [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 5 & 6 Important note: the ASHRAE formulae are defined above and below the freezing point but have a discontinuity at the freezing point. This is a small inaccuracy on ASHRAE's part: the formulae should be defined above and below the triple point of water (not the feezing point) in which case the discontinuity vanishes. It is essential to use the triple point of water otherwise function GetTDewPointFromVapPres, which inverts the present function, does not converge properly around the freezing point. """ if isIP(): if (TDryBulb < -148 or TDryBulb > 392): raise ValueError("Dry bulb temperature must be in range [-148, 392]°F") T = GetTRankineFromTFahrenheit(TDryBulb) if (TDryBulb <= TRIPLE_POINT_WATER_IP): LnPws = (-1.0214165E+04 / T - 4.8932428 - 5.3765794E-03 * T + 1.9202377E-07 * T**2 \ + 3.5575832E-10 * math.pow(T, 3) - 9.0344688E-14 * math.pow(T, 4) + 4.1635019 * math.log(T)) else: LnPws = -1.0440397E+04 / T - 1.1294650E+01 - 2.7022355E-02* T + 1.2890360E-05 * T**2 \ - 2.4780681E-09 * math.pow(T, 3) + 6.5459673 * math.log(T) else: if (TDryBulb < -100 or TDryBulb > 200): raise ValueError("Dry bulb temperature must be in range [-100, 200]°C") T = GetTKelvinFromTCelsius(TDryBulb) if (TDryBulb <= TRIPLE_POINT_WATER_SI): LnPws = -5.6745359E+03 / T + 6.3925247 - 9.677843E-03 * T + 6.2215701E-07 * T**2 \ + 2.0747825E-09 * math.pow(T, 3) - 9.484024E-13 * math.pow(T, 4) + 4.1635019 * math.log(T) else: LnPws = -5.8002206E+03 / T + 1.3914993 - 4.8640239E-02 * T + 4.1764768E-05 * T**2 \ - 1.4452093E-08 * math.pow(T, 3) + 6.5459673 * math.log(T) SatVapPres = math.exp(LnPws) return SatVapPres def GetSatHumRatio(TDryBulb: float, Pressure: float) -> float: """ Return humidity ratio of saturated air given dry-bulb temperature and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio of saturated air in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36, solved for W """ SatVaporPres = GetSatVapPres(TDryBulb) SatHumRatio = 0.621945 * SatVaporPres / (Pressure - SatVaporPres) # Validity check. return max(SatHumRatio, MIN_HUM_RATIO) def GetSatAirEnthalpy(TDryBulb: float, Pressure: float) -> float: """ Return saturated air enthalpy given dry-bulb temperature and pressure. Args: TDryBulb: Dry-bulb temperature in °F [IP] or °C [SI] Pressure: Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Saturated air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 """ SatHumRatio = GetSatHumRatio(TDryBulb, Pressure) SatAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, SatHumRatio) return SatAirEnthalpy ####################################################################################################### # Moist Air Calculations ####################################################################################################### def GetVaporPressureDeficit(TDryBulb: float, HumRatio: float, Pressure: float) -> float: """ Return Vapor pressure deficit given dry-bulb temperature, humidity ratio, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Vapor pressure deficit in Psi [IP] or Pa [SI] Reference: Oke (1987) eqn 2.13a """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VaporPressureDeficit = GetSatVapPres(TDryBulb) * (1 - RelHum) return VaporPressureDeficit def GetDegreeOfSaturation(TDryBulb: float, HumRatio: float, Pressure: float) -> float: """ Return the degree of saturation (i.e humidity ratio of the air / humidity ratio of the air at saturation at the same temperature and pressure) given dry-bulb temperature, humidity ratio, and atmospheric pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Degree of saturation in arbitrary unit Reference: ASHRAE Handbook - Fundamentals (2009) ch. 1 eqn 12 Notes: This definition is absent from the 2017 Handbook. Using 2009 version instead. """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) SatHumRatio = GetSatHumRatio(TDryBulb, Pressure) DegreeOfSaturation = BoundedHumRatio / SatHumRatio return DegreeOfSaturation def GetMoistAirEnthalpy(TDryBulb: float, HumRatio: float) -> float: """ Return moist air enthalpy given dry-bulb temperature and humidity ratio. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Returns: Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if isIP(): MoistAirEnthalpy = 0.240 * TDryBulb + BoundedHumRatio * (1061 + 0.444 * TDryBulb) else: MoistAirEnthalpy = (1.006 * TDryBulb + BoundedHumRatio * (2501. + 1.86 * TDryBulb)) * 1000 return MoistAirEnthalpy def GetMoistAirVolume(TDryBulb: float, HumRatio: float, Pressure: float) -> float: """ Return moist air specific volume given dry-bulb temperature, humidity ratio, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 Notes: In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if isIP(): MoistAirVolume = R_DA_IP * GetTRankineFromTFahrenheit(TDryBulb) * (1 + 1.607858 * BoundedHumRatio) / (144 * Pressure) else: MoistAirVolume = R_DA_SI * GetTKelvinFromTCelsius(TDryBulb) * (1 + 1.607858 * BoundedHumRatio) / Pressure return MoistAirVolume def GetTDryBulbFromMoistAirVolumeAndHumRatio(MoistAirVolume: float, HumRatio: float, Pressure: float) -> float: """ Return dry-bulb temperature given moist air specific volume, humidity ratio, and pressure. Args: MoistAirVolume: Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 Notes: In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². Based on the `GetMoistAirVolume` function, rearranged for dry-bulb temperature. """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if isIP(): TDryBulb = GetTFahrenheitFromTRankine(MoistAirVolume * (144 * Pressure) / (R_DA_IP * (1 + 1.607858 * BoundedHumRatio))) else: TDryBulb = GetTCelsiusFromTKelvin(MoistAirVolume * Pressure / (R_DA_SI * (1 + 1.607858 * BoundedHumRatio))) return TDryBulb def GetMoistAirDensity(TDryBulb: float, HumRatio: float, Pressure:float) -> float: """ Return moist air density given humidity ratio, dry bulb temperature, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: MoistAirDensity: Moist air density in lb ft⁻³ [IP] or kg m⁻³ [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 11 """ if HumRatio < 0: raise ValueError("Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) MoistAirVolume = GetMoistAirVolume(TDryBulb, BoundedHumRatio, Pressure) MoistAirDensity = (1 + BoundedHumRatio) / MoistAirVolume return MoistAirDensity ####################################################################################################### # Standard atmosphere ####################################################################################################### def GetStandardAtmPressure(Altitude: float) -> float: """ Return standard atmosphere barometric pressure, given the elevation (altitude). Args: Altitude: Altitude in ft [IP] or m [SI] Returns: Standard atmosphere barometric pressure in Psi [IP] or Pa [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 3 """ if isIP(): StandardAtmPressure = 14.696 * math.pow(1 - 6.8754e-06 * Altitude, 5.2559) else: StandardAtmPressure = 101325 * math.pow(1 - 2.25577e-05 * Altitude, 5.2559) return StandardAtmPressure def GetStandardAtmTemperature(Altitude: float) -> float: """ Return standard atmosphere temperature, given the elevation (altitude). Args: Altitude: Altitude in ft [IP] or m [SI] Returns: Standard atmosphere dry-bulb temperature in °F [IP] or °C [SI] Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 4 """ if isIP(): StandardAtmTemperature = 59 - 0.00356620 * Altitude else: StandardAtmTemperature = 15 - 0.0065 * Altitude return StandardAtmTemperature def GetSeaLevelPressure(StationPressure: float, Altitude: float, TDryBulb: float) -> float: """ Return sea level pressure given dry-bulb temperature, altitude above sea level and pressure. Args: StationPressure : Observed station pressure in Psi [IP] or Pa [SI] Altitude: Altitude in ft [IP] or m [SI] TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Returns: Sea level barometric pressure in Psi [IP] or Pa [SI] Reference: Hess SL, Introduction to theoretical meteorology, Holt Rinehart and Winston, NY 1959, ch. 6.5; Stull RB, Meteorology for scientists and engineers, 2nd edition, Brooks/Cole 2000, ch. 1. Notes: The standard procedure for the US is to use for TDryBulb the average of the current station temperature and the station temperature from 12 hours ago. """ if isIP(): # Calculate average temperature in column of air, assuming a lapse rate # of 3.6 °F/1000ft TColumn = TDryBulb + 0.0036 * Altitude / 2 # Determine the scale height H = 53.351 * GetTRankineFromTFahrenheit(TColumn) else: # Calculate average temperature in column of air, assuming a lapse rate # of 6.5 °C/km TColumn = TDryBulb + 0.0065 * Altitude / 2 # Determine the scale height H = 287.055 * GetTKelvinFromTCelsius(TColumn) / 9.807 # Calculate the sea level pressure SeaLevelPressure = StationPressure * math.exp(Altitude / H) return SeaLevelPressure def GetStationPressure(SeaLevelPressure: float, Altitude: float, TDryBulb: float) -> float: """ Return station pressure from sea level pressure. Args: SeaLevelPressure : Sea level barometric pressure in Psi [IP] or Pa [SI] Altitude: Altitude in ft [IP] or m [SI] TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] Returns: Station pressure in Psi [IP] or Pa [SI] Reference: See 'GetSeaLevelPressure' Notes: This function is just the inverse of 'GetSeaLevelPressure'. """ StationPressure = SeaLevelPressure / GetSeaLevelPressure(1, Altitude, TDryBulb) return StationPressure ###################################################################################################### # Functions to set all psychrometric values ####################################################################################################### def CalcPsychrometricsFromTWetBulb(TDryBulb: float, TWetBulb: float, Pressure: float) -> tuple: """ Utility function to calculate humidity ratio, dew-point temperature, relative humidity, vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given dry-bulb temperature, wet-bulb temperature, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Dew-point temperature in °F [IP] or °C [SI] Relative humidity in range [0, 1] Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] Specific volume of moist air in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] Degree of saturation [unitless] """ HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) return HumRatio, TDewPoint, RelHum, VapPres, MoistAirEnthalpy, MoistAirVolume, DegreeOfSaturation def CalcPsychrometricsFromTDewPoint(TDryBulb: float, TDewPoint: float, Pressure: float) -> tuple: """ Utility function to calculate humidity ratio, wet-bulb temperature, relative humidity, vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given dry-bulb temperature, dew-point temperature, and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] TDewPoint : Dew-point temperature in °F [IP] or °C [SI] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Wet-bulb temperature in °F [IP] or °C [SI] Relative humidity in range [0, 1] Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] Specific volume of moist air in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] Degree of saturation [unitless] """ HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) return HumRatio, TWetBulb, RelHum, VapPres, MoistAirEnthalpy, MoistAirVolume, DegreeOfSaturation def CalcPsychrometricsFromRelHum(TDryBulb: float, RelHum: float, Pressure: float) -> tuple: """ Utility function to calculate humidity ratio, wet-bulb temperature, dew-point temperature, vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given dry-bulb temperature, relative humidity and pressure. Args: TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] RelHum : Relative humidity in range [0, 1] Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] Returns: Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] Wet-bulb temperature in °F [IP] or °C [SI] Dew-point temperature in °F [IP] or °C [SI]. Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] Specific volume of moist air in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] Degree of saturation [unitless] """ HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) return HumRatio, TWetBulb, TDewPoint, VapPres, MoistAirEnthalpy, MoistAirVolume, DegreeOfSaturation |
C_sharp
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 | /* * PsychroLib (version 2.3.0) (https://github.com/psychrometrics/psychrolib) * Copyright (c) 2018 D. Thevenard and D. Meyer, D. Gosnell for the current library implementation * Copyright (c) 2017 ASHRAE Handbook — Fundamentals for ASHRAE equations and coefficients * Ported to C# by https://github.com/DJGosnell * Licensed under the MIT License. */ using System; namespace PsychroLib { /// <summary> /// Class of functions to enable the calculation of psychrometric properties of moist and dry air. /// </summary> public class Psychrometrics { /****************************************************************************************************** * Global constants *****************************************************************************************************/ /// <summary> /// Zero degree Fahrenheit (°F) expressed as degree Rankine (°R). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. /// </summary> private const double ZERO_FAHRENHEIT_AS_RANKINE = 459.67; /// <summary> /// Zero degree Celsius (°C) expressed as Kelvin (K). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. /// </summary> private const double ZERO_CELSIUS_AS_KELVIN = 273.15; /// <summary> /// Universal gas constant for dry air (IP version) in ft lb_Force lb_DryAir⁻¹ R⁻¹. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. /// </summary> private const double R_DA_IP = 53.350; /// <summary> /// Universal gas constant for dry air (SI version) in J kg_DryAir⁻¹ K⁻¹. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. /// </summary> private const double R_DA_SI = 287.042; /// <summary> /// Invalid value (dimensionless). /// </summary> private const double INVALID = -99999; /// <summary> /// Maximum number of iterations before exiting while loops. /// </summary> private const double MAX_ITER_COUNT = 100; /// <summary> /// Minimum acceptable humidity ratio used/returned by any functions. /// Any value above 0 or below the MIN_HUM_RATIO will be reset to this value. /// </summary> private const double MIN_HUM_RATIO = 1e-7; /// <summary> /// Freezing point of water in Fahrenheit. /// </summary> private const double FREEZING_POINT_WATER_IP = 32.0; /// <summary> /// Freezing point of water in Celsius. /// </summary> private const double FREEZING_POINT_WATER_SI = 0.0; /// <summary> /// Triple point of water in Fahrenheit. /// </summary> private const double TRIPLE_POINT_WATER_IP = 32.018; /// <summary> /// Triple point of water in Celsius. /// </summary> private const double TRIPLE_POINT_WATER_SI = 0.01; /// <summary> /// Gets or Sets the current system of units for the calculations. /// </summary> public UnitSystem UnitSystem { get => _unitSystem; set { _unitSystem = value; if (value == UnitSystem.IP) PSYCHROLIB_TOLERANCE = 0.001 * 9.0 / 5.0; else PSYCHROLIB_TOLERANCE = 0.001; } } private double PSYCHROLIB_TOLERANCE; private UnitSystem _unitSystem; /// <summary> /// Constructor to create instance with the specified unit system. /// </summary> /// <param name="unitSystem">System of units to utilize for calculations.</param> public Psychrometrics(UnitSystem unitSystem) { UnitSystem = unitSystem; } /****************************************************************************************************** * Conversion between temperature units *****************************************************************************************************/ /// <summary> /// Utility function to convert temperature to degree Rankine (°R) /// given temperature in degree Fahrenheit (°F). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 /// </summary> /// <param name="tF">Temperature in Fahrenheit (°F)</param> /// <returns>Rankine (°R)</returns> public double GetTRankineFromTFahrenheit(double tF) { return tF + ZERO_FAHRENHEIT_AS_RANKINE; /* exact */ } /// <summary> /// Utility function to convert temperature to degree Fahrenheit (°F) /// given temperature in degree Rankine (°R). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 /// </summary> /// <param name="tR">Temperature in Rankine (°R)</param> /// <returns>Fahrenheit (°F)</returns> public double GetTFahrenheitFromTRankine(double tR) { return tR - ZERO_FAHRENHEIT_AS_RANKINE; /* exact */ } /// <summary> /// Utility function to convert temperature to Kelvin (K) /// given temperature in degree Celsius (°C). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 /// </summary> /// <param name="tC">Temperature in Celsius (°C)</param> /// <returns>Rankine (°R)</returns> public double GetTKelvinFromTCelsius(double tC) { return tC + ZERO_CELSIUS_AS_KELVIN; /* exact */ } /// <summary> /// Utility function to convert temperature to degree Celsius (°C) /// given temperature in Kelvin (K). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 /// </summary> /// <param name="tK">Temperature in Rankine (°R)</param> /// <returns>Celsius (°C)</returns> public double GetTCelsiusFromTKelvin(double tK) { return tK - ZERO_CELSIUS_AS_KELVIN; /* exact */ } /****************************************************************************************************** * Conversions between dew point, wet bulb, and relative humidity *****************************************************************************************************/ /// <summary> /// Return wet-bulb temperature given dry-bulb temperature, dew-point temperature, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tDewPoint">Dew point temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Wet bulb temperature in °F [IP] or °C [SI]</returns> public double GetTWetBulbFromTDewPoint(double tDryBulb, double tDewPoint, double pressure) { if (!(tDewPoint <= tDryBulb)) throw new InvalidOperationException("Dew point temperature is above dry bulb temperature"); var humRatio = GetHumRatioFromTDewPoint(tDewPoint, pressure); return GetTWetBulbFromHumRatio(tDryBulb, humRatio, pressure); } /// <summary> /// Return wet-bulb temperature given dry-bulb temperature, relative humidity, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="relHum">Relative humidity [0-1]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Wet bulb temperature in °F [IP] or °C [SI]</returns> public double GetTWetBulbFromRelHum(double tDryBulb, double relHum, double pressure) { if (!(relHum >= 0.0 && relHum <= 1.0)) throw new InvalidOperationException("Relative humidity is outside range [0,1]"); var humRatio = GetHumRatioFromRelHum(tDryBulb, relHum, pressure); return GetTWetBulbFromHumRatio(tDryBulb, humRatio, pressure); } /// <summary> /// Return relative humidity given dry-bulb temperature and dew-point temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 22 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tDewPoint">Dew point temperature in °F [IP] or °C [SI]</param> /// <returns>Relative humidity [0-1]</returns> public double GetRelHumFromTDewPoint(double tDryBulb, double tDewPoint) { if (!(tDewPoint <= tDryBulb)) throw new InvalidOperationException("Dew point temperature is above dry bulb temperature"); var vapPres = GetSatVapPres(tDewPoint); var satVapPres = GetSatVapPres(tDryBulb); return vapPres / satVapPres; } /// <summary> /// Return relative humidity given dry-bulb temperature, wet bulb temperature and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tWetBulb">Wet bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Relative humidity [0-1]</returns> public double GetRelHumFromTWetBulb(double tDryBulb, double tWetBulb, double pressure) { if (!(tWetBulb <= tDryBulb)) throw new InvalidOperationException("Wet bulb temperature is above dry bulb temperature"); var humRatio = GetHumRatioFromTWetBulb(tDryBulb, tWetBulb, pressure); return GetRelHumFromHumRatio(tDryBulb, humRatio, pressure); } /// <summary> /// Return dew-point temperature given dry-bulb temperature and relative humidity. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="relHum">Relative humidity [0-1]</param> /// <returns>Dew Point temperature in °F [IP] or °C [SI]</returns> public double GetTDewPointFromRelHum(double tDryBulb, double relHum) { if (!(relHum >= 0.0 && relHum <= 1.0)) throw new InvalidOperationException("Relative humidity is outside range [0,1]"); var vapPres = GetVapPresFromRelHum(tDryBulb, relHum); return GetTDewPointFromVapPres(tDryBulb, vapPres); } /// <summary> /// Return dew-point temperature given dry-bulb temperature, wet-bulb temperature, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tWetBulb">Wet bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Dew Point temperature in °F [IP] or °C [SI]</returns> public double GetTDewPointFromTWetBulb(double tDryBulb, double tWetBulb, double pressure) { if (!(tWetBulb <= tDryBulb)) throw new InvalidOperationException("Wet bulb temperature is above dry bulb temperature"); var humRatio = GetHumRatioFromTWetBulb(tDryBulb, tWetBulb, pressure); return GetTDewPointFromHumRatio(tDryBulb, humRatio, pressure); } /****************************************************************************************************** * Conversions between dew point, or relative humidity and vapor pressure *****************************************************************************************************/ /// <summary> /// Return partial pressure of water vapor as a function of relative humidity and temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="relHum">Relative humidity [0-1]</param> /// <returns>Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI]</returns> public double GetVapPresFromRelHum(double tDryBulb, double relHum) { if (!(relHum >= 0.0 && relHum <= 1.0)) throw new InvalidOperationException("Relative humidity is outside range [0,1]"); return relHum * GetSatVapPres(tDryBulb); } /// <summary> /// Return relative humidity given dry-bulb temperature and vapor pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="vapPres">Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI]</param> /// <returns>Relative humidity [0-1]</returns> public double GetRelHumFromVapPres(double tDryBulb, double vapPres) { if (!(vapPres >= 0.0)) throw new InvalidOperationException("Partial pressure of water vapor in moist air is negative"); return vapPres / GetSatVapPres(tDryBulb); } /// <summary> /// Helper function returning the derivative of the natural log of the saturation vapor pressure /// as a function of dry-bulb temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 & 6 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <returns>Derivative of natural log of vapor pressure of saturated air in Psi [IP] or Pa [SI]</returns> private double dLnPws_(double tDryBulb) { double dLnPws, T; if (UnitSystem == UnitSystem.IP) { T = GetTRankineFromTFahrenheit(tDryBulb); if (tDryBulb <= TRIPLE_POINT_WATER_IP) dLnPws = 1.0214165E+04 / Math.Pow(T, 2) - 5.3765794E-03 + 2 * 1.9202377E-07 * T + 3 * 3.5575832E-10 * Math.Pow(T, 2) - 4 * 9.0344688E-14 * Math.Pow(T, 3) + 4.1635019 / T; else dLnPws = 1.0440397E+04 / Math.Pow(T, 2) - 2.7022355E-02 + 2 * 1.2890360E-05 * T - 3 * 2.4780681E-09 * Math.Pow(T, 2) + 6.5459673 / T; } else { T = GetTKelvinFromTCelsius(tDryBulb); if (tDryBulb <= TRIPLE_POINT_WATER_SI) dLnPws = 5.6745359E+03 / Math.Pow(T, 2) - 9.677843E-03 + 2 * 6.2215701E-07 * T + 3 * 2.0747825E-09 * Math.Pow(T, 2) - 4 * 9.484024E-13 * Math.Pow(T, 3) + 4.1635019 / T; else dLnPws = 5.8002206E+03 / Math.Pow(T, 2) - 4.8640239E-02 + 2 * 4.1764768E-05 * T - 3 * 1.4452093E-08 * Math.Pow(T, 2) + 6.5459673 / T; } return dLnPws; } /// <summary> /// Return dew-point temperature given dry-bulb temperature and vapor pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 and 6 /// Notes: the dew point temperature is solved by inverting the equation giving water vapor pressure /// at saturation from temperature rather than using the regressions provided /// by ASHRAE (eqn. 37 and 38) which are much less accurate and have a /// narrower range of validity. /// The Newton-Raphson (NR) method is used on the logarithm of water vapour /// pressure as a function of temperature, which is a very smooth function /// Convergence is usually achieved in 3 to 5 iterations. /// tDryBulb is not really needed here, just used for convenience. /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="vapPres">Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI]</param> /// <returns>(o) Dew Point temperature in °F [IP] or °C [SI]</returns> public double GetTDewPointFromVapPres(double tDryBulb, double vapPres) { // Bounds function of the system of units var bounds = UnitSystem == UnitSystem.IP ? new[] {-148.0, 392.0} : new[] {-100.0, 200.0}; // Bounds outside which a solution cannot be found if (vapPres < GetSatVapPres(bounds[0]) || vapPres > GetSatVapPres(bounds[1])) throw new InvalidOperationException( "Partial pressure of water vapor is outside range of validity of equations"); // We use NR to approximate the solution. // First guess var tDewPoint = tDryBulb; // Calculated value of dew point temperatures, solved for iteratively in °F [IP] or °C [SI] var lnVP = Math.Log(vapPres); // Natural logarithm of partial pressure of water vapor pressure in moist air double tDewPoint_iter; // Value of tDewPoint used in NR calculation double lnVP_iter; // Value of log of vapor water pressure used in NR calculation var index = 1; do { // Current point tDewPoint_iter = tDewPoint; lnVP_iter = Math.Log(GetSatVapPres(tDewPoint_iter)); // Derivative of function, calculated analytically var d_lnVP = dLnPws_(tDewPoint_iter); // New estimate, bounded by domain of validity of eqn. 5 and 6 tDewPoint = tDewPoint_iter - (lnVP_iter - lnVP) / d_lnVP; tDewPoint = Math.Max(tDewPoint, bounds[0]); tDewPoint = Math.Min(tDewPoint, bounds[1]); if (index > MAX_ITER_COUNT) throw new InvalidOperationException( "Convergence not reached in GetTDewPointFromVapPres. Stopping."); index++; } while (Math.Abs(tDewPoint - tDewPoint_iter) > PSYCHROLIB_TOLERANCE); return Math.Min(tDewPoint, tDryBulb); } /// <summary> /// Return vapor pressure given dew point temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 36 /// </summary> /// <param name="tDewPoint">Dew point temperature in °F [IP] or °C [SI]</param> /// <returns>Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI]</returns> public double GetVapPresFromTDewPoint(double tDewPoint) { return GetSatVapPres(tDewPoint); } /****************************************************************************************************** * Conversions from wet-bulb temperature, dew-point temperature, or relative humidity to humidity ratio *****************************************************************************************************/ /// <summary> /// Return wet-bulb temperature given dry-bulb temperature, humidity ratio, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 solved for Tstar /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Wet bulb temperature in °F [IP] or °C [SI]</returns> public double GetTWetBulbFromHumRatio(double tDryBulb, double humRatio, double pressure) { // Declarations double Wstar; double tDewPoint, tWetBulb, tWetBulbSup, tWetBulbInf, boundedHumRatio; var index = 1; if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); tDewPoint = GetTDewPointFromHumRatio(tDryBulb, boundedHumRatio, pressure); // Initial guesses tWetBulbSup = tDryBulb; tWetBulbInf = tDewPoint; tWetBulb = (tWetBulbInf + tWetBulbSup) / 2.0; // Bisection loop while ((tWetBulbSup - tWetBulbInf) > PSYCHROLIB_TOLERANCE) { // Compute humidity ratio at temperature Tstar Wstar = GetHumRatioFromTWetBulb(tDryBulb, tWetBulb, pressure); // Get new bounds if (Wstar > boundedHumRatio) tWetBulbSup = tWetBulb; else tWetBulbInf = tWetBulb; // New guess of wet bulb temperature tWetBulb = (tWetBulbSup + tWetBulbInf) / 2.0; if (index > MAX_ITER_COUNT) throw new InvalidOperationException( "Convergence not reached in GetTWetBulbFromHumRatio. Stopping."); index++; } return tWetBulb; } /// <summary> /// Return humidity ratio given dry-bulb temperature, wet-bulb temperature, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tWetBulb">Wet bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</returns> public double GetHumRatioFromTWetBulb(double tDryBulb, double tWetBulb, double pressure) { double wsstar; double humRatio = INVALID; if (!(tWetBulb <= tDryBulb)) throw new InvalidOperationException("Wet bulb temperature is above dry bulb temperature"); wsstar = GetSatHumRatio(tWetBulb, pressure); if (UnitSystem == UnitSystem.IP) { if (tWetBulb >= FREEZING_POINT_WATER_IP) humRatio = ((1093.0 - 0.556 * tWetBulb) * wsstar - 0.240 * (tDryBulb - tWetBulb)) / (1093.0 + 0.444 * tDryBulb - tWetBulb); else humRatio = ((1220.0 - 0.04 * tWetBulb) * wsstar - 0.240 * (tDryBulb - tWetBulb)) / (1220.0 + 0.444 * tDryBulb - 0.48 * tWetBulb); } else { if (tWetBulb >= FREEZING_POINT_WATER_SI) humRatio = ((2501.0 - 2.326 * tWetBulb) * wsstar - 1.006 * (tDryBulb - tWetBulb)) / (2501.0 + 1.86 * tDryBulb - 4.186 * tWetBulb); else humRatio = ((2830.0 - 0.24 * tWetBulb) * wsstar - 1.006 * (tDryBulb - tWetBulb)) / (2830.0 + 1.86 * tDryBulb - 2.1 * tWetBulb); } // Validity check. return Math.Max(humRatio, MIN_HUM_RATIO); } /// <summary> /// Return humidity ratio given dry-bulb temperature, relative humidity, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="relHum">Relative humidity [0-1]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</returns> public double GetHumRatioFromRelHum(double tDryBulb, double relHum, double pressure) { if (!(relHum >= 0.0 && relHum <= 1.0)) throw new InvalidOperationException("Relative humidity is outside range [0,1]"); var vapPres = GetVapPresFromRelHum(tDryBulb, relHum); return GetHumRatioFromVapPres(vapPres, pressure); } /// <summary> /// Return relative humidity given dry-bulb temperature, humidity ratio, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Relative humidity [0-1]</returns> public double GetRelHumFromHumRatio(double tDryBulb, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var vapPres = GetVapPresFromHumRatio(humRatio, pressure); return GetRelHumFromVapPres(tDryBulb, vapPres); } /// <summary> /// Return humidity ratio given dew-point temperature and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDewPoint">Dew point temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</returns> public double GetHumRatioFromTDewPoint(double tDewPoint, double pressure) { var vapPres = GetSatVapPres(tDewPoint); return GetHumRatioFromVapPres(vapPres, pressure); } /// <summary> /// Return dew-point temperature given dry-bulb temperature, humidity ratio, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Dew Point temperature in °F [IP] or °C [SI]</returns> public double GetTDewPointFromHumRatio(double tDryBulb, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var vapPres = GetVapPresFromHumRatio(humRatio, pressure); return GetTDewPointFromVapPres(tDryBulb, vapPres); } /****************************************************************************************************** * Conversions between humidity ratio and vapor pressure *****************************************************************************************************/ /// <summary> /// Return humidity ratio given water vapor pressure and atmospheric pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 /// </summary> /// <param name="vapPres">Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</returns> public double GetHumRatioFromVapPres(double vapPres, double pressure) { if (!(vapPres >= 0.0)) throw new InvalidOperationException("Partial pressure of water vapor in moist air is negative"); var humRatio = 0.621945 * vapPres / (pressure - vapPres); // Validity check. return Math.Max(humRatio, MIN_HUM_RATIO); } /// <summary> /// Return vapor pressure given humidity ratio and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 solved for pw /// </summary> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI]</returns> public double GetVapPresFromHumRatio(double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); var vapPres = pressure * boundedHumRatio / (0.621945 + boundedHumRatio); return vapPres; } /****************************************************************************************************** * Conversions between humidity ratio and specific humidity *****************************************************************************************************/ /// <summary> /// Return the specific humidity from humidity ratio (aka mixing ratio) /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b /// </summary> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <returns>Specific humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</returns> public double GetSpecificHumFromHumRatio(double humRatio) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); return boundedHumRatio / (1.0 + boundedHumRatio); } /// <summary> /// Return the humidity ratio (aka mixing ratio) from specific humidity /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b (solved for humidity ratio) /// </summary> /// <param name="specificHum"></param> /// <returns>Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI]</returns> public double GetHumRatioFromSpecificHum(double specificHum) { if (!(specificHum >= 0.0 && specificHum < 1.0)) throw new InvalidOperationException("Specific humidity is outside range [0, 1]"); var humRatio = specificHum / (1.0 - specificHum); // Validity check return Math.Max(humRatio, MIN_HUM_RATIO); } /****************************************************************************************************** * Dry Air Calculations *****************************************************************************************************/ /// <summary> /// Return dry-air enthalpy given dry-bulb temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 28 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <returns>Dry air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI]</returns> public double GetDryAirEnthalpy(double tDryBulb) { if (UnitSystem == UnitSystem.IP) return 0.240 * tDryBulb; return 1006.0 * tDryBulb; } /// <summary> /// Return dry-air density given dry-bulb temperature and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// Notes: eqn 14 for the perfect gas relationship for dry air. /// Eqn 1 for the universal gas constant. /// The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Dry air density in lb ft⁻³ [IP] or kg m⁻³ [SI]</returns> public double GetDryAirDensity(double tDryBulb, double pressure) { if (UnitSystem == UnitSystem.IP) return (144.0 * pressure) / R_DA_IP / GetTRankineFromTFahrenheit(tDryBulb); return pressure / R_DA_SI / GetTKelvinFromTCelsius(tDryBulb); } /// <summary> /// Return dry-air volume given dry-bulb temperature and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. /// Notes: eqn 14 for the perfect gas relationship for dry air. /// Eqn 1 for the universal gas constant. /// The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Dry air volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI]</returns> public double GetDryAirVolume(double tDryBulb, double pressure) { if (UnitSystem == UnitSystem.IP) return R_DA_IP * GetTRankineFromTFahrenheit(tDryBulb) / (144.0 * pressure); return R_DA_SI * GetTKelvinFromTCelsius(tDryBulb) / pressure; } /// <summary> /// Return dry bulb temperature from enthalpy and humidity ratio. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. /// Notes: based on the `GetMoistAirEnthalpy` function, rearranged for temperature. /// </summary> /// <param name="moistAirEnthalpy">Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <returns>Dry-bulb temperature in °F [IP] or °C [SI]</returns> public double GetTDryBulbFromEnthalpyAndHumRatio(double moistAirEnthalpy, double humRatio) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); if (UnitSystem == UnitSystem.IP) return (moistAirEnthalpy - 1061.0 * boundedHumRatio) / (0.240 + 0.444 * boundedHumRatio); return (moistAirEnthalpy / 1000.0 - 2501.0 * boundedHumRatio) / (1.006 + 1.86 * boundedHumRatio); } /// <summary> /// Return humidity ratio from enthalpy and dry-bulb temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. /// Notes: based on the `GetMoistAirEnthalpy` function, rearranged for humidity ratio. /// </summary> /// <param name="moistAirEnthalpy">Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹</param> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <returns>Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻</returns> public double GetHumRatioFromEnthalpyAndTDryBulb(double moistAirEnthalpy, double tDryBulb) { { double humRatio; if (UnitSystem == UnitSystem.IP) humRatio = (moistAirEnthalpy - 0.240 * tDryBulb) / (1061.0 + 0.444 * tDryBulb); else humRatio = (moistAirEnthalpy / 1000.0 - 1.006 * tDryBulb) / (2501.0 + 1.86 * tDryBulb); // Validity check. return Math.Max(humRatio, MIN_HUM_RATIO); } } /****************************************************************************************************** * Saturated Air Calculations *****************************************************************************************************/ /// <summary> /// Return saturation vapor pressure given dry-bulb temperature. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 & 6 /// Important note: the ASHRAE formulae are defined above and below the freezing point but have /// a discontinuity at the freezing point. This is a small inaccuracy on ASHRAE's part: the formulae /// should be defined above and below the triple point of water (not the feezing point) in which case /// the discontinuity vanishes. It is essential to use the triple point of water otherwise function /// GetTDewPointFromVapPres, which inverts the present function, does not converge properly around /// the freezing point. /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <returns>Vapor pressure of saturated air in Psi [IP] or Pa [SI]</returns> public double GetSatVapPres(double tDryBulb) { double lnPws; if (UnitSystem == UnitSystem.IP) { if (!(tDryBulb >= -148.0 && tDryBulb <= 392.0)) throw new InvalidOperationException("Dry bulb temperature is outside range [-148, 392]"); var T = GetTRankineFromTFahrenheit(tDryBulb); if (tDryBulb <= TRIPLE_POINT_WATER_IP) lnPws = (-1.0214165E+04 / T - 4.8932428 - 5.3765794E-03 * T + 1.9202377E-07 * T * T + 3.5575832E-10 * Math.Pow(T, 3) - 9.0344688E-14 * Math.Pow(T, 4) + 4.1635019 * Math.Log(T)); else lnPws = -1.0440397E+04 / T - 1.1294650E+01 - 2.7022355E-02 * T + 1.2890360E-05 * T * T - 2.4780681E-09 * Math.Pow(T, 3) + 6.5459673 * Math.Log(T); } else { if (!(tDryBulb >= -100.0 && tDryBulb <= 200.0)) throw new InvalidOperationException("Dry bulb temperature is outside range [-100, 200]"); var T = GetTKelvinFromTCelsius(tDryBulb); if (tDryBulb <= TRIPLE_POINT_WATER_SI) lnPws = -5.6745359E+03 / T + 6.3925247 - 9.677843E-03 * T + 6.2215701E-07 * T * T + 2.0747825E-09 * Math.Pow(T, 3) - 9.484024E-13 * Math.Pow(T, 4) + 4.1635019 * Math.Log(T); else lnPws = -5.8002206E+03 / T + 1.3914993 - 4.8640239E-02 * T + 4.1764768E-05 * T * T - 1.4452093E-08 * Math.Pow(T, 3) + 6.5459673 * Math.Log(T); } return Math.Exp(lnPws); } /// <summary> /// Return humidity ratio of saturated air given dry-bulb temperature and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36, solved for W /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Humidity ratio of saturated air in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</returns> public double GetSatHumRatio(double tDryBulb, double pressure) { var satVaporPres = GetSatVapPres(tDryBulb); var satHumRatio = 0.621945 * satVaporPres / (pressure - satVaporPres); // Validity check. return Math.Max(satHumRatio, MIN_HUM_RATIO); } /// <summary> /// Return saturated air enthalpy given dry-bulb temperature and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Saturated air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI]</returns> public double GetSatAirEnthalpy(double tDryBulb, double pressure) { return GetMoistAirEnthalpy(tDryBulb, GetSatHumRatio(tDryBulb, pressure)); } /****************************************************************************************************** * Moist Air Calculations *****************************************************************************************************/ /// <summary> /// Return Vapor pressure deficit given dry-bulb temperature, humidity ratio, and pressure. /// Reference: see Oke (1987) eqn. 2.13a /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Vapor pressure deficit in Psi [IP] or Pa [SI]</returns> public double GetVaporPressureDeficit(double tDryBulb, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var relHum = GetRelHumFromHumRatio(tDryBulb, humRatio, pressure); return GetSatVapPres(tDryBulb) * (1.0 - relHum); } /// <summary> /// Return the degree of saturation (i.e humidity ratio of the air / humidity ratio of the air at saturation /// at the same temperature and pressure) given dry-bulb temperature, humidity ratio, and atmospheric pressure. /// Reference: ASHRAE Handbook - Fundamentals (2009) ch. 1 eqn. 12 /// Notes: the definition is absent from the 2017 Handbook /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Degree of saturation (unitless)</returns> public double GetDegreeOfSaturation(double tDryBulb, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); return boundedHumRatio / GetSatHumRatio(tDryBulb, pressure); } /// <summary> /// Return moist air enthalpy given dry-bulb temperature and humidity ratio. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 30 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <returns>Moist Air Enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI]</returns> public double GetMoistAirEnthalpy(double tDryBulb, double humRatio) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); if (UnitSystem == UnitSystem.IP) return 0.240 * tDryBulb + boundedHumRatio * (1061.0 + 0.444 * tDryBulb); return (1.006 * tDryBulb + boundedHumRatio * (2501.0 + 1.86 * tDryBulb)) * 1000.0; } /// <summary> /// Return moist air specific volume given dry-bulb temperature, humidity ratio, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 26 /// Notes: in IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26. /// The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Specific Volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI]</returns> public double GetMoistAirVolume(double tDryBulb, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); if (UnitSystem == UnitSystem.IP) return R_DA_IP * GetTRankineFromTFahrenheit(tDryBulb) * (1.0 + 1.607858 * boundedHumRatio) / (144.0 * pressure); return R_DA_SI * GetTKelvinFromTCelsius(tDryBulb) * (1.0 + 1.607858 * boundedHumRatio) / pressure; } /// <summary> /// Return dry-bulb temperature given moist air specific volume, humidity ratio, and pressure. /// Reference: /// ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 /// Notes: /// In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 /// The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². /// Based on the `GetMoistAirVolume` function, rearranged for dry-bulb temperature. /// </summary> /// <param name="MoistAirVolume">Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Dry-bulb temperature in °F [IP] or °C [SI]</returns> public double GetTDryBulbFromMoistAirVolumeAndHumRatio(double MoistAirVolume, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); if (UnitSystem == UnitSystem.IP) return GetTFahrenheitFromTRankine(MoistAirVolume * (144 * pressure) / (R_DA_IP * (1 + 1.607858 * boundedHumRatio))); return GetTCelsiusFromTKelvin(MoistAirVolume * pressure / (R_DA_SI * (1 + 1.607858 * boundedHumRatio))); } /// <summary> /// Return moist air density given humidity ratio, dry bulb temperature, and pressure. /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 11 /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="humRatio">Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Moist air density in lb ft⁻³ [IP] or kg m⁻³ [SI]</returns> public double GetMoistAirDensity(double tDryBulb, double humRatio, double pressure) { if (!(humRatio >= 0.0)) throw new InvalidOperationException("Humidity ratio is negative"); var boundedHumRatio = Math.Max(humRatio, MIN_HUM_RATIO); return (1.0 + boundedHumRatio) / GetMoistAirVolume(tDryBulb, boundedHumRatio, pressure); } /****************************************************************************************************** * Standard atmosphere *****************************************************************************************************/ /// <summary> /// Return standard atmosphere barometric pressure, given the elevation (altitude). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 3 /// </summary> /// <param name="altitude">altitude in ft [IP] or m [SI]</param> /// <returns>Standard atmosphere barometric pressure in Psi [IP] or Pa [SI]</returns> public double GetStandardAtmPressure(double altitude) { if (UnitSystem == UnitSystem.IP) return 14.696 * Math.Pow(1.0 - 6.8754e-06 * altitude, 5.2559); return 101325.0 * Math.Pow(1.0 - 2.25577e-05 * altitude, 5.2559); } /// <summary> /// Return standard atmosphere temperature, given the elevation (altitude). /// Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 4 /// </summary> /// <param name="altitude">altitude in ft [IP] or m [SI]</param> /// <returns> Standard atmosphere dry bulb temperature in °F [IP] or °C [SI]</returns> public double GetStandardAtmTemperature(double altitude) { if (UnitSystem == UnitSystem.IP) return 59.0 - 0.00356620 * altitude; return 15.0 - 0.0065 * altitude; } /// <summary> /// Return sea level pressure given dry-bulb temperature, altitude above sea level and pressure. /// Reference: Hess SL, Introduction to theoretical meteorology, Holt Rinehart and Winston, NY 1959, /// ch. 6.5; Stull RB, Meteorology for scientists and engineers, 2nd edition, /// Brooks/Cole 2000, ch. 1. /// Notes: the standard procedure for the US is to use for tDryBulb the average /// of the current station temperature and the station temperature from 12 hours ago. /// </summary> /// <param name="stnPressure">Observed station pressure in Psi [IP] or Pa [SI]</param> /// <param name="altitude">Altitude above sea level in ft [IP] or m [SI]</param> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <returns>Sea level barometric pressure in Psi [IP] or Pa [SI]</returns> public double GetSeaLevelPressure(double stnPressure, double altitude, double tDryBulb) { double h; if (UnitSystem == UnitSystem.IP) { // Calculate average temperature in column of air, assuming a lapse rate // of 3.6 °F/1000ft var tColumn = tDryBulb + 0.0036 * altitude / 2.0; // Determine the scale height h = 53.351 * GetTRankineFromTFahrenheit(tColumn); } else { // Calculate average temperature in column of air, assuming a lapse rate // of 6.5 °C/km var tColumn = tDryBulb + 0.0065 * altitude / 2.0; // Determine the scale height h = 287.055 * GetTKelvinFromTCelsius(tColumn) / 9.807; } // Calculate the sea level pressure var seaLevelPressure = stnPressure * Math.Exp(altitude / h); return seaLevelPressure; } /// <summary> /// Return station pressure from sea level pressure /// Reference: see 'GetSeaLevelPressure' /// Notes: this function is just the inverse of 'GetSeaLevelPressure'. /// </summary> /// <param name="seaLevelPressure">Sea level barometric pressure in Psi [IP] or Pa [SI]</param> /// <param name="altitude">Altitude above sea level in ft [IP] or m [SI]</param> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <returns>Station pressure in Psi [IP] or Pa [SI]</returns> public double GetStationPressure(double seaLevelPressure, double altitude, double tDryBulb) { return seaLevelPressure / GetSeaLevelPressure(1.0, altitude, tDryBulb); } /****************************************************************************************************** * Functions to set all psychrometric values *****************************************************************************************************/ /// <summary> /// Utility function to calculate humidity ratio, dew-point temperature, relative humidity, /// vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given /// dry-bulb temperature, wet-bulb temperature, and pressure. /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tWetBulb">Wet bulb temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Calculated values.</returns> public PsychrometricValue CalcPsychrometricsFromTWetBulb(double tDryBulb, double tWetBulb, double pressure) { var value = new PsychrometricValue { TDryBulb = tDryBulb, TWetBulb = tWetBulb, Pressure = pressure }; value.HumRatio = GetHumRatioFromTWetBulb(tDryBulb, tWetBulb, pressure); value.TDewPoint = GetTDewPointFromHumRatio(tDryBulb, value.HumRatio, pressure); value.RelHum = GetRelHumFromHumRatio(tDryBulb, value.HumRatio, pressure); value.VapPres = GetVapPresFromHumRatio(value.HumRatio, pressure); value.MoistAirEnthalpy = GetMoistAirEnthalpy(tDryBulb, value.HumRatio); value.MoistAirVolume = GetMoistAirVolume(tDryBulb, value.HumRatio, pressure); value.DegreeOfSaturation = GetDegreeOfSaturation(tDryBulb, value.HumRatio, pressure); return value; } /// <summary> /// Utility function to calculate humidity ratio, wet-bulb temperature, relative humidity, /// vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given /// dry-bulb temperature, dew-point temperature, and pressure. /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="tDewPoint">Dew point temperature in °F [IP] or °C [SI]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Calculated values.</returns> public PsychrometricValue CalcPsychrometricsFromTDewPoint(double tDryBulb, double tDewPoint, double pressure) { var value = new PsychrometricValue { TDryBulb = tDryBulb, TDewPoint = tDewPoint, Pressure = pressure }; value.HumRatio = GetHumRatioFromTDewPoint(tDewPoint, pressure); value.TWetBulb = GetTWetBulbFromHumRatio(tDryBulb, value.HumRatio, pressure); value.RelHum = GetRelHumFromHumRatio(tDryBulb, value.HumRatio, pressure); value.VapPres = GetVapPresFromHumRatio(value.HumRatio, pressure); value.MoistAirEnthalpy = GetMoistAirEnthalpy(tDryBulb, value.HumRatio); value.MoistAirVolume = GetMoistAirVolume(tDryBulb, value.HumRatio, pressure); value.DegreeOfSaturation = GetDegreeOfSaturation(tDryBulb, value.HumRatio, pressure); return value; } /// <summary> /// Utility function to calculate humidity ratio, wet-bulb temperature, dew-point temperature, /// vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given /// dry-bulb temperature, relative humidity and pressure. /// </summary> /// <param name="tDryBulb">Dry bulb temperature in °F [IP] or °C [SI]</param> /// <param name="relHum">Relative humidity [0-1]</param> /// <param name="pressure">Atmospheric pressure in Psi [IP] or Pa [SI]</param> /// <returns>Calculated values.</returns> public PsychrometricValue CalcPsychrometricsFromRelHum(double tDryBulb, double relHum, double pressure) { var value = new PsychrometricValue { TDryBulb = tDryBulb, RelHum = relHum, Pressure = pressure }; value.HumRatio = GetHumRatioFromRelHum(tDryBulb, relHum, pressure); value.TWetBulb = GetTWetBulbFromHumRatio(tDryBulb, value.HumRatio, pressure); value.TDewPoint = GetTDewPointFromHumRatio(tDryBulb, value.HumRatio, pressure); value.VapPres = GetVapPresFromHumRatio(value.HumRatio, pressure); value.MoistAirEnthalpy = GetMoistAirEnthalpy(tDryBulb, value.HumRatio); value.MoistAirVolume = GetMoistAirVolume(tDryBulb, value.HumRatio, pressure); value.DegreeOfSaturation = GetDegreeOfSaturation(tDryBulb, value.HumRatio, pressure); return value; } } /// <summary> /// Contains output results of a Psychrometric calculation. /// </summary> public class PsychrometricValue { /// <summary> /// Dry bulb temperature in °F [IP] or °C [SI] /// </summary> public double TDryBulb { get; set; } /// <summary> /// Wet bulb temperature in °F [IP] or °C [SI] /// </summary> public double TWetBulb { get; set; } /// <summary> /// Atmospheric pressure in Psi [IP] or Pa [SI] /// </summary> public double Pressure { get; set; } /// <summary> /// Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] /// </summary> public double HumRatio { get; set; } /// <summary> /// Dew point temperature in °F [IP] or °C [SI] /// </summary> public double TDewPoint { get; set; } /// <summary> /// Relative humidity [0-1] /// </summary> public double RelHum { get; set; } /// <summary> /// Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] /// </summary> public double VapPres { get; set; } /// <summary> /// Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] /// </summary> public double MoistAirEnthalpy { get; set; } /// <summary> /// Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] /// </summary> public double MoistAirVolume { get; set; } /// <summary> /// Degree of saturation [unitless] /// </summary> public double DegreeOfSaturation { get; set; } } /// <summary> /// Standard unit systems /// </summary> public enum UnitSystem { /// <summary> /// Imperial Units /// </summary> IP = 1, /// <summary> /// Metric System Units /// </summary> SI = 2 } } |
Fortran
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 | ! PsychroLib (version 2.3.0) (https://github.com/psychrometrics/psychrolib) ! Copyright (c) 2018 D. Thevenard and D. Meyer for the current library implementation ! Copyright (c) 2017 ASHRAE Handbook — Fundamentals for ASHRAE equations and coefficients ! Licensed under the MIT License. module psychrolib !+ Module overview !+ Contains functions for calculating thermodynamic properties of gas-vapor mixtures !+ and standard atmosphere suitable for most engineering, physical, and meteorological !+ applications. !+ !+ Most of the functions are an implementation of the formulae found in the !+ 2017 ASHRAE Handbook - Fundamentals, in both International System (SI), !+ and Imperial (IP) units. Please refer to the information included in !+ each function for their respective reference. !+ !+ Example !+ use psychrolib, only: GetTDewPointFromRelHum, SetUnitSystem, SI !+ ! Set the unit system, for example to SI (can be either 'SI' or 'IP') !+ call SetUnitSystem(SI) !+ ! Calculate the dew point temperature for a dry bulb temperature of 25 C and a relative humidity of 80% !+ print *, GetTDewPointFromRelHum(25.0, 0.80) !+ 21.3094 !+ !+ Copyright !+ - For the current library implementation !+ Copyright (c) 2018 D. Thevenard and D. Meyer. !+ - For equations and coefficients published ASHRAE Handbook — Fundamentals, Chapter 1 !+ Copyright (c) 2017 ASHRAE Handbook — Fundamentals (https://www.ashrae.org) !+ !+ License !+ MIT (https://github.com/psychrometrics/psychrolib/LICENSE.txt) !+ !+ Note from the Authors !+ We have made every effort to ensure that the code is adequate, however, we make no !+ representation with respect to its accuracy. Use at your own risk. Should you notice !+ an error, or if you have a suggestion, please notify us through GitHub at !+ https://github.com/psychrometrics/psychrolib/issues. implicit none private public :: IP public :: SI public :: SetUnitSystem public :: GetUnitSystem public :: isIP public :: GetTRankineFromTFahrenheit public :: GetTFahrenheitFromTRankine public :: GetTKelvinFromTCelsius public :: GetTCelsiusFromTKelvin public :: GetTWetBulbFromTDewPoint public :: GetTWetBulbFromRelHum public :: GetRelHumFromTDewPoint public :: GetRelHumFromTWetBulb public :: GetTDewPointFromRelHum public :: GetTDewPointFromTWetBulb public :: GetVapPresFromRelHum public :: GetRelHumFromVapPres public :: GetTDewPointFromVapPres public :: GetVapPresFromTDewPoint public :: GetTWetBulbFromHumRatio public :: GetHumRatioFromTWetBulb public :: GetHumRatioFromRelHum public :: GetRelHumFromHumRatio public :: GetHumRatioFromTDewPoint public :: GetTDewPointFromHumRatio public :: GetHumRatioFromVapPres public :: GetVapPresFromHumRatio public :: GetDryAirEnthalpy public :: GetDryAirDensity public :: GetDryAirVolume public :: GetTDryBulbFromEnthalpyAndHumRatio public :: GetHumRatioFromEnthalpyAndTDryBulb public :: GetSatVapPres public :: GetSatHumRatio public :: GetSatAirEnthalpy public :: GetVaporPressureDeficit public :: GetDegreeOfSaturation public :: GetMoistAirEnthalpy public :: GetMoistAirVolume public :: GetTDryBulbFromMoistAirVolumeAndHumRatio public :: GetMoistAirDensity public :: GetStandardAtmPressure public :: GetStandardAtmTemperature public :: GetSeaLevelPressure public :: GetStationPressure public :: GetSpecificHumFromHumRatio public :: GetHumRatioFromSpecificHum public :: CalcPsychrometricsFromTWetBulb public :: CalcPsychrometricsFromTDewPoint public :: CalcPsychrometricsFromRelHum public :: dLnPws_ !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Global constants !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! real, parameter :: ZERO_FAHRENHEIT_AS_RANKINE = 459.67 !+ Zero degree Fahrenheit (°F) expressed as degree Rankine (°R). !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 39. real, parameter :: ZERO_CELSIUS_AS_KELVIN = 273.15 !+ Zero degree Celsius (°C) expressed as Kelvin (K). !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 39. real, parameter :: R_DA_IP = 53.350 !+ Universal gas constant for dry air (IP version) in ft lb_Force lb_DryAir⁻¹ R⁻¹. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1. real, parameter :: R_DA_SI = 287.042 !+ Universal gas constant for dry air (SI version) in J kg_DryAir⁻¹ K⁻¹. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1. integer, parameter :: IP = 1 integer, parameter :: SI = 2 integer :: PSYCHROLIB_UNITS = 0 ! 0 = undefined. !+ Unit system to use. real :: PSYCHROLIB_TOLERANCE = 1.0 !+ Tolerance of temperature calculations. integer, parameter :: MAX_ITER_COUNT = 100 !+ Maximum number of iterations before exiting while loops. real, parameter :: MIN_HUM_RATIO = 1e-7 !+ Minimum acceptable humidity ratio used/returned by any functions. !+ Any value above 0 or below the MIN_HUM_RATIO will be reset to this value. real, parameter :: FREEZING_POINT_WATER_IP = 32.0 !+ float: Freezing point of water in Fahrenheit. real, parameter :: FREEZING_POINT_WATER_SI = 0.0 !+ float: Freezing point of water in Celsius. real, parameter :: TRIPLE_POINT_WATER_IP = 32.018 !+ float: Triple point of water in Fahrenheit. real, parameter :: TRIPLE_POINT_WATER_SI = 0.01 !+ float: Triple point of water in Celsius. contains !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Helper functions !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine SetUnitSystem(UnitSystem) !+ Set the system of units to use (SI or IP). !+ Notes: this function *HAS TO BE CALLED* before the library can be used integer, intent(in) :: UnitSystem !+ Units: string indicating the system of units chosen (SI or IP) if (.not. (UnitSystem == SI .or. UnitSystem == IP)) then error stop "The system of units has to be either SI or IP." end if PSYCHROLIB_UNITS = UnitSystem ! Define tolerance on temperature calculations ! The tolerance is the same in IP and SI if (UnitSystem == IP) then PSYCHROLIB_TOLERANCE = 0.001 * 9.0 / 5.0 else PSYCHROLIB_TOLERANCE = 0.001 end if end subroutine SetUnitSystem function GetUnitSystem() result(UnitSystem) !+ Return the system of units in use. integer :: UnitSystem UnitSystem = PSYCHROLIB_UNITS end function GetUnitSystem function isIP() !+ Check whether the system in use is IP or SI logical :: isIP if (PSYCHROLIB_UNITS == IP) then isIP = .true. else if (PSYCHROLIB_UNITS == SI) then isIP = .false. else error stop "The system of units has not been defined." end if end function isIP !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Conversion between temperature units !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetTRankineFromTFahrenheit(TFahrenheit) result(TRankine) !+ Utility function to convert temperature to degree Rankine (°R) !+ given temperature in degree Fahrenheit (°F). !+ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 real, intent(in) :: TFahrenheit !+ Temperature in degree Fahrenheit real :: TRankine !+ Temperature in degree Rankine TRankine = TFahrenheit + ZERO_FAHRENHEIT_AS_RANKINE end function GetTRankineFromTFahrenheit function GetTFahrenheitFromTRankine(TRankine) result(TFahrenheit) !+ Utility function to convert temperature to degree Fahrenheit (°F) !+ given temperature in degree Rankine (°R). !+ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 real, intent(in) :: TRankine !+ Temperature in degree Rankine real :: TFahrenheit !+ Temperature in degree Fahrenheit TFahrenheit = TRankine - ZERO_FAHRENHEIT_AS_RANKINE end function GetTFahrenheitFromTRankine function GetTKelvinFromTCelsius(TCelsius) result(TKelvin) !+ Utility function to convert temperature to Kelvin (K) !+ given temperature in degree Celsius (°C). !+ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 real, intent(in) :: TCelsius !+ Temperature in degree Celsius real :: TKelvin !+ Tempearatyre in Kelvin TKelvin = TCelsius + ZERO_CELSIUS_AS_KELVIN end function GetTKelvinFromTCelsius function GetTCelsiusFromTKelvin(TKelvin) result(TCelsius) !+ Utility function to convert temperature to degree Celsius (°C) !+ given temperature in Kelvin (K). !+ Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 real, intent(in) :: TKelvin !+ Tempearatyre in Kelvin real :: TCelsius !+ Temperature in degree Celsius TCelsius = TKelvin - ZERO_CELSIUS_AS_KELVIN end function GetTCelsiusFromTKelvin !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Conversions between dew point, wet bulb, and relative humidity !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetTWetBulbFromTDewPoint(TDryBulb, TDewPoint, Pressure) result(TWetBulb) !+ Return wet-bulb temperature given dry-bulb temperature, dew-point temperature, and pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (TDewPoint > TDryBulb) then error stop "Error: dew point temperature is above dry bulb temperature" end if HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) end function GetTWetBulbFromTDewPoint function GetTWetBulbFromRelHum(TDryBulb, RelHum, Pressure) result(TWetBulb) !+ Return wet-bulb temperature given dry-bulb temperature, relative humidity, and pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: RelHum !+ Relative humidity in range [0, 1] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (RelHum < 0.0 .or. RelHum > 1.0) then error stop "Error: relative humidity is outside range [0,1]" end if HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) end function GetTWetBulbFromRelHum function GetRelHumFromTDewPoint(TDryBulb, TDewPoint) result(RelHum) !+ Return relative humidity given dry-bulb temperature and dew-point temperature. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 22 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real :: RelHum !+ Relative humidity in range [0, 1] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real :: SatVapPres !+ Vapor pressure of saturated air in Psi [IP] or Pa [SI] if (TDewPoint > TDryBulb) then error stop "Error: dew point temperature is above dry bulb temperature" end if VapPres = GetSatVapPres(TDewPoint) SatVapPres = GetSatVapPres(TDryBulb) RelHum = VapPres / SatVapPres end function GetRelHumFromTDewPoint function GetRelHumFromTWetBulb(TDryBulb, TWetBulb, Pressure) result(RelHum) !+ Return relative humidity given dry-bulb temperature, wet bulb temperature and pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: RelHum !+ Relative humidity in range [0, 1] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (TWetBulb > TDryBulb) then error stop "Error: wet bulb temperature is above dry bulb temperature" end if HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) end function GetRelHumFromTWetBulb function GetTDewPointFromRelHum(TDryBulb, RelHum) result(TDewPoint) !+ Return dew-point temperature given dry-bulb temperature and relative humidity. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: RelHum !+ Relative humidity in range [0, 1] real :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] if (RelHum < 0.0 .or. RelHum > 1.0) then error stop "Error: relative humidity is outside range [0,1]" end if VapPres = GetVapPresFromRelHum(TDryBulb, RelHum) TDewPoint = GetTDewPointFromVapPres(TDryBulb, VapPres) end function GetTDewPointFromRelHum function GetTDewPointFromTWetBulb(TDryBulb, TWetBulb, Pressure) result(TDewPoint) !+ Return dew-point temperature given dry-bulb temperature, wet-bulb temperature, and pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (TWetBulb > TDryBulb) then error stop "Error: wet bulb temperature is above dry bulb temperature" end if HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) end function GetTDewPointFromTWetBulb !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Conversions between dew point, or relative humidity and vapor pressure !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetVapPresFromRelHum(TDryBulb, RelHum) result(VapPres) !+ Return partial pressure of water vapor as a function of relative humidity and temperature. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: RelHum !+ Relative humidity in range [0, 1] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] if (RelHum < 0.0 .or. RelHum > 1.0) then error stop "Error: relative humidity is outside range [0,1]" end if VapPres = RelHum * GetSatVapPres(TDryBulb) end function GetVapPresFromRelHum function GetRelHumFromVapPres(TDryBulb, VapPres) result(RelHum) !+ Return relative humidity given dry-bulb temperature and vapor pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real :: RelHum !+ Relative humidity in range [0, 1] if (VapPres < 0.0) then error stop "Error: partial pressure of water vapor in moist air cannot be negative" end if RelHum = VapPres / GetSatVapPres(TDryBulb) end function GetRelHumFromVapPres function dLnPws_(TDryBulb) result(dLnPws) !+ Helper function returning the derivative of the natural log of the saturation vapor pressure !+ as a function of dry-bulb temperature. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 5 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: dLnPws !+ Derivative of natural log of vapor pressure of saturated air in Psi [IP] or Pa [SI] real :: T !+ Dry bulb temperature in R [IP] or K [SI] if (isIP()) then T = GetTRankineFromTFahrenheit(TDryBulb) if (TDryBulb <= TRIPLE_POINT_WATER_IP) then dLnPws = 1.0214165E+04 / T**2 - 5.3765794E-03 + 2 * 1.9202377E-07 * T & + 3 * 3.5575832E-10 * T**2 - 4 * 9.0344688E-14 * T**3 + 4.1635019 / T else dLnPws = 1.0440397E+04 / T**2 - 2.7022355E-02 + 2 * 1.2890360E-05 * T & - 3 * 2.4780681E-09 * T**2 + 6.5459673 / T end if else T = GetTKelvinFromTCelsius(TDryBulb) if (TDryBulb <= TRIPLE_POINT_WATER_SI) then dLnPws = 5.6745359E+03 / T**2 - 9.677843E-03 + 2 * 6.2215701E-07 * T & + 3 * 2.0747825E-09 * T**2 - 4 * 9.484024E-13 * T**3 + 4.1635019 / T else dLnPws = 5.8002206E+03 / T**2 - 4.8640239E-02 + 2 * 4.1764768E-05 * T & - 3 * 1.4452093E-08 * T**2 + 6.5459673 / T end if end if end function dLnPws_ function GetTDewPointFromVapPres(TDryBulb, VapPres) result(TDewPoint) !+ Return dew-point temperature given dry-bulb temperature and vapor pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 and 6 !+ Notes: !+ The dew point temperature is solved by inverting the equation giving water vapor pressure !+ at saturation from temperature rather than using the regressions provided !+ by ASHRAE (eqn. 37 and 38) which are much less accurate and have a !+ narrower range of validity. !+ The Newton-Raphson (NR) method is used on the logarithm of water vapour !+ pressure as a function of temperature, which is a very smooth function !+ Convergence is usually achieved in 3 to 5 iterations. !+ TDryBulb is not really needed here, just used for convenience. real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real :: lnVP !+ Natural logarithm of partial pressure of water vapor pressure in moist air real :: d_lnVP !+ Derivative of function, calculated numerically real :: lnVP_iter !+ Value of log of vapor water pressure used in NR calculation real :: TDewPoint_iter !+ Value of TDewPoint used in NR calculation real, dimension(2) :: BOUNDS !+ Valid temperature range in °F [IP] or °C [SI] integer :: index !+ Index used in the calculation ! Bounds and step size as a function of the system of units if (isIP()) then BOUNDS(1) = -148.0 BOUNDS(2) = 392.0 else BOUNDS(1) = -100.0 BOUNDS(2) = 200.0 end if ! Validity check -- bounds outside which a solution cannot be found if (VapPres < GetSatVapPres(BOUNDS(1)) .or. VapPres > GetSatVapPres(BOUNDS(2))) then error stop "Error: partial pressure of water vapor is outside range of validity of equations" end if ! We use NR to approximate the solution. TDewPoint = TDryBulb lnVP = log(VapPres) index = 1 do while (.true.) TDewPoint_iter = TDewPoint ! TDewPoint_iter used in NR calculation lnVP_iter = log(GetSatVapPres(TDewPoint_iter)) ! Derivative of function, calculated analytically d_lnVP = dLnPws_(TDewPoint_iter) ! New estimate, bounded by the search domain defined above TDewPoint = TDewPoint_iter - (lnVP_iter - lnVP) / d_lnVP TDewPoint = max(TDewPoint, BOUNDS(1)) TDewPoint = min(TDewPoint, BOUNDS(2)) if (abs(TDewPoint - TDewPoint_iter) <= PSYCHROLIB_TOLERANCE) then exit end if if (index > MAX_ITER_COUNT) then error stop "Convergence not reached in GetTDewPointFromVapPres. Stopping." end if index = index + 1 end do TDewPoint = min(TDewPoint, TDryBulb) end function GetTDewPointFromVapPres function GetVapPresFromTDewPoint(TDewPoint) result(VapPres) !+ Return vapor pressure given dew point temperature. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36 real, intent(in) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] VapPres = GetSatVapPres(TDewPoint) end function GetVapPresFromTDewPoint !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Conversions from wet-bulb temperature, dew-point temperature, or relative humidity to humidity ratio !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) result(TWetBulb) !+ Return wet-bulb temperature given dry-bulb temperature, humidity ratio, and pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 solved for Tstar real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real :: TDewPoint !+ TDewPoint : Dew-point temperature in °F [IP] or °C [SI] real :: TWetBulbSup !+ Upper value of wet bulb temperature in bissection method (initial guess is from dry bulb temperature) in °F [IP] or °C [SI] real :: TWetBulbInf !+ Lower value of wet bulb temperature in bissection method (initial guess is from dew point temperature) in °F [IP] or °C [SI] real :: Wstar !+ Humidity ratio at temperature Tstar in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO integer :: index !+ index used in iteration if (HumRatio < 0.0) then error stop "Error: humidity ratio cannot be negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, BoundedHumRatio, Pressure) ! Initial guesses TWetBulbSup = TDryBulb TWetBulbInf = TDewPoint TWetBulb = (TWetBulbInf + TWetBulbSup) / 2.0 index = 1 ! Bisection loop do while ((TWetBulbSup - TWetBulbInf) > PSYCHROLIB_TOLERANCE) ! Compute humidity ratio at temperature Tstar Wstar = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) ! Get new bounds if (Wstar > BoundedHumRatio) then TWetBulbSup = TWetBulb else TWetBulbInf = TWetBulb end if ! New guess of wet bulb temperature TWetBulb = (TWetBulbSup + TWetBulbInf) / 2.0 if (index > MAX_ITER_COUNT) then error stop "Convergence not reached in GetTWetBulbFromHumRatio. Stopping." end if index = index + 1 end do end function GetTWetBulbFromHumRatio function GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) result(HumRatio) !+ Return humidity ratio given dry-bulb temperature, wet-bulb temperature, and pressure. !+ References: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: Wsstar !+ Humidity ratio at temperature Tstar in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (TWetBulb > TDryBulb) then error stop "Error: wet bulb temperature is above dry bulb temperature" end if Wsstar = GetSatHumRatio(TWetBulb, Pressure) if (isIP()) then if (TWetBulb >= FREEZING_POINT_WATER_IP) then HumRatio = ((1093.0 - 0.556 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) & / (1093.0 + 0.444 * TDryBulb - TWetBulb) else HumRatio = ((1220.0 - 0.04 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) & / (1220.0 + 0.444 * TDryBulb - 0.48 * TWetBulb) end if else if (TWetBulb >= FREEZING_POINT_WATER_SI) then HumRatio = ((2501.0 - 2.326 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) & / (2501.0 + 1.86 * TDryBulb - 4.186 * TWetBulb) else HumRatio = ((2830.0 - 0.24 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) & / (2830.0 + 1.86 * TDryBulb - 2.1 * TWetBulb) end if end if ! Validity check. HumRatio = max(HumRatio, MIN_HUM_RATIO) end function GetHumRatioFromTWetBulb function GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) result(HumRatio) !+ Return humidity ratio given dry-bulb temperature, relative humidity, and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: RelHum !+ Relative humidity in range [0, 1] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] if (RelHum < 0.0 .or. RelHum > 1.0) then error stop "Error: relative humidity is outside range [0,1]" end if VapPres = GetVapPresFromRelHum(TDryBulb, RelHum) HumRatio = GetHumRatioFromVapPres(VapPres, Pressure) end function GetHumRatioFromRelHum function GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) result(RelHum) !+ Return relative humidity given dry-bulb temperature, humidity ratio, and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: RelHum !+ Relative humidity in range [0, 1] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] if (HumRatio < 0.0) then error stop "Error: humidity ratio cannot be negative" end if VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) RelHum = GetRelHumFromVapPres(TDryBulb, VapPres) end function GetRelHumFromHumRatio function GetHumRatioFromTDewPoint(TDewPoint, Pressure) result(HumRatio) !+ Return humidity ratio given dew-point temperature and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] VapPres = GetSatVapPres(TDewPoint) HumRatio = GetHumRatioFromVapPres(VapPres, Pressure) end function GetHumRatioFromTDewPoint function GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) result(TDewPoint) !+ Return dew-point temperature given dry-bulb temperature, humidity ratio, and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] if (HumRatio < 0.0) then error stop "Error: humidity ratio cannot be negative" end if VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) TDewPoint = GetTDewPointFromVapPres(TDryBulb, VapPres) end function GetTDewPointFromHumRatio !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Conversions between humidity ratio and vapor pressure !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetHumRatioFromVapPres(VapPres, Pressure) result(HumRatio) !+ Return humidity ratio given water vapor pressure and atmospheric pressure. !+ Reference: !+ ASHRAE Fundamentals (2005) ch. 6 eqn. 22; !+ ASHRAE Fundamentals (2009) ch. 1 eqn. 22. real, intent(in) :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (VapPres < 0.0) then error stop "Error: partial pressure of water vapor in moist air cannot be negative" end if HumRatio = 0.621945 * VapPres / (Pressure-VapPres) ! Validity check. HumRatio = max(HumRatio, MIN_HUM_RATIO) end function GetHumRatioFromVapPres function GetVapPresFromHumRatio(HumRatio, Pressure) result(VapPres) !+ Return vapor pressure given humidity ratio and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 solved for pw real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) VapPres = Pressure * BoundedHumRatio / (0.621945 + BoundedHumRatio) end function GetVapPresFromHumRatio !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Conversions between humidity ratio and specific humidity !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetSpecificHumFromHumRatio(HumRatio) result(SpecificHum) !+ Return the specific humidity from humidity ratio (aka mixing ratio). !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] real :: SpecificHum !+ Specific humidity in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio cannot be negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) SpecificHum = BoundedHumRatio / (1.0 + BoundedHumRatio) end function GetSpecificHumFromHumRatio function GetHumRatioFromSpecificHum(SpecificHum) result(HumRatio) !+ Return the humidity ratio (aka mixing ratio) from specific humidity. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b (solved for humidity ratio) real, intent(in) :: SpecificHum !+ Specific humidity in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] if (SpecificHum < 0.0 .or. SpecificHum >= 1.0) then error stop "Error: specific humidity is outside range [0, 1[" end if HumRatio = SpecificHum / (1.0 - SpecificHum) ! Validity check. HumRatio = max(HumRatio, MIN_HUM_RATIO) end function GetHumRatioFromSpecificHum !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Dry Air Calculations !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetDryAirEnthalpy(TDryBulb) result(DryAirEnthalpy) !+ Return dry-air enthalpy given dry-bulb temperature. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 28 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: DryAirEnthalpy !+ Dry air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] if (isIP()) then DryAirEnthalpy = 0.240 * TDryBulb else DryAirEnthalpy = 1006 * TDryBulb end if end function GetDryAirEnthalpy function GetDryAirDensity(TDryBulb, Pressure) result(DryAirDensity) !+ Return dry-air density given dry-bulb temperature and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 !+ Notes: !+ Eqn 14 for the perfect gas relationship for dry air. !+ Eqn 1 for the universal gas constant. !+ The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: DryAirDensity !+ Dry air density in lb ft⁻³ [IP] or kg m⁻³ [SI] if (isIP()) then DryAirDensity = (144 * Pressure) / R_DA_IP / GetTRankineFromTFahrenheit(TDryBulb) else DryAirDensity = Pressure / R_DA_SI / GetTKelvinFromTCelsius(TDryBulb) end if end function GetDryAirDensity function GetDryAirVolume(TDryBulb, Pressure) result(DryAirVolume) !+ Return dry-air volume given dry-bulb temperature and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 !+ Notes: !+ Eqn 14 for the perfect gas relationship for dry air. !+ Eqn 1 for the universal gas constant. !+ The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: DryAirVolume !+ Dry air volume in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] if (isIP()) then DryAirVolume = GetTRankineFromTFahrenheit(TDryBulb) * R_DA_IP / (144 * Pressure) else DryAirVolume = GetTKelvinFromTCelsius(TDryBulb) * R_DA_SI / Pressure end if end function GetDryAirVolume function GetTDryBulbFromEnthalpyAndHumRatio(MoistAirEnthalpy, HumRatio) result(TDryBulb) !+ Return dry bulb temperature from enthalpy and humidity ratio. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 !+ Notes: !+ Based on the `GetMoistAirEnthalpy` function, rearranged for temperature. real, intent(in) :: MoistAirEnthalpy !+ Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if (isIP()) then TDryBulb = (MoistAirEnthalpy - 1061.0 * BoundedHumRatio) / (0.240 + 0.444 * BoundedHumRatio) else TDryBulb = (MoistAirEnthalpy / 1000.0 - 2501.0 * BoundedHumRatio) / (1.006 + 1.86 * BoundedHumRatio) end if end function GetTDryBulbFromEnthalpyAndHumRatio function GetHumRatioFromEnthalpyAndTDryBulb(MoistAirEnthalpy, TDryBulb) result(HumRatio) !+ Return humidity ratio from enthalpy and dry-bulb temperature. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 !+ Notes: !+ Based on the `GetMoistAirEnthalpy` function, rearranged for humidity ratio. real, intent(in) :: MoistAirEnthalpy !+ Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] if (isIP()) then HumRatio = (MoistAirEnthalpy - 0.240 * TDryBulb) / (1061.0 + 0.444 * TDryBulb) else HumRatio = (MoistAirEnthalpy / 1000.0 - 1.006 * TDryBulb) / (2501.0 + 1.86 * TDryBulb) end if ! Validity check. HumRatio = max(HumRatio, MIN_HUM_RATIO) end function GetHumRatioFromEnthalpyAndTDryBulb !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Saturated Air Calculations !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetSatVapPres(TDryBulb) result(SatVapPres) !+ Return saturation vapor pressure given dry-bulb temperature. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 5 !+ Important note: the ASHRAE formulae are defined above and below the freezing point but have !+ a discontinuity at the freezing point. This is a small inaccuracy on ASHRAE's part: the formulae !+ should be defined above and below the triple point of water (not the feezing point) in which case !+ the discontinuity vanishes. It is essential to use the triple point of water otherwise function !+ GetTDewPointFromVapPres, which inverts the present function, does not converge properly around !+ the freezing point. real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: SatVapPres !+ Vapor pressure of saturated air in Psi [IP] or Pa [SI] real :: LnPws !+ Log of Vapor Pressure of saturated air (dimensionless) real :: T !+ Dry bulb temperature in R [IP] or K [SI] if (isIP()) then if (TDryBulb < -148.0 .or. TDryBulb > 392.0) then error stop "Error: dry bulb temperature must be in range [-148, 392]°F" end if T = GetTRankineFromTFahrenheit(TDryBulb) if (TDryBulb <= TRIPLE_POINT_WATER_IP) then LnPws = (-1.0214165E+04 / T - 4.8932428 - 5.3765794E-03 * T + 1.9202377E-07 * T**2 & + 3.5575832E-10 * T**3 - 9.0344688E-14 * T**4 + 4.1635019 * log(T)) else LnPws = -1.0440397E+04 / T - 1.1294650E+01 - 2.7022355E-02* T + 1.2890360E-05 * T**2 & - 2.4780681E-09 * T**3 + 6.5459673 * log(T) end if else if (TDryBulb < -100.0 .or. TDryBulb > 200.0) then error stop "Error: dry bulb temperature must be in range [-100, 200]°C" end if T = GetTKelvinFromTCelsius(TDryBulb) if (TDryBulb <= TRIPLE_POINT_WATER_SI) then LnPws = -5.6745359E+03 / T + 6.3925247 - 9.677843E-03 * T + 6.2215701E-07 * T**2 & + 2.0747825E-09 * T**3 - 9.484024E-13 * T**4 + 4.1635019 * log(T) else LnPws = -5.8002206E+03 / T + 1.3914993 - 4.8640239E-02 * T + 4.1764768E-05 * T**2 & - 1.4452093E-08 * T**3 + 6.5459673 * log(T) end if end if SatVapPres = exp(LnPws) end function GetSatVapPres function GetSatHumRatio(TDryBulb, Pressure) result(SatHumRatio) !+ Return humidity ratio of saturated air given dry-bulb temperature and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36, solved for W real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: SatHumRatio !+ Humidity ratio of saturated air in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: SatVaporPres !+ Vapor pressure of saturated air in in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] SatVaporPres = GetSatVapPres(TDryBulb) SatHumRatio = 0.621945 * SatVaporPres / (Pressure-SatVaporPres) ! Validity check. SatHumRatio = max(SatHumRatio, MIN_HUM_RATIO) end function GetSatHumRatio function GetSatAirEnthalpy(TDryBulb, Pressure) result(SatAirEnthalpy) !+ Return saturated air enthalpy given dry-bulb temperature and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: SatAirEnthalpy !+ Saturated air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] SatAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, GetSatHumRatio(TDryBulb, Pressure)) end function GetSatAirEnthalpy !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Moist Air Calculations !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetVaporPressureDeficit(TDryBulb, HumRatio, Pressure) result(VaporPressureDeficit) !+ Return Vapor pressure deficit given dry-bulb temperature, humidity ratio, and pressure. !+ Reference: !+ Oke (1987) eqn 2.13a real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: VaporPressureDeficit !+ Vapor pressure deficit in Psi [IP] or Pa [SI] real :: RelHum !+ Relative humidity in range [0, 1] if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VaporPressureDeficit = GetSatVapPres(TDryBulb) * (1.0 - RelHum) end function GetVaporPressureDeficit function GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) result(DegreeOfSaturation) !+ Return the degree of saturation (i.e humidity ratio of the air / humidity ratio of the air at saturation !+ at the same temperature and pressure) given dry-bulb temperature, humidity ratio, and atmospheric pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2009) ch. 1 eqn 12 !+ Notes: !+ This definition is absent from the 2017 Handbook. Using 2009 version instead. real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: DegreeOfSaturation !+ Degree of saturation in arbitrary unit real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) DegreeOfSaturation = BoundedHumRatio / GetSatHumRatio(TDryBulb, Pressure) end function GetDegreeOfSaturation function GetMoistAirEnthalpy(TDryBulb, HumRatio) result(MoistAirEnthalpy) !+ Return moist air enthalpy given dry-bulb temperature and humidity ratio. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real :: MoistAirEnthalpy !+ Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if (isIP()) then MoistAirEnthalpy = 0.240 * TDryBulb + BoundedHumRatio * (1061.0 + 0.444 * TDryBulb) else MoistAirEnthalpy = (1.006 * TDryBulb + BoundedHumRatio * (2501.0 + 1.86 * TDryBulb)) * 1000.0 end if end function GetMoistAirEnthalpy function GetMoistAirVolume(TDryBulb, HumRatio, Pressure) result(MoistAirVolume) !+ Return moist air specific volume given dry-bulb temperature, humidity ratio, and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 !+ Notes: !+ In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 !+ The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: MoistAirVolume !+ Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if (isIP()) then MoistAirVolume = R_DA_IP * GetTRankineFromTFahrenheit(TDryBulb) * (1.0 + 1.607858 * BoundedHumRatio) / (144.0 * Pressure) else MoistAirVolume = R_DA_SI * GetTKelvinFromTCelsius(TDryBulb) * (1.0 + 1.607858 * BoundedHumRatio) / Pressure end if end function GetMoistAirVolume function GetTDryBulbFromMoistAirVolumeAndHumRatio(MoistAirVolume, HumRatio, Pressure) result(TDryBulb) !+ Return dry-bulb temperature given moist air specific volume, humidity ratio, and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 !+ Notes: !+ In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 !+ The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². !+ Based on the `GetMoistAirVolume` function, rearranged for dry-bulb temperature. real, intent(in) :: MoistAirVolume !+ Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) if (isIP()) then TDryBulb = GetTFahrenheitFromTRankine(MoistAirVolume * (144 * Pressure) & / (R_DA_IP * (1 + 1.607858 * BoundedHumRatio))) else TDryBulb = GetTCelsiusFromTKelvin(MoistAirVolume * Pressure & / (R_DA_SI * (1 + 1.607858 * BoundedHumRatio))) end if end function GetTDryBulbFromMoistAirVolumeAndHumRatio function GetMoistAirDensity(TDryBulb, HumRatio, Pressure) result(MoistAirDensity) !+ Return moist air density given humidity ratio, dry bulb temperature, and pressure. !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 11 real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real :: MoistAirDensity !+ Moist air density in lb ft⁻³ [IP] or kg m⁻³ [SI] real :: BoundedHumRatio !+ Humidity ratio bounded to MIN_HUM_RATIO if (HumRatio < 0.0) then error stop "Error: humidity ratio is negative" end if BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) MoistAirDensity = (1.0 + BoundedHumRatio) / GetMoistAirVolume(TDryBulb, BoundedHumRatio, Pressure) end function GetMoistAirDensity !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Standard atmosphere !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function GetStandardAtmPressure(Altitude) result(StandardAtmPressure) !+ Return standard atmosphere barometric pressure, given the elevation (altitude). !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 3 real, intent(in) :: Altitude !+ Altitude in ft [IP] or m [SI] real :: StandardAtmPressure !+ Standard atmosphere barometric pressure in Psi [IP] or Pa [SI] if (isIP()) then StandardAtmPressure = 14.696 * (1.0 - 6.8754e-06 * Altitude)**5.2559 else StandardAtmPressure = 101325 * (1 - 2.25577e-05 * Altitude)**5.2559 end if end function GetStandardAtmPressure function GetStandardAtmTemperature(Altitude) result(StandardAtmTemperature) !+ Return standard atmosphere temperature, given the elevation (altitude). !+ Reference: !+ ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 4 real, intent(in) :: Altitude !+ Altitude in ft [IP] or m [SI] real :: StandardAtmTemperature !+ Standard atmosphere dry-bulb temperature in °F [IP] or °C [SI] if (isIP()) then StandardAtmTemperature = 59.0 - 0.00356620 * Altitude else StandardAtmTemperature = 15.0 - 0.0065 * Altitude end if end function GetStandardAtmTemperature function GetSeaLevelPressure(StnPressure, Altitude, TDryBulb) result(SeaLevelPressure) !+ Return sea level pressure given dry-bulb temperature, altitude above sea level and pressure. !+ Reference: !+ Hess SL, Introduction to theoretical meteorology, Holt Rinehart and Winston, NY 1959, !+ ch. 6.5; Stull RB, Meteorology for scientists and engineers, 2nd edition, !+ Brooks/Cole 2000, ch. 1. !+ Notes: !+ The standard procedure for the US is to use for TDryBulb the average !+ of the current station temperature and the station temperature from 12 hours ago. real, intent(in) :: StnPressure !+ Observed station pressure in Psi [IP] or Pa [SI] real, intent(in) :: Altitude !+ Altitude in ft [IP] or m [SI] real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: SeaLevelPressure !+ Sea level barometric pressure in Psi [IP] or Pa [SI] real :: TColumn !+ Average temperature in column of air in R [IP] or K [SI] real :: H !+ scale height (dimensionless) if (isIP()) then ! Calculate average temperature in column of air, assuming a lapse rate ! of 3.6 °F/1000ft TColumn = TDryBulb + 0.0036 * Altitude / 2.0 ! Determine the scale height H = 53.351 * GetTRankineFromTFahrenheit(TColumn) else ! Calculate average temperature in column of air, assuming a lapse rate ! of 6.5 °C/km TColumn = TDryBulb + 0.0065 * Altitude / 2.0 ! Determine the scale height H = 287.055 * GetTKelvinFromTCelsius(TColumn) / 9.807 end if ! Calculate the sea level pressure SeaLevelPressure = StnPressure * exp(Altitude / H) end function GetSeaLevelPressure function GetStationPressure(SeaLevelPressure, Altitude, TDryBulb) result(StationPressure) !+ Return station pressure from sea level pressure. !+ Reference: !+ See 'GetSeaLevelPressure' !+ Notes: !+ This function is just the inverse of 'GetSeaLevelPressure'. real, intent(in) :: SeaLevelPressure !+ Sea level barometric pressure in Psi [IP] or Pa [SI] real, intent(in) :: Altitude !+ Altitude in ft [IP] or m [SI] real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real :: StationPressure !+ Station pressure in Psi [IP] or Pa [SI] StationPressure = SeaLevelPressure / GetSeaLevelPressure(1.0, Altitude, TDryBulb) end function GetStationPressure !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ! Functions to set all psychrometric values !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! subroutine CalcPsychrometricsFromTWetBulb(TDryBulb, & TWetBulb, & Pressure, & HumRatio, & TDewPoint, & RelHum, & VapPres, & MoistAirEnthalpy, & MoistAirVolume, & DegreeOfSaturation) !+ Utility function to calculate humidity ratio, dew-point temperature, relative humidity, !+ vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given !+ dry-bulb temperature, wet-bulb temperature, and pressure. real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real, intent(out) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(out) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real, intent(out) :: RelHum !+ Relative humidity in range [0, 1] real, intent(out) :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real, intent(out) :: MoistAirEnthalpy !+ Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] real, intent(out) :: MoistAirVolume !+ Specific volume of moist air in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] real, intent(out) :: DegreeOfSaturation !+ Degree of saturation [unitless] HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) end subroutine CalcPsychrometricsFromTWetBulb subroutine CalcPsychrometricsFromTDewPoint(TDryBulb, & TDewPoint, & Pressure, & HumRatio, & TWetBulb, & RelHum, & VapPres, & MoistAirEnthalpy, & MoistAirVolume, & DegreeOfSaturation) !+ Utility function to calculate humidity ratio, wet-bulb temperature, relative humidity, !+ vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given !+ dry-bulb temperature, dew-point temperature, and pressure. real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real, intent(out) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(out) :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real, intent(out) :: RelHum !+ Relative humidity in range [0, 1] real, intent(out) :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real, intent(out) :: MoistAirEnthalpy !+ Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] real, intent(out) :: MoistAirVolume !+ Specific volume of moist air in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] real, intent(out) :: DegreeOfSaturation !+ Degree of saturation [unitless] HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) end subroutine CalcPsychrometricsFromTDewPoint subroutine CalcPsychrometricsFromRelHum(TDryBulb, & RelHum, & Pressure, & HumRatio, & TWetBulb, & TDewPoint, & VapPres, & MoistAirEnthalpy, & MoistAirVolume, & DegreeOfSaturation) !+ Utility function to calculate humidity ratio, wet-bulb temperature, dew-point temperature, !+ vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given !+ dry-bulb temperature, relative humidity and pressure. real, intent(in) :: TDryBulb !+ Dry-bulb temperature in °F [IP] or °C [SI] real, intent(in) :: RelHum !+ Relative humidity in range [0, 1] real, intent(in) :: Pressure !+ Atmospheric pressure in Psi [IP] or Pa [SI] real, intent(out) :: HumRatio !+ Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] real, intent(out) :: TWetBulb !+ Wet-bulb temperature in °F [IP] or °C [SI] real, intent(out) :: TDewPoint !+ Dew-point temperature in °F [IP] or °C [SI] real, intent(out) :: VapPres !+ Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] real, intent(out) :: MoistAirEnthalpy !+ Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] real, intent(out) :: MoistAirVolume !+ Specific volume of moist air in ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] real, intent(out) :: DegreeOfSaturation !+ Degree of saturation [unitless] HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) end subroutine CalcPsychrometricsFromRelHum end module psychrolib |
Visual Basic, VBA
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 | ' PsychroLib (version 2.3.0) (https://github.com/psychrometrics/psychrolib) ' Copyright (c) 2018 D. Thevenard and D. Meyer for the current library implementation ' Copyright (c) 2017 ASHRAE Handbook — Fundamentals for ASHRAE equations and coefficients ' Licensed under the MIT License. ' ' psychrolib.vba ' ' Contains functions for calculating thermodynamic properties of gas-vapor mixtures ' and standard atmosphere suitable for most engineering, physical and meteorological ' applications. ' ' Most of the functions are an implementation of the formulae found in the ' 2017 ASHRAE Handbook - Fundamentals, in both International System (SI), ' and Imperial (IP) units. Please refer to the information included in ' each function for their respective reference. ' ' Example ' ' Set the unit system, for example to SI (can be either ' SI' or ' IP' ) ' ' by uncommenting the following line in the psychrolib module ' Const PSYCHROLIB_UNITS = UnitSystem.SI ' ' ' Calculate the dew point temperature for a dry bulb temperature of 25 C and a relative humidity of 80% ' TDewPoint = GetTDewPointFromRelHum(25.0, 0.80) ' Debug.Print(TDewPoint) ' 21.309397163661785 ' ' Copyright ' - For the current library implementation ' Copyright (c) 2018 D. Thevenard and D. Meyer. ' - For equations and coefficients published ASHRAE Handbook — Fundamentals, Chapter 1 ' Copyright (c) 2017 ASHRAE Handbook — Fundamentals (https://www.ashrae.org) ' ' License ' MIT (https://github.com/psychrometrics/psychrolib/LICENSE.txt) ' ' Note from the Authors ' We have made every effort to ensure that the code is adequate, however, we make no ' representation with respect to its accuracy. Use at your own risk. Should you notice ' an error, or if you have a suggestion, please notify us through GitHub at ' https://github.com/psychrometrics/psychrolib/issues. ' Option Explicit '****************************************************************************************************** ' IMPORTANT: Manually uncomment the system of units to use '****************************************************************************************************** 'Enumeration to define systems of units Enum UnitSystem IP = 1 SI = 2 End Enum ' Uncomment one of these two lines to define the system of units ("IP" or "SI") 'Const PSYCHROLIB_UNITS = UnitSystem.IP 'Const PSYCHROLIB_UNITS = UnitSystem.SI '****************************************************************************************************** ' Global constants '****************************************************************************************************** Private Const ZERO_FAHRENHEIT_AS_RANKINE = 459.67 ' Zero degree Fahrenheit (°F) expressed as degree Rankine (°R). 'Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. Private Const ZERO_CELSIUS_AS_KELVIN = 273.15 ' Zero degree Celsius (°C) expressed as Kelvin (K). ' Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. Private Const R_DA_IP = 53.35 ' Universal gas constant for dry air (IP version) in ft lbf/lb_DryAir/R. Private Const R_DA_SI = 287.042 ' Universal gas constant for dry air (SI version) in J/kg_DryAir/K. Private Const MAX_ITER_COUNT = 100 ' Maximum number of iterations before exiting while loops. Private Const MIN_HUM_RATIO = 1e-7 ' Minimum acceptable humidity ratio used/returned by any functions. ' Any value above 0 or below the MIN_HUM_RATIO will be reset to this value. Private Const FREEZING_POINT_WATER_IP = 32.0 ' Freezing point of water, in °F Private Const FREEZING_POINT_WATER_SI = 0.0 ' Freezing point of water, in °C Private Const TRIPLE_POINT_WATER_IP = 32.018 ' Triple point of water, in °F Private Const TRIPLE_POINT_WATER_SI = 0.01 ' Triple point of water, in °C '****************************************************************************************************** ' Helper functions '****************************************************************************************************** Function GetUnitSystem() As UnitSystem ' ' This function returns the system of units currently in use (SI or IP). ' ' Args: ' none ' ' Returns: ' The system of units currently in use ('SI' or 'IP') ' ' Note: ' ' If you get an error here, it's because you have not uncommented one of the two lines ' defining PSYCHROLIB_UNITS (see Global Constants section) ' GetUnitSystem = PSYCHROLIB_UNITS End Function Private Function isIP() As Variant ' ' This function checks whether the system of units currently in use is IP or SI. ' ' Args: ' none ' ' Returns: ' True if IP, False if SI, and raises error if undefined ' If (PSYCHROLIB_UNITS = UnitSystem.IP) Then isIP = True ElseIf (PSYCHROLIB_UNITS = UnitSystem.SI) Then isIP = False Else MsgBox ("The system of units has not been defined.") isIP = CVErr(xlErrNA) End If End Function Private Function GetTol() As Variant ' ' This function returns the tolerance on temperatures used for iterative solving. ' The value is physically the same in IP or SI. ' ' Args: ' none ' ' Returns: ' Tolerance on temperatures ' If (PSYCHROLIB_UNITS = UnitSystem.IP) Then GetTol = 0.001 * 9 / 5 Else GetTol = 0.001 End If End Function Private Sub MyMsgBox(ByVal ErrMsg As String) ' ' Error message output ' Override this function with your own if needed, or comment its code out if you don't want to see the messages ' ' Message disabled by default ' MsgBox (ErrMsg) End Sub Private Function Min(ByVal Num1 As Variant, ByVal Num2 As Variant) As Variant ' ' Min function to return minimum of two numbers ' If (Num1 <= Num2) Then Min = Num1 Else Min = Num2 End If End Function Private Function Max(ByVal Num1 As Variant, ByVal Num2 As Variant) As Variant ' ' Max function to return maximum of two numbers ' If (Num1 >= Num2) Then Max = Num1 Else Max = Num2 End If End Function '***************************************************************************** ' Conversions between temperature units '***************************************************************************** Function GetTRankineFromTFahrenheit(ByVal T_Fahrenheit As Variant) As Variant ' ' Utility function to convert temperature to degree Rankine (°R) ' given temperature in degree Fahrenheit (°F). ' 'Args: ' T_Fahrenheit: Temperature in degree Fahrenheit (°F) ' 'Returns: ' Temperature in degree Rankine (°R) ' 'Reference: ' Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 ' 'Notes: ' Exact conversion. ' On Error GoTo ErrHandler GetTRankineFromTFahrenheit = (T_Fahrenheit + ZERO_FAHRENHEIT_AS_RANKINE) Exit Function ErrHandler: GetTRankineFromTFahrenheit = CVErr(xlErrNA) End Function Function GetTFahrenheitFromTRankine(ByVal T_Rankine As Variant) As Variant ' ' Utility function to convert temperature to degree Fahrenheit (°F) ' given temperature in degree Rankine (°R). ' 'Args: ' TRankine: Temperature in degree Rankine (°R) ' 'Returns: ' Temperature in degree Fahrenheit (°F) ' 'Reference: ' Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 ' 'Notes: ' Exact conversion. ' On Error GoTo ErrHandler GetTFahrenheitFromTRankine = (T_Rankine - ZERO_FAHRENHEIT_AS_RANKINE) Exit Function ErrHandler: GetTFahrenheitFromTRankine = CVErr(xlErrNA) End Function Function GetTKelvinFromTCelsius(ByVal T_Celsius As Variant) As Variant ' ' Utility function to convert temperature to Kelvin (K) ' given temperature in degree Celsius (°C). ' 'Args: ' TCelsius: Temperature in degree Celsius (°C) ' 'Returns: ' Temperature in Kelvin (K) ' 'Reference: ' Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 ' 'Notes: ' Exact conversion. ' On Error GoTo ErrHandler GetTKelvinFromTCelsius = (T_Celsius + ZERO_CELSIUS_AS_KELVIN) Exit Function ErrHandler: GetTKelvinFromTCelsius = CVErr(xlErrNA) End Function Function GetTCelsiusFromTKelvin(ByVal T_Kelvin As Variant) As Variant ' ' Utility function to convert temperature to degree Celsius (°C) ' given temperature in Kelvin (K). ' 'Args: ' TKelvin: Temperature in Kelvin (K) ' 'Returns: ' Temperature in degree Celsius (°C) ' 'Reference: ' Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 ' 'Notes: ' Exact conversion. ' On Error GoTo ErrHandler GetTCelsiusFromTKelvin = (T_Kelvin - ZERO_CELSIUS_AS_KELVIN) Exit Function ErrHandler: GetTCelsiusFromTKelvin = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Conversions between dew point, wet bulb, and relative humidity '****************************************************************************************************** Function GetTWetBulbFromTDewPoint(ByVal TDryBulb As Variant, ByVal TDewPoint As Variant, ByVal Pressure As Variant) As Variant ' ' Return wet-bulb temperature given dry-bulb temperature, dew-point temperature, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TDewPoint : Dew-point temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Wet-bulb temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim HumRatio As Variant On Error GoTo ErrHandler If TDewPoint > TDryBulb Then MyMsgBox ("Dew point temperature is above dry bulb temperature") GoTo ErrHandler End If HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure) GetTWetBulbFromTDewPoint = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) Exit Function ErrHandler: GetTWetBulbFromTDewPoint = CVErr(xlErrNA) End Function Function GetTWetBulbFromRelHum(ByVal TDryBulb As Variant, ByVal RelHum As Variant, ByVal Pressure As Variant) As Variant ' ' Return wet-bulb temperature given dry-bulb temperature, relative humidity, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' RelHum : Relative humidity in range [0, 1] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Wet-bulb temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim HumRatio As Variant On Error GoTo ErrHandler If (RelHum < 0 Or RelHum > 1) Then MyMsgBox ("Relative humidity is outside range [0,1]") GoTo ErrHandler End If HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) GetTWetBulbFromRelHum = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) Exit Function ErrHandler: GetTWetBulbFromRelHum = CVErr(xlErrNA) End Function Function GetRelHumFromTDewPoint(ByVal TDryBulb As Variant, ByVal TDewPoint As Variant) As Variant ' ' Return relative humidity given dry-bulb temperature and dew-point temperature. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TDewPoint : Dew-point temperature in °F [IP] or °C [SI] ' ' Returns: ' Relative humidity in range [0, 1] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 22 ' Dim VapPres As Variant Dim SatVapPres As Variant On Error GoTo ErrHandler If (TDewPoint > TDryBulb) Then MyMsgBox ("Dew point temperature is above dry bulb temperature") GoTo ErrHandler End If VapPres = GetSatVapPres(TDewPoint) SatVapPres = GetSatVapPres(TDryBulb) GetRelHumFromTDewPoint = VapPres / SatVapPres Exit Function ErrHandler: GetRelHumFromTDewPoint = CVErr(xlErrNA) End Function Function GetRelHumFromTWetBulb(ByVal TDryBulb As Variant, ByVal TWetBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return relative humidity given dry-bulb temperature, wet bulb temperature and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Relative humidity in range [0, 1] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim HumRatio As Variant On Error GoTo ErrHandler If TWetBulb > TDryBulb Then MyMsgBox ("Wet bulb temperature is above dry bulb temperature") GoTo ErrHandler End If HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) GetRelHumFromTWetBulb = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) Exit Function ErrHandler: GetRelHumFromTWetBulb = CVErr(xlErrNA) End Function Function GetTDewPointFromRelHum(ByVal TDryBulb As Variant, ByVal RelHum As Variant) As Variant ' ' Return dew-point temperature given dry-bulb temperature and relative humidity. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' RelHum: Relative humidity in range [0, 1] ' ' Returns: ' Dew-point temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim VapPres As Variant On Error GoTo ErrHandler If RelHum < 0 Or RelHum > 1 Then MyMsgBox ("Relative humidity is outside range [0, 1]") GoTo ErrHandler End If VapPres = GetVapPresFromRelHum(TDryBulb, RelHum) GetTDewPointFromRelHum = GetTDewPointFromVapPres(TDryBulb, VapPres) Exit Function ErrHandler: GetTDewPointFromRelHum = CVErr(xlErrNA) End Function Function GetTDewPointFromTWetBulb(ByVal TDryBulb As Variant, ByVal TWetBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return dew-point temperature given dry-bulb temperature, wet-bulb temperature, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Dew-point temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim HumRatio As Variant On Error GoTo ErrHandler If TWetBulb > TDryBulb Then MyMsgBox ("Wet bulb temperature is above dry bulb temperature") GoTo ErrHandler End If HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) GetTDewPointFromTWetBulb = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) Exit Function ErrHandler: GetTDewPointFromTWetBulb = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Conversions between dew point, or relative humidity and vapor pressure '****************************************************************************************************** Function GetVapPresFromRelHum(ByVal TDryBulb As Variant, ByVal RelHum As Variant) As Variant ' ' Return partial pressure of water vapor as a function of relative humidity and temperature. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' RelHum : Relative humidity in range [0, 1] ' ' Returns: ' Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 ' On Error GoTo ErrHandler If RelHum < 0 Or RelHum > 1 Then MyMsgBox ("Relative humidity is outside range [0, 1]") GoTo ErrHandler End If GetVapPresFromRelHum = RelHum * GetSatVapPres(TDryBulb) Exit Function ErrHandler: GetVapPresFromRelHum = CVErr(xlErrNA) End Function Function GetRelHumFromVapPres(ByVal TDryBulb As Variant, ByVal VapPres As Variant) As Variant ' Return relative humidity given dry-bulb temperature and vapor pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' VapPres: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' ' Returns: ' Relative humidity in range [0, 1] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 ' On Error GoTo ErrHandler If (VapPres < 0) Then MyMsgBox ("Partial pressure of water vapor in moist air is negative") GoTo ErrHandler End If GetRelHumFromVapPres = VapPres / GetSatVapPres(TDryBulb) Exit Function ErrHandler: GetRelHumFromVapPres = CVErr(xlErrNA) End Function Private Function dLnPws_(TDryBulb As Variant) As Variant ' ' Helper function returning the derivative of the natural log of the saturation vapor pressure ' as a function of dry-bulb temperature. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' ' Returns: ' Derivative of natural log of vapor pressure of saturated air in Psi [IP] or Pa [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 5 & 6 ' Dim T As Variant If (isIP()) Then T = GetTRankineFromTFahrenheit(TDryBulb) If (TDryBulb <= TRIPLE_POINT_WATER_IP) Then dLnPws_ = 10214.165 / T ^ 2 - 0.0053765794 + 2 * 0.00000019202377 * T _ + 3 * 3.5575832E-10 * T ^ 2 - 4 * 9.0344688E-14 * T ^ 3 + 4.1635019 / T Else dLnPws_ = 10440.397 / T ^ 2 - 0.027022355 + 2 * 0.00001289036 * T _ - 3 * 2.4780681E-09 * T ^ 2 + 6.5459673 / T End If Else T = GetTKelvinFromTCelsius(TDryBulb) If (TDryBulb <= TRIPLE_POINT_WATER_SI) Then dLnPws_ = 5674.5359 / T ^ 2 - 0.009677843 + 2 * 0.00000062215701 * T _ + 3 * 2.0747825E-09 * T ^ 2 - 4 * 9.484024E-13 * T ^ 3 + 4.1635019 / T Else dLnPws_ = 5800.2206 / T ^ 2 - 0.048640239 + 2 * 0.000041764768 * T _ - 3 * 0.000000014452093 * T ^ 2 + 6.5459673 / T End If End If End Function Function GetTDewPointFromVapPres(ByVal TDryBulb As Variant, ByVal VapPres As Variant) As Variant ' ' Return dew-point temperature given dry-bulb temperature and vapor pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' VapPres: Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' ' Returns: ' Dew-point temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 and 6 ' ' Notes: ' The dew point temperature is solved by inverting the equation giving water vapor pressure ' at saturation from temperature rather than using the regressions provided ' by ASHRAE (eqn. 37 and 38) which are much less accurate and have a ' narrower range of validity. ' The Newton-Raphson (NR) method is used on the logarithm of water vapour ' pressure as a function of temperature, which is a very smooth function ' Convergence is usually achieved in 3 to 5 iterations. ' TDryBulb is not really needed here, just used for convenience. ' Dim BOUNDS(2) As Variant Dim PSYCHROLIB_TOLERANCE As Variant If (isIP()) Then BOUNDS(1) = -148. BOUNDS(2) = 392. Else BOUNDS(1) = -100. BOUNDS(2) = 200. End If On Error GoTo ErrHandler If ((VapPres < GetSatVapPres(BOUNDS(1))) Or (VapPres > GetSatVapPres(BOUNDS(2)))) Then MyMsgBox ("Partial pressure of water vapor is outside range of validity of equations") GoTo ErrHandler End If PSYCHROLIB_TOLERANCE = GetTol() Dim TDewPoint As Variant Dim lnVP As Variant Dim d_lnVP As Variant Dim TDewPoint_iter As Variant Dim lnVP_iter Dim index As Variant index = 1 ' We use NR to approximate the solution. ' First guess TDewPoint = TDryBulb ' Calculated value of dew point temperatures, solved for iteratively lnVP = Log(VapPres) ' Partial pressure of water vapor in moist air ' Iteration Do TDewPoint_iter = TDewPoint ' Value of Tdp used in NR calculation lnVP_iter = Log(GetSatVapPres(TDewPoint_iter)) ' Derivative of function, calculated analytically d_lnVP = dLnPws_(TDewPoint_iter) ' New estimate, bounded by domain of validity of eqn. 5 and 6 and by the freezing point TDewPoint = TDewPoint_iter - (lnVP_iter - lnVP) / d_lnVP TDewPoint = Max(TDewPoint, BOUNDS(1)) TDewPoint = Min(TDewPoint, BOUNDS(2)) If (index > MAX_ITER_COUNT) Then GoTo ErrHandler End If index = index + 1 Loop While (Abs(TDewPoint - TDewPoint_iter) > PSYCHROLIB_TOLERANCE) TDewPoint = Min(TDewPoint, TDryBulb) GetTDewPointFromVapPres = TDewPoint Exit Function ErrHandler: GetTDewPointFromVapPres = CVErr(xlErrNA) End Function Function GetVapPresFromTDewPoint(ByVal TDewPoint As Variant) As Variant ' ' Return vapor pressure given dew point temperature. ' ' Args: ' TDewPoint : Dew-point temperature in °F [IP] or °C [SI] ' ' Returns: ' Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36 ' On Error GoTo ErrHandler GetVapPresFromTDewPoint = GetSatVapPres(TDewPoint) Exit Function ErrHandler: GetVapPresFromTDewPoint = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Conversions from wet-bulb temperature, dew-point temperature, or relative humidity to humidity ratio '****************************************************************************************************** Function GetTWetBulbFromHumRatio(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return wet-bulb temperature given dry-bulb temperature, humidity ratio, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Wet-bulb temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 solved for Tstar ' ' Declarations Dim Wstar As Variant Dim TDewPoint As Variant, TWetBulb As Variant, TWetBulbSup As Variant, TWetBulbInf As Variant Dim Tol As Variant, BoundedHumRatio As Variant, index As Variant On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio cannot be negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, BoundedHumRatio, Pressure) ' Initial guesses TWetBulbSup = TDryBulb TWetBulbInf = TDewPoint TWetBulb = (TWetBulbInf + TWetBulbSup) / 2 ' Bisection loop Tol = GetTol() index = 0 While ((TWetBulbSup - TWetBulbInf) > Tol) ' Compute humidity ratio at temperature Tstar Wstar = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) ' Get new bounds If (Wstar > BoundedHumRatio) Then TWetBulbSup = TWetBulb Else TWetBulbInf = TWetBulb End If ' New guess of wet bulb temperature TWetBulb = (TWetBulbSup + TWetBulbInf) / 2 If (index > MAX_ITER_COUNT) Then GoTo ErrHandler End If index = index + 1 Wend GetTWetBulbFromHumRatio = TWetBulb Exit Function ErrHandler: GetTWetBulbFromHumRatio = CVErr(xlErrNA) End Function Function GetHumRatioFromTWetBulb(ByVal TDryBulb As Variant, ByVal TWetBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return humidity ratio given dry-bulb temperature, wet-bulb temperature, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 Dim Wsstar As Variant, HumRatio As Variant Wsstar = GetSatHumRatio(TWetBulb, Pressure) On Error GoTo ErrHandler If TWetBulb > TDryBulb Then MyMsgBox ("Wet bulb temperature is above dry bulb temperature") GoTo ErrHandler End If If isIP() Then If (TWetBulb >= FREEZING_POINT_WATER_IP) Then HumRatio = ((1093 - 0.556 * TWetBulb) * Wsstar - 0.24 * (TDryBulb - TWetBulb)) / (1093 + 0.444 * TDryBulb - TWetBulb) Else HumRatio = ((1220 - 0.04 * TWetBulb) * Wsstar - 0.24 * (TDryBulb - TWetBulb)) / (1220 + 0.444 * TDryBulb - 0.48 * TWetBulb) End If Else If (TWetBulb >= FREEZING_POINT_WATER_SI) Then HumRatio = ((2501 - 2.326 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) / (2501 + 1.86 * TDryBulb - 4.186 * TWetBulb) Else HumRatio = ((2830 - 0.24 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) / (2830 + 1.86 * TDryBulb - 2.1 * TWetBulb) End If End If ' Validity check. GetHumRatioFromTWetBulb = max(HumRatio, MIN_HUM_RATIO) Exit Function ErrHandler: GetHumRatioFromTWetBulb = CVErr(xlErrNA) End Function Function GetHumRatioFromRelHum(ByVal TDryBulb As Variant, ByVal RelHum As Variant, ByVal Pressure As Variant) As Variant ' ' Return humidity ratio given dry-bulb temperature, relative humidity, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' RelHum : Relative humidity in range [0, 1] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim VapPres As Variant On Error GoTo ErrHandler If RelHum < 0 Or RelHum > 1 Then MyMsgBox ("Relative humidity is outside range [0, 1]") GoTo ErrHandler End If VapPres = GetVapPresFromRelHum(TDryBulb, RelHum) GetHumRatioFromRelHum = GetHumRatioFromVapPres(VapPres, Pressure) Exit Function ErrHandler: GetHumRatioFromRelHum = CVErr(xlErrNA) End Function Function GetRelHumFromHumRatio(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return relative humidity given dry-bulb temperature, humidity ratio, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Relative humidity in range [0, 1] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim VapPres As Variant On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) GetRelHumFromHumRatio = GetRelHumFromVapPres(TDryBulb, VapPres) Exit Function ErrHandler: GetRelHumFromHumRatio = CVErr(xlErrNA) End Function Function GetHumRatioFromTDewPoint(ByVal TDewPoint As Variant, ByVal Pressure As Variant) As Variant ' ' Return humidity ratio given dew-point temperature and pressure. ' ' Args: ' TDewPoint : Dew-point temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 13 ' Dim VapPres As Variant On Error GoTo ErrHandler VapPres = GetSatVapPres(TDewPoint) GetHumRatioFromTDewPoint = GetHumRatioFromVapPres(VapPres, Pressure) Exit Function ErrHandler: GetHumRatioFromTDewPoint = CVErr(xlErrNA) End Function Function GetTDewPointFromHumRatio(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return dew-point temperature given dry-bulb temperature, humidity ratio, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Dew-point temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' Dim VapPres As Variant On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) GetTDewPointFromHumRatio = GetTDewPointFromVapPres(TDryBulb, VapPres) Exit Function ErrHandler: GetTDewPointFromHumRatio = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Conversions between humidity ratio and vapor pressure '****************************************************************************************************** Function GetHumRatioFromVapPres(ByVal VapPres As Variant, ByVal Pressure As Variant) As Variant ' ' Return humidity ratio given water vapor pressure and atmospheric pressure. ' ' Args: ' VapPres : Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 ' Dim HumRatio As Variant On Error GoTo ErrHandler If VapPres < 0 Then MyMsgBox ("Partial pressure of water vapor in moist air is negative") GoTo ErrHandler End If HumRatio = 0.621945 * VapPres / (Pressure - VapPres) ' Validity check. GetHumRatioFromVapPres = max(HumRatio, MIN_HUM_RATIO) Exit Function ErrHandler: GetHumRatioFromVapPres = CVErr(xlErrNA) End Function Function GetVapPresFromHumRatio(ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return vapor pressure given humidity ratio and pressure. ' ' Args: ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 solved for pw ' Dim VapPres As Variant, BoundedHumRatio As Variant On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) VapPres = Pressure * BoundedHumRatio / (0.621945 + BoundedHumRatio) GetVapPresFromHumRatio = VapPres Exit Function ErrHandler: GetVapPresFromHumRatio = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Conversions between humidity ratio and specific humidity '****************************************************************************************************** Function GetSpecificHumFromHumRatio(ByVal HumRatio As Variant) As Variant ' ' Return the specific humidity from humidity ratio (aka mixing ratio). ' ' Args: ' HumRatio : Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] ' ' Returns: ' Specific humidity in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b ' ' Dim SpecificHum As Variant On Error GoTo ErrHandler If (HumRatio < 0) Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If SpecificHum = HumRatio / (1.0 + HumRatio) GetSpecificHumFromHumRatio = SpecificHum Exit Function ErrHandler: GetSpecificHumFromHumRatio = CVErr(xlErrNA) End Function Function GetHumRatioFromSpecificHum(ByVal SpecificHum As Variant) As Variant ' ' Return the humidity ratio (aka mixing ratio) from specific humidity. ' ' Args: ' SpecificHum : Specific Humidity in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ' ' Returns: ' Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b (solved for humidity ratio) ' ' Dim HumRatio as Variant On Error GoTo ErrHandler If (SpecificHum < 0 Or SpecificHum >= 1) Then MyMsgBox ("Specific humidity is outside range [0, 1[") GoTo ErrHandler End If HumRatio = SpecificHum / (1.0 - SpecificHum) GetHumRatioFromSpecificHum = max(HumRatio, MIN_HUM_RATIO) Exit Function ErrHandler: GetHumRatioFromSpecificHum = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Dry Air Calculations '****************************************************************************************************** Function GetDryAirEnthalpy(ByVal TDryBulb As Variant) As Variant ' ' Return dry-air enthalpy given dry-bulb temperature. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' ' Returns: ' Dry air enthalpy in Btu/lb [IP] or J/kg [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 28 ' On Error GoTo ErrHandler If (isIP()) Then GetDryAirEnthalpy = 0.24 * TDryBulb Else GetDryAirEnthalpy = 1006 * TDryBulb End If Exit Function ErrHandler: GetDryAirEnthalpy = CVErr(xlErrNA) End Function Function GetDryAirDensity(ByVal TDryBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return dry-air density given dry-bulb temperature and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Dry air density in lb/ft³ [IP] or kg/m³ [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' ' Notes: ' Eqn 14 for the perfect gas relationship for dry air. ' Eqn 1 for the universal gas constant. ' The factor 144 in IP is for the conversion of Psi = lb/in² to lb/ft². ' On Error GoTo ErrHandler If (isIP()) Then GetDryAirDensity = (144 * Pressure) / R_DA_IP / GetTRankineFromTFahrenheit(TDryBulb) Else GetDryAirDensity = Pressure / R_DA_SI / GetTKelvinFromTCelsius(TDryBulb) End If Exit Function ErrHandler: GetDryAirDensity = CVErr(xlErrNA) End Function Function GetDryAirVolume(ByVal TDryBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return dry-air volume given dry-bulb temperature and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Dry air volume in ft³/lb [IP] or in m³/kg [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' ' Notes: ' Eqn 14 for the perfect gas relationship for dry air. ' Eqn 1 for the universal gas constant. ' The factor 144 in IP is for the conversion of Psi = lb/in² to lb/ft². ' On Error GoTo ErrHandler If (isIP()) Then GetDryAirVolume = GetTRankineFromTFahrenheit(TDryBulb) * R_DA_IP / (144 * Pressure) Else: GetDryAirVolume = GetTKelvinFromTCelsius(TDryBulb) * R_DA_SI / Pressure End If Exit Function ErrHandler: GetDryAirVolume = CVErr(xlErrNA) End Function Function GetTDryBulbFromEnthalpyAndHumRatio(ByVal MoistAirEnthalpy As Variant, ByVal HumRatio As Variant) As Variant ' ' Return dry bulb temperature from enthalpy and humidity ratio. ' ' ' Args: ' MoistAirEnthalpy : Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ ' HumRatio : Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ' ' Returns: ' Dry-bulb temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 ' ' Notes: ' Based on the `GetMoistAirEnthalpy` function, rearranged for temperature. ' On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If If (isIP()) Then GetTDryBulbFromEnthalpyAndHumRatio = (MoistAirEnthalpy - 1061.0 * HumRatio) / (0.24 + 0.444 * HumRatio) Else: GetTDryBulbFromEnthalpyAndHumRatio = (MoistAirEnthalpy / 1000.0 - 2501.0 * HumRatio) / (1.006 + 1.86 * HumRatio) End If Exit Function ErrHandler: GetTDryBulbFromEnthalpyAndHumRatio = CVErr(xlErrNA) End Function Function GetHumRatioFromEnthalpyAndTDryBulb(ByVal MoistAirEnthalpy As Variant, ByVal TDryBulb As Variant) As Variant ' ' Return humidity ratio from enthalpy and dry-bulb temperature. ' ' ' Args: ' MoistAirEnthalpy : Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' ' Returns: ' Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 ' ' Notes: ' Based on the `GetMoistAirEnthalpy` function, rearranged for humidity ratio. ' On Error GoTo ErrHandler If (isIP()) Then GetHumRatioFromEnthalpyAndTDryBulb = (MoistAirEnthalpy - 0.24 * TDryBulb) / (1061.0 + 0.444 * TDryBulb) Else: GetHumRatioFromEnthalpyAndTDryBulb = (MoistAirEnthalpy / 1000.0 - 1.006 * TDryBulb) / (2501.0 + 1.86 * TDryBulb) End If Exit Function ErrHandler: GetHumRatioFromEnthalpyAndTDryBulb = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Saturated Air Calculations '****************************************************************************************************** Function GetSatVapPres(ByVal TDryBulb As Variant) As Variant ' ' Return saturation vapor pressure given dry-bulb temperature. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' ' Returns: ' Vapor pressure of saturated air in Psi [IP] or Pa [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 5 & 6 ' Important note: the ASHRAE formulae are defined above and below the freezing point but have ' a discontinuity at the freezing point. This is a small inaccuracy on ASHRAE's part: the formulae ' should be defined above and below the triple point of water (not the feezing point) in which case ' the discontinuity vanishes. It is essential to use the triple point of water otherwise function ' GetTDewPointFromVapPres, which inverts the present function, does not converge properly around ' the freezing point. ' Dim LnPws As Variant, T As Variant On Error GoTo ErrHandler If (isIP()) Then If (TDryBulb < -148 Or TDryBulb > 392) Then MyMsgBox ("Dry bulb temperature is outside range [-148, 392] °F") GoTo ErrHandler End If T = GetTRankineFromTFahrenheit(TDryBulb) If (TDryBulb <= TRIPLE_POINT_WATER_IP) Then LnPws = (-10214.165 / T - 4.8932428 - 0.0053765794 * T + 0.00000019202377 * T ^ 2 _ + 3.5575832E-10 * T ^ 3 - 9.0344688E-14 * T ^ 4 + 4.1635019 * Log(T)) Else LnPws = -10440.397 / T - 11.29465 - 0.027022355 * T + 0.00001289036 * T ^ 2 _ - 2.4780681E-09 * T ^ 3 + 6.5459673 * Log(T) End If Else If (TDryBulb < -100 Or TDryBulb > 200) Then MyMsgBox ("Dry bulb temperature is outside range [-100, 200] °C") GoTo ErrHandler End If T = GetTKelvinFromTCelsius(TDryBulb) If (TDryBulb <= TRIPLE_POINT_WATER_SI) Then LnPws = -5674.5359 / T + 6.3925247 - 0.009677843 * T + 0.00000062215701 * T ^ 2 _ + 2.0747825E-09 * T ^ 3 - 9.484024E-13 * T ^ 4 + 4.1635019 * Log(T) Else LnPws = -5800.2206 / T + 1.3914993 - 0.048640239 * T + 0.000041764768 * T ^ 2 _ - 0.000000014452093 * T ^ 3 + 6.5459673 * Log(T) End If End If GetSatVapPres = Exp(LnPws) Exit Function ErrHandler: GetSatVapPres = CVErr(xlErrNA) End Function Function GetSatHumRatio(ByVal TDryBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return humidity ratio of saturated air given dry-bulb temperature and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio of saturated air in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36, solved for W ' Dim SatVaporPres As Variant, SatHumRatio As Variant On Error GoTo ErrHandler SatVaporPres = GetSatVapPres(TDryBulb) SatHumRatio = 0.621945 * SatVaporPres / (Pressure - SatVaporPres) GetSatHumRatio = max(SatHumRatio, MIN_HUM_RATIO) Exit Function ErrHandler: GetSatHumRatio = CVErr(xlErrNA) End Function Function GetSatAirEnthalpy(ByVal TDryBulb As Variant, ByVal Pressure As Variant) As Variant ' ' Return saturated air enthalpy given dry-bulb temperature and pressure. ' ' Args: ' TDryBulb: Dry-bulb temperature in °F [IP] or °C [SI] ' Pressure: Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Saturated air enthalpy in Btu/lb [IP] or J/kg [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 ' On Error GoTo ErrHandler GetSatAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, GetSatHumRatio(TDryBulb, Pressure)) Exit Function ErrHandler: GetSatAirEnthalpy = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Moist Air Calculations '****************************************************************************************************** Function GetVaporPressureDeficit(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return Vapor pressure deficit given dry-bulb temperature, humidity ratio, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Vapor pressure deficit in Psi [IP] or Pa [SI] ' ' Reference: ' Oke (1987) eqn 2.13a ' Dim RelHum As Variant On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) GetVaporPressureDeficit = GetSatVapPres(TDryBulb) * (1 - RelHum) Exit Function ErrHandler: GetVaporPressureDeficit = CVErr(xlErrNA) End Function Function GetDegreeOfSaturation(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return the degree of saturation (i.e humidity ratio of the air / humidity ratio of the air at saturation ' at the same temperature and pressure) given dry-bulb temperature, humidity ratio, and atmospheric pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Degree of saturation in arbitrary unit ' ' Reference: ' ASHRAE Handbook - Fundamentals (2009) ch. 1 eqn 12 ' ' Notes: ' This definition is absent from the 2017 Handbook. Using 2009 version instead. ' Dim BoundedHumRatio As Variant On Error GoTo ErrHandler If HumRatio < 0 Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) GetDegreeOfSaturation = BoundedHumRatio / GetSatHumRatio(TDryBulb, Pressure) Exit Function ErrHandler: GetDegreeOfSaturation = CVErr(xlErrNA) End Function Function GetMoistAirEnthalpy(ByVal TDryBulb As Variant, ByVal HumRatio As Variant) As Variant ' ' Return moist air enthalpy given dry-bulb temperature and humidity ratio. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' ' Returns: ' Moist air enthalpy in Btu/lb [IP] or J/kg ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30 ' Dim BoundedHumRatio As Variant On Error GoTo ErrHandler If (HumRatio < 0) Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) If (isIP()) Then GetMoistAirEnthalpy = 0.24 * TDryBulb + BoundedHumRatio * (1061 + 0.444 * TDryBulb) Else GetMoistAirEnthalpy = (1.006 * TDryBulb + BoundedHumRatio * (2501 + 1.86 * TDryBulb)) * 1000 End If Exit Function ErrHandler: GetMoistAirEnthalpy = CVErr(xlErrNA) End Function Function GetMoistAirVolume(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return moist air specific volume given dry-bulb temperature, humidity ratio, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Specific volume of moist air in ft³/lb of dry air [IP] or in m³/kg of dry air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 ' ' Notes: ' In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 ' The factor 144 is for the conversion of Psi = lb/in² to lb/ft². ' Dim BoundedHumRatio As Variant On Error GoTo ErrHandler If (HumRatio < 0) Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) If (isIP()) Then GetMoistAirVolume = R_DA_IP * GetTRankineFromTFahrenheit(TDryBulb) * (1 + 1.607858 * BoundedHumRatio) / (144 * Pressure) Else GetMoistAirVolume = R_DA_SI * GetTKelvinFromTCelsius(TDryBulb) * (1 + 1.607858 * BoundedHumRatio) / Pressure End If Exit Function ErrHandler: GetMoistAirVolume = CVErr(xlErrNA) End Function Function GetTDryBulbFromMoistAirVolumeAndHumRatio(ByVal MoistAirVolume As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return dry-bulb temperature given moist air specific volume, humidity ratio, and pressure. ' ' Args: ' MoistAirVolume: Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Specific volume of moist air in ft³/lb of dry air [IP] or in m³/kg of dry air [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 ' ' Notes: ' In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 ' The factor 144 is for the conversion of Psi = lb/in² to lb/ft². ' Based on the `GetMoistAirVolume` function, rearranged for dry-bulb temperature. ' Dim BoundedHumRatio As Variant On Error GoTo ErrHandler If (HumRatio < 0) Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) If (isIP()) Then GetTDryBulbFromMoistAirVolumeAndHumRatio = GetTFahrenheitFromTRankine(MoistAirVolume * (144 * Pressure) / (R_DA_IP * (1 + 1.607858 * BoundedHumRatio))) Else GetTDryBulbFromMoistAirVolumeAndHumRatio = GetTCelsiusFromTKelvin(MoistAirVolume * Pressure / (R_DA_SI * (1 + 1.607858 * BoundedHumRatio))) End If Exit Function ErrHandler: GetTDryBulbFromMoistAirVolumeAndHumRatio = CVErr(xlErrNA) End Function Function GetMoistAirDensity(ByVal TDryBulb As Variant, ByVal HumRatio As Variant, ByVal Pressure As Variant) As Variant ' ' Return moist air density given humidity ratio, dry bulb temperature, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' HumRatio : Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' MoistAirDensity: Moist air density in lb/ft³ [IP] or kg/m³ [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 11 ' Dim MoistAirVolume As Variant, BoundedHumRatio As Variant On Error GoTo ErrHandler If (HumRatio < 0) Then MyMsgBox ("Humidity ratio is negative") GoTo ErrHandler End If BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO) MoistAirVolume = GetMoistAirVolume(TDryBulb, BoundedHumRatio, Pressure) GetMoistAirDensity = (1 + BoundedHumRatio) / MoistAirVolume Exit Function ErrHandler: GetMoistAirDensity = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Standard atmosphere '****************************************************************************************************** Function GetStandardAtmPressure(ByVal Altitude As Variant) As Variant ' ' Return standard atmosphere barometric pressure, given the elevation (altitude). ' ' Args: ' Altitude: Altitude in ft [IP] or m [SI] ' ' Returns: ' Standard atmosphere barometric pressure in Psi [IP] or Pa [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 3 ' On Error GoTo ErrHandler If (isIP()) Then GetStandardAtmPressure = 14.696 * (1 - 0.0000068754 * Altitude) ^ 5.2559 Else GetStandardAtmPressure = 101325 * (1 - 0.0000225577 * Altitude) ^ 5.2559 End If Exit Function ErrHandler: GetStandardAtmPressure = CVErr(xlErrNA) End Function Function GetStandardAtmTemperature(ByVal Altitude As Variant) As Variant ' ' Return standard atmosphere temperature, given the elevation (altitude). ' ' Args: ' Altitude: Altitude in ft ' ' Returns: ' Standard atmosphere dry-bulb temperature in °F [IP] or °C [SI] ' ' Reference: ' ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 4 ' On Error GoTo ErrHandler If (isIP()) Then GetStandardAtmTemperature = 59 - 0.0035662 * Altitude Else GetStandardAtmTemperature = 15 - 0.0065 * Altitude End If Exit Function ErrHandler: GetStandardAtmTemperature = CVErr(xlErrNA) End Function Function GetSeaLevelPressure(ByVal StationPressure As Variant, ByVal Altitude As Variant, ByVal TDryBulb As Variant) As Variant ' ' Return sea level pressure given dry-bulb temperature, altitude above sea level and pressure. ' ' Args: ' StationPressure : Observed station pressure in Psi [IP] or Pa [SI] ' Altitude: Altitude in ft [IP] or m [SI] ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' ' Returns: ' Sea level barometric pressure in Psi [IP] or Pa [SI] ' ' Reference: ' Hess SL, Introduction to theoretical meteorology, Holt Rinehart and Winston, NY 1959, ' ch. 6.5; Stull RB, Meteorology for scientists and engineers, 2nd edition, ' Brooks/Cole 2000, ch. 1. ' ' Notes: ' The standard procedure for the US is to use for TDryBulb the average ' of the current station temperature and the station temperature from 12 hours ago. ' ' Calculate average temperature in column of air, assuming a lapse rate ' of 6.5 °C/km Dim TColumn As Variant Dim H As Variant On Error GoTo ErrHandler If (isIP()) Then ' Calculate average temperature in column of air, assuming a lapse rate ' of 3.6 °F/1000ft TColumn = TDryBulb + 0.0036 * Altitude / 2 ' Determine the scale height H = 53.351 * GetTRankineFromTFahrenheit(TColumn) Else ' Calculate average temperature in column of air, assuming a lapse rate ' of 6.5 °C/km TColumn = TDryBulb + 0.0065 * Altitude / 2 ' Determine the scale height H = 287.055 * GetTKelvinFromTCelsius(TColumn) / 9.807 End If ' Calculate the sea level pressure GetSeaLevelPressure = StationPressure * Exp(Altitude / H) Exit Function ErrHandler: GetSeaLevelPressure = CVErr(xlErrNA) End Function Function GetStationPressure(ByVal SeaLevelPressure As Variant, ByVal Altitude As Variant, ByVal TDryBulb As Variant) As Variant ' ' Args: ' SeaLevelPressure : Sea level barometric pressure in Psi [IP] or Pa [SI] ' Altitude: Altitude in ft [IP] or m [SI] ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' ' Returns: ' Station pressure in Psi [IP] or Pa [SI] ' ' Reference: ' See 'GetSeaLevelPressure' ' ' Notes: ' This function is just the inverse of 'GetSeaLevelPressure'. ' On Error GoTo ErrHandler GetStationPressure = SeaLevelPressure / GetSeaLevelPressure(1, Altitude, TDryBulb) Exit Function ErrHandler: GetStationPressure = CVErr(xlErrNA) End Function '****************************************************************************************************** ' Functions to set all psychrometric values '****************************************************************************************************** Sub CalcPsychrometricsFromTWetBulb(ByVal TDryBulb As Variant, ByVal TWetBulb As Variant, ByVal Pressure As Variant, _ ByRef HumRatio As Variant, ByRef TDewPoint As Variant, ByRef RelHum As Variant, ByRef VapPres As Variant, _ ByRef MoistAirEnthalpy As Variant, ByRef MoistAirVolume As Variant, ByRef DegreeOfSaturation As Variant) ' ' Utility function to calculate humidity ratio, dew-point temperature, relative humidity, ' vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given ' dry-bulb temperature, wet-bulb temperature, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TWetBulb : Wet-bulb temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Dew-point temperature in °F [IP] or °C [SI] ' Relative humidity in range [0, 1] ' Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' Moist air enthalpy in Btu/lb [IP] or J/kg [SI] ' Specific volume of moist air in ft³/lb [IP] or in m³/kg [SI] ' Degree of saturation [unitless] ' HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) End Sub Sub CalcPsychrometricsFromTDewPoint(ByVal TDryBulb As Variant, ByVal TDewPoint As Variant, ByVal Pressure As Variant, _ ByRef HumRatio As Variant, ByRef TWetBulb As Variant, ByRef RelHum As Variant, ByRef VapPres As Variant, _ ByRef MoistAirEnthalpy As Variant, ByRef MoistAirVolume As Variant, ByRef DegreeOfSaturation As Variant) ' ' Utility function to calculate humidity ratio, wet-bulb temperature, relative humidity, ' vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given ' dry-bulb temperature, dew-point temperature, and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' TDewPoint : Dew-point temperature in °F [IP] or °C [SI] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Wet-bulb temperature in °F [IP] or °C [SI] ' Relative humidity in range [0, 1] ' Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' Moist air enthalpy in Btu/lb [IP] or J/kg [SI] ' Specific volume of moist air in ft³/lb [IP] or in m³/kg [SI] ' Degree of saturation [unitless] ' HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) End Sub Sub CalcPsychrometricsFromRelHum(ByVal TDryBulb As Variant, ByVal RelHum As Variant, ByVal Pressure As Variant, _ ByRef HumRatio As Variant, ByRef TWetBulb As Variant, ByRef TDewPoint As Variant, ByRef VapPres As Variant, _ ByRef MoistAirEnthalpy As Variant, ByRef MoistAirVolume As Variant, ByRef DegreeOfSaturation As Variant) ' ' Utility function to calculate humidity ratio, wet-bulb temperature, dew-point temperature, ' vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given ' dry-bulb temperature, relative humidity and pressure. ' ' Args: ' TDryBulb : Dry-bulb temperature in °F [IP] or °C [SI] ' RelHum : Relative humidity in range [0, 1] ' Pressure : Atmospheric pressure in Psi [IP] or Pa [SI] ' ' Returns: ' Humidity ratio in lb_H2O/lb_Air [IP] or kg_H2O/kg_Air [SI] ' Wet-bulb temperature in °F [IP] or °C [SI] ' Dew-point temperature in °F [IP] or °C [SI]. ' Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ' Moist air enthalpy in Btu/lb [IP] or J/kg [SI] ' Specific volume of moist air in ft³/lb [IP] or in m³/kg [SI] ' Degree of saturation [unitless] ' HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure) TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure) TDewPoint = GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure) VapPres = GetVapPresFromHumRatio(HumRatio, Pressure) MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, HumRatio) MoistAirVolume = GetMoistAirVolume(TDryBulb, HumRatio, Pressure) DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure) End Sub |
Javascript
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 | /** * PsychroLib (version 2.3.0) (https://github.com/psychrometrics/psychrolib) * Copyright (c) 2018 D. Thevenard and D. Meyer for the current library implementation * Copyright (c) 2017 ASHRAE Handbook — Fundamentals for ASHRAE equations and coefficients * Licensed under the MIT License. */ function Psychrometrics() { /** * Module overview * Contains functions for calculating thermodynamic properties of gas-vapor mixtures * and standard atmosphere suitable for most engineering, physical and meteorological * applications. * * Most of the functions are an implementation of the formulae found in the * 2017 ASHRAE Handbook - Fundamentals, in both International System (SI), * and Imperial (IP) units. Please refer to the information included in * each function for their respective reference. * * Example (e.g. Node.JS) * // Import the PsychroLib * var psychrolib = require('psychrolib.js') * // Set unit system * psychrolib.SetUnitSystem(psychrolib.SI) * // Calculate the dew point temperature for a dry bulb temperature of 25 C and a relative humidity of 80% * var TDewPoint = psychrolib.GetTDewPointFromRelHum(25.0, 0.80); * console.log('TDewPoint: %d', TDewPoint); * 21.3094 * * Copyright * - For the current library implementation * Copyright (c) 2018 D. Thevenard and D. Meyer. * - For equations and coefficients published ASHRAE Handbook — Fundamentals, Chapter 1 * Copyright (c) 2017 ASHRAE Handbook — Fundamentals (https://www.ashrae.org) * * License * MIT (https://github.com/psychrometrics/psychrolib/LICENSE.txt) * * Note from the Authors * We have made every effort to ensure that the code is adequate, however, we make no * representation with respect to its accuracy. Use at your own risk. Should you notice * an error, or if you have a suggestion, please notify us through GitHub at * https://github.com/psychrometrics/psychrolib/issues. */ // Standard functions var log = Math.log; var exp = Math.exp; var pow = Math.pow; var min = Math.min; var max = Math.max; var abs = Math.abs; /****************************************************************************************************** * Global constants *****************************************************************************************************/ var ZERO_FAHRENHEIT_AS_RANKINE = 459.67; // Zero degree Fahrenheit (°F) expressed as degree Rankine (°R). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. var ZERO_CELSIUS_AS_KELVIN = 273.15; // Zero degree Celsius (°C) expressed as Kelvin (K). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. var R_DA_IP = 53.350; // Universal gas constant for dry air (IP version) in ft lb_Force lb_DryAir⁻¹ R⁻¹. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. var R_DA_SI = 287.042; // Universal gas constant for dry air (SI version) in J kg_DryAir⁻¹ K⁻¹. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. var INVALID = -99999; // Invalid value (dimensionless). var MAX_ITER_COUNT = 100 // Maximum number of iterations before exiting while loops. var MIN_HUM_RATIO = 1e-7 // Minimum acceptable humidity ratio used/returned by any functions. // Any value above 0 or below the MIN_HUM_RATIO will be reset to this value. var FREEZING_POINT_WATER_IP = 32.0 // Freezing point of water in Fahrenheit. var FREEZING_POINT_WATER_SI = 0.0 // Freezing point of water in Celsius. var TRIPLE_POINT_WATER_IP = 32.018 // Triple point of water in Fahrenheit. var TRIPLE_POINT_WATER_SI = 0.01 // Triple point of water in Celsius. /****************************************************************************************************** * Helper functions *****************************************************************************************************/ // Systems of units (IP or SI) var PSYCHROLIB_UNITS = undefined; this.IP = 1; this.SI = 2; // Function to set the system of units // Note: this function *HAS TO BE CALLED* before the library can be used this.SetUnitSystem = function(UnitSystem) { if (UnitSystem != this.IP && UnitSystem != this.SI) { throw new Error('UnitSystem must be IP or SI'); } PSYCHROLIB_UNITS = UnitSystem; // Define tolerance of temperature calculations // The tolerance is the same in IP and SI if (PSYCHROLIB_UNITS == this.IP) PSYCHROLIB_TOLERANCE = 0.001 * 9. / 5.; else PSYCHROLIB_TOLERANCE = 0.001; } // Return system of units in use. this.GetUnitSystem = function() { return PSYCHROLIB_UNITS; } // Function to check if the current system of units is SI or IP // The function exits in error if the system of units is undefined this.isIP = function() { if (PSYCHROLIB_UNITS == this.IP) return true; else if (PSYCHROLIB_UNITS == this.SI) return false; else throw new Error("Unit system is not defined"); } /****************************************************************************************************** * Conversion between temperature units *****************************************************************************************************/ // Utility function to convert temperature to degree Rankine (°R) // given temperature in degree Fahrenheit (°F). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 this.GetTRankineFromTFahrenheit = function (T_F) { return T_F + ZERO_FAHRENHEIT_AS_RANKINE; } /* exact */ // Utility function to convert temperature to degree Fahrenheit (°F) // given temperature in degree Rankine (°R). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 this.GetTFahrenheitFromTRankine = function (T_R) { return T_R - ZERO_FAHRENHEIT_AS_RANKINE; } /* exact */ // Utility function to convert temperature to Kelvin (K) // given temperature in degree Celsius (°C). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 this.GetTKelvinFromTCelsius = function (T_C) { return T_C + ZERO_CELSIUS_AS_KELVIN; } /* exact */ // Utility function to convert temperature to degree Celsius (°C) // given temperature in Kelvin (K). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 this.GetTCelsiusFromTKelvin = function (T_K) { return T_K - ZERO_CELSIUS_AS_KELVIN; } /* exact */ /****************************************************************************************************** * Conversions between dew point, wet bulb, and relative humidity *****************************************************************************************************/ // Return wet-bulb temperature given dry-bulb temperature, dew-point temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetTWetBulbFromTDewPoint = function // (o) Wet bulb temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio; if (!(TDewPoint <= TDryBulb)) throw new Error("Dew point temperature is above dry bulb temperature"); HumRatio = this.GetHumRatioFromTDewPoint(TDewPoint, Pressure); return this.GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure); } // Return wet-bulb temperature given dry-bulb temperature, relative humidity, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetTWetBulbFromRelHum = function // (o) Wet bulb temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , RelHum // (i) Relative humidity [0-1] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio; if (!(RelHum >= 0. && RelHum <= 1.)) throw new Error("Relative humidity is outside range [0,1]"); HumRatio = this.GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure); return this.GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure); } // Return relative humidity given dry-bulb temperature and dew-point temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 22 this.GetRelHumFromTDewPoint = function // (o) Relative humidity [0-1] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] ) { var VapPres, SatVapPres; if (!(TDewPoint <= TDryBulb)) throw new Error("Dew point temperature is above dry bulb temperature"); VapPres = this.GetSatVapPres(TDewPoint); SatVapPres = this.GetSatVapPres(TDryBulb); return VapPres / SatVapPres; } // Return relative humidity given dry-bulb temperature, wet bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetRelHumFromTWetBulb = function // (o) Relative humidity [0-1] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio; if (!(TWetBulb <= TDryBulb)) throw new Error("Wet bulb temperature is above dry bulb temperature"); HumRatio = this.GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); return this.GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure); } // Return dew-point temperature given dry-bulb temperature and relative humidity. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetTDewPointFromRelHum = function // (o) Dew Point temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , RelHum // (i) Relative humidity [0-1] ) { var VapPres; if (!(RelHum >= 0. && RelHum <= 1.)) throw new Error("Relative humidity is outside range [0,1]"); VapPres = this.GetVapPresFromRelHum(TDryBulb, RelHum); return this.GetTDewPointFromVapPres(TDryBulb, VapPres); } // Return dew-point temperature given dry-bulb temperature, wet-bulb temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetTDewPointFromTWetBulb = function // (o) Dew Point temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio; if (!(TWetBulb <= TDryBulb)) throw new Error("Wet bulb temperature is above dry bulb temperature"); HumRatio = this.GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); return this.GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure); } /****************************************************************************************************** * Conversions between dew point, or relative humidity and vapor pressure *****************************************************************************************************/ // Return partial pressure of water vapor as a function of relative humidity and temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 this.GetVapPresFromRelHum = function // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , RelHum // (i) Relative humidity [0-1] ) { if (!(RelHum >= 0. && RelHum <= 1.)) throw new Error("Relative humidity is outside range [0,1]"); return RelHum * this.GetSatVapPres(TDryBulb); } // Return relative humidity given dry-bulb temperature and vapor pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 this.GetRelHumFromVapPres = function // (o) Relative humidity [0-1] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , VapPres // (i) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ) { if (!(VapPres >= 0.)) throw new Error("Partial pressure of water vapor in moist air is negative"); return VapPres / this.GetSatVapPres(TDryBulb); } // Helper function returning the derivative of the natural log of the saturation vapor pressure // as a function of dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 & 6 this.dLnPws_ = function // (o) Derivative of natural log of vapor pressure of saturated air in Psi [IP] or Pa [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { var dLnPws, T; if (this.isIP()) { T = this.GetTRankineFromTFahrenheit(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_IP) dLnPws = 1.0214165E+04 / pow(T, 2) - 5.3765794E-03 + 2 * 1.9202377E-07 * T + 3 * 3.5575832E-10 * pow(T, 2) - 4 * 9.0344688E-14 * pow(T, 3) + 4.1635019 / T; else dLnPws = 1.0440397E+04 / pow(T, 2) - 2.7022355E-02 + 2 * 1.2890360E-05 * T - 3 * 2.4780681E-09 * pow(T, 2) + 6.5459673 / T; } else { T = this.GetTKelvinFromTCelsius(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_SI) dLnPws = 5.6745359E+03 / pow(T, 2) - 9.677843E-03 + 2 * 6.2215701E-07 * T + 3 * 2.0747825E-09 * pow(T, 2) - 4 * 9.484024E-13 * pow(T, 3) + 4.1635019 / T; else dLnPws = 5.8002206E+03 / pow(T, 2) - 4.8640239E-02 + 2 * 4.1764768E-05 * T - 3 * 1.4452093E-08 * pow(T, 2) + 6.5459673 / T; } return dLnPws; } // Return dew-point temperature given dry-bulb temperature and vapor pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 and 6 // Notes: the dew point temperature is solved by inverting the equation giving water vapor pressure // at saturation from temperature rather than using the regressions provided // by ASHRAE (eqn. 37 and 38) which are much less accurate and have a // narrower range of validity. // The Newton-Raphson (NR) method is used on the logarithm of water vapour // pressure as a function of temperature, which is a very smooth function // Convergence is usually achieved in 3 to 5 iterations. // TDryBulb is not really needed here, just used for convenience. this.GetTDewPointFromVapPres = function // (o) Dew Point temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , VapPres // (i) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ) { // Bounds function of the system of units var BOUNDS // Domain of validity of the equations if (this.isIP()) { BOUNDS = [-148., 392.]; // Domain of validity of the equations } else { BOUNDS = [-100., 200.]; // Domain of validity of the equations } // Bounds outside which a solution cannot be found if (VapPres < this.GetSatVapPres(BOUNDS[0]) || VapPres > this.GetSatVapPres(BOUNDS[1])) throw new Error("Partial pressure of water vapor is outside range of validity of equations"); // We use NR to approximate the solution. // First guess var TDewPoint = TDryBulb; // Calculated value of dew point temperatures, solved for iteratively in °F [IP] or °C [SI] var lnVP = log(VapPres); // Natural logarithm of partial pressure of water vapor pressure in moist air var TDewPoint_iter; // Value of TDewPoint used in NR calculation var lnVP_iter; // Value of log of vapor water pressure used in NR calculation var index = 1; do { // Current point TDewPoint_iter = TDewPoint; lnVP_iter = log(this.GetSatVapPres(TDewPoint_iter)); // Derivative of function, calculated analytically var d_lnVP = this.dLnPws_(TDewPoint_iter); // New estimate, bounded by domain of validity of eqn. 5 and 6 TDewPoint = TDewPoint_iter - (lnVP_iter - lnVP) / d_lnVP; TDewPoint = max(TDewPoint, BOUNDS[0]); TDewPoint = min(TDewPoint, BOUNDS[1]); if (index > MAX_ITER_COUNT) throw new Error("Convergence not reached in GetTDewPointFromVapPres. Stopping."); index++; } while (abs(TDewPoint - TDewPoint_iter) > PSYCHROLIB_TOLERANCE); return min(TDewPoint, TDryBulb); } // Return vapor pressure given dew point temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 36 this.GetVapPresFromTDewPoint = function // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ( TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] ) { return this.GetSatVapPres(TDewPoint); } /****************************************************************************************************** * Conversions from wet-bulb temperature, dew-point temperature, or relative humidity to humidity ratio *****************************************************************************************************/ // Return wet-bulb temperature given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 solved for Tstar this.GetTWetBulbFromHumRatio = function // (o) Wet bulb temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { // Declarations var Wstar; var TDewPoint, TWetBulb, TWetBulbSup, TWetBulbInf, BoundedHumRatio; var index = 1; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); TDewPoint = this.GetTDewPointFromHumRatio(TDryBulb, BoundedHumRatio, Pressure); // Initial guesses TWetBulbSup = TDryBulb; TWetBulbInf = TDewPoint; TWetBulb = (TWetBulbInf + TWetBulbSup) / 2.; // Bisection loop while ((TWetBulbSup - TWetBulbInf) > PSYCHROLIB_TOLERANCE) { // Compute humidity ratio at temperature Tstar Wstar = this.GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); // Get new bounds if (Wstar > BoundedHumRatio) TWetBulbSup = TWetBulb; else TWetBulbInf = TWetBulb; // New guess of wet bulb temperature TWetBulb = (TWetBulbSup + TWetBulbInf) / 2.; if (index > MAX_ITER_COUNT) throw new Error("Convergence not reached in GetTWetBulbFromHumRatio. Stopping."); index++; } return TWetBulb; } // Return humidity ratio given dry-bulb temperature, wet-bulb temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 this.GetHumRatioFromTWetBulb = function // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var Wsstar; HumRatio = INVALID if (!(TWetBulb <= TDryBulb)) throw new Error("Wet bulb temperature is above dry bulb temperature"); Wsstar = this.GetSatHumRatio(TWetBulb, Pressure); if (this.isIP()) { if (TWetBulb >= FREEZING_POINT_WATER_IP) HumRatio = ((1093. - 0.556 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) / (1093. + 0.444 * TDryBulb - TWetBulb); else HumRatio = ((1220. - 0.04 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) / (1220. + 0.444 * TDryBulb - 0.48 * TWetBulb); } else { if (TWetBulb >= FREEZING_POINT_WATER_SI) HumRatio = ((2501. - 2.326 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) / (2501. + 1.86 * TDryBulb - 4.186 * TWetBulb); else HumRatio = ((2830. - 0.24 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) / (2830. + 1.86 * TDryBulb - 2.1 * TWetBulb); } // Validity check. return max(HumRatio, MIN_HUM_RATIO); } // Return humidity ratio given dry-bulb temperature, relative humidity, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetHumRatioFromRelHum = function // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature [F] , RelHum // (i) Relative humidity [0-1] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var VapPres; if (!(RelHum >= 0. && RelHum <= 1.)) throw new Error("Relative humidity is outside range [0,1]"); VapPres = this.GetVapPresFromRelHum(TDryBulb, RelHum); return this.GetHumRatioFromVapPres(VapPres, Pressure); } // Return relative humidity given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetRelHumFromHumRatio = function // (o) Relative humidity [0-1] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var VapPres; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); VapPres = this.GetVapPresFromHumRatio(HumRatio, Pressure); return this.GetRelHumFromVapPres(TDryBulb, VapPres); } // Return humidity ratio given dew-point temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetHumRatioFromTDewPoint = function // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var VapPres; VapPres = this.GetSatVapPres(TDewPoint); return this.GetHumRatioFromVapPres(VapPres, Pressure); } // Return dew-point temperature given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetTDewPointFromHumRatio = function // (o) Dew Point temperature in °F [IP] or °C [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var VapPres; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); VapPres = this.GetVapPresFromHumRatio(HumRatio, Pressure); return this.GetTDewPointFromVapPres(TDryBulb, VapPres); } /****************************************************************************************************** * Conversions between humidity ratio and vapor pressure *****************************************************************************************************/ // Return humidity ratio given water vapor pressure and atmospheric pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 this.GetHumRatioFromVapPres = function // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( VapPres // (i) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio; if (!(VapPres >= 0.)) throw new Error("Partial pressure of water vapor in moist air is negative"); HumRatio = 0.621945 * VapPres / (Pressure - VapPres); // Validity check. return max(HumRatio, MIN_HUM_RATIO); } // Return vapor pressure given humidity ratio and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 solved for pw this.GetVapPresFromHumRatio = function // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ( HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var VapPres, BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); VapPres = Pressure * BoundedHumRatio / (0.621945 + BoundedHumRatio); return VapPres; } /****************************************************************************************************** * Conversions between humidity ratio and specific humidity *****************************************************************************************************/ // Return the specific humidity from humidity ratio (aka mixing ratio) // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b this.GetSpecificHumFromHumRatio = function // (o) Specific humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( HumRatio // (i) Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); return BoundedHumRatio / (1.0 + BoundedHumRatio); } // Return the humidity ratio (aka mixing ratio) from specific humidity // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b (solved for humidity ratio) this.GetHumRatioFromSpecificHum = function // (o) Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] ( SpecificHum // (i) Specific humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ) { var HumRatio; if (!(SpecificHum >= 0.0 && SpecificHum < 1.0)) throw new Error("Specific humidity is outside range [0, 1["); HumRatio = SpecificHum / (1.0 - SpecificHum); // Validity check return max(HumRatio, MIN_HUM_RATIO); } /****************************************************************************************************** * Dry Air Calculations *****************************************************************************************************/ // Return dry-air enthalpy given dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 28 this.GetDryAirEnthalpy = function // (o) Dry air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { if (this.isIP()) return 0.240 * TDryBulb; else return 1006. * TDryBulb; } // Return dry-air density given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 // Notes: eqn 14 for the perfect gas relationship for dry air. // Eqn 1 for the universal gas constant. // The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². this.GetDryAirDensity = function // (o) Dry air density in lb ft⁻³ [IP] or kg m⁻³ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { if (this.isIP()) return (144. * Pressure) / R_DA_IP / this.GetTRankineFromTFahrenheit(TDryBulb); else return Pressure / R_DA_SI / this.GetTKelvinFromTCelsius(TDryBulb); } // Return dry-air volume given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. // Notes: eqn 14 for the perfect gas relationship for dry air. // Eqn 1 for the universal gas constant. // The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². this.GetDryAirVolume = function // (o) Dry air volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { if (this.isIP()) return R_DA_IP * this.GetTRankineFromTFahrenheit(TDryBulb) / (144. * Pressure); else return R_DA_SI * this.GetTKelvinFromTCelsius(TDryBulb) / Pressure; } // Return dry bulb temperature from enthalpy and humidity ratio. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. // Notes: based on the `GetMoistAirEnthalpy` function, rearranged for temperature. this.GetTDryBulbFromEnthalpyAndHumRatio = function // (o) Dry-bulb temperature in °F [IP] or °C [SI] ( MoistAirEnthalpy // (i) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (this.isIP()) return (MoistAirEnthalpy - 1061.0 * BoundedHumRatio) / (0.240 + 0.444 * BoundedHumRatio); else return (MoistAirEnthalpy / 1000.0 - 2501.0 * BoundedHumRatio) / (1.006 + 1.86 * BoundedHumRatio); } // Return humidity ratio from enthalpy and dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. // Notes: based on the `GetMoistAirEnthalpy` function, rearranged for humidity ratio. this.GetHumRatioFromEnthalpyAndTDryBulb = function // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻ ( MoistAirEnthalpy // (i) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ , TDryBulb // (i) Dry-bulb temperature in °F [IP] or °C [SI] ) { var HumRatio; if (this.isIP()) HumRatio = (MoistAirEnthalpy - 0.240 * TDryBulb) / (1061.0 + 0.444 * TDryBulb); else HumRatio = (MoistAirEnthalpy / 1000.0 - 1.006 * TDryBulb) / (2501.0 + 1.86 * TDryBulb); // Validity check. return max(HumRatio, MIN_HUM_RATIO); } /****************************************************************************************************** * Saturated Air Calculations *****************************************************************************************************/ // Return saturation vapor pressure given dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 & 6 // Important note: the ASHRAE formulae are defined above and below the freezing point but have // a discontinuity at the freezing point. This is a small inaccuracy on ASHRAE's part: the formulae // should be defined above and below the triple point of water (not the feezing point) in which case // the discontinuity vanishes. It is essential to use the triple point of water otherwise function // GetTDewPointFromVapPres, which inverts the present function, does not converge properly around // the freezing point. this.GetSatVapPres = function // (o) Vapor Pressure of saturated air in Psi [IP] or Pa [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { var LnPws, T; if (this.isIP()) { if (!(TDryBulb >= -148. && TDryBulb <= 392.)) throw new Error("Dry bulb temperature is outside range [-148, 392]"); T = this.GetTRankineFromTFahrenheit(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_IP) LnPws = (-1.0214165E+04 / T - 4.8932428 - 5.3765794E-03 * T + 1.9202377E-07 * T * T + 3.5575832E-10 * pow(T, 3) - 9.0344688E-14 * pow(T, 4) + 4.1635019 * log(T)); else LnPws = -1.0440397E+04 / T - 1.1294650E+01 - 2.7022355E-02 * T + 1.2890360E-05 * T * T - 2.4780681E-09 * pow(T, 3) + 6.5459673 * log(T); } else { if (!(TDryBulb >= -100. && TDryBulb <= 200.)) throw new Error("Dry bulb temperature is outside range [-100, 200]"); T = this.GetTKelvinFromTCelsius(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_SI) LnPws = -5.6745359E+03 / T + 6.3925247 - 9.677843E-03 * T + 6.2215701E-07 * T * T + 2.0747825E-09 * pow(T, 3) - 9.484024E-13 * pow(T, 4) + 4.1635019 * log(T); else LnPws = -5.8002206E+03 / T + 1.3914993 - 4.8640239E-02 * T + 4.1764768E-05 * T * T - 1.4452093E-08 * pow(T, 3) + 6.5459673 * log(T); } return exp(LnPws); } // Return humidity ratio of saturated air given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36, solved for W this.GetSatHumRatio = function // (o) Humidity ratio of saturated air in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var SatVaporPres, SatHumRatio; SatVaporPres = this.GetSatVapPres(TDryBulb); SatHumRatio = 0.621945 * SatVaporPres / (Pressure - SatVaporPres); // Validity check. return max(SatHumRatio, MIN_HUM_RATIO); } // Return saturated air enthalpy given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 this.GetSatAirEnthalpy = function // (o) Saturated air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { return this.GetMoistAirEnthalpy(TDryBulb, this.GetSatHumRatio(TDryBulb, Pressure)); } /****************************************************************************************************** * Moist Air Calculations *****************************************************************************************************/ // Return Vapor pressure deficit given dry-bulb temperature, humidity ratio, and pressure. // Reference: see Oke (1987) eqn. 2.13a this.GetVaporPressureDeficit = function // (o) Vapor pressure deficit in Psi [IP] or Pa [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var RelHum; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); RelHum = this.GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure); return this.GetSatVapPres(TDryBulb) * (1. - RelHum); } // Return the degree of saturation (i.e humidity ratio of the air / humidity ratio of the air at saturation // at the same temperature and pressure) given dry-bulb temperature, humidity ratio, and atmospheric pressure. // Reference: ASHRAE Handbook - Fundamentals (2009) ch. 1 eqn. 12 // Notes: the definition is absent from the 2017 Handbook this.GetDegreeOfSaturation = function // (o) Degree of saturation (unitless) ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); return BoundedHumRatio / this.GetSatHumRatio(TDryBulb, Pressure); } // Return moist air enthalpy given dry-bulb temperature and humidity ratio. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 30 this.GetMoistAirEnthalpy = function // (o) Moist Air Enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (this.isIP()) return 0.240 * TDryBulb + BoundedHumRatio * (1061. + 0.444 * TDryBulb); else return (1.006 * TDryBulb + BoundedHumRatio * (2501. + 1.86 * TDryBulb)) * 1000.; } // Return moist air specific volume given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 26 // Notes: in IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26. // The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². this.GetMoistAirVolume = function // (o) Specific Volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (this.isIP()) return R_DA_IP * this.GetTRankineFromTFahrenheit(TDryBulb) * (1. + 1.607858 * BoundedHumRatio) / (144. * Pressure); else return R_DA_SI * this.GetTKelvinFromTCelsius(TDryBulb) * (1. + 1.607858 * BoundedHumRatio) / Pressure; } // Return dry-bulb temperature given moist air specific volume, humidity ratio, and pressure. // Reference: // ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 // Notes: // In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 // The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². // Based on the `GetMoistAirVolume` function, rearranged for dry-bulb temperature. this.GetTDryBulbFromMoistAirVolumeAndHumRatio = function // (o) Dry-bulb temperature in °F [IP] or °C [SI] ( MoistAirVolume // (i) Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (this.isIP()) return this.GetTFahrenheitFromTRankine(MoistAirVolume * (144 * Pressure) / (R_DA_IP * (1 + 1.607858 * BoundedHumRatio))); else return this.GetTCelsiusFromTKelvin(MoistAirVolume * Pressure / (R_DA_SI * (1 + 1.607858 * BoundedHumRatio))); } // Return moist air density given humidity ratio, dry bulb temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 11 this.GetMoistAirDensity = function // (o) Moist air density in lb ft⁻³ [IP] or kg m⁻³ [SI] ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var BoundedHumRatio; if (!(HumRatio >= 0.)) throw new Error("Humidity ratio is negative"); BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); return (1. + BoundedHumRatio) / this.GetMoistAirVolume(TDryBulb, BoundedHumRatio, Pressure); } /****************************************************************************************************** * Standard atmosphere *****************************************************************************************************/ // Return standard atmosphere barometric pressure, given the elevation (altitude). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 3 this.GetStandardAtmPressure = function // (o) Standard atmosphere barometric pressure in Psi [IP] or Pa [SI] ( Altitude // (i) Altitude in ft [IP] or m [SI] ) { var Pressure; if (this.isIP()) Pressure = 14.696 * pow(1. - 6.8754e-06 * Altitude, 5.2559); else Pressure = 101325.* pow(1. - 2.25577e-05 * Altitude, 5.2559); return Pressure; } // Return standard atmosphere temperature, given the elevation (altitude). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 4 this.GetStandardAtmTemperature = function // (o) Standard atmosphere dry bulb temperature in °F [IP] or °C [SI] ( Altitude // (i) Altitude in ft [IP] or m [SI] ) { var Temperature; if (this.isIP()) Temperature = 59. - 0.00356620 * Altitude; else Temperature = 15. - 0.0065 * Altitude; return Temperature; } // Return sea level pressure given dry-bulb temperature, altitude above sea level and pressure. // Reference: Hess SL, Introduction to theoretical meteorology, Holt Rinehart and Winston, NY 1959, // ch. 6.5; Stull RB, Meteorology for scientists and engineers, 2nd edition, // Brooks/Cole 2000, ch. 1. // Notes: the standard procedure for the US is to use for TDryBulb the average // of the current station temperature and the station temperature from 12 hours ago. this.GetSeaLevelPressure = function // (o) Sea level barometric pressure in Psi [IP] or Pa [SI] ( StnPressure // (i) Observed station pressure in Psi [IP] or Pa [SI] , Altitude // (i) Altitude above sea level in ft [IP] or m [SI] , TDryBulb // (i) Dry bulb temperature ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] ) { var TColumn, H; if (this.isIP()) { // Calculate average temperature in column of air, assuming a lapse rate // of 3.6 °F/1000ft TColumn = TDryBulb + 0.0036 * Altitude / 2.; // Determine the scale height H = 53.351 * this.GetTRankineFromTFahrenheit(TColumn); } else { // Calculate average temperature in column of air, assuming a lapse rate // of 6.5 °C/km TColumn = TDryBulb + 0.0065 * Altitude / 2.; // Determine the scale height H = 287.055 * this.GetTKelvinFromTCelsius(TColumn) / 9.807; } // Calculate the sea level pressure var SeaLevelPressure = StnPressure * exp(Altitude / H); return SeaLevelPressure; } // Return station pressure from sea level pressure // Reference: see 'GetSeaLevelPressure' // Notes: this function is just the inverse of 'GetSeaLevelPressure'. this.GetStationPressure = function // (o) Station pressure in Psi [IP] or Pa [SI] ( SeaLevelPressure // (i) Sea level barometric pressure in Psi [IP] or Pa [SI] , Altitude // (i) Altitude above sea level in ft [IP] or m [SI] , TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { return SeaLevelPressure / this.GetSeaLevelPressure(1., Altitude, TDryBulb); } /****************************************************************************************************** * Functions to set all psychrometric values *****************************************************************************************************/ // Utility function to calculate humidity ratio, dew-point temperature, relative humidity, // vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given // dry-bulb temperature, wet-bulb temperature, and pressure. this.CalcPsychrometricsFromTWetBulb = function /** * HumRatio // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] * TDewPoint // (o) Dew point temperature in °F [IP] or °C [SI] * RelHum // (o) Relative humidity [0-1] * VapPres // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] * MoistAirEnthalpy // (o) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] * MoistAirVolume // (o) Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] * DegreeOfSaturation // (o) Degree of saturation [unitless] */ ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio = this.GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); var TDewPoint = this.GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure); var RelHum = this.GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure); var VapPres = this.GetVapPresFromHumRatio(HumRatio, Pressure); var MoistAirEnthalpy = this.GetMoistAirEnthalpy(TDryBulb, HumRatio); var MoistAirVolume = this.GetMoistAirVolume(TDryBulb, HumRatio, Pressure); var DegreeOfSaturation = this.GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure); return [HumRatio, TDewPoint, RelHum, VapPres, MoistAirEnthalpy, MoistAirVolume, DegreeOfSaturation]; } // Utility function to calculate humidity ratio, wet-bulb temperature, relative humidity, // vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given // dry-bulb temperature, dew-point temperature, and pressure. this.CalcPsychrometricsFromTDewPoint = function /** * HumRatio // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] * TWetBulb // (o) Wet bulb temperature in °F [IP] or °C [SI] * RelHum // (o) Relative humidity [0-1] * VapPres // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] * MoistAirEnthalpy // (o) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] * MoistAirVolume // (o) Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] * DegreeOfSaturation // (o) Degree of saturation [unitless] */ ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio = this.GetHumRatioFromTDewPoint(TDewPoint, Pressure); var TWetBulb = this.GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure); var RelHum = this.GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure); var VapPres = this.GetVapPresFromHumRatio(HumRatio, Pressure); var MoistAirEnthalpy = this.GetMoistAirEnthalpy(TDryBulb, HumRatio); var MoistAirVolume = this.GetMoistAirVolume(TDryBulb, HumRatio, Pressure); var DegreeOfSaturation = this.GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure); return [HumRatio, TWetBulb, RelHum, VapPres, MoistAirEnthalpy, MoistAirVolume, DegreeOfSaturation]; } // Utility function to calculate humidity ratio, wet-bulb temperature, dew-point temperature, // vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given // dry-bulb temperature, relative humidity and pressure. this.CalcPsychrometricsFromRelHum = function /** * HumRatio // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] * TWetBulb // (o) Wet bulb temperature in °F [IP] or °C [SI] * TDewPoint // (o) Dew point temperature in °F [IP] or °C [SI] * VapPres // (o) Partial pressure of water vapor in moist air [Psi] * MoistAirEnthalpy // (o) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] * MoistAirVolume // (o) Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] * DegreeOfSaturation // (o) Degree of saturation [unitless] */ ( TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , RelHum // (i) Relative humidity [0-1] , Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { var HumRatio = this.GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure); var TWetBulb = this.GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure); var TDewPoint = this.GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure); var VapPres = this.GetVapPresFromHumRatio(HumRatio, Pressure); var MoistAirEnthalpy = this.GetMoistAirEnthalpy(TDryBulb, HumRatio); var MoistAirVolume = this.GetMoistAirVolume(TDryBulb, HumRatio, Pressure); var DegreeOfSaturation = this.GetDegreeOfSaturation(TDryBulb, HumRatio, Pressure); return [HumRatio, TWetBulb, TDewPoint, VapPres, MoistAirEnthalpy, MoistAirVolume, DegreeOfSaturation]; } } // https://github.com/umdjs/umd (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else if (typeof module === 'object' && module.exports) { // Node. Does not work with strict CommonJS, but // only CommonJS-like environments that support module.exports, // like Node. module.exports = factory(); } else { // Browser globals (root is window) root.psychrolib = factory(); } }(typeof self !== 'undefined' ? self : this, function () { return new Psychrometrics(); })); |
C
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 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 | /** * PsychroLib (version 2.3.0) (https://github.com/psychrometrics/psychrolib) * Copyright (c) 2018 D. Thevenard and D. Meyer for the current library implementation * Copyright (c) 2017 ASHRAE Handbook — Fundamentals for ASHRAE equations and coefficients * Licensed under the MIT License. * * Module overview * Contains functions for calculating thermodynamic properties of gas-vapor mixtures * and standard atmosphere suitable for most engineering, physical and meteorological * applications. * * Most of the functions are an implementation of the formulae found in the * 2017 ASHRAE Handbook - Fundamentals, in both International System (SI), * and Imperial (IP) units. Please refer to the information included in * each function for their respective reference. * * Example * #include "psychrolib.h" * // Set the unit system, for example to SI (can be either 'SI' or 'IP') * SetUnitSystem(SI); * // Calculate the dew point temperature for a dry bulb temperature of 25 C and a relative humidity of 80% * double TDewPoint = GetTDewPointFromRelHum(25.0, 0.80); * printf("%lg", TDewPoint); * 21.3094 * * Copyright * - For the current library implementation * Copyright (c) 2018 D. Thevenard and D. Meyer. * - For equations and coefficients published ASHRAE Handbook — Fundamentals, Chapter 1 * Copyright (c) 2017 ASHRAE Handbook — Fundamentals (https://www.ashrae.org) * * License * MIT (https://github.com/psychrometrics/psychrolib/LICENSE.txt) * * Note from the Authors * We have made every effort to ensure that the code is adequate, however, we make no * representation with respect to its accuracy. Use at your own risk. Should you notice * an error, or if you have a suggestion, please notify us through GitHub at * https://github.com/psychrometrics/psychrolib/issues. */ // Standard C header files #include <float.h> #include <math.h> #include <stdio.h> #include <stdlib.h> // Header specific to this file #include "psychrolib.h" /****************************************************************************************************** * Global constants *****************************************************************************************************/ # define ZERO_FAHRENHEIT_AS_RANKINE 459.67 // Zero degree Fahrenheit (°F) expressed as degree Rankine (°R). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. # define ZERO_CELSIUS_AS_KELVIN 273.15 // Zero degree Celsius (°C) expressed as Kelvin (K). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 39. #define R_DA_IP 53.350 // Universal gas constant for dry air (IP version) in ft∙lbf/lb_da/R. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. #define R_DA_SI 287.042 // Universal gas constant for dry air (SI version) in J/kg_da/K. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1. #define INVALID -99999 // Invalid value. #define MAX_ITER_COUNT 100 // Maximum number of iterations before exiting while loops. #define MIN_HUM_RATIO 1e-7 // Minimum acceptable humidity ratio used/returned by any functions. // Any value above 0 or below the MIN_HUM_RATIO will be reset to this value. #define FREEZING_POINT_WATER_IP 32.0 // Freezing point of water in Fahrenheit. #define FREEZING_POINT_WATER_SI 0.0 // Freezing point of water in Celsius. #define TRIPLE_POINT_WATER_IP 32.018 // Triple point of water in Fahrenheit. #define TRIPLE_POINT_WATER_SI 0.01 // Triple point of water in Celsius. /****************************************************************************************************** * Helper functions *****************************************************************************************************/ #define ASSERT(condition, msg) \ if (! (condition)) \ { \ Assert(msg, __FILE__, __LINE__); \ } // Function called if an assertion fails // Replace this function with your own function for better error processing void Assert ( char *Msg // (i) message to print to screen , char *FileName // (i) name of file in which error occurred , int LineNo // (i) number of line in which error occurred ) { printf("Assert failed in file %s at line %d:\n", FileName, LineNo); printf("%s\n", Msg); printf("Aborting program..."); printf("\a"); exit(1); } // Min and max macros (in case they are not defined) #ifndef min #define min(a,b) (((a) < (b)) ? (a) : (b)) #endif #ifndef max #define max(a,b) (((a) > (b)) ? (a) : (b)) #endif // Systems of units (IP or SI) static enum UnitSystem PSYCHROLIB_UNITS = UNDEFINED; // Tolerance of temperature calculations static double PSYCHROLIB_TOLERANCE = 1.; // Set the system of units to use (SI or IP). // Note: this function *HAS TO BE CALLED* before the library can be used void SetUnitSystem ( enum UnitSystem Units // (i) System of units (IP or SI) ) { PSYCHROLIB_UNITS = Units; // Define tolerance on temperature calculations // The tolerance is the same in IP and SI if (PSYCHROLIB_UNITS == IP) PSYCHROLIB_TOLERANCE = 0.001 * 9. / 5.; else PSYCHROLIB_TOLERANCE = 0.001; } // Return system of units in use. enum UnitSystem GetUnitSystem // (o) System of units (SI or IP) ( ) { return PSYCHROLIB_UNITS; } // Check whether the system in use is IP or SI. // The function exits in error if the system of units is undefined int isIP // (o) 1 if IP, 0 if SI, error otherwise ( ) { if (PSYCHROLIB_UNITS == IP) return 1; else if (PSYCHROLIB_UNITS == SI) return 0; else { printf("The system of units has not been defined"); exit(1); } } /****************************************************************************************************** * Conversion between temperature units *****************************************************************************************************/ // Utility function to convert temperature to degree Rankine (°R) // given temperature in degree Fahrenheit (°F). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 double GetTRankineFromTFahrenheit(double T_F) { return T_F + ZERO_FAHRENHEIT_AS_RANKINE; } /* exact */ // Utility function to convert temperature to degree Fahrenheit (°F) // given temperature in degree Rankine (°R). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 double GetTFahrenheitFromTRankine(double T_R) { return T_R - ZERO_FAHRENHEIT_AS_RANKINE; } /* exact */ // Utility function to convert temperature to Kelvin (K) // given temperature in degree Celsius (°C). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 double GetTKelvinFromTCelsius(double T_C) { return T_C + ZERO_CELSIUS_AS_KELVIN; } /* exact */ // Utility function to convert temperature to degree Celsius (°C) // given temperature in Kelvin (K). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 section 3 double GetTCelsiusFromTKelvin(double T_K) { return T_K - ZERO_CELSIUS_AS_KELVIN; } /* exact */ /****************************************************************************************************** * Conversions between dew point, wet bulb, and relative humidity *****************************************************************************************************/ // Return wet-bulb temperature given dry-bulb temperature, dew-point temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetTWetBulbFromTDewPoint // (o) Wet bulb temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double HumRatio; ASSERT (TDewPoint <= TDryBulb, "Dew point temperature is above dry bulb temperature") HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure); return GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure); } // Return wet-bulb temperature given dry-bulb temperature, relative humidity, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetTWetBulbFromRelHum // (o) Wet bulb temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double RelHum // (i) Relative humidity [0-1] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double HumRatio; ASSERT (RelHum >= 0 && RelHum <= 1, "Relative humidity is outside range [0,1]") HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure); return GetTWetBulbFromHumRatio(TDryBulb, HumRatio, Pressure); } // Return relative humidity given dry-bulb temperature and dew-point temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 22 double GetRelHumFromTDewPoint // (o) Relative humidity [0-1] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] ) { double VapPres, SatVapPres; ASSERT (TDewPoint <= TDryBulb, "Dew point temperature is above dry bulb temperature") VapPres = GetSatVapPres(TDewPoint); SatVapPres = GetSatVapPres(TDryBulb); return VapPres/SatVapPres; } // Return relative humidity given dry-bulb temperature, wet bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetRelHumFromTWetBulb // (o) Relative humidity [0-1] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double HumRatio; ASSERT (TWetBulb <= TDryBulb, "Wet bulb temperature is above dry bulb temperature") HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); return GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure); } // Return dew-point temperature given dry-bulb temperature and relative humidity. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetTDewPointFromRelHum // (o) Dew Point temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double RelHum // (i) Relative humidity [0-1] ) { double VapPres; ASSERT (RelHum >= 0 && RelHum <= 1, "Relative humidity is outside range [0,1]") VapPres = GetVapPresFromRelHum(TDryBulb, RelHum); return GetTDewPointFromVapPres(TDryBulb, VapPres); } // Return dew-point temperature given dry-bulb temperature, wet-bulb temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetTDewPointFromTWetBulb // (o) Dew Point temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double HumRatio; ASSERT (TWetBulb <= TDryBulb, "Wet bulb temperature is above dry bulb temperature") HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); return GetTDewPointFromHumRatio(TDryBulb, HumRatio, Pressure); } /****************************************************************************************************** * Conversions between dew point, or relative humidity and vapor pressure *****************************************************************************************************/ // Return partial pressure of water vapor as a function of relative humidity and temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 double GetVapPresFromRelHum // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double RelHum // (i) Relative humidity [0-1] ) { ASSERT (RelHum >= 0. && RelHum <= 1., "Relative humidity is outside range [0,1]") return RelHum*GetSatVapPres(TDryBulb); } // Return relative humidity given dry-bulb temperature and vapor pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 12, 22 double GetRelHumFromVapPres // (o) Relative humidity [0-1] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double VapPres // (i) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ) { ASSERT (VapPres >= 0., "Partial pressure of water vapor in moist air is negative") return VapPres/GetSatVapPres(TDryBulb); } // Helper function returning the derivative of the natural log of the saturation vapor pressure // as a function of dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 & 6 double dLnPws_ // (o) Derivative of natural log of vapor pressure of saturated air in Psi [IP] or Pa [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { double dLnPws, T; if (isIP()) { T = GetTRankineFromTFahrenheit(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_IP) dLnPws = 1.0214165E+04 / pow(T, 2) - 5.3765794E-03 + 2 * 1.9202377E-07 * T + 3 * 3.5575832E-10 * pow(T, 2) - 4 * 9.0344688E-14 * pow(T, 3) + 4.1635019 / T; else dLnPws = 1.0440397E+04 / pow(T, 2) - 2.7022355E-02 + 2 * 1.2890360E-05 * T - 3 * 2.4780681E-09 * pow(T, 2) + 6.5459673 / T; } else { T = GetTKelvinFromTCelsius(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_SI) dLnPws = 5.6745359E+03 / pow(T, 2) - 9.677843E-03 + 2 * 6.2215701E-07 * T + 3 * 2.0747825E-09 * pow(T, 2) - 4 * 9.484024E-13 * pow(T, 3) + 4.1635019 / T; else dLnPws = 5.8002206E+03 / pow(T, 2) - 4.8640239E-02 + 2 * 4.1764768E-05 * T - 3 * 1.4452093E-08 * pow(T, 2) + 6.5459673 / T; } return dLnPws; } // Return dew-point temperature given dry-bulb temperature and vapor pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 and 6 // Notes: the dew point temperature is solved by inverting the equation giving water vapor pressure // at saturation from temperature rather than using the regressions provided // by ASHRAE (eqn. 37 and 38) which are much less accurate and have a // narrower range of validity. // The Newton-Raphson (NR) method is used on the logarithm of water vapour // pressure as a function of temperature, which is a very smooth function // Convergence is usually achieved in 3 to 5 iterations. // TDryBulb is not really needed here, just used for convenience. double GetTDewPointFromVapPres // (o) Dew Point temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double VapPres // (i) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ) { // Bounds function of the system of units double BOUNDS[2]; // Domain of validity of the equations if (isIP()) { BOUNDS[0] = -148.; BOUNDS[1] = 392.; } else { BOUNDS[0] = -100.; BOUNDS[1] = 200.; } // Bounds outside which a solution cannot be found ASSERT (VapPres >= GetSatVapPres(BOUNDS[0]) && VapPres <= GetSatVapPres(BOUNDS[1]), "Partial pressure of water vapor is outside range of validity of equations") // We use NR to approximate the solution. // First guess double TDewPoint = TDryBulb; // Calculated value of dew point temperatures, solved for iteratively in °F [IP] or °C [SI] double lnVP = log(VapPres); // Natural logarithm of partial pressure of water vapor pressure in moist air double TDewPoint_iter; // Value of TDewPoint used in NR calculation double lnVP_iter; // Value of log of vapor water pressure used in NR calculation int index = 1; do { TDewPoint_iter = TDewPoint; // TDewPoint used in NR calculation lnVP_iter = log(GetSatVapPres(TDewPoint_iter)); // Derivative of function, calculated analytically double d_lnVP = dLnPws_(TDewPoint_iter); // New estimate, bounded by domain of validity of eqn. 5 and 6 TDewPoint = TDewPoint_iter - (lnVP_iter - lnVP) / d_lnVP; TDewPoint = max(TDewPoint, BOUNDS[0]); TDewPoint = min(TDewPoint, BOUNDS[1]); ASSERT (index <= MAX_ITER_COUNT, "Convergence not reached in GetTDewPointFromVapPres. Stopping.") index++; } while (fabs(TDewPoint - TDewPoint_iter) > PSYCHROLIB_TOLERANCE); return min(TDewPoint, TDryBulb); } // Return vapor pressure given dew point temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 36 double GetVapPresFromTDewPoint // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ( double TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] ) { return GetSatVapPres(TDewPoint); } /****************************************************************************************************** * Conversions from wet-bulb temperature, dew-point temperature, or relative humidity to humidity ratio *****************************************************************************************************/ // Return wet-bulb temperature given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 solved for Tstar double GetTWetBulbFromHumRatio // (o) Wet bulb temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { // Declarations double Wstar; double TDewPoint, TWetBulb, TWetBulbSup, TWetBulbInf, BoundedHumRatio; int index = 1; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); TDewPoint = GetTDewPointFromHumRatio(TDryBulb, BoundedHumRatio, Pressure); // Initial guesses TWetBulbSup = TDryBulb; TWetBulbInf = TDewPoint; TWetBulb = (TWetBulbInf + TWetBulbSup) / 2.; // Bisection loop while ((TWetBulbSup - TWetBulbInf) > PSYCHROLIB_TOLERANCE) { // Compute humidity ratio at temperature Tstar Wstar = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); // Get new bounds if (Wstar > BoundedHumRatio) TWetBulbSup = TWetBulb; else TWetBulbInf = TWetBulb; // New guess of wet bulb temperature TWetBulb = (TWetBulbSup+TWetBulbInf) / 2.; ASSERT (index <= MAX_ITER_COUNT, "Convergence not reached in GetTWetBulbFromHumRatio. Stopping.") index++; } return TWetBulb; } // Return humidity ratio given dry-bulb temperature, wet-bulb temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 33 and 35 double GetHumRatioFromTWetBulb // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double Wsstar; double HumRatio = INVALID; ASSERT (TWetBulb <= TDryBulb, "Wet bulb temperature is above dry bulb temperature") Wsstar = GetSatHumRatio(TWetBulb, Pressure); if (isIP()) { if (TWetBulb >= FREEZING_POINT_WATER_IP) HumRatio = ((1093. - 0.556 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) / (1093. + 0.444 * TDryBulb - TWetBulb); else HumRatio = ((1220. - 0.04 * TWetBulb) * Wsstar - 0.240 * (TDryBulb - TWetBulb)) / (1220. + 0.444 * TDryBulb - 0.48 * TWetBulb); } else { if (TWetBulb >= FREEZING_POINT_WATER_SI) HumRatio = ((2501. - 2.326 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) / (2501. + 1.86 * TDryBulb - 4.186 * TWetBulb); else HumRatio = ((2830. - 0.24 * TWetBulb) * Wsstar - 1.006 * (TDryBulb - TWetBulb)) / (2830. + 1.86 * TDryBulb - 2.1 * TWetBulb); } // Validity check. return max(HumRatio, MIN_HUM_RATIO); } // Return humidity ratio given dry-bulb temperature, relative humidity, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetHumRatioFromRelHum // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double RelHum // (i) Relative humidity [0-1] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double VapPres; ASSERT (RelHum >= 0. && RelHum <= 1., "Relative humidity is outside range [0,1]") VapPres = GetVapPresFromRelHum(TDryBulb, RelHum); return GetHumRatioFromVapPres(VapPres, Pressure); } // Return relative humidity given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetRelHumFromHumRatio // (o) Relative humidity [0-1] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double VapPres; ASSERT (HumRatio >= 0., "Humidity ratio is negative") VapPres = GetVapPresFromHumRatio(HumRatio, Pressure); return GetRelHumFromVapPres(TDryBulb, VapPres); } // Return humidity ratio given dew-point temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetHumRatioFromTDewPoint // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double VapPres; VapPres = GetSatVapPres(TDewPoint); return GetHumRatioFromVapPres(VapPres, Pressure); } // Return dew-point temperature given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetTDewPointFromHumRatio // (o) Dew Point temperature in °F [IP] or °C [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double VapPres; ASSERT (HumRatio >= 0., "Humidity ratio is negative") VapPres = GetVapPresFromHumRatio(HumRatio, Pressure); return GetTDewPointFromVapPres(TDryBulb, VapPres); } /****************************************************************************************************** * Conversions between humidity ratio and vapor pressure *****************************************************************************************************/ // Return humidity ratio given water vapor pressure and atmospheric pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 double GetHumRatioFromVapPres // (o) Humidity Ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double VapPres // (i) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double HumRatio; ASSERT (VapPres >= 0., "Partial pressure of water vapor in moist air is negative") HumRatio = 0.621945 * VapPres / (Pressure - VapPres); // Validity check. return max(HumRatio, MIN_HUM_RATIO); } // Return vapor pressure given humidity ratio and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 20 solved for pw double GetVapPresFromHumRatio // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] ( double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double VapPres, BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); VapPres = Pressure * BoundedHumRatio / (0.621945 + BoundedHumRatio); return VapPres; } /****************************************************************************************************** * Conversions between humidity ratio and specific humidity *****************************************************************************************************/ // Return the specific humidity from humidity ratio (aka mixing ratio) // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b double GetSpecificHumFromHumRatio // (o) Specific humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double HumRatio // (i) Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); return BoundedHumRatio / (1.0 + BoundedHumRatio); } // Return the humidity ratio (aka mixing ratio) from specific humidity // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 9b (solved for humidity ratio) double GetHumRatioFromSpecificHum // (o) Humidity ratio in lb_H₂O lb_Dry_Air⁻¹ [IP] or kg_H₂O kg_Dry_Air⁻¹ [SI] ( double SpecificHum // (i) Specific humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ) { double HumRatio; ASSERT (SpecificHum >= 0.0 && SpecificHum < 1.0, "Specific humidity is outside range [0,1[") HumRatio = SpecificHum / (1.0 - SpecificHum); // Validity check return max(HumRatio, MIN_HUM_RATIO); } /****************************************************************************************************** * Dry Air Calculations *****************************************************************************************************/ // Return dry-air enthalpy given dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 28 double GetDryAirEnthalpy // (o) Dry air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { if (isIP()) return 0.240 * TDryBulb; else return 1006 * TDryBulb; } // Return dry-air density given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 // Notes: eqn 14 for the perfect gas relationship for dry air. // Eqn 1 for the universal gas constant. // The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². double GetDryAirDensity // (o) Dry air density in lb ft⁻³ [IP] or kg m⁻³ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { if (isIP()) return (144. * Pressure) / R_DA_IP / GetTRankineFromTFahrenheit(TDryBulb); else return Pressure / R_DA_SI / GetTKelvinFromTCelsius(TDryBulb); } // Return dry-air volume given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 // Notes: eqn 14 for the perfect gas relationship for dry air. // Eqn 1 for the universal gas constant. // The factor 144 in IP is for the conversion of Psi = lb in⁻² to lb ft⁻². double GetDryAirVolume // (o) Dry air volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { if (isIP()) return R_DA_IP * GetTRankineFromTFahrenheit(TDryBulb) / (144. * Pressure); else return R_DA_SI * GetTKelvinFromTCelsius(TDryBulb) / Pressure; } // Return dry bulb temperature from enthalpy and humidity ratio. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. // Notes: based on the `GetMoistAirEnthalpy` function, rearranged for temperature. double GetTDryBulbFromEnthalpyAndHumRatio // (o) Dry-bulb temperature in °F [IP] or °C [SI] ( double MoistAirEnthalpy // (i) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (isIP()) return (MoistAirEnthalpy - 1061.0 * BoundedHumRatio) / (0.240 + 0.444 * BoundedHumRatio); else return (MoistAirEnthalpy / 1000.0 - 2501.0 * BoundedHumRatio) / (1.006 + 1.86 * BoundedHumRatio); } // Return humidity ratio from enthalpy and dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 30. // Notes: based on the `GetMoistAirEnthalpy` function, rearranged for humidity ratio. double GetHumRatioFromEnthalpyAndTDryBulb // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double MoistAirEnthalpy // (i) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ , double TDryBulb // (i) Dry-bulb temperature in °F [IP] or °C [SI] ) { double HumRatio; if (isIP()) HumRatio = (MoistAirEnthalpy - 0.240 * TDryBulb) / (1061.0 + 0.444 * TDryBulb); else HumRatio = (MoistAirEnthalpy / 1000.0 - 1.006 * TDryBulb) / (2501.0 + 1.86 * TDryBulb); // Validity check. return max(HumRatio, MIN_HUM_RATIO); } /****************************************************************************************************** * Saturated Air Calculations *****************************************************************************************************/ // Return saturation vapor pressure given dry-bulb temperature. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 5 & 6 // Important note: the ASHRAE formulae are defined above and below the freezing point but have // a discontinuity at the freezing point. This is a small inaccuracy on ASHRAE's part: the formulae // should be defined above and below the triple point of water (not the feezing point) in which case // the discontinuity vanishes. It is essential to use the triple point of water otherwise function // GetTDewPointFromVapPres, which inverts the present function, does not converge properly around // the freezing point. double GetSatVapPres // (o) Vapor Pressure of saturated air in Psi [IP] or Pa [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { double LnPws, T; if (isIP()) { ASSERT(TDryBulb >= -148. && TDryBulb <= 392., "Dry bulb temperature is outside range [-148, 392]") T = GetTRankineFromTFahrenheit(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_IP) LnPws = (-1.0214165E+04 / T - 4.8932428 - 5.3765794E-03 * T + 1.9202377E-07 * T * T + 3.5575832E-10 * pow(T, 3) - 9.0344688E-14 * pow(T, 4) + 4.1635019 * log(T)); else LnPws = -1.0440397E+04 / T - 1.1294650E+01 - 2.7022355E-02 * T + 1.2890360E-05 * T * T - 2.4780681E-09 * pow(T, 3) + 6.5459673 * log(T); } else { ASSERT(TDryBulb >= -100. && TDryBulb <= 200., "Dry bulb temperature is outside range [-100, 200]") T = GetTKelvinFromTCelsius(TDryBulb); if (TDryBulb <= TRIPLE_POINT_WATER_SI) LnPws = -5.6745359E+03 / T + 6.3925247 - 9.677843E-03 * T + 6.2215701E-07 * T * T + 2.0747825E-09 * pow(T, 3) - 9.484024E-13 * pow(T, 4) + 4.1635019 * log(T); else LnPws = -5.8002206E+03 / T + 1.3914993 - 4.8640239E-02 * T + 4.1764768E-05 * T * T - 1.4452093E-08 * pow(T, 3) + 6.5459673 * log(T); } return exp(LnPws); } // Return humidity ratio of saturated air given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 36, solved for W double GetSatHumRatio // (o) Humidity ratio of saturated air in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double SatVaporPres, SatHumRatio; SatVaporPres = GetSatVapPres(TDryBulb); SatHumRatio = 0.621945 * SatVaporPres / (Pressure - SatVaporPres); // Validity check. return max(SatHumRatio, MIN_HUM_RATIO); } // Return saturated air enthalpy given dry-bulb temperature and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 double GetSatAirEnthalpy // (o) Saturated air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { return GetMoistAirEnthalpy(TDryBulb, GetSatHumRatio(TDryBulb, Pressure)); } /****************************************************************************************************** * Moist Air Calculations *****************************************************************************************************/ // Return Vapor pressure deficit given dry-bulb temperature, humidity ratio, and pressure. // Reference: see Oke (1987) eqn. 2.13a double GetVaporPressureDeficit // (o) Vapor pressure deficit in Psi [IP] or Pa [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double RelHum; ASSERT (HumRatio >= 0., "Humidity ratio is negative") RelHum = GetRelHumFromHumRatio(TDryBulb, HumRatio, Pressure); return GetSatVapPres(TDryBulb) * (1. - RelHum); } // Return the degree of saturation (i.e humidity ratio of the air / humidity ratio of the air at saturation // at the same temperature and pressure) given dry-bulb temperature, humidity ratio, and atmospheric pressure. // Reference: ASHRAE Handbook - Fundamentals (2009) ch. 1 eqn. 12 // Notes: the definition is absent from the 2017 Handbook double GetDegreeOfSaturation // (o) Degree of saturation [] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); return BoundedHumRatio / GetSatHumRatio(TDryBulb, Pressure); } // Return moist air enthalpy given dry-bulb temperature and humidity ratio. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 30 double GetMoistAirEnthalpy // (o) Moist Air Enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (isIP()) return 0.240 * TDryBulb + BoundedHumRatio*(1061. + 0.444 * TDryBulb); else return (1.006 * TDryBulb + BoundedHumRatio*(2501. + 1.86 * TDryBulb)) * 1000.; } // Return moist air specific volume given dry-bulb temperature, humidity ratio, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 26 // Notes: in IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26. // The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². double GetMoistAirVolume // (o) Specific Volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (isIP()) return R_DA_IP * GetTRankineFromTFahrenheit(TDryBulb) * (1. + 1.607858 * BoundedHumRatio) / (144. * Pressure); else return R_DA_SI * GetTKelvinFromTCelsius(TDryBulb) * (1. + 1.607858 * BoundedHumRatio) / Pressure; } // Return dry-bulb temperature given moist air specific volume, humidity ratio, and pressure. // Reference: // ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 26 // Notes: // In IP units, R_DA_IP / 144 equals 0.370486 which is the coefficient appearing in eqn 26 // The factor 144 is for the conversion of Psi = lb in⁻² to lb ft⁻². // Based on the `GetMoistAirVolume` function, rearranged for dry-bulb temperature. double GetTDryBulbFromMoistAirVolumeAndHumRatio // (o) Dry-bulb temperature in °F [IP] or °C [SI] ( double MoistAirVolume // (i) Specific volume of moist air in ft³ lb⁻¹ of dry air [IP] or in m³ kg⁻¹ of dry air [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); if (isIP()) return GetTFahrenheitFromTRankine(MoistAirVolume * (144 * Pressure) / (R_DA_IP * (1 + 1.607858 * BoundedHumRatio))); else return GetTCelsiusFromTKelvin(MoistAirVolume * Pressure / (R_DA_SI * (1 + 1.607858 * BoundedHumRatio))); } // Return moist air density given humidity ratio, dry bulb temperature, and pressure. // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn. 11 double GetMoistAirDensity // (o) Moist air density in lb ft⁻³ [IP] or kg m⁻³ [SI] ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double HumRatio // (i) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] ) { double BoundedHumRatio; ASSERT (HumRatio >= 0., "Humidity ratio is negative") BoundedHumRatio = max(HumRatio, MIN_HUM_RATIO); return (1. + BoundedHumRatio) / GetMoistAirVolume(TDryBulb, BoundedHumRatio, Pressure); } /****************************************************************************************************** * Standard atmosphere *****************************************************************************************************/ // Return standard atmosphere barometric pressure, given the elevation (altitude). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 3 double GetStandardAtmPressure // (o) Standard atmosphere barometric pressure in Psi [IP] or Pa [SI] ( double Altitude // (i) Altitude in ft [IP] or m [SI] ) { double Pressure; if (isIP()) Pressure = 14.696 * pow(1. - 6.8754e-06 * Altitude, 5.2559); else Pressure = 101325. * pow(1. - 2.25577e-05 * Altitude, 5.2559); return Pressure; } // Return standard atmosphere temperature, given the elevation (altitude). // Reference: ASHRAE Handbook - Fundamentals (2017) ch. 1 eqn 4 double GetStandardAtmTemperature // (o) Standard atmosphere dry bulb temperature in °F [IP] or °C [SI] ( double Altitude // (i) Altitude in ft [IP] or m [SI] ) { double Temperature; if (isIP()) Temperature = 59. - 0.00356620 * Altitude; else Temperature = 15. - 0.0065 * Altitude; return Temperature; } // Return sea level pressure given dry-bulb temperature, altitude above sea level and pressure. // Reference: Hess SL, Introduction to theoretical meteorology, Holt Rinehart and Winston, NY 1959, // ch. 6.5; Stull RB, Meteorology for scientists and engineers, 2nd edition, // Brooks/Cole 2000, ch. 1. // Notes: the standard procedure for the US is to use for TDryBulb the average // of the current station temperature and the station temperature from 12 hours ago. double GetSeaLevelPressure // (o) Sea level barometric pressure in Psi [IP] or Pa [SI] ( double StnPressure // (i) Observed station pressure in Psi [IP] or Pa [SI] , double Altitude // (i) Altitude above sea level in ft [IP] or m [SI] , double TDryBulb // (i) Dry bulb temperature ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] ) { double TColumn, H; if (isIP()) { // Calculate average temperature in column of air, assuming a lapse rate // of 3.6 °F/1000ft TColumn = TDryBulb + 0.0036 * Altitude / 2.; // Determine the scale height H = 53.351 * GetTRankineFromTFahrenheit(TColumn); } else { // Calculate average temperature in column of air, assuming a lapse rate // of 6.5 °C/km TColumn = TDryBulb + 0.0065 * Altitude / 2.; // Determine the scale height H = 287.055 * GetTKelvinFromTCelsius(TColumn) / 9.807; } // Calculate the sea level pressure double SeaLevelPressure = StnPressure * exp(Altitude / H); return SeaLevelPressure; } // Return station pressure from sea level pressure // Reference: see 'GetSeaLevelPressure' // Notes: this function is just the inverse of 'GetSeaLevelPressure'. double GetStationPressure // (o) Station pressure in Psi [IP] or Pa [SI] ( double SeaLevelPressure // (i) Sea level barometric pressure in Psi [IP] or Pa [SI] , double Altitude // (i) Altitude above sea level in ft [IP] or m [SI] , double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] ) { return SeaLevelPressure / GetSeaLevelPressure(1., Altitude, TDryBulb); } /****************************************************************************************************** * Functions to set all psychrometric values *****************************************************************************************************/ // Utility function to calculate humidity ratio, dew-point temperature, relative humidity, // vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given // dry-bulb temperature, wet-bulb temperature, and pressure. void CalcPsychrometricsFromTWetBulb ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TWetBulb // (i) Wet bulb temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] , double *HumRatio // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double *TDewPoint // (o) Dew point temperature in °F [IP] or °C [SI] , double *RelHum // (o) Relative humidity [0-1] , double *VapPres // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] , double *MoistAirEnthalpy // (o) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] , double *MoistAirVolume // (o) Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] , double *DegreeOfSaturation // (o) Degree of saturation [unitless] ) { ASSERT(TWetBulb <= TDryBulb, "Wet bulb temperature is above dry bulb temperature") *HumRatio = GetHumRatioFromTWetBulb(TDryBulb, TWetBulb, Pressure); *TDewPoint = GetTDewPointFromHumRatio(TDryBulb, *HumRatio, Pressure); *RelHum = GetRelHumFromHumRatio(TDryBulb, *HumRatio, Pressure); *VapPres = GetVapPresFromHumRatio(*HumRatio, Pressure); *MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, *HumRatio); *MoistAirVolume = GetMoistAirVolume(TDryBulb, *HumRatio, Pressure); *DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, *HumRatio, Pressure); } // Utility function to calculate humidity ratio, wet-bulb temperature, relative humidity, // vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given // dry-bulb temperature, dew-point temperature, and pressure. void CalcPsychrometricsFromTDewPoint ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double TDewPoint // (i) Dew point temperature in °F [IP] or °C [SI] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] , double *HumRatio // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double *TWetBulb // (o) Wet bulb temperature in °F [IP] or °C [SI] , double *RelHum // (o) Relative humidity [0-1] , double *VapPres // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] , double *MoistAirEnthalpy // (o) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] , double *MoistAirVolume // (o) Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] , double *DegreeOfSaturation // (o) Degree of saturation [unitless] ) { ASSERT(TDewPoint <= TDryBulb, "Dew point temperature is above dry bulb temperature") *HumRatio = GetHumRatioFromTDewPoint(TDewPoint, Pressure); *TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, *HumRatio, Pressure); *RelHum = GetRelHumFromHumRatio(TDryBulb, *HumRatio, Pressure); *VapPres = GetVapPresFromHumRatio(*HumRatio, Pressure); *MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, *HumRatio); *MoistAirVolume = GetMoistAirVolume(TDryBulb, *HumRatio, Pressure); *DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, *HumRatio, Pressure); } // Utility function to calculate humidity ratio, wet-bulb temperature, dew-point temperature, // vapour pressure, moist air enthalpy, moist air volume, and degree of saturation of air given // dry-bulb temperature, relative humidity and pressure. void CalcPsychrometricsFromRelHum ( double TDryBulb // (i) Dry bulb temperature in °F [IP] or °C [SI] , double RelHum // (i) Relative humidity [0-1] , double Pressure // (i) Atmospheric pressure in Psi [IP] or Pa [SI] , double *HumRatio // (o) Humidity ratio in lb_H₂O lb_Air⁻¹ [IP] or kg_H₂O kg_Air⁻¹ [SI] , double *TWetBulb // (o) Wet bulb temperature in °F [IP] or °C [SI] , double *TDewPoint // (o) Dew point temperature in °F [IP] or °C [SI] , double *VapPres // (o) Partial pressure of water vapor in moist air in Psi [IP] or Pa [SI] , double *MoistAirEnthalpy // (o) Moist air enthalpy in Btu lb⁻¹ [IP] or J kg⁻¹ [SI] , double *MoistAirVolume // (o) Specific volume ft³ lb⁻¹ [IP] or in m³ kg⁻¹ [SI] , double *DegreeOfSaturation // (o) Degree of saturation [unitless] ) { ASSERT(RelHum >= 0 && RelHum <= 1, "Relative humidity is outside range [0,1]") *HumRatio = GetHumRatioFromRelHum(TDryBulb, RelHum, Pressure); *TWetBulb = GetTWetBulbFromHumRatio(TDryBulb, *HumRatio, Pressure); *TDewPoint = GetTDewPointFromHumRatio(TDryBulb, *HumRatio, Pressure); *VapPres = GetVapPresFromHumRatio(*HumRatio, Pressure); *MoistAirEnthalpy = GetMoistAirEnthalpy(TDryBulb, *HumRatio); *MoistAirVolume = GetMoistAirVolume(TDryBulb, *HumRatio, Pressure); *DegreeOfSaturation = GetDegreeOfSaturation(TDryBulb, *HumRatio, Pressure); } |
FREQUENTLY ASKED QUESTIONS
Implementing psychrometric formulas in computer programs or spreadsheets can be challenging and time-consuming due to the complexity of the equations involved. Some common challenges include ensuring accuracy and precision, handling unit conversions, and dealing with iterative calculations. Additionally, implementing these formulas requires a deep understanding of the underlying thermodynamic principles and mathematical concepts, which can be a barrier for many engineers and researchers.
PsychroLib supports a range of programming languages, including Python, C, C#, Fortran, JavaScript, and VBA/Excel. The library is designed to be easily accessible and can be downloaded from the PsychroLib website. Once downloaded, users can integrate the library into their preferred programming environment and start using the psychrometric functions to calculate thermodynamic properties of air.
PsychroLib simplifies the process of calculating psychrometric properties of air by providing a comprehensive library of functions that can be easily integrated into computer programs or spreadsheets. This eliminates the need for users to implement complex formulas and equations from scratch, saving time and reducing the risk of errors. The library also provides a consistent and accurate way of calculating psychrometric properties, ensuring that results are reliable and trustworthy.
PsychroLib can be used for both research and development purposes, as well as practical applications. The library provides a robust and accurate way of calculating psychrometric properties of air, making it an ideal tool for researchers and developers working on HVAC and meteorology-related projects. At the same time, the library is also suitable for practical applications, such as designing and optimizing HVAC systems, and analyzing weather patterns and climate phenomena.