diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..fb36cbd2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.cache/ +build/ +dist/ +tests/__pycache__/ +thermo.egg-info/ +.vscode/ +*.pyc +.pytest_cache/ \ No newline at end of file diff --git a/setup.py b/setup.py index 3232f5cd..74438601 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ package_data={'thermo': ['Critical Properties/*', 'Density/*', 'Electrolytes/*', 'Environment/*', 'Heat Capacity/*', 'Identifiers/*', 'Law/*', 'Misc/*', 'Phase Change/*', 'Reactions/*', 'Safety/*', - 'Solubility/*', 'Interface/*', 'Triple Properties/*', + 'Solubility/*', 'Interface/*', 'Steam Properties/*', 'Triple Properties/*', 'Thermal Conductivity/*', 'Vapor Pressure/*', 'Viscosity/*']} ) diff --git a/tests/test_steam_properties.py b/tests/test_steam_properties.py new file mode 100644 index 00000000..68ea4501 --- /dev/null +++ b/tests/test_steam_properties.py @@ -0,0 +1,354 @@ +# -*- coding: utf-8 -*- +'''Chemical Engineering Design Library (ChEDL). Utilities for process modeling. +Copyright (C) 2016, Caleb Bell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''' + +from numpy.testing import assert_allclose +import pytest +from pytest import approx +from thermo.steam_properties import get_steam_table_entry, PTVEntry, PhaseRegion, PhaseInfo + +def inc(x): + return x + 1 + +# temperature, pressure, enthalpy, entropy, phase_region, is_saturated, expected_is_success, expected_entry, expected_error_message +testdata = [ + # temperature and pressure + (750, 78.309563916917e6, None, None, None, None, + True, + PTVEntry( + _temperature=750, + _pressure=78.309563916917e6, + _phase_info=PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0), + _internal_energy=2102.069317626429e3, + _enthalpy=2258.688445460262e3, + _entropy=4.469719056217e3, + _isochoric_heat_capacity=2.71701677121e3, + _isobaric_heat_capacity=6.341653594791e3, + _speed_of_sound=760.696040876798, + _density=500 + ), + None), + (473.15, 40e6, None, None, None, None, + True, + PTVEntry( + _temperature=473.15, + _pressure=40e6, + _phase_info=PhaseInfo(PhaseRegion.Liquid, 0, 1, 0), + _internal_energy=825.228016170348e3, + _enthalpy=870.124259682489e3, + _entropy=2.275752861241e3, + _isochoric_heat_capacity=3.292858637199e3, + _isobaric_heat_capacity=4.315767590903e3, + _speed_of_sound=1457.418351596083, + _density=1.0 / 0.001122406088 + ), + None), + (2000, 30e6, None, None, None, None, + True, + PTVEntry( + _temperature=2000, + _pressure=30e6, + _phase_info=PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0), + _internal_energy=5637.070382521894e3, + _enthalpy=6571.226038618478e3, + _entropy=8.536405231138e3, + _isochoric_heat_capacity=2.395894362358e3, + _isobaric_heat_capacity=2.885698818781e3, + _speed_of_sound=1067.369478777425, + _density=1.0 / 0.03113852187 + ), + None), + (823.15, 14e6, None, None, None, None, + True, + PTVEntry( + _temperature=823.15, + _pressure=14e6, + _phase_info=PhaseInfo(PhaseRegion.Gas, 0, 0, 0), + _internal_energy=3114.302136294585e3, + _enthalpy=3460.987255128561e3, + _entropy=6.564768889364e3, + _isochoric_heat_capacity=1.892708832325e3, + _isobaric_heat_capacity=2.666558503968e3, + _speed_of_sound=666.050616844223, + _density=1.0 / 0.024763222774 + ), + None), + # saturated and pressure + (None, 0.2e6, None, None, PhaseRegion.Liquid, True, + True, + PTVEntry( + _temperature=393.361545936488, + _pressure=0.2e6, + _phase_info=PhaseInfo(PhaseRegion.Liquid, 0, 1, 0), + _internal_energy=504471.741847973, + _enthalpy=504683.84552926, + _entropy=1530.0982011075, + _isochoric_heat_capacity=3666.99397284121, + _isobaric_heat_capacity=4246.73524917536, + _speed_of_sound=1520.69128792808, + _density=1.0 / 0.00106051840643552 + ), + None), + (None, 0.2e6, None, None, PhaseRegion.Vapor, True, + True, + PTVEntry( + _temperature=393.361545936488, + _pressure=0.2e6, + _phase_info=PhaseInfo(PhaseRegion.Vapor, 1, 0, 0), + _internal_energy=2529094.32835793, + _enthalpy=2706241.34137425, + _entropy=7126.8563914686, + _isochoric_heat_capacity=1615.96336473298, + _isobaric_heat_capacity=2175.22318865273, + _speed_of_sound=481.883535821489, + _density=1.0 / 0.885735065081644 + ), + None), + # saturated and temperature + (393.361545936488, None, None, None, PhaseRegion.Liquid, True, + True, + PTVEntry( + _temperature=393.361545936488, + _pressure=0.2e6, + _phase_info=PhaseInfo(PhaseRegion.Liquid, 0, 1, 0), + _internal_energy=504471.741847973, + _enthalpy=504683.84552926, + _entropy=1530.0982011075, + _isochoric_heat_capacity=3666.99397284121, + _isobaric_heat_capacity=4246.73524917536, + _speed_of_sound=1520.69128792808, + _density=1.0 / 0.00106051840643552 + ), + None), + (393.361545936488, None, None, None, PhaseRegion.Vapor, True, + True, + PTVEntry( + _temperature=393.361545936488, + _pressure=0.2e6, + _phase_info=PhaseInfo(PhaseRegion.Vapor, 1, 0, 0), + _internal_energy=2529094.32835793, + _enthalpy=2706241.34137425, + _entropy=7126.8563914686, + _isochoric_heat_capacity=1615.96336473298, + _isobaric_heat_capacity=2175.22318865273, + _speed_of_sound=481.883535821489, + _density=1.0 / 0.885735065081644 + ), + None), + # entropy and pressure + (None, 78.309563916917e6, None, 4.469719056217e3, None, None, + True, + PTVEntry( + _temperature=750, + _pressure=78.309563916917e6, + _phase_info=PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0), + _internal_energy=2102.069317626429e3, + _enthalpy=2258.688445460262e3, + _entropy=4.469719056217e3, + _isochoric_heat_capacity=2.71701677121e3, + _isobaric_heat_capacity=6.341653594791e3, + _speed_of_sound=760.696040876798, + _density=500 + ), + None), + (None, 40e6, None, 2.275752861241e3, None, None, + True, + PTVEntry( + _temperature=473.15, + _pressure=40e6, + _phase_info=PhaseInfo(PhaseRegion.Liquid, 0, 1, 0), + _internal_energy=825.228016170348e3, + _enthalpy=870.124259682489e3, + _entropy=2.275752861241e3, + _isochoric_heat_capacity=3.292858637199e3, + _isobaric_heat_capacity=4.315767590903e3, + _speed_of_sound=1457.418351596083, + _density=1.0 / 0.001122406088 + ), + None), + (None, 30e6, None, 8.536405231138e3, None, None, + True, + PTVEntry( + _temperature=2000, + _pressure=30e6, + _phase_info=PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0), + _internal_energy=5637.070382521894e3, + _enthalpy=6571.226038618478e3, + _entropy=8.536405231138e3, + _isochoric_heat_capacity=2.395894362358e3, + _isobaric_heat_capacity=2.885698818781e3, + _speed_of_sound=1067.369478777425, + _density=1.0 / 0.03113852187 + ), + None), + (None, 14e6, None, 6.564768889364e3, None, None, + True, + PTVEntry( + _temperature=823.15, + _pressure=14e6, + _phase_info=PhaseInfo(PhaseRegion.Gas, 0, 0, 0), + _internal_energy=3114.302136294585e3, + _enthalpy=3460.987255128561e3, + _entropy=6.564768889364e3, + _isochoric_heat_capacity=1.892708832325e3, + _isobaric_heat_capacity=2.666558503968e3, + _speed_of_sound=666.050616844223, + _density=1.0 / 0.024763222774 + ), + None), + (None, 10e3, None, 6.6858e3, None, None, + True, + PTVEntry( + _temperature=318.957548207023, + _pressure=10e3, + _phase_info=PhaseInfo(PhaseRegion.LiquidVapor, 0.8049124470781327, 0.1950875529218673, 0), + _internal_energy=1999135.82661328, + _enthalpy=2117222.94886314, + _entropy=6.6858e3, + _isochoric_heat_capacity=1966.28009225455, + _isobaric_heat_capacity=2377.86300751001, + _speed_of_sound=655.005141924186, + _density=193.16103883 + ), + None), + #enthalpy and pressure + (None, 78.309563916917e6, 2258.688445460262e3, None, None, None, + True, + PTVEntry( + _temperature=750, + _pressure=78.309563916917e6, + _phase_info=PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0), + _internal_energy=2102.069317626429e3, + _enthalpy=2258.688445460262e3, + _entropy=4.469719056217e3, + _isochoric_heat_capacity=2.71701677121e3, + _isobaric_heat_capacity=6.341653594791e3, + _speed_of_sound=760.696040876798, + _density=500 + ), + None), + (None, 40e6, 870.124259682489e3, None, None, None, + True, + PTVEntry( + _temperature=473.15, + _pressure=40e6, + _phase_info=PhaseInfo(PhaseRegion.Liquid, 0, 1, 0), + _internal_energy=825.228016170348e3, + _enthalpy=870.124259682489e3, + _entropy=2.275752861241e3, + _isochoric_heat_capacity=3.292858637199e3, + _isobaric_heat_capacity=4.315767590903e3, + _speed_of_sound=1457.418351596083, + _density=1.0 / 0.001122406088 + ), + None), + (None, 30e6, 6571.226038618478e3, None, None, None, + True, + PTVEntry( + _temperature=2000, + _pressure=30e6, + _phase_info=PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0), + _internal_energy=5637.070382521894e3, + _enthalpy=6571.226038618478e3, + _entropy=8.536405231138e3, + _isochoric_heat_capacity=2.395894362358e3, + _isobaric_heat_capacity=2.885698818781e3, + _speed_of_sound=1067.369478777425, + _density=1.0 / 0.03113852187 + ), + None), + (None, 14e6, 3460.987255128561e3, None, None, None, + True, + PTVEntry( + _temperature=823.15, + _pressure=14e6, + _phase_info=PhaseInfo(PhaseRegion.Gas, 0, 0, 0), + _internal_energy=3114.302136294585e3, + _enthalpy=3460.987255128561e3, + _entropy=6.564768889364e3, + _isochoric_heat_capacity=1.892708832325e3, + _isobaric_heat_capacity=2.666558503968e3, + _speed_of_sound=666.050616844223, + _density=1.0 / 0.024763222774 + ), + None), + (None, 10e3, 2117222.94886314, None, None, None, + True, + PTVEntry( + _temperature=318.957548207023, + _pressure=10e3, + _phase_info=PhaseInfo(PhaseRegion.LiquidVapor, 0.804912447078132, 0.195087552921868, 0), + _internal_energy=1999135.82661328, + _enthalpy=2117222.94886314, + _entropy=6.6858e3, + _isochoric_heat_capacity=1966.28009225455, + _isobaric_heat_capacity=2377.86300751001, + _speed_of_sound=655.005141924186, + _density=193.16103883 + ), + None), +] + +@pytest.mark.parametrize("temperature, pressure, enthalpy, entropy, phase_region, is_saturated, expected_is_success, expected_entry, expected_error_message", testdata) +def test_answer(temperature, + pressure, + enthalpy, + entropy, + phase_region, + is_saturated, + expected_is_success, + expected_entry, + expected_error_message): + + + (actual_is_success, actual_entry, actual_error_message) = get_steam_table_entry( + pressure=pressure, + temperature=temperature, + enthalpy=enthalpy, + entropy=entropy, + phase_region=phase_region, + is_saturated=is_saturated) + + assert actual_is_success == expected_is_success + if expected_is_success: + assert actual_entry is not None + assert expected_entry is not None + assert expected_entry.get_pressure() == approx(actual_entry.get_pressure()) + assert expected_entry.get_temperature() == approx(actual_entry.get_temperature()) + assert expected_entry.get_enthalpy() == approx(actual_entry.get_enthalpy()) + assert expected_entry.get_entropy() == approx(actual_entry.get_entropy()) + assert expected_entry.get_isochoric_heat_capacity() == approx(actual_entry.get_isochoric_heat_capacity()) + assert expected_entry.get_isobaric_heat_capacity() == approx(actual_entry.get_isobaric_heat_capacity()) + assert expected_entry.get_isobaric_heat_capacity() == approx(actual_entry.get_isobaric_heat_capacity()) + assert expected_entry.get_speed_of_sound() == approx(actual_entry.get_speed_of_sound()) + assert expected_entry.get_density() == approx(actual_entry.get_density()) + + expect_phase_info = expected_entry.get_phase_info() + actual_phase_info = actual_entry.get_phase_info() + assert expect_phase_info is not None + assert actual_phase_info is not None + assert expect_phase_info.get_phase_region() == actual_phase_info.get_phase_region() + assert expect_phase_info.get_vapor() == actual_phase_info.get_vapor() + assert expect_phase_info.get_liquid() == actual_phase_info.get_liquid() + assert expect_phase_info.get_solid() == actual_phase_info.get_solid() + else : + assert actual_entry is None + assert actual_error_message == expected_error_message diff --git a/thermo/Steam Properties/Region1and4.csv b/thermo/Steam Properties/Region1and4.csv new file mode 100644 index 00000000..cfcac59d --- /dev/null +++ b/thermo/Steam Properties/Region1and4.csv @@ -0,0 +1,35 @@ +I, J, N +0, -2, 1.4632971213167E-01 +0, -1, -8.4548187169114E-01 +0, 0, -3.756360367204 +0, 1, 3.3855169168385E+00 +0, 2, -9.5791963387872E-01 +0, 3, 1.5772038513228E-01 +0, 4, -1.6616417199501E-02 +0, 5, 8.1214629983568E-04 +1, -9, 2.8319080123804E-04 +1, -7, -6.0706301565874E-04 +1, -1, -1.8990068218419E-02 +1, 0, -3.2529748770505E-02 +1, 1, -2.1841717175414E-02 +1, 3, -5.2838357969930E-05 +2, -3, -4.7184321073267E-04 +2, 0, -3.0001780793026E-04 +2, 1, 4.7661393906987E-05 +2, 3, -4.4141845330846E-06 +2, 17, -7.2694996297594E-16 +3, -4, -3.1679644845054E-05 +3, 0, -2.8270797985312E-06 +3, 6, -8.5205128120103E-10 +4, -5, -2.2425281908000E-06 +4, -2, -6.5171222895601E-07 +4, 10, -1.4341729937924E-13 +5, -8, -4.0516996860117E-07 +8, -11, -1.2734301741641E-09 +8, -6, -1.7424871230634E-10 +21, -29, -6.8762131295531E-19 +23, -31, 1.4478307828521E-20 +29, -38, 2.6335781662795E-23 +30, -39, -1.1947622640071E-23 +31, -40, 1.8228094581404E-24 +32, -41, -9.3537087292458E-26 \ No newline at end of file diff --git a/thermo/Steam Properties/Region2Ideal.csv b/thermo/Steam Properties/Region2Ideal.csv new file mode 100644 index 00000000..8c8dab06 --- /dev/null +++ b/thermo/Steam Properties/Region2Ideal.csv @@ -0,0 +1,10 @@ +J, N +0, -9.6927686500217E+00 +1, 1.0086655968018E+01 +-5, -5.6087911283020E-03 +-4, 7.1452738081455E-02 +-3, -4.0710498223928E-01 +-2, 1.4240819171444E+00 +-1, -4.3839511319450E+00 +2, -2.8408632460772E-01 +3, 2.1268463753307E-02 \ No newline at end of file diff --git a/thermo/Steam Properties/Region2Residual.csv b/thermo/Steam Properties/Region2Residual.csv new file mode 100644 index 00000000..a5f9341c --- /dev/null +++ b/thermo/Steam Properties/Region2Residual.csv @@ -0,0 +1,44 @@ +I, J, N +1, 0, -1.7731742473213E-03 +1, 1, -1.7834862292358E-02 +1, 2, -4.5996013696365E-02 +1, 3, -5.7581259083432E-02 +1, 6, -5.0325278727930E-02 +2, 1, -3.3032641670203E-05 +2, 2, -1.8948987516315E-04 +2, 4, -3.9392777243355E-03 +2, 7, -4.3797295650573E-02 +2, 36, -2.6674547914087E-05 +3, 0, 2.0481737692309E-08 +3, 1, 4.3870667284435E-07 +3, 3, -3.2277677238570E-05 +3, 6, -1.5033924542148E-03 +3, 35, -4.0668253562649E-02 +4, 1, -7.8847309559367E-10 +4, 2, 1.2790717852285E-08 +4, 3, 4.8225372718507E-07 +5, 7, 2.2922076337661E-06 +6, 3, -1.6714766451061E-11 +6, 16, -2.1171472321355E-03 +6, 35, -2.3895741934104E+01 +7, 0, -5.9059564324270E-18 +7, 11, -1.2621808899101E-06 +7, 25, -3.8946842435739E-02 +8, 8, 1.1256211360459E-11 +8, 36, -8.2311340897998E+00 +9, 13, 1.9809712802088E-08 +10, 4, 1.0406965210174E-19 +10, 10, -1.0234747095929E-13 +10, 14, -1.0018179379511E-09 +16, 29, -8.0882908646985E-11 +16, 50, 1.0693031879409E-01 +18, 57, -3.3662250574171E-01 +20, 20, 8.9185845355421E-25 +20, 35, 3.0629316876232E-13 +20, 48, -4.2002467698208E-06 +21, 21, -5.9056029685639E-26 +22, 53, 3.7826947613457E-06 +23, 39, -1.2768608934681E-15 +24, 26, 7.3087610595061E-29 +24, 40, 5.5414715350778E-17 +24, 58, -9.4369707241210E-07 \ No newline at end of file diff --git a/thermo/Steam Properties/Region3.csv b/thermo/Steam Properties/Region3.csv new file mode 100644 index 00000000..0ae0014c --- /dev/null +++ b/thermo/Steam Properties/Region3.csv @@ -0,0 +1,41 @@ +I, J, N +NaN, NaN, 1.0658070028513E+00 +0, 0, -1.5732845290239E+01 +0, 1, 2.0944396974307E+01 +0, 2, -7.6867707878716E+00 +0, 7, 2.6185947787954E+00 +0, 10, -2.8080781148620E+00 +0, 12, 1.2053369696517E+00 +0, 23, -8.4566812812502E-03 +1, 2, -1.2654315477714E+00 +1, 6, -1.1524407806681E+00 +1, 15, 8.8521043984318E-01 +1, 17, -6.4207765181607E-01 +2, 0, 3.8493460186671E-01 +2, 2, -8.5214708824206E-01 +2, 6, 4.8972281541877E+00 +2, 7, -3.0502617256965E+00 +2, 22, 3.9420536879154E-02 +2, 26, 1.2558408424308E-01 +3, 0, -2.7999329698710E-01 +3, 2, 1.3899799569460E+00 +3, 4, -2.0189915023570E+00 +3, 16, -8.2147637173963E-03 +3, 26, -4.7596035734923E-01 +4, 0, 4.3984074473500E-02 +4, 2, -4.4476435428739E-01 +4, 4, 9.0572070719733E-01 +4, 26, 7.0522450087967E-01 +5, 1, 1.0770512626332E-01 +5, 3, -3.2913623258954E-01 +5, 26, -5.0871062041158E-01 +6, 0, -2.2175400873096E-02 +6, 2, 9.4260751665092E-02 +6, 26, 1.6436278447961E-01 +7, 2, -1.3503372241348E-02 +8, 26, -1.4834345352472E-02 +9, 2, 5.7922953628084E-04 +9, 26, 3.2308904703711E-03 +10, 0, 8.0964802996215E-05 +10, 1, -1.6557679795037E-04 +11, 26, -4.4923899061815E-05 \ No newline at end of file diff --git a/thermo/Steam Properties/Region5Ideal.csv b/thermo/Steam Properties/Region5Ideal.csv new file mode 100644 index 00000000..6ad18a47 --- /dev/null +++ b/thermo/Steam Properties/Region5Ideal.csv @@ -0,0 +1,7 @@ +J, N +0, -1.3179983674201E+01 +1, 6.8540841634434E+00 +-3, -2.4805148933466E-02 +-2, 3.6901534980333E-01 +-1, -3.1161318213925E+00 +2, -3.2961626538917E-01 \ No newline at end of file diff --git a/thermo/Steam Properties/Region5Residual.csv b/thermo/Steam Properties/Region5Residual.csv new file mode 100644 index 00000000..96718d7e --- /dev/null +++ b/thermo/Steam Properties/Region5Residual.csv @@ -0,0 +1,7 @@ +I, J, N +1, 1, 1.5736404855259E-03 +1, 2, 9.0153761673944E-04 +1, 3, -5.0270077677648E-03 +2, 3, 2.2440037409485E-06 +2, 9, -4.1163275453471E-06 +3, 7, 3.7919454822955E-08 \ No newline at end of file diff --git a/thermo/Steam Properties/nBoundary34.csv b/thermo/Steam Properties/nBoundary34.csv new file mode 100644 index 00000000..c7a0966b --- /dev/null +++ b/thermo/Steam Properties/nBoundary34.csv @@ -0,0 +1,6 @@ +N +348.05185628969 +-1.1671859879975 +0.0010192970039326 +572.54459862746 +13.91883977887 \ No newline at end of file diff --git a/thermo/Steam Properties/nRegion4.csv b/thermo/Steam Properties/nRegion4.csv new file mode 100644 index 00000000..afab2e15 --- /dev/null +++ b/thermo/Steam Properties/nRegion4.csv @@ -0,0 +1,11 @@ +N +1167.0521452767 +-724213.16703206 +-17.073846940092 +12020.82470247 +-3232555.0322333 +14.91510861353 +-4823.2657361591 +405113.40542057 +-0.23855557567849 +650.17534844798 \ No newline at end of file diff --git a/thermo/steam_properties.py b/thermo/steam_properties.py new file mode 100644 index 00000000..0924d7a6 --- /dev/null +++ b/thermo/steam_properties.py @@ -0,0 +1,903 @@ +# -*- coding: utf-8 -*- +'''Chemical Engineering Design Library (ChEDL). Utilities for process modeling. +Copyright (C) 2016, Caleb Bell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''' + +import os +import numpy as np +import pandas as pd +from enum import Enum +import csv +from math import pow, log, sqrt +from scipy.optimize import newton +from numpy import isclose + +class RegionPoint(object): + def __init__(self, i=None, j=None, n=None): + if i is not None: + self.i = i + if j is not None: + self.j = j + if n is not None: + self.n = n + + i = float('nan') + j = float('nan') + n = float('nan') + + +def __GetRegionPoints(fileName): + folder = os.path.join(os.path.dirname(__file__), 'Steam Properties') + iterator = csv.reader(open(os.path.join(folder, fileName))) + headers = next(iterator) + i_getter = lambda row : float('NaN') + j_getter = lambda row : float('NaN') + n_getter = lambda row : float('NaN') + for i, header in enumerate(headers): + header = header.lower() + getter = lambda row, idx = i : float(row[idx]) + if 'i' in header: + i_getter = getter + elif 'j' in header: + j_getter = getter + elif 'n' in header: + n_getter = getter + + return [RegionPoint(i_getter(row), j_getter(row), n_getter(row)) for row in iterator] + +__region1and4Points = __GetRegionPoints('Region1and4.csv') +__region2IdealPoints = __GetRegionPoints('Region2Ideal.csv') +__region2ResiduaPoints = __GetRegionPoints('Region2Residual.csv') +__region3Points = __GetRegionPoints('Region3.csv') +__nRegion4Points = __GetRegionPoints('nRegion4.csv') +__region5IdealPoints = __GetRegionPoints('Region5Ideal.csv') +__region5ResiduaPoints = __GetRegionPoints('Region5Residual.csv') +__nBoundary34Points = __GetRegionPoints('nBoundary34.csv') +# In K +__critical_temperature = 647.096 +# In Pa +__critical_pressure = 22.06e6 +# In J/(kg * K) +__water_gas_constant = 461.526 + +class __SteamEquationRegion(Enum): + OutOfRange = 0 + Region1 = 1 + Region2 = 2 + Region3 = 3 + Region4 = 4 + Region5 = 5 + +class PhaseRegion(Enum): + OutOfRange = 0 + + SupercriticalFluid = 1 + ''' + Temperature and pressure is above the critical point + ''' + + Gas = 2 + ''' + Pressure is less than both the sublimation and vaporization curve and is below the critical temperature + ''' + + Vapor = 3 + ''' + Pressure is above the vaporization curve and the temperature is greater than the fusion curve and less than the critical temperature + ''' + + Liquid = 4 + ''' + Pressure is above the sublimation curve and temperature is less than the fusion curve + ''' + + Solid = 5 + SolidLiquid = 6 + LiquidVapor = 7 + SolidVapor = 8 + SolidLiquidVapor = 9 + + +class PhaseInfo(object): + def __init__(self, _phase_region = PhaseRegion.OutOfRange, _vapor = 0, _liquid = 0, _solid = 0): + self._phase_region = _phase_region + self._vapor = _vapor + self._liquid = _liquid + self._solid = _solid + + def get_phase_region(self): + ''' + ''' + return self._phase_region + + def get_vapor(self): + ''' + Vapor fraction + ''' + return self._vapor + + def get_liquid(self): + ''' + Liquid fraction + ''' + return self._liquid + + def get_solid(self): + ''' + Solid fraction + ''' + return self._solid + + +class PTVEntry(object): + def __init__(self, + _temperature = None, + _pressure = None, + _phase_info = None, + _internal_energy = None, + _enthalpy = None, + _entropy = None, + _isochoric_heat_capacity = None, + _isobaric_heat_capacity = None, + _speed_of_sound = None, + _density = None): + self._temperature = _temperature + self._pressure = _pressure + self._phase_info = _phase_info + self._internal_energy = _internal_energy + self._enthalpy = _enthalpy + self._entropy = _entropy + self._isochoric_heat_capacity = _isochoric_heat_capacity + self._isobaric_heat_capacity = _isobaric_heat_capacity + self._speed_of_sound = _speed_of_sound + self._density = _density + + def get_temperature(self): + ''' + Temperature, K + ''' + return self._temperature + + def get_pressure(self): + ''' + Pressure, Pa + ''' + return self._pressure + + def get_phase_info(self): + ''' + ''' + return self._phase_info + + def get_specific_volume(self): + ''' + Specific Volume, m3/kg + ''' + return 1.0 / self._density + + def get_internal_energy(self): + ''' + Internal Energy, J/kg + ''' + return self._internal_energy + + def get_enthalpy(self): + ''' + Enthalpy, J/kg + ''' + return self._enthalpy + + def get_entropy(self): + ''' + Entropy, J/kg + ''' + return self._entropy + + def get_isochoric_heat_capacity(self): + ''' + Cv, Heat Capacity at constant volume (J/(kg*K)) + ''' + return self._isochoric_heat_capacity + + def get_isobaric_heat_capacity(self): + ''' + Cp, Heat Capacity at constant pressure (J/(kg*K)) + ''' + return self._isobaric_heat_capacity + + def get_speed_of_sound(self): + ''' + Speed of Sound, m/s + ''' + return self._speed_of_sound + + def get_density(self): + ''' + Density, kg/m3 + ''' + return self._density + +class __SpecificRegionPoint(object): + + def __init__(self, _temperature = 0, _pressure = 0, _tau = 0, _pi = 0, _gamma = 0, + _gamma_pi = 0, _gamma_pi_pi = 0, _gamma_tau = 0, _gamma_tau_tau = 0, _gamma_pi_tau = 0): + self._temperature = _temperature + self._pressure = _pressure + self._tau = _tau + self._pi = _pi + self._gamma = _gamma + self._gamma_pi = _gamma_pi + self._gamma_pi_pi = _gamma_pi_pi + self._gamma_tau = _gamma_tau + self._gamma_tau_tau = _gamma_tau_tau + self._gamma_pi_tau = _gamma_pi_tau + + def get_temperature(self): + ''' + Temperature, In Kelvin + ''' + return self._temperature + + def get_pressure(self): + ''' + Pressure, In Pascals + ''' + return self._pressure + + def get_tau(self): + ''' + Inverse reduced temperature, T*/T + ''' + return self._tau + + def get_pi(self): + ''' + Reduced pressure, p/p* + ''' + return self._pi + + def get_gamma(self): + ''' + Dimensionless Gibbs free energy, g/(RT) + ''' + return self._gamma + + def get_gamma_pi(self): + ''' + Derivative of gamma with respect to pi + ''' + return self._gamma_pi + + def get_gamma_pi_pi(self): + ''' + Derivative of gamma with respect to pi with respect to pi + ''' + return self._gamma_pi_pi + + def get_gamma_tau(self): + ''' + Derivative of gamma with respect to tau + ''' + return self._gamma_tau + + def get_gamma_tau_tau(self): + ''' + Derivative of gamma with respect to tau with respect to tau + ''' + return self._gamma_tau_tau + + def get_gamma_pi_tau(self): + ''' + Derivative of gamma with respect to pi with respect to tau + ''' + return self._gamma_pi_tau + +class SteamTableQuery(object): + def __init__(self, + _temperature = None, + _pressure = None, + _enthalpy = None, + _entropy = None, + _phase_region = None, + _is_saturated = None + ): + self._pressure = _pressure + self._temperature = _temperature + self._enthalpy = _enthalpy + self._entropy = _entropy + self._phase_region = _phase_region + self._is_saturated = _is_saturated + + def get_temperature(self): + ''' + Temperature, In Kelvin + ''' + return self._temperature + + def has_temperature(self): + return self.get_temperature() is not None + + def get_pressure(self): + ''' + Pressure, In Pascals + ''' + return self._pressure + + def has_pressure(self): + return self.get_pressure() is not None + + def get_enthalpy(self): + ''' + enthalpy, In kJ / kg + ''' + return self._enthalpy + + def has_enthalpy(self): + return self.get_enthalpy() is not None + + def get_entropy(self): + ''' + Entropy, In kJ / (kg * K) + ''' + return self._entropy + + def has_entropy(self): + return self.get_entropy() is not None + + def get_phase_region(self): + ''' + ''' + return self._phase_region + + def has_phase_region(self): + return self.get_phase_region() is not None + + def get_is_saturated(self): + ''' + ''' + return self._is_saturated + + def has_is_saturated(self): + return self.get_is_saturated() is not None + + +class __ValueRange(object): + def __init__(self, min=None, max=None): + if min is not None: + self.min = min + if max is not None: + self.max = max + min = float('nan') + max = float('nan') + def isWithinRange(self, value): + return self.min <= value and value <= self.max + +class __PT_Point(object): + def __init__(self, pressure=None, temperature=None): + if pressure is not None: + self.pressure = pressure + if temperature is not None: + self.temperature = temperature + pressure = float('nan') + temperature = float('nan') + +def __determine_temperature_range(pressure): + ''' + Temperature is in Kelvin + Pressure is in Pascals + ''' + if pressure > 50e6: + return __ValueRange(min=273.15, max=800 + 273.15) + return __ValueRange(min=273.15, max=2000 + 273.15) + +def __determine_pressure_range(temperature): + ''' + Temperature is in Kelvin + Pressure is in Pascals + ''' + if temperature > 800 + 273.15: + return __ValueRange(min=0, max=50e6) + return __ValueRange(min=0, max=100e6) + +def __get_sat_pressure(temperature): + ''' + Temperature is in Kelvin + Pressure is in Pascals + ''' + if (temperature >= __critical_temperature): + return (False, None, 'Temperature is greater than the critical temperature') + + sat_temp_ratio = temperature / 1 + theta = sat_temp_ratio + (__nRegion4Points[8].n / (sat_temp_ratio - __nRegion4Points[9].n)) + A = pow(theta, 2) + __nRegion4Points[0].n * theta + __nRegion4Points[1].n + B = __nRegion4Points[2].n * pow(theta, 2) + __nRegion4Points[3].n * theta + __nRegion4Points[4].n + C = __nRegion4Points[5].n * pow(theta, 2) + __nRegion4Points[6].n * theta + __nRegion4Points[7].n + pressure = pow((2 * C) / (-B + pow(pow(B, 2) - 4 * A * C, 0.5)), 4) * 1e6 + return (True, pressure, None) + +def __get_sat_temperature(pressure): + ''' + Temperature is in Kelvin + Pressure is in Pascals + ''' + if (pressure >= __critical_pressure): + return (False, None, 'Pressure is greater than the critical pressure') + beta = pow(pressure / 1e6, 0.25) + E = pow(beta, 2) + __nRegion4Points[2].n * beta + __nRegion4Points[5].n + F = __nRegion4Points[0].n * pow(beta, 2) + __nRegion4Points[3].n * beta + __nRegion4Points[6].n + G = __nRegion4Points[1].n * pow(beta, 2) + __nRegion4Points[4].n * beta + __nRegion4Points[7].n + D = (2 * G) / (-F - pow(pow(F, 2) - 4 * E * G, 0.5)) + pressure = (__nRegion4Points[9].n + D - pow(pow(__nRegion4Points[9].n + D, 2) - 4 * (__nRegion4Points[8].n + __nRegion4Points[9].n * D),0.5)) / 2 + return (True, pressure, None) + +def __get_boundary_34_pressure(temperature): + ''' + Temperature is in Kelvin + Pressure is in Pascals + ''' + if (temperature < __critical_temperature): + return (False, None, 'Temperature is less than the critical temperature') + + theta = temperature / 1 + pressure = (__nBoundary34Points[0].n + __nBoundary34Points[1].n * theta + __nBoundary34Points[2].n * pow(theta , 2)) * 1e6 + return (True, pressure, None) + +def __temperature_is_within_range(steam_table_query): + if (not steam_table_query.has_pressure() or not steam_table_query.has_temperature()): + return True + temperature_range = __determine_temperature_range(steam_table_query.get_pressure()) + return temperature_range.isWithinRange(steam_table_query.get_temperature()) + +def __pressure_is_within_range(steam_table_query): + if (not steam_table_query.has_pressure() or not steam_table_query.has_temperature()): + return True + pressure_range = __determine_pressure_range(steam_table_query.get_temperature()) + return pressure_range.isWithinRange(steam_table_query.get_pressure()) + +def __is_saturation_point_query(steam_table_query): + has_temperature = steam_table_query.has_temperature() + has_pressure = steam_table_query.has_pressure() + has_phase_region = steam_table_query.get_phase_region() is not None + has_saturated_flag = steam_table_query.get_is_saturated() is not None + return (has_temperature or has_pressure) and has_phase_region and has_saturated_flag and steam_table_query.get_is_saturated + +def __perform_saturation_point_query(steam_table_query): + temperature = steam_table_query.get_temperature() + pressure = steam_table_query.get_pressure() + + if (steam_table_query.has_pressure()): + (_, temperature, error_msg) = __get_sat_temperature(pressure) + elif (steam_table_query.has_temperature()): + (_, pressure, error_msg) = __get_sat_pressure(temperature) + else : + error_msg = 'Must have pressure or temperature' + + equation_region = __SteamEquationRegion.OutOfRange + phase_region = steam_table_query.get_phase_region() + + if (error_msg is None and phase_region == PhaseRegion.Liquid): + equation_region = __SteamEquationRegion.Region1 + elif (error_msg is None and phase_region == PhaseRegion.Vapor): + equation_region = __SteamEquationRegion.Region2 + else : + error_msg = 'No saturated points can be found for following region : ' + str(phase_region) + + return (equation_region, __PT_Point(pressure=pressure, temperature=temperature), error_msg) + +def __perform_non_saturation_point_query(steam_table_query): + temperature = steam_table_query.get_temperature() + pressure = steam_table_query.get_pressure() + equation_region = __SteamEquationRegion.OutOfRange + (found_sat_pressure, satPressure, _) = __get_sat_pressure(temperature) + (found_boundary_pressure, boundaryPressure, _) = __get_boundary_34_pressure(temperature) + + error_msg = None + if (temperature > (273.15 + 800)): + equation_region = __SteamEquationRegion.Region5 + elif (temperature > (600 + 273.15)): + equation_region = __SteamEquationRegion.Region2 + elif (found_sat_pressure): + if (satPressure == pressure): + equation_region = __SteamEquationRegion.Region4 + elif (satPressure > pressure): + equation_region = __SteamEquationRegion.Region2 + else : + equation_region = __SteamEquationRegion.Region1 + elif (found_boundary_pressure): + if (boundaryPressure > pressure): + equation_region = __SteamEquationRegion.Region2 + else : + equation_region = __SteamEquationRegion.Region3 + else : + error_msg = 'Out of range' + + return (equation_region, __PT_Point(pressure=pressure, temperature=temperature), error_msg) + +def __resolve_region(steam_table_query): + if (not __temperature_is_within_range(steam_table_query)): + return (__SteamEquationRegion.OutOfRange, None, 'Temperature is out of range') + + if (not __pressure_is_within_range(steam_table_query)): + return (__SteamEquationRegion.OutOfRange, None, 'Pressure is out of range') + + if (__is_saturation_point_query(steam_table_query)): + return __perform_saturation_point_query(steam_table_query) + + if (steam_table_query.has_temperature() and steam_table_query.has_pressure()): + return __perform_non_saturation_point_query(steam_table_query) + + return (__SteamEquationRegion.OutOfRange, None, 'Not enough constraints') + +def __create_region_1245_entry(specificRegionPoint, phase_info): + temperature = specificRegionPoint.get_temperature() + pressure = specificRegionPoint.get_pressure() + pi = specificRegionPoint.get_pi() + tau = specificRegionPoint.get_tau() + gamma = specificRegionPoint.get_gamma() + gamma_pi = specificRegionPoint.get_gamma_pi() + gamma_pi_pi = specificRegionPoint.get_gamma_pi_pi() + gamma_tau = specificRegionPoint.get_gamma_tau() + gamma_tau_tau = specificRegionPoint.get_gamma_tau_tau() + gamma_pi_tau = specificRegionPoint.get_gamma_pi_tau() + + return (True, PTVEntry( + _temperature=temperature, + _pressure=pressure, + _phase_info=phase_info, + _internal_energy=__water_gas_constant * temperature * (tau * gamma_tau - pi * gamma_pi), + _enthalpy=__water_gas_constant * temperature * tau * gamma_tau, + _entropy=__water_gas_constant * (tau * gamma_tau - gamma), + _isochoric_heat_capacity=__water_gas_constant * (-pow(-tau, 2) * gamma_tau_tau + pow(gamma_pi - tau * gamma_pi_tau, 2) / gamma_pi_pi), + _isobaric_heat_capacity=__water_gas_constant * -pow(-tau, 2) * gamma_tau_tau, + _speed_of_sound=sqrt(__water_gas_constant * temperature * + ((pow(gamma_pi, 2)) / ((pow(gamma_pi - tau * gamma_pi_tau, 2) / (pow(tau, 2) * gamma_tau_tau)) - gamma_pi_pi))), + _density=1.0 / (pi * (gamma_pi * __water_gas_constant * temperature) / pressure) + ), None) + +def __gibbs_method(pt_point): + pressure = pt_point.pressure + temperature = pt_point.temperature + pi = pressure / 16.53e6 + tau = 1386.0 / temperature + gamma = 0 + gamma_pi = 0 + gamma_pi_pi = 0 + gamma_tau = 0 + gamma_tau_tau = 0 + gamma_pi_tau = 0 + phaseInfo = PhaseInfo(PhaseRegion.Liquid, 0, 1, 0) + + for regionPoint in __region1and4Points: + gamma += regionPoint.n * pow(7.1 - pi, regionPoint.i) * pow(tau - 1.222, regionPoint.j) + gamma_pi += -regionPoint.n * regionPoint.i * pow(7.1 - pi, regionPoint.i - 1) * pow(tau - 1.222, regionPoint.j) + gamma_pi_pi += regionPoint.n * regionPoint.i * (regionPoint.i - 1) * pow(7.1 - pi, regionPoint.i - 2) * pow(tau - 1.222, regionPoint.j) + gamma_tau += regionPoint.n * regionPoint.j * pow(7.1 - pi, regionPoint.i) * pow(tau - 1.222, regionPoint.j - 1) + gamma_tau_tau += regionPoint.n * regionPoint.j * (regionPoint.j - 1) * pow(7.1 - pi, regionPoint.i) * pow(tau - 1.222, regionPoint.j - 2) + gamma_pi_tau += -regionPoint.n * regionPoint.i * regionPoint.j * pow(7.1 - pi, regionPoint.i - 1) * pow(tau - 1.222, regionPoint.j - 1) + + specificRegionPoint = __SpecificRegionPoint( + _temperature=temperature, + _pressure=pressure, + _tau=tau, + _pi=pi, + _gamma=gamma, + _gamma_pi=gamma_pi, + _gamma_pi_pi=gamma_pi_pi, + _gamma_tau=gamma_tau, + _gamma_tau_tau=gamma_tau_tau, + _gamma_pi_tau=gamma_pi_tau + ) + + return __create_region_1245_entry(specificRegionPoint, phaseInfo) + +def __create_phase_info(pt_point): + region = PhaseRegion.Vapor + vaporFrac = 1 + if (pt_point.temperature > __critical_temperature): + if (pt_point.pressure > __critical_pressure): + region = PhaseRegion.SupercriticalFluid + else : + region = PhaseRegion.Gas + vaporFrac = 0 + return PhaseInfo(region, vaporFrac, 0, 0) + +def __vapor_method(tau, + tau_shift, + pt_point, + idealPoints, + residualPoints): + pressure = pt_point.pressure + temperature = pt_point.temperature + pi = pressure / 1.0e6 + gamma = log(pi) + gamma_pi = 1.0 / pi + gamma_pi_pi = -1.0 / pow(pi, 2) + gamma_tau = 0 + gamma_tau_tau = 0 + gamma_pi_tau = 0 + phaseInfo = __create_phase_info(pt_point) + + for regionPoint in idealPoints: + gamma += regionPoint.n * pow(tau, regionPoint.j) + gamma_tau += regionPoint.n * regionPoint.j * pow(tau, regionPoint.j - 1) + gamma_tau_tau += regionPoint.n * regionPoint.j * (regionPoint.j - 1) * pow(tau, regionPoint.j - 2) + + for regionPoint in residualPoints: + gamma += regionPoint.n * pow(pi, regionPoint.i) * pow(tau - tau_shift, regionPoint.j) + gamma_pi += regionPoint.n * regionPoint.i * pow(pi, regionPoint.i - 1) * pow(tau - tau_shift, regionPoint.j) + gamma_pi_pi += regionPoint.n * regionPoint.i * (regionPoint.i - 1) * pow(pi, regionPoint.i - 2) * pow(tau - tau_shift, regionPoint.j) + gamma_tau += regionPoint.n * pow(pi, regionPoint.i) * regionPoint.j * pow(tau - tau_shift, regionPoint.j - 1) + gamma_tau_tau += regionPoint.n * pow(pi, regionPoint.i) * regionPoint.j * (regionPoint.j - 1) * pow(tau - tau_shift, regionPoint.j - 2) + gamma_pi_tau += regionPoint.n * regionPoint.i * pow(pi, regionPoint.i - 1) * regionPoint.j * pow(tau - tau_shift, regionPoint.j - 1) + + specificRegionPoint = __SpecificRegionPoint( + _temperature=temperature, + _pressure=pressure, + _tau=tau, + _pi=pi, + _gamma=gamma, + _gamma_pi=gamma_pi, + _gamma_pi_pi=gamma_pi_pi, + _gamma_tau=gamma_tau, + _gamma_tau_tau=gamma_tau_tau, + _gamma_pi_tau=gamma_pi_tau + ) + + return __create_region_1245_entry(specificRegionPoint, phaseInfo) + +def __region3_density(pt_point, density): + region3_iter = iter(__region3Points) + n1 = next(region3_iter).n + delta = density / 322.0 + temperature = pt_point.temperature + tau = 647.096 / temperature + phi = n1 * log(delta) + phi_delta = n1 / delta + phi_delta_delta = -n1 / pow(delta, 2) + phi_tau = 0 + phi_tau_tau = 0 + phi_delta_tau = 0 + + for regionPoint in region3_iter: + phi += regionPoint.n * pow(delta, regionPoint.i) * pow(tau, regionPoint.j) + phi_delta += regionPoint.n * regionPoint.i * pow(delta, regionPoint.i - 1) * pow(tau, regionPoint.j) + phi_delta_delta += regionPoint.n * regionPoint.i * (regionPoint.i - 1) * pow(delta, regionPoint.i - 2) * pow(tau, regionPoint.j) + phi_tau += regionPoint.n * pow(delta, regionPoint.i) * regionPoint.j * pow(tau, regionPoint.j - 1) + phi_tau_tau += regionPoint.n * pow(delta, regionPoint.i) * regionPoint.j * (regionPoint.j - 1) * pow(tau, regionPoint.j - 2) + phi_delta_tau += regionPoint.n * regionPoint.i * pow(delta, regionPoint.i - 1) * regionPoint.j * pow(tau, regionPoint.j - 1) + + pressure = phi_delta * delta * density * __water_gas_constant * temperature + + phase_info = PhaseInfo(PhaseRegion.SupercriticalFluid, 0, 0, 0) + internal_energy = tau * phi_tau * __water_gas_constant * temperature + enthalpy = (tau * phi_tau + delta * phi_delta) * __water_gas_constant * temperature + entropy = (tau * phi_tau - phi) * __water_gas_constant + isochoric_heat_capacity = -pow(tau, 2) * phi_tau_tau * __water_gas_constant + isobaric_heat_capacity = (-pow(tau, 2) * phi_tau_tau + + pow(delta * phi_delta - delta * tau * phi_delta_tau, 2) + / (2 * delta * phi_delta + pow(delta, 2) * phi_delta_delta)) * __water_gas_constant + + try: + speed_of_sound = sqrt((2 * delta * phi_delta + pow(delta, 2) * phi_delta_delta - + pow(delta * phi_delta - delta * tau * phi_delta_tau, 2) + / (pow(tau, 2) * phi_tau_tau)) * __water_gas_constant * temperature) + except: + speed_of_sound = float('NaN') + + + return PTVEntry( + _temperature=temperature, + _pressure=pressure, + _phase_info=phase_info, + _internal_energy=internal_energy, + _enthalpy=enthalpy, + _entropy=entropy, + _isochoric_heat_capacity=isochoric_heat_capacity, + _isobaric_heat_capacity=isobaric_heat_capacity, + _speed_of_sound=speed_of_sound, + _density=density + ) + +def __region3_method(pt_point): + result = newton(func=lambda x:__region3_density(pt_point, x).get_pressure() - pt_point.pressure, x0=1) + return (True, __region3_density(pt_point, result), None) + +def __is_within_tolerance(actualValue, queryParameter): + if (queryParameter is None): + return True + return isclose(actualValue, queryParameter) + +def __entry_is_within_query_tol(entry, steam_table_query): + if (steam_table_query.has_phase_region() and entry.get_phase_info().get_phase_region() != steam_table_query.get_phase_region()): + return False + + pressure_is_okay = __is_within_tolerance(entry.get_pressure(), steam_table_query.get_pressure()) + temperature_is_okay = __is_within_tolerance(entry.get_temperature(), steam_table_query.get_temperature()) + enthalpy_is_okay = __is_within_tolerance(entry.get_enthalpy(), steam_table_query.get_enthalpy()) + entropy_is_okay = __is_within_tolerance(entry.get_entropy(), steam_table_query.get_entropy()) + + return pressure_is_okay and temperature_is_okay and entropy_is_okay and enthalpy_is_okay + +def __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, get_property): + vaporFrac = 1 - liqFrac + return (get_property(liquid_entry) * liqFrac) + (get_property(vapor_entry) * vaporFrac) + +def __interpolate_entry(liquid_entry, vapor_entry, liqFrac): + phase_info = PhaseInfo(PhaseRegion.LiquidVapor, _vapor=(1 - liqFrac), _liquid=liqFrac, _solid=0) + temperature = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_temperature()) + pressure = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_pressure()) + internal_energy = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_internal_energy()) + enthalpy = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_enthalpy()) + entropy = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_entropy()) + isochoric_heat_capacity = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_isochoric_heat_capacity()) + isobaric_heat_capacity = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_isobaric_heat_capacity()) + speed_of_sound = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_speed_of_sound()) + density = __interpolate_entry_property(liquid_entry, vapor_entry, liqFrac, lambda entry: entry.get_density()) + return PTVEntry( + _temperature=temperature, + _pressure=pressure, + _phase_info=phase_info, + _internal_energy=internal_energy, + _enthalpy=enthalpy, + _entropy=entropy, + _isochoric_heat_capacity=isochoric_heat_capacity, + _isobaric_heat_capacity=isobaric_heat_capacity, + _speed_of_sound=speed_of_sound, + _density=density + ) + +def __get_prop_at_t_and_p(pressure, temperature, get_prop_value): + query = SteamTableQuery(_pressure=pressure, _temperature=temperature) + (passed, entry, _) = __perform_steam_table_query(query) + if (not passed): + return float('NaN') + return get_prop_value(entry) + +def __resolve_non_pressure_temperature_property(pressure, target_value, get_prop_value): + liquid_query = SteamTableQuery(_pressure=pressure, _phase_region=PhaseRegion.Liquid, _is_saturated=True) + vapor_query = SteamTableQuery(_pressure=pressure, _phase_region=PhaseRegion.Vapor, _is_saturated=True) + (liquid_success, liquid_entry, _) = __perform_steam_table_query(liquid_query) + (vapor_success, vapor_entry, _) = __perform_steam_table_query(vapor_query) + is_success = False + entry = None + err_msg = None + if (liquid_success and vapor_success and get_prop_value(vapor_entry) >= target_value and get_prop_value(liquid_entry) <= target_value): + is_success = True + liqFrac = (get_prop_value(vapor_entry) - target_value) / (get_prop_value(vapor_entry) - get_prop_value(liquid_entry)) + entry = __interpolate_entry(liquid_entry, vapor_entry, liqFrac) + else : + result = newton(func=lambda x:__get_prop_at_t_and_p(pressure, x, get_prop_value) - target_value, x0=300) + query = SteamTableQuery(_pressure=pressure, _temperature=result) + (is_success, entry, err_msg) = __perform_steam_table_query(query) + + return (is_success, entry, err_msg) + +def __handle_out_of_range(steam_table_query, default_err_msg): + is_success = False + entry = None + err_msg = default_err_msg + pressure = steam_table_query.get_pressure() + enthalpy = steam_table_query.get_enthalpy() + entropy = steam_table_query.get_entropy() + + if (enthalpy is not None and pressure is not None): + (is_success, entry, err_msg) = __resolve_non_pressure_temperature_property(pressure, enthalpy, lambda entry: entry.get_enthalpy()) + elif (entropy is not None and pressure is not None): + (is_success, entry, err_msg) = __resolve_non_pressure_temperature_property(pressure, entropy, lambda entry: entry.get_entropy()) + return (is_success, entry, err_msg) + +def __perform_steam_table_query(steam_table_query): + (region, pt_point, err_msg) = __resolve_region(steam_table_query) + + if (region == __SteamEquationRegion.OutOfRange): + (is_success, entry, err_msg) = __handle_out_of_range(steam_table_query, err_msg) + elif (region == __SteamEquationRegion.Region1): + (is_success, entry, err_msg) = __gibbs_method(pt_point) + elif (region == __SteamEquationRegion.Region2): + (is_success, entry, err_msg) = __vapor_method(540.0 / pt_point.temperature, 0.5, pt_point, __region2IdealPoints, __region2ResiduaPoints) + elif (region == __SteamEquationRegion.Region3): + (is_success, entry, err_msg) = __region3_method(pt_point) + elif (region == __SteamEquationRegion.Region4): + (is_success, entry, err_msg) = __gibbs_method(pt_point) + elif (region == __SteamEquationRegion.Region5): + (is_success, entry, err_msg) = __vapor_method(1000.0 / pt_point.temperature, 0, pt_point, __region5IdealPoints, __region5ResiduaPoints) + else : + is_success = False + entry = None + err_msg = 'Unknown Steam Region' + + if (is_success and not __entry_is_within_query_tol(entry, steam_table_query)): + is_success = False + entry = None + err_msg = 'Could not find an entry which was within the given tolerance' + + return (is_success, entry, err_msg) + +def get_steam_table_entry( + temperature = None, + pressure = None, + enthalpy = None, + entropy = None, + phase_region = None, + is_saturated = None): + r'''Calculates steam properties given passed values using the IAPWS-97 standard for Industrial Formulation [1]. + + Parameters + ---------- + temperature : float + Temperature [K] + pressure : float + Pressure [Pa] + enthalpy : float + Enthalpy [j / kg] + entropy : float + Entropy [j / (kg * K)] + phase_region : PhaseRegion + The phase of the entry + is_saturated : bool + Entry must be saturated + + Returns + ------- + is_success : bool + did the query execute without error? + entry : PTVEntry + the result of the query - Returns None if is_success = false + error_message : string + error message of query - Returns None if is_success = true + + Notes + ----- + Original model is in terms of J/g/K. Note that the model is for predicting + mass heat capacity, not molar heat capacity like most other methods! + Integral was computed with SymPy. + + Examples + -------- + >>> get_steam_table_entry(pressure=40e6, temperature=473.15) + (True, <__main__.PTVEntry object at 0x7fb0b86add90>, None) + + References + ---------- + [1] Wagner, W., et al. “The IAPWS Industrial Formulation 1997 for + the Thermodynamic Properties of Water and Steam.” + Journal of Engineering for Gas Turbines and Power, + vol. 122, no. 1, 2000, pp. 150–184., + doi:10.1115/1.483186. + ''' + steam_table_query = SteamTableQuery( + _temperature= temperature, + _pressure= pressure, + _enthalpy= enthalpy, + _entropy= entropy, + _phase_region= phase_region, + _is_saturated= is_saturated + ) + return __perform_steam_table_query(steam_table_query) + + +(actual_is_success, actual_entry, actual_error_message) = get_steam_table_entry( + pressure=10e3, + temperature=None, + enthalpy=None, + entropy=6.6858e3, + phase_region=None, + is_saturated=None) +_ = 1