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.
Files changed (133) hide show
  1. pychnosz/.dylibs/libgcc_s.1.1.dylib +0 -0
  2. pychnosz/.dylibs/libgfortran.5.dylib +0 -0
  3. pychnosz/.dylibs/libquadmath.0.dylib +0 -0
  4. pychnosz/__init__.py +129 -0
  5. pychnosz/_version.py +34 -0
  6. pychnosz/biomolecules/__init__.py +29 -0
  7. pychnosz/biomolecules/ionize_aa.py +197 -0
  8. pychnosz/biomolecules/proteins.py +595 -0
  9. pychnosz/core/__init__.py +46 -0
  10. pychnosz/core/affinity.py +1256 -0
  11. pychnosz/core/animation.py +593 -0
  12. pychnosz/core/balance.py +334 -0
  13. pychnosz/core/basis.py +716 -0
  14. pychnosz/core/diagram.py +3336 -0
  15. pychnosz/core/equilibrate.py +813 -0
  16. pychnosz/core/equilibrium.py +554 -0
  17. pychnosz/core/info.py +821 -0
  18. pychnosz/core/retrieve.py +364 -0
  19. pychnosz/core/speciation.py +580 -0
  20. pychnosz/core/species.py +599 -0
  21. pychnosz/core/subcrt.py +1696 -0
  22. pychnosz/core/thermo.py +593 -0
  23. pychnosz/core/unicurve.py +1226 -0
  24. pychnosz/data/__init__.py +11 -0
  25. pychnosz/data/add_obigt.py +327 -0
  26. pychnosz/data/extdata/Berman/BDat17_2017.csv +2 -0
  27. pychnosz/data/extdata/Berman/Ber88_1988.csv +68 -0
  28. pychnosz/data/extdata/Berman/Ber90_1990.csv +5 -0
  29. pychnosz/data/extdata/Berman/DS10_2010.csv +6 -0
  30. pychnosz/data/extdata/Berman/FDM+14_2014.csv +2 -0
  31. pychnosz/data/extdata/Berman/Got04_2004.csv +5 -0
  32. pychnosz/data/extdata/Berman/JUN92_1992.csv +3 -0
  33. pychnosz/data/extdata/Berman/SHD91_1991.csv +12 -0
  34. pychnosz/data/extdata/Berman/VGT92_1992.csv +2 -0
  35. pychnosz/data/extdata/Berman/VPT01_2001.csv +3 -0
  36. pychnosz/data/extdata/Berman/VPV05_2005.csv +2 -0
  37. pychnosz/data/extdata/Berman/ZS92_1992.csv +11 -0
  38. pychnosz/data/extdata/Berman/sympy.R +99 -0
  39. pychnosz/data/extdata/Berman/testing/BA96.bib +12 -0
  40. pychnosz/data/extdata/Berman/testing/BA96_Berman.csv +21 -0
  41. pychnosz/data/extdata/Berman/testing/BA96_OBIGT.csv +21 -0
  42. pychnosz/data/extdata/Berman/testing/BA96_refs.csv +6 -0
  43. pychnosz/data/extdata/OBIGT/AD.csv +25 -0
  44. pychnosz/data/extdata/OBIGT/Berman_cr.csv +93 -0
  45. pychnosz/data/extdata/OBIGT/DEW.csv +211 -0
  46. pychnosz/data/extdata/OBIGT/H2O_aq.csv +4 -0
  47. pychnosz/data/extdata/OBIGT/SLOP98.csv +411 -0
  48. pychnosz/data/extdata/OBIGT/SUPCRT92.csv +178 -0
  49. pychnosz/data/extdata/OBIGT/inorganic_aq.csv +729 -0
  50. pychnosz/data/extdata/OBIGT/inorganic_cr.csv +273 -0
  51. pychnosz/data/extdata/OBIGT/inorganic_gas.csv +20 -0
  52. pychnosz/data/extdata/OBIGT/organic_aq.csv +1104 -0
  53. pychnosz/data/extdata/OBIGT/organic_cr.csv +481 -0
  54. pychnosz/data/extdata/OBIGT/organic_gas.csv +268 -0
  55. pychnosz/data/extdata/OBIGT/organic_liq.csv +533 -0
  56. pychnosz/data/extdata/OBIGT/testing/GEMSFIT.csv +43 -0
  57. pychnosz/data/extdata/OBIGT/testing/IGEM.csv +17 -0
  58. pychnosz/data/extdata/OBIGT/testing/Sandia.csv +8 -0
  59. pychnosz/data/extdata/OBIGT/testing/SiO2.csv +4 -0
  60. pychnosz/data/extdata/misc/AD03_Fig1a.csv +69 -0
  61. pychnosz/data/extdata/misc/AD03_Fig1b.csv +43 -0
  62. pychnosz/data/extdata/misc/AD03_Fig1c.csv +89 -0
  63. pychnosz/data/extdata/misc/AD03_Fig1d.csv +30 -0
  64. pychnosz/data/extdata/misc/BZA10.csv +5 -0
  65. pychnosz/data/extdata/misc/HW97_Cp.csv +90 -0
  66. pychnosz/data/extdata/misc/HWM96_V.csv +229 -0
  67. pychnosz/data/extdata/misc/LA19_test.csv +7 -0
  68. pychnosz/data/extdata/misc/Mer75_Table4.csv +42 -0
  69. pychnosz/data/extdata/misc/OBIGT_check.csv +423 -0
  70. pychnosz/data/extdata/misc/PM90.csv +7 -0
  71. pychnosz/data/extdata/misc/RH95.csv +23 -0
  72. pychnosz/data/extdata/misc/RH98_Table15.csv +17 -0
  73. pychnosz/data/extdata/misc/SC10_Rainbow.csv +19 -0
  74. pychnosz/data/extdata/misc/SK95.csv +55 -0
  75. pychnosz/data/extdata/misc/SOJSH.csv +61 -0
  76. pychnosz/data/extdata/misc/SS98_Fig5a.csv +81 -0
  77. pychnosz/data/extdata/misc/SS98_Fig5b.csv +84 -0
  78. pychnosz/data/extdata/misc/TKSS14_Fig2.csv +25 -0
  79. pychnosz/data/extdata/misc/bluered.txt +1000 -0
  80. pychnosz/data/extdata/protein/Cas/Cas_aa.csv +177 -0
  81. pychnosz/data/extdata/protein/Cas/Cas_uniprot.csv +186 -0
  82. pychnosz/data/extdata/protein/Cas/download.R +34 -0
  83. pychnosz/data/extdata/protein/Cas/mkaa.R +34 -0
  84. pychnosz/data/extdata/protein/POLG.csv +12 -0
  85. pychnosz/data/extdata/protein/TBD+05.csv +393 -0
  86. pychnosz/data/extdata/protein/TBD+05_aa.csv +393 -0
  87. pychnosz/data/extdata/protein/rubisco.csv +28 -0
  88. pychnosz/data/extdata/protein/rubisco.fasta +239 -0
  89. pychnosz/data/extdata/protein/rubisco_aa.csv +28 -0
  90. pychnosz/data/extdata/src/H2O92D.f.orig +3457 -0
  91. pychnosz/data/extdata/src/README.txt +5 -0
  92. pychnosz/data/extdata/taxonomy/names.dmp +215 -0
  93. pychnosz/data/extdata/taxonomy/nodes.dmp +63 -0
  94. pychnosz/data/extdata/thermo/Bdot_acirc.csv +60 -0
  95. pychnosz/data/extdata/thermo/buffer.csv +40 -0
  96. pychnosz/data/extdata/thermo/element.csv +135 -0
  97. pychnosz/data/extdata/thermo/groups.csv +6 -0
  98. pychnosz/data/extdata/thermo/opt.csv +2 -0
  99. pychnosz/data/extdata/thermo/protein.csv +506 -0
  100. pychnosz/data/extdata/thermo/refs.csv +343 -0
  101. pychnosz/data/extdata/thermo/stoich.csv.xz +0 -0
  102. pychnosz/data/loader.py +431 -0
  103. pychnosz/data/mod_obigt.py +322 -0
  104. pychnosz/data/obigt.py +471 -0
  105. pychnosz/data/worm.py +228 -0
  106. pychnosz/fortran/.gitignore +6 -0
  107. pychnosz/fortran/__init__.py +16 -0
  108. pychnosz/fortran/h2o92.dylib +0 -0
  109. pychnosz/fortran/h2o92_interface.py +527 -0
  110. pychnosz/geochemistry/__init__.py +21 -0
  111. pychnosz/geochemistry/minerals.py +514 -0
  112. pychnosz/geochemistry/redox.py +500 -0
  113. pychnosz/models/__init__.py +47 -0
  114. pychnosz/models/archer_wang.py +165 -0
  115. pychnosz/models/berman.py +309 -0
  116. pychnosz/models/cgl.py +381 -0
  117. pychnosz/models/dew.py +997 -0
  118. pychnosz/models/hkf.py +523 -0
  119. pychnosz/models/hkf_helpers.py +231 -0
  120. pychnosz/models/iapws95.py +1113 -0
  121. pychnosz/models/supcrt92_fortran.py +238 -0
  122. pychnosz/models/water.py +480 -0
  123. pychnosz/utils/__init__.py +27 -0
  124. pychnosz/utils/expression.py +1074 -0
  125. pychnosz/utils/formula.py +830 -0
  126. pychnosz/utils/formula_ox.py +227 -0
  127. pychnosz/utils/reset.py +33 -0
  128. pychnosz/utils/units.py +259 -0
  129. pychnosz-1.1.12.dist-info/METADATA +197 -0
  130. pychnosz-1.1.12.dist-info/RECORD +133 -0
  131. pychnosz-1.1.12.dist-info/WHEEL +5 -0
  132. pychnosz-1.1.12.dist-info/licenses/LICENSE.txt +19 -0
  133. pychnosz-1.1.12.dist-info/top_level.txt +1 -0
@@ -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')