pychnosz 1.1.12__cp310-cp310-macosx_15_0_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pychnosz/.dylibs/libgcc_s.1.1.dylib +0 -0
- pychnosz/.dylibs/libgfortran.5.dylib +0 -0
- pychnosz/.dylibs/libquadmath.0.dylib +0 -0
- pychnosz/__init__.py +129 -0
- pychnosz/_version.py +34 -0
- pychnosz/biomolecules/__init__.py +29 -0
- pychnosz/biomolecules/ionize_aa.py +197 -0
- pychnosz/biomolecules/proteins.py +595 -0
- pychnosz/core/__init__.py +46 -0
- pychnosz/core/affinity.py +1256 -0
- pychnosz/core/animation.py +593 -0
- pychnosz/core/balance.py +334 -0
- pychnosz/core/basis.py +716 -0
- pychnosz/core/diagram.py +3336 -0
- pychnosz/core/equilibrate.py +813 -0
- pychnosz/core/equilibrium.py +554 -0
- pychnosz/core/info.py +821 -0
- pychnosz/core/retrieve.py +364 -0
- pychnosz/core/speciation.py +580 -0
- pychnosz/core/species.py +599 -0
- pychnosz/core/subcrt.py +1696 -0
- pychnosz/core/thermo.py +593 -0
- pychnosz/core/unicurve.py +1226 -0
- pychnosz/data/__init__.py +11 -0
- pychnosz/data/add_obigt.py +327 -0
- pychnosz/data/extdata/Berman/BDat17_2017.csv +2 -0
- pychnosz/data/extdata/Berman/Ber88_1988.csv +68 -0
- pychnosz/data/extdata/Berman/Ber90_1990.csv +5 -0
- pychnosz/data/extdata/Berman/DS10_2010.csv +6 -0
- pychnosz/data/extdata/Berman/FDM+14_2014.csv +2 -0
- pychnosz/data/extdata/Berman/Got04_2004.csv +5 -0
- pychnosz/data/extdata/Berman/JUN92_1992.csv +3 -0
- pychnosz/data/extdata/Berman/SHD91_1991.csv +12 -0
- pychnosz/data/extdata/Berman/VGT92_1992.csv +2 -0
- pychnosz/data/extdata/Berman/VPT01_2001.csv +3 -0
- pychnosz/data/extdata/Berman/VPV05_2005.csv +2 -0
- pychnosz/data/extdata/Berman/ZS92_1992.csv +11 -0
- pychnosz/data/extdata/Berman/sympy.R +99 -0
- pychnosz/data/extdata/Berman/testing/BA96.bib +12 -0
- pychnosz/data/extdata/Berman/testing/BA96_Berman.csv +21 -0
- pychnosz/data/extdata/Berman/testing/BA96_OBIGT.csv +21 -0
- pychnosz/data/extdata/Berman/testing/BA96_refs.csv +6 -0
- pychnosz/data/extdata/OBIGT/AD.csv +25 -0
- pychnosz/data/extdata/OBIGT/Berman_cr.csv +93 -0
- pychnosz/data/extdata/OBIGT/DEW.csv +211 -0
- pychnosz/data/extdata/OBIGT/H2O_aq.csv +4 -0
- pychnosz/data/extdata/OBIGT/SLOP98.csv +411 -0
- pychnosz/data/extdata/OBIGT/SUPCRT92.csv +178 -0
- pychnosz/data/extdata/OBIGT/inorganic_aq.csv +729 -0
- pychnosz/data/extdata/OBIGT/inorganic_cr.csv +273 -0
- pychnosz/data/extdata/OBIGT/inorganic_gas.csv +20 -0
- pychnosz/data/extdata/OBIGT/organic_aq.csv +1104 -0
- pychnosz/data/extdata/OBIGT/organic_cr.csv +481 -0
- pychnosz/data/extdata/OBIGT/organic_gas.csv +268 -0
- pychnosz/data/extdata/OBIGT/organic_liq.csv +533 -0
- pychnosz/data/extdata/OBIGT/testing/GEMSFIT.csv +43 -0
- pychnosz/data/extdata/OBIGT/testing/IGEM.csv +17 -0
- pychnosz/data/extdata/OBIGT/testing/Sandia.csv +8 -0
- pychnosz/data/extdata/OBIGT/testing/SiO2.csv +4 -0
- pychnosz/data/extdata/misc/AD03_Fig1a.csv +69 -0
- pychnosz/data/extdata/misc/AD03_Fig1b.csv +43 -0
- pychnosz/data/extdata/misc/AD03_Fig1c.csv +89 -0
- pychnosz/data/extdata/misc/AD03_Fig1d.csv +30 -0
- pychnosz/data/extdata/misc/BZA10.csv +5 -0
- pychnosz/data/extdata/misc/HW97_Cp.csv +90 -0
- pychnosz/data/extdata/misc/HWM96_V.csv +229 -0
- pychnosz/data/extdata/misc/LA19_test.csv +7 -0
- pychnosz/data/extdata/misc/Mer75_Table4.csv +42 -0
- pychnosz/data/extdata/misc/OBIGT_check.csv +423 -0
- pychnosz/data/extdata/misc/PM90.csv +7 -0
- pychnosz/data/extdata/misc/RH95.csv +23 -0
- pychnosz/data/extdata/misc/RH98_Table15.csv +17 -0
- pychnosz/data/extdata/misc/SC10_Rainbow.csv +19 -0
- pychnosz/data/extdata/misc/SK95.csv +55 -0
- pychnosz/data/extdata/misc/SOJSH.csv +61 -0
- pychnosz/data/extdata/misc/SS98_Fig5a.csv +81 -0
- pychnosz/data/extdata/misc/SS98_Fig5b.csv +84 -0
- pychnosz/data/extdata/misc/TKSS14_Fig2.csv +25 -0
- pychnosz/data/extdata/misc/bluered.txt +1000 -0
- pychnosz/data/extdata/protein/Cas/Cas_aa.csv +177 -0
- pychnosz/data/extdata/protein/Cas/Cas_uniprot.csv +186 -0
- pychnosz/data/extdata/protein/Cas/download.R +34 -0
- pychnosz/data/extdata/protein/Cas/mkaa.R +34 -0
- pychnosz/data/extdata/protein/POLG.csv +12 -0
- pychnosz/data/extdata/protein/TBD+05.csv +393 -0
- pychnosz/data/extdata/protein/TBD+05_aa.csv +393 -0
- pychnosz/data/extdata/protein/rubisco.csv +28 -0
- pychnosz/data/extdata/protein/rubisco.fasta +239 -0
- pychnosz/data/extdata/protein/rubisco_aa.csv +28 -0
- pychnosz/data/extdata/src/H2O92D.f.orig +3457 -0
- pychnosz/data/extdata/src/README.txt +5 -0
- pychnosz/data/extdata/taxonomy/names.dmp +215 -0
- pychnosz/data/extdata/taxonomy/nodes.dmp +63 -0
- pychnosz/data/extdata/thermo/Bdot_acirc.csv +60 -0
- pychnosz/data/extdata/thermo/buffer.csv +40 -0
- pychnosz/data/extdata/thermo/element.csv +135 -0
- pychnosz/data/extdata/thermo/groups.csv +6 -0
- pychnosz/data/extdata/thermo/opt.csv +2 -0
- pychnosz/data/extdata/thermo/protein.csv +506 -0
- pychnosz/data/extdata/thermo/refs.csv +343 -0
- pychnosz/data/extdata/thermo/stoich.csv.xz +0 -0
- pychnosz/data/loader.py +431 -0
- pychnosz/data/mod_obigt.py +322 -0
- pychnosz/data/obigt.py +471 -0
- pychnosz/data/worm.py +228 -0
- pychnosz/fortran/.gitignore +6 -0
- pychnosz/fortran/__init__.py +16 -0
- pychnosz/fortran/h2o92.dylib +0 -0
- pychnosz/fortran/h2o92_interface.py +527 -0
- pychnosz/geochemistry/__init__.py +21 -0
- pychnosz/geochemistry/minerals.py +514 -0
- pychnosz/geochemistry/redox.py +500 -0
- pychnosz/models/__init__.py +47 -0
- pychnosz/models/archer_wang.py +165 -0
- pychnosz/models/berman.py +309 -0
- pychnosz/models/cgl.py +381 -0
- pychnosz/models/dew.py +997 -0
- pychnosz/models/hkf.py +523 -0
- pychnosz/models/hkf_helpers.py +231 -0
- pychnosz/models/iapws95.py +1113 -0
- pychnosz/models/supcrt92_fortran.py +238 -0
- pychnosz/models/water.py +480 -0
- pychnosz/utils/__init__.py +27 -0
- pychnosz/utils/expression.py +1074 -0
- pychnosz/utils/formula.py +830 -0
- pychnosz/utils/formula_ox.py +227 -0
- pychnosz/utils/reset.py +33 -0
- pychnosz/utils/units.py +259 -0
- pychnosz-1.1.12.dist-info/METADATA +197 -0
- pychnosz-1.1.12.dist-info/RECORD +133 -0
- pychnosz-1.1.12.dist-info/WHEEL +5 -0
- pychnosz-1.1.12.dist-info/licenses/LICENSE.txt +19 -0
- pychnosz-1.1.12.dist-info/top_level.txt +1 -0
pychnosz/models/water.py
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Water properties calculation module.
|
|
3
|
+
|
|
4
|
+
This module provides Python equivalents of the R functions in water.R:
|
|
5
|
+
- water(): Calculate thermodynamic and electrostatic properties of H2O
|
|
6
|
+
- Support for multiple water models (SUPCRT92, IAPWS95, DEW)
|
|
7
|
+
- Automatic model selection and property calculation
|
|
8
|
+
|
|
9
|
+
Author: CHNOSZ Python port
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import pandas as pd
|
|
13
|
+
import numpy as np
|
|
14
|
+
import warnings
|
|
15
|
+
from typing import Union, List, Optional, Dict, Any
|
|
16
|
+
|
|
17
|
+
from ..core.thermo import thermo
|
|
18
|
+
from .supcrt92_fortran import water_SUPCRT92
|
|
19
|
+
from .iapws95 import water_IAPWS95
|
|
20
|
+
from .dew import water_DEW
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class WaterModelError(Exception):
|
|
24
|
+
"""Exception raised for water model calculation errors."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def water(property: Optional[Union[str, List[str]]] = None,
|
|
29
|
+
T: Union[float, np.ndarray, List[float]] = 298.15,
|
|
30
|
+
P: Union[float, np.ndarray, List[float], str] = 1.0,
|
|
31
|
+
Psat_floor: Union[float, None] = 1.0,
|
|
32
|
+
model: Optional[str] = None,
|
|
33
|
+
messages: bool = True) -> Union[str, float, np.ndarray, Dict[str, Any]]:
|
|
34
|
+
"""
|
|
35
|
+
Calculate thermodynamic and electrostatic properties of liquid H2O.
|
|
36
|
+
|
|
37
|
+
This is the main water function that provides the same interface as the
|
|
38
|
+
R CHNOSZ water() function, with support for multiple water models.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
property : str, list of str, or None
|
|
43
|
+
Properties to calculate. If None, returns current water model.
|
|
44
|
+
If water model name (SUPCRT92, IAPWS95, DEW), sets the water model.
|
|
45
|
+
Available properties depend on the water model used.
|
|
46
|
+
T : float or array-like
|
|
47
|
+
Temperature in Kelvin
|
|
48
|
+
P : float, array-like, or "Psat"
|
|
49
|
+
Pressure in bar, or "Psat" for saturation pressure
|
|
50
|
+
Psat_floor : float or None
|
|
51
|
+
Minimum pressure floor for Psat calculations (SUPCRT92 only)
|
|
52
|
+
model : str, optional
|
|
53
|
+
Override the default water model for this calculation
|
|
54
|
+
messages : bool, default True
|
|
55
|
+
Whether to print informational messages
|
|
56
|
+
|
|
57
|
+
Returns
|
|
58
|
+
-------
|
|
59
|
+
str, float, array, or dict
|
|
60
|
+
Current water model name, single property value, array of values,
|
|
61
|
+
or dictionary with calculated properties
|
|
62
|
+
|
|
63
|
+
Examples
|
|
64
|
+
--------
|
|
65
|
+
>>> import pychnosz
|
|
66
|
+
>>> pychnosz.reset()
|
|
67
|
+
>>>
|
|
68
|
+
>>> # Get current water model
|
|
69
|
+
>>> model = pychnosz.water()
|
|
70
|
+
>>> print(model) # 'SUPCRT92'
|
|
71
|
+
>>>
|
|
72
|
+
>>> # Set water model
|
|
73
|
+
>>> old_model = pychnosz.water('IAPWS95')
|
|
74
|
+
>>>
|
|
75
|
+
>>> # Calculate single property
|
|
76
|
+
>>> density = pychnosz.water('rho', T=298.15, P=1.0)
|
|
77
|
+
>>>
|
|
78
|
+
>>> # Calculate multiple properties
|
|
79
|
+
>>> props = pychnosz.water(['rho', 'epsilon'], T=298.15, P=1.0)
|
|
80
|
+
>>>
|
|
81
|
+
>>> # Temperature array
|
|
82
|
+
>>> temps = np.array([273.15, 298.15, 373.15])
|
|
83
|
+
>>> densities = pychnosz.water('rho', T=temps, P=1.0)
|
|
84
|
+
>>>
|
|
85
|
+
>>> # Saturation pressure
|
|
86
|
+
>>> psat = pychnosz.water('Psat', T=373.15)
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
# Get thermo system
|
|
90
|
+
thermo_system = thermo()
|
|
91
|
+
|
|
92
|
+
# Ensure thermo is initialized before accessing/setting options
|
|
93
|
+
# This prevents reset() from clearing options later
|
|
94
|
+
if not thermo_system.is_initialized():
|
|
95
|
+
thermo_system.reset(messages=False)
|
|
96
|
+
|
|
97
|
+
# Case 1: Query current water model
|
|
98
|
+
if property is None:
|
|
99
|
+
return thermo_system.get_option('water', 'SUPCRT92')
|
|
100
|
+
|
|
101
|
+
# Case 2: Set water model
|
|
102
|
+
if isinstance(property, str) and property.upper() in ['SUPCRT92', 'SUPCRT', 'IAPWS95', 'IAPWS', 'DEW']:
|
|
103
|
+
old_model = thermo_system.get_option('water', 'SUPCRT92')
|
|
104
|
+
|
|
105
|
+
# Normalize model name
|
|
106
|
+
if property.upper() in ['SUPCRT92', 'SUPCRT']:
|
|
107
|
+
new_model = 'SUPCRT92'
|
|
108
|
+
elif property.upper() in ['IAPWS95', 'IAPWS']:
|
|
109
|
+
new_model = 'IAPWS95'
|
|
110
|
+
elif property.upper() == 'DEW':
|
|
111
|
+
new_model = 'DEW'
|
|
112
|
+
|
|
113
|
+
thermo_system.set_option('water', new_model)
|
|
114
|
+
if messages:
|
|
115
|
+
print(f"water: setting water model to {new_model}")
|
|
116
|
+
return # Return None instead of the old model
|
|
117
|
+
|
|
118
|
+
# Case 3: Calculate properties
|
|
119
|
+
# Determine which model to use
|
|
120
|
+
if model is not None:
|
|
121
|
+
water_model = model.upper()
|
|
122
|
+
else:
|
|
123
|
+
water_model = thermo_system.get_option('water', 'SUPCRT92').upper()
|
|
124
|
+
|
|
125
|
+
# Normalize model names
|
|
126
|
+
if water_model in ['SUPCRT92', 'SUPCRT']:
|
|
127
|
+
water_model = 'SUPCRT92'
|
|
128
|
+
elif water_model in ['IAPWS95', 'IAPWS']:
|
|
129
|
+
water_model = 'IAPWS95'
|
|
130
|
+
elif water_model == 'DEW':
|
|
131
|
+
water_model = 'DEW'
|
|
132
|
+
else:
|
|
133
|
+
warnings.warn(f"Unknown water model '{water_model}', using SUPCRT92")
|
|
134
|
+
water_model = 'SUPCRT92'
|
|
135
|
+
|
|
136
|
+
# Convert inputs
|
|
137
|
+
T = np.atleast_1d(np.asarray(T, dtype=float))
|
|
138
|
+
|
|
139
|
+
if isinstance(P, str):
|
|
140
|
+
P_input = P
|
|
141
|
+
else:
|
|
142
|
+
P_input = np.atleast_1d(np.asarray(P, dtype=float))
|
|
143
|
+
# Make T and P same length
|
|
144
|
+
if len(P_input) < len(T):
|
|
145
|
+
P_input = np.resize(P_input, len(T))
|
|
146
|
+
elif len(T) < len(P_input):
|
|
147
|
+
T = np.resize(T, len(P_input))
|
|
148
|
+
|
|
149
|
+
# Call appropriate water model
|
|
150
|
+
try:
|
|
151
|
+
if water_model == 'SUPCRT92':
|
|
152
|
+
result = _call_supcrt92(property, T, P_input, Psat_floor)
|
|
153
|
+
elif water_model == 'IAPWS95':
|
|
154
|
+
result = _call_iapws95(property, T, P_input, Psat_floor)
|
|
155
|
+
elif water_model == 'DEW':
|
|
156
|
+
result = _call_dew(property, T, P_input)
|
|
157
|
+
else:
|
|
158
|
+
raise ValueError(f"Unsupported water model: {water_model}")
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
raise WaterModelError(f"Error calculating water properties with {water_model} model: {e}")
|
|
162
|
+
|
|
163
|
+
# Apply Psat rounding to match R CHNOSZ behavior
|
|
164
|
+
# Round Psat values to 4 decimal places (round up to ensure liquid phase)
|
|
165
|
+
result = _apply_psat_rounding(result, property)
|
|
166
|
+
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _call_supcrt92(property: Union[str, List[str]],
|
|
171
|
+
T: np.ndarray,
|
|
172
|
+
P: Union[np.ndarray, str],
|
|
173
|
+
Psat_floor: Union[float, None]) -> Union[float, np.ndarray, Dict[str, Any]]:
|
|
174
|
+
"""Call SUPCRT92 water model."""
|
|
175
|
+
|
|
176
|
+
# Check if Psat property is requested - if so, automatically set P="Psat"
|
|
177
|
+
# This matches R CHNOSZ behavior where water("Psat", T=298.15) works
|
|
178
|
+
properties_list = property if isinstance(property, list) else [property]
|
|
179
|
+
if "Psat" in properties_list:
|
|
180
|
+
P_converted = "Psat"
|
|
181
|
+
elif isinstance(P, str) and P == "Psat":
|
|
182
|
+
P_converted = "Psat"
|
|
183
|
+
else:
|
|
184
|
+
P_converted = P # Already in bar
|
|
185
|
+
|
|
186
|
+
# Handle Psat_floor
|
|
187
|
+
kwargs = {}
|
|
188
|
+
if Psat_floor is not None:
|
|
189
|
+
kwargs['Psat_floor'] = Psat_floor
|
|
190
|
+
|
|
191
|
+
return water_SUPCRT92(property, T, P_converted, **kwargs)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _call_iapws95(property: Union[str, List[str]],
|
|
195
|
+
T: np.ndarray,
|
|
196
|
+
P: Union[np.ndarray, str],
|
|
197
|
+
Psat_floor: Union[float, None]) -> Union[float, np.ndarray, Dict[str, Any]]:
|
|
198
|
+
"""Call IAPWS95 water model using accurate implementation."""
|
|
199
|
+
|
|
200
|
+
# Use the accurate IAPWS95 implementation that matches R CHNOSZ exactly
|
|
201
|
+
from .iapws95 import water_IAPWS95_accurate
|
|
202
|
+
|
|
203
|
+
# Check if Psat property is requested - if so, automatically set P="Psat"
|
|
204
|
+
# This matches R CHNOSZ behavior where water("Psat", T=298.15) works
|
|
205
|
+
properties_list = property if isinstance(property, list) else [property]
|
|
206
|
+
if "Psat" in properties_list:
|
|
207
|
+
P = "Psat"
|
|
208
|
+
|
|
209
|
+
# Handle Psat calculation
|
|
210
|
+
if isinstance(P, str) and P == "Psat":
|
|
211
|
+
# For Psat requests, we need to calculate saturation pressure
|
|
212
|
+
# This is not directly implemented yet, so we'll fall back to SUPCRT92
|
|
213
|
+
try:
|
|
214
|
+
# Use the FORTRAN SUPCRT92 implementation for Psat calculation
|
|
215
|
+
kwargs = {}
|
|
216
|
+
if Psat_floor is not None:
|
|
217
|
+
kwargs['Psat_floor'] = Psat_floor
|
|
218
|
+
return water_SUPCRT92(property, T, P, **kwargs)
|
|
219
|
+
except Exception:
|
|
220
|
+
raise NotImplementedError("Psat calculation not yet implemented for IAPWS95")
|
|
221
|
+
|
|
222
|
+
# Use accurate IAPWS95 implementation
|
|
223
|
+
# P is already in bar, which is what the accurate implementation expects
|
|
224
|
+
result = water_IAPWS95_accurate(property, T=T, P=P)
|
|
225
|
+
|
|
226
|
+
# Check if any properties returned NaN and fall back to SUPCRT92 for those
|
|
227
|
+
if isinstance(result, dict):
|
|
228
|
+
# Multiple properties case
|
|
229
|
+
fallback_needed = {}
|
|
230
|
+
for prop, value in result.items():
|
|
231
|
+
if isinstance(value, np.ndarray):
|
|
232
|
+
if np.any(np.isnan(value)):
|
|
233
|
+
fallback_needed[prop] = value
|
|
234
|
+
elif np.isnan(value):
|
|
235
|
+
fallback_needed[prop] = value
|
|
236
|
+
|
|
237
|
+
if fallback_needed:
|
|
238
|
+
# Get fallback values from SUPCRT92
|
|
239
|
+
fallback_props = list(fallback_needed.keys())
|
|
240
|
+
kwargs = {}
|
|
241
|
+
if Psat_floor is not None:
|
|
242
|
+
kwargs['Psat_floor'] = Psat_floor
|
|
243
|
+
fallback_result = water_SUPCRT92(fallback_props, T, P, **kwargs)
|
|
244
|
+
|
|
245
|
+
# Replace NaN values with SUPCRT92 results
|
|
246
|
+
if isinstance(fallback_result, dict):
|
|
247
|
+
for prop in fallback_props:
|
|
248
|
+
if prop in fallback_result:
|
|
249
|
+
result[prop] = fallback_result[prop]
|
|
250
|
+
elif len(fallback_props) == 1:
|
|
251
|
+
result[fallback_props[0]] = fallback_result
|
|
252
|
+
|
|
253
|
+
elif isinstance(result, np.ndarray) and np.any(np.isnan(result)):
|
|
254
|
+
# Single property array case with NaN values
|
|
255
|
+
kwargs = {}
|
|
256
|
+
if Psat_floor is not None:
|
|
257
|
+
kwargs['Psat_floor'] = Psat_floor
|
|
258
|
+
result = water_SUPCRT92(property, T, P, **kwargs)
|
|
259
|
+
|
|
260
|
+
elif np.isscalar(result) and np.isnan(result):
|
|
261
|
+
# Single property scalar case with NaN
|
|
262
|
+
kwargs = {}
|
|
263
|
+
if Psat_floor is not None:
|
|
264
|
+
kwargs['Psat_floor'] = Psat_floor
|
|
265
|
+
result = water_SUPCRT92(property, T, P, **kwargs)
|
|
266
|
+
|
|
267
|
+
return result
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _call_dew(property: Union[str, List[str]],
|
|
271
|
+
T: np.ndarray,
|
|
272
|
+
P: Union[np.ndarray, str]) -> Union[float, np.ndarray, Dict[str, Any]]:
|
|
273
|
+
"""Call DEW water model."""
|
|
274
|
+
# DEW uses bar, same as input
|
|
275
|
+
return water_DEW(property, T, P)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _apply_psat_rounding(result: Union[float, np.ndarray, Dict[str, Any]],
|
|
279
|
+
property: Union[str, List[str]]) -> Union[float, np.ndarray, Dict[str, Any]]:
|
|
280
|
+
"""
|
|
281
|
+
Apply Psat rounding to match R CHNOSZ behavior.
|
|
282
|
+
|
|
283
|
+
R CHNOSZ rounds Psat to 4 decimal places. This ensures we get the same
|
|
284
|
+
pressure values and helps maintain water in liquid phase at saturation.
|
|
285
|
+
|
|
286
|
+
Examples:
|
|
287
|
+
- 165.21128856501093 -> 165.2113 (rounds up, stays in liquid phase)
|
|
288
|
+
"""
|
|
289
|
+
import math
|
|
290
|
+
|
|
291
|
+
if isinstance(result, dict):
|
|
292
|
+
# Multiple properties - check if Psat is among them
|
|
293
|
+
if 'Psat' in result:
|
|
294
|
+
psat_val = result['Psat']
|
|
295
|
+
if isinstance(psat_val, np.ndarray):
|
|
296
|
+
# Round up each element to 4 decimal places, handle NaN values
|
|
297
|
+
result['Psat'] = np.where(np.isnan(psat_val), psat_val, np.ceil(psat_val * 10000) / 10000)
|
|
298
|
+
else:
|
|
299
|
+
# Single value - round up to 4 decimal places, handle NaN
|
|
300
|
+
if not np.isnan(psat_val):
|
|
301
|
+
result['Psat'] = math.ceil(psat_val * 10000) / 10000
|
|
302
|
+
|
|
303
|
+
elif isinstance(property, str) and property == 'Psat':
|
|
304
|
+
# Single Psat property
|
|
305
|
+
if isinstance(result, np.ndarray):
|
|
306
|
+
# Round up each element to 4 decimal places, handle NaN values
|
|
307
|
+
result = np.where(np.isnan(result), result, np.ceil(result * 10000) / 10000)
|
|
308
|
+
else:
|
|
309
|
+
# Single value - round up to 4 decimal places, handle NaN
|
|
310
|
+
if not np.isnan(result):
|
|
311
|
+
result = math.ceil(result * 10000) / 10000
|
|
312
|
+
|
|
313
|
+
elif isinstance(property, list) and 'Psat' in property:
|
|
314
|
+
# Property list containing Psat - this shouldn't happen with current structure
|
|
315
|
+
# but handle it for completeness
|
|
316
|
+
pass
|
|
317
|
+
|
|
318
|
+
return result
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def available_properties(model: str = 'SUPCRT92') -> List[str]:
|
|
322
|
+
"""
|
|
323
|
+
Get list of available properties for a water model.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
model : str
|
|
328
|
+
Water model name ('SUPCRT92', 'IAPWS95', or 'DEW')
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
List[str]
|
|
333
|
+
List of available property names
|
|
334
|
+
"""
|
|
335
|
+
model = model.upper()
|
|
336
|
+
|
|
337
|
+
if model in ['SUPCRT92', 'SUPCRT']:
|
|
338
|
+
from .supcrt92 import supcrt92_water
|
|
339
|
+
return supcrt92_water.available_properties()
|
|
340
|
+
elif model in ['IAPWS95', 'IAPWS']:
|
|
341
|
+
from .iapws95 import iapws95_water
|
|
342
|
+
return iapws95_water.available_properties()
|
|
343
|
+
elif model == 'DEW':
|
|
344
|
+
from .dew import dew_water
|
|
345
|
+
return dew_water.available_properties()
|
|
346
|
+
else:
|
|
347
|
+
raise ValueError(f"Unknown water model: {model}")
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def get_water_models() -> List[str]:
|
|
351
|
+
"""
|
|
352
|
+
Get list of available water models.
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
List[str]
|
|
357
|
+
List of available water model names
|
|
358
|
+
"""
|
|
359
|
+
return ['SUPCRT92', 'IAPWS95', 'DEW']
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def compare_models(property: str,
|
|
363
|
+
T: Union[float, np.ndarray] = 298.15,
|
|
364
|
+
P: Union[float, np.ndarray] = 1.0) -> pd.DataFrame:
|
|
365
|
+
"""
|
|
366
|
+
Compare water property calculations across different models.
|
|
367
|
+
|
|
368
|
+
Parameters
|
|
369
|
+
----------
|
|
370
|
+
property : str
|
|
371
|
+
Property to compare
|
|
372
|
+
T : float or array
|
|
373
|
+
Temperature in Kelvin
|
|
374
|
+
P : float or array
|
|
375
|
+
Pressure in bar
|
|
376
|
+
|
|
377
|
+
Returns
|
|
378
|
+
-------
|
|
379
|
+
pd.DataFrame
|
|
380
|
+
Comparison of property values from different models
|
|
381
|
+
"""
|
|
382
|
+
T = np.atleast_1d(np.asarray(T, dtype=float))
|
|
383
|
+
P = np.atleast_1d(np.asarray(P, dtype=float))
|
|
384
|
+
|
|
385
|
+
results = {}
|
|
386
|
+
models = ['SUPCRT92', 'IAPWS95', 'DEW']
|
|
387
|
+
|
|
388
|
+
for model in models:
|
|
389
|
+
try:
|
|
390
|
+
if model == 'SUPCRT92':
|
|
391
|
+
result = water_SUPCRT92(property, T, P)
|
|
392
|
+
elif model == 'IAPWS95':
|
|
393
|
+
# Convert bar to kPa for IAPWS95
|
|
394
|
+
result = water_IAPWS95(property, T, P * 100.0)
|
|
395
|
+
# Convert units back if needed
|
|
396
|
+
if property == 'rho':
|
|
397
|
+
result = result / 1000.0 # kg/m³ to g/cm³
|
|
398
|
+
elif model == 'DEW':
|
|
399
|
+
result = water_DEW(property, T, P)
|
|
400
|
+
|
|
401
|
+
results[model] = result
|
|
402
|
+
|
|
403
|
+
except Exception as e:
|
|
404
|
+
print(f"Error with {model}: {e}")
|
|
405
|
+
results[model] = np.full_like(T, np.nan)
|
|
406
|
+
|
|
407
|
+
# Create DataFrame
|
|
408
|
+
if len(T) == 1 and len(P) == 1:
|
|
409
|
+
# Single point
|
|
410
|
+
data = {model: [results[model]] if np.isscalar(results[model]) else results[model]
|
|
411
|
+
for model in models}
|
|
412
|
+
df = pd.DataFrame(data, index=[f"T={T[0]:.1f}K, P={P[0]:.1f}bar"])
|
|
413
|
+
else:
|
|
414
|
+
# Multiple points
|
|
415
|
+
data = {model: results[model] for model in models}
|
|
416
|
+
index = [f"T={t:.1f}K, P={p:.1f}bar" for t, p in zip(T, P)]
|
|
417
|
+
df = pd.DataFrame(data, index=index)
|
|
418
|
+
|
|
419
|
+
return df
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
if __name__ == "__main__":
|
|
423
|
+
# Quick test of water models
|
|
424
|
+
print("CHNOSZ Water Models Test")
|
|
425
|
+
print("=" * 30)
|
|
426
|
+
|
|
427
|
+
# Test basic functionality
|
|
428
|
+
T_test = 298.15
|
|
429
|
+
P_test = 1.0
|
|
430
|
+
|
|
431
|
+
print(f"Water at {T_test} K, {P_test} bar:")
|
|
432
|
+
|
|
433
|
+
# Test each model
|
|
434
|
+
for model in ['SUPCRT92', 'IAPWS95', 'DEW']:
|
|
435
|
+
try:
|
|
436
|
+
if model == 'SUPCRT92':
|
|
437
|
+
rho = water_SUPCRT92('rho', T_test, P_test)
|
|
438
|
+
epsilon = water_SUPCRT92('epsilon', T_test, P_test)
|
|
439
|
+
elif model == 'IAPWS95':
|
|
440
|
+
rho = water_IAPWS95('rho', T_test, P_test * 100) / 1000.0 # Convert to g/cm³
|
|
441
|
+
epsilon = water_IAPWS95('epsilon', T_test, P_test * 100)
|
|
442
|
+
elif model == 'DEW':
|
|
443
|
+
rho = water_DEW('rho', T_test, P_test)
|
|
444
|
+
epsilon = water_DEW('epsilon', T_test, P_test)
|
|
445
|
+
|
|
446
|
+
print(f" {model}: ρ = {rho:.3f} g/cm³, ε = {epsilon:.1f}")
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
print(f" {model}: Error - {e}")
|
|
450
|
+
|
|
451
|
+
# Test unified interface
|
|
452
|
+
print("\nTesting unified water() interface:")
|
|
453
|
+
|
|
454
|
+
# This would require the thermo system to be initialized
|
|
455
|
+
try:
|
|
456
|
+
from ..core.thermo import thermo
|
|
457
|
+
from ..utils.reset import reset
|
|
458
|
+
|
|
459
|
+
reset() # Initialize system
|
|
460
|
+
|
|
461
|
+
# Test model switching
|
|
462
|
+
old_model = water('IAPWS95')
|
|
463
|
+
current_model = water()
|
|
464
|
+
print(f"Switched from {old_model} to {current_model}")
|
|
465
|
+
|
|
466
|
+
# Test property calculation
|
|
467
|
+
density = water('rho', T=298.15, P=1.0)
|
|
468
|
+
print(f"Density: {density:.3f}")
|
|
469
|
+
|
|
470
|
+
except Exception as e:
|
|
471
|
+
print(f"Unified interface test failed: {e}")
|
|
472
|
+
print("(This is expected if run standalone)")
|
|
473
|
+
|
|
474
|
+
# Test comparison
|
|
475
|
+
print("\nModel comparison:")
|
|
476
|
+
try:
|
|
477
|
+
comp = compare_models('rho', T=298.15, P=1.0)
|
|
478
|
+
print(comp)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
print(f"Comparison failed: {e}")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Utility functions for CHNOSZ calculations."""
|
|
2
|
+
|
|
3
|
+
from .reset import reset
|
|
4
|
+
from .formula import makeup, get_formula, as_chemical_formula, mass, entropy, species_basis, calculate_ghs, ZC, i2A, FormulaError
|
|
5
|
+
|
|
6
|
+
# Optional imports for modules that may not exist yet
|
|
7
|
+
try:
|
|
8
|
+
from .units import convert_units
|
|
9
|
+
except ImportError:
|
|
10
|
+
convert_units = None
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from .data import load_data
|
|
14
|
+
except ImportError:
|
|
15
|
+
load_data = None
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
'reset',
|
|
19
|
+
'makeup', 'get_formula', 'as_chemical_formula', 'mass', 'entropy',
|
|
20
|
+
'species_basis', 'calculate_ghs', 'ZC', 'i2A', 'FormulaError'
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
# Add optional functions if they exist
|
|
24
|
+
if convert_units is not None:
|
|
25
|
+
__all__.append('convert_units')
|
|
26
|
+
if load_data is not None:
|
|
27
|
+
__all__.append('load_data')
|