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,514 @@
1
+ """
2
+ Mineral equilibria and phase diagram calculations for CHNOSZ.
3
+
4
+ This module implements mineral stability calculations, phase diagrams,
5
+ and solid-solution equilibria for geological applications.
6
+ """
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ from typing import Union, Dict, List, Optional, Tuple, Any
11
+ import warnings
12
+
13
+ from ..core.subcrt import subcrt
14
+ from ..core.equilibrium import EquilibriumSolver
15
+ from ..core.speciation import SpeciationEngine
16
+
17
+
18
+ class MineralEquilibria:
19
+ """
20
+ Mineral equilibria calculator for geological systems.
21
+
22
+ This class handles mineral stability calculations, including:
23
+ - Mineral solubility equilibria
24
+ - Phase stability diagrams
25
+ - Mineral-water reactions
26
+ - Solid solution equilibria
27
+ """
28
+
29
+ def __init__(self):
30
+ """Initialize the mineral equilibria calculator."""
31
+ self.equilibrium_solver = EquilibriumSolver()
32
+ self.speciation_engine = SpeciationEngine()
33
+
34
+ # Common mineral dissolution reactions (simplified)
35
+ self.dissolution_reactions = {
36
+ 'quartz': {'SiO2': 1, 'H2O': 0, 'H4SiO4': -1},
37
+ 'calcite': {'CaCO3': 1, 'H+': 2, 'Ca+2': -1, 'HCO3-': -1, 'H2O': -1},
38
+ 'pyrite': {'FeS2': 1, 'H+': 8, 'SO4-2': -2, 'Fe+2': -1, 'H2O': -4},
39
+ 'hematite': {'Fe2O3': 1, 'H+': 6, 'Fe+3': -2, 'H2O': -3},
40
+ 'magnetite': {'Fe3O4': 1, 'H+': 8, 'Fe+2': -1, 'Fe+3': -2, 'H2O': -4},
41
+ 'albite': {'NaAlSi3O8': 1, 'H+': 1, 'H2O': 4, 'Na+': -1, 'Al+3': -1, 'H4SiO4': -3},
42
+ 'anorthite': {'CaAl2Si2O8': 1, 'H+': 8, 'H2O': 0, 'Ca+2': -1, 'Al+3': -2, 'H4SiO4': -2},
43
+ 'kaolinite': {'Al2Si2O5(OH)4': 1, 'H+': 6, 'Al+3': -2, 'H4SiO4': -2, 'H2O': -1}
44
+ }
45
+
46
+ def mineral_solubility(self, mineral: str,
47
+ T: float = 298.15, P: float = 1.0,
48
+ pH_range: Optional[Tuple[float, float]] = None,
49
+ ionic_strength: float = 0.0) -> Dict[str, Any]:
50
+ """
51
+ Calculate mineral solubility as a function of pH.
52
+
53
+ Parameters
54
+ ----------
55
+ mineral : str
56
+ Mineral name
57
+ T : float, default 298.15
58
+ Temperature in Kelvin
59
+ P : float, default 1.0
60
+ Pressure in bar
61
+ pH_range : tuple, optional
62
+ pH range (min, max). Default: (0, 14)
63
+ ionic_strength : float, default 0.0
64
+ Solution ionic strength
65
+
66
+ Returns
67
+ -------
68
+ dict
69
+ Solubility data vs pH
70
+ """
71
+
72
+ if pH_range is None:
73
+ pH_range = (0, 14)
74
+
75
+ pH_values = np.linspace(pH_range[0], pH_range[1], 100)
76
+
77
+ if mineral.lower() not in self.dissolution_reactions:
78
+ warnings.warn(f"Dissolution reaction not defined for {mineral}")
79
+ return {'pH': pH_values, 'solubility': np.zeros_like(pH_values)}
80
+
81
+ reaction = self.dissolution_reactions[mineral.lower()]
82
+
83
+ try:
84
+ # Get equilibrium constant for dissolution
85
+ species_names = list(reaction.keys())
86
+ coefficients = list(reaction.values())
87
+
88
+ # Calculate logK at T, P
89
+ result = subcrt(species_names, coefficients, T=T, P=P, show=False)
90
+ if result.out is not None and 'logK' in result.out.columns:
91
+ logK = result.out['logK'].iloc[0]
92
+ else:
93
+ logK = 0.0
94
+ warnings.warn(f"Could not calculate logK for {mineral} dissolution")
95
+
96
+ except Exception as e:
97
+ warnings.warn(f"Error calculating equilibrium constant: {e}")
98
+ logK = 0.0
99
+
100
+ # Calculate solubility at each pH
101
+ solubility = np.zeros_like(pH_values)
102
+
103
+ for i, pH in enumerate(pH_values):
104
+ try:
105
+ # Simplified solubility calculation
106
+ # This would be more complex in a full implementation
107
+
108
+ if 'H+' in reaction:
109
+ h_coeff = reaction['H+']
110
+ # Basic pH dependence
111
+ log_solubility = logK - h_coeff * pH
112
+ else:
113
+ log_solubility = logK
114
+
115
+ # Convert to molality
116
+ solubility[i] = 10**log_solubility
117
+
118
+ # Apply ionic strength corrections if needed
119
+ if ionic_strength > 0:
120
+ # Simple activity coefficient correction
121
+ gamma = self._estimate_activity_coefficient(ionic_strength)
122
+ solubility[i] *= gamma
123
+
124
+ except:
125
+ solubility[i] = 1e-20 # Very low solubility
126
+
127
+ return {
128
+ 'pH': pH_values,
129
+ 'solubility': solubility,
130
+ 'logK': logK,
131
+ 'mineral': mineral,
132
+ 'T': T,
133
+ 'P': P
134
+ }
135
+
136
+ def stability_diagram(self, minerals: List[str],
137
+ x_axis: str, x_range: Tuple[float, float],
138
+ y_axis: str, y_range: Tuple[float, float],
139
+ T: float = 298.15, P: float = 1.0,
140
+ resolution: int = 50) -> Dict[str, Any]:
141
+ """
142
+ Create mineral stability diagram.
143
+
144
+ Parameters
145
+ ----------
146
+ minerals : list of str
147
+ Mineral names to consider
148
+ x_axis : str
149
+ X-axis variable ('pH', 'log_activity_SiO2', etc.)
150
+ x_range : tuple
151
+ X-axis range (min, max)
152
+ y_axis : str
153
+ Y-axis variable
154
+ y_range : tuple
155
+ Y-axis range (min, max)
156
+ T : float, default 298.15
157
+ Temperature in Kelvin
158
+ P : float, default 1.0
159
+ Pressure in bar
160
+ resolution : int, default 50
161
+ Grid resolution
162
+
163
+ Returns
164
+ -------
165
+ dict
166
+ Stability diagram data
167
+ """
168
+
169
+ x_values = np.linspace(x_range[0], x_range[1], resolution)
170
+ y_values = np.linspace(y_range[0], y_range[1], resolution)
171
+ X, Y = np.meshgrid(x_values, y_values)
172
+
173
+ # Initialize stability field array
174
+ stable_mineral = np.zeros_like(X, dtype=int)
175
+
176
+ # Calculate stability at each grid point
177
+ for i in range(len(y_values)):
178
+ for j in range(len(x_values)):
179
+ x_val, y_val = X[i, j], Y[i, j]
180
+
181
+ # Find most stable mineral at this point
182
+ min_energy = np.inf
183
+ stable_idx = 0
184
+
185
+ for k, mineral in enumerate(minerals):
186
+ try:
187
+ # Calculate relative stability
188
+ # This would involve full equilibrium calculations
189
+ # For now, use simplified energy estimates
190
+
191
+ energy = self._calculate_stability_energy(
192
+ mineral, x_axis, x_val, y_axis, y_val, T, P)
193
+
194
+ if energy < min_energy:
195
+ min_energy = energy
196
+ stable_idx = k
197
+
198
+ except Exception as e:
199
+ warnings.warn(f"Error calculating stability for {mineral}: {e}")
200
+ continue
201
+
202
+ stable_mineral[i, j] = stable_idx
203
+
204
+ return {
205
+ x_axis: x_values,
206
+ y_axis: y_values,
207
+ 'stable_mineral': stable_mineral,
208
+ 'mineral_names': minerals,
209
+ 'T': T,
210
+ 'P': P
211
+ }
212
+
213
+ def phase_equilibrium(self, reaction: Dict[str, float],
214
+ T_range: Optional[Tuple[float, float]] = None,
215
+ P_range: Optional[Tuple[float, float]] = None) -> Dict[str, Any]:
216
+ """
217
+ Calculate phase equilibrium curve.
218
+
219
+ Parameters
220
+ ----------
221
+ reaction : dict
222
+ Reaction stoichiometry {species: coefficient}
223
+ T_range : tuple, optional
224
+ Temperature range (min, max) in K
225
+ P_range : tuple, optional
226
+ Pressure range (min, max) in bar
227
+
228
+ Returns
229
+ -------
230
+ dict
231
+ Equilibrium curve data
232
+ """
233
+
234
+ if T_range is None and P_range is None:
235
+ T_range = (273.15, 673.15) # 0-400°C
236
+
237
+ if T_range is not None:
238
+ # T-P curve at equilibrium
239
+ T_values = np.linspace(T_range[0], T_range[1], 50)
240
+ P_equilibrium = np.zeros_like(T_values)
241
+
242
+ for i, T in enumerate(T_values):
243
+ try:
244
+ # Calculate equilibrium pressure
245
+ # This would involve iterative solution
246
+ # For now, use simplified correlation
247
+ P_equilibrium[i] = self._calculate_equilibrium_pressure(reaction, T)
248
+
249
+ except:
250
+ P_equilibrium[i] = 1.0 # Default
251
+
252
+ return {
253
+ 'T': T_values,
254
+ 'P': P_equilibrium,
255
+ 'reaction': reaction
256
+ }
257
+
258
+ else:
259
+ # P-T curve - similar logic
260
+ P_values = np.linspace(P_range[0], P_range[1], 50)
261
+ T_equilibrium = np.zeros_like(P_values)
262
+
263
+ for i, P in enumerate(P_values):
264
+ try:
265
+ T_equilibrium[i] = self._calculate_equilibrium_temperature(reaction, P)
266
+ except:
267
+ T_equilibrium[i] = 298.15
268
+
269
+ return {
270
+ 'P': P_values,
271
+ 'T': T_equilibrium,
272
+ 'reaction': reaction
273
+ }
274
+
275
+ def solid_solution(self, end_members: List[str], compositions: List[float],
276
+ T: float = 298.15, P: float = 1.0,
277
+ model: str = 'ideal') -> Dict[str, Any]:
278
+ """
279
+ Calculate solid solution thermodynamics.
280
+
281
+ Parameters
282
+ ----------
283
+ end_members : list of str
284
+ End-member mineral names
285
+ compositions : list of float
286
+ Mole fractions of end-members (must sum to 1)
287
+ T : float, default 298.15
288
+ Temperature in Kelvin
289
+ P : float, default 1.0
290
+ Pressure in bar
291
+ model : str, default 'ideal'
292
+ Mixing model ('ideal', 'regular', 'margules')
293
+
294
+ Returns
295
+ -------
296
+ dict
297
+ Solid solution properties
298
+ """
299
+
300
+ if len(end_members) != len(compositions):
301
+ raise ValueError("Number of end-members must match number of compositions")
302
+
303
+ if abs(sum(compositions) - 1.0) > 1e-6:
304
+ raise ValueError("Compositions must sum to 1.0")
305
+
306
+ # Calculate end-member properties
307
+ end_member_props = []
308
+ for mineral in end_members:
309
+ try:
310
+ # This would get mineral properties from database
311
+ props = self._get_mineral_properties(mineral, T, P)
312
+ end_member_props.append(props)
313
+ except:
314
+ # Default properties
315
+ props = {'G': -1000000, 'H': -1200000, 'S': 100, 'V': 50, 'Cp': 100}
316
+ end_member_props.append(props)
317
+
318
+ # Calculate mixing properties
319
+ if model == 'ideal':
320
+ # Ideal mixing
321
+ G_mix = 8.314 * T * sum(x * np.log(x) for x in compositions if x > 0)
322
+ H_mix = 0.0
323
+ S_mix = -8.314 * sum(x * np.log(x) for x in compositions if x > 0)
324
+
325
+ elif model == 'regular':
326
+ # Regular solution (symmetric)
327
+ W = 10000 # Interaction parameter (J/mol) - would be mineral-specific
328
+ G_mix = W * compositions[0] * compositions[1] # Binary case
329
+ H_mix = G_mix
330
+ S_mix = 0.0
331
+
332
+ else:
333
+ # Default to ideal
334
+ G_mix = 8.314 * T * sum(x * np.log(x) for x in compositions if x > 0)
335
+ H_mix = 0.0
336
+ S_mix = -8.314 * sum(x * np.log(x) for x in compositions if x > 0)
337
+
338
+ # Total properties
339
+ total_props = {}
340
+ for prop in ['G', 'H', 'S', 'V', 'Cp']:
341
+ total_props[prop] = sum(compositions[i] * end_member_props[i][prop]
342
+ for i in range(len(end_members)))
343
+
344
+ # Add mixing contributions
345
+ total_props['G'] += G_mix
346
+ total_props['H'] += H_mix
347
+ total_props['S'] += S_mix
348
+
349
+ return {
350
+ 'properties': total_props,
351
+ 'mixing': {'G': G_mix, 'H': H_mix, 'S': S_mix},
352
+ 'end_members': end_members,
353
+ 'compositions': compositions,
354
+ 'model': model
355
+ }
356
+
357
+ def _estimate_activity_coefficient(self, ionic_strength: float) -> float:
358
+ """Estimate activity coefficient from ionic strength."""
359
+ if ionic_strength <= 0:
360
+ return 1.0
361
+ else:
362
+ # Simple Debye-Hückel approximation
363
+ A = 0.509
364
+ return 10**(-A * np.sqrt(ionic_strength))
365
+
366
+ def _calculate_stability_energy(self, mineral: str, x_axis: str, x_val: float,
367
+ y_axis: str, y_val: float, T: float, P: float) -> float:
368
+ """Calculate relative stability energy for mineral."""
369
+
370
+ # Simplified stability calculation
371
+ # Would involve full thermodynamic analysis
372
+
373
+ base_energy = np.random.random() * 1000 # Placeholder
374
+
375
+ # Add dependencies on axes
376
+ if 'pH' in x_axis:
377
+ base_energy += abs(x_val - 7) * 100 # pH dependence
378
+
379
+ if 'log' in y_axis:
380
+ base_energy += abs(y_val + 3) * 50 # Activity dependence
381
+
382
+ return base_energy
383
+
384
+ def _calculate_equilibrium_pressure(self, reaction: Dict[str, float], T: float) -> float:
385
+ """Calculate equilibrium pressure for reaction at given T."""
386
+
387
+ # Simplified using Clapeyron equation approximation
388
+ # dP/dT = ΔS/ΔV
389
+
390
+ # Estimate volume and entropy changes
391
+ delta_V = 5.0e-6 # m³/mol (typical for mineral reactions)
392
+ delta_S = 20.0 # J/(mol·K) (typical)
393
+
394
+ # Integrate from reference conditions
395
+ T0, P0 = 298.15, 1.0
396
+ dP_dT = delta_S / delta_V * 1e-5 # Convert to bar/K
397
+
398
+ P_eq = P0 + dP_dT * (T - T0)
399
+
400
+ return max(P_eq, 0.1) # Minimum 0.1 bar
401
+
402
+ def _calculate_equilibrium_temperature(self, reaction: Dict[str, float], P: float) -> float:
403
+ """Calculate equilibrium temperature for reaction at given P."""
404
+
405
+ # Inverse of pressure calculation
406
+ delta_V = 5.0e-6
407
+ delta_S = 20.0
408
+
409
+ T0, P0 = 298.15, 1.0
410
+ dT_dP = delta_V / delta_S * 1e5 # K/bar
411
+
412
+ T_eq = T0 + dT_dP * (P - P0)
413
+
414
+ return max(T_eq, 273.15) # Minimum 0°C
415
+
416
+ def _get_mineral_properties(self, mineral: str, T: float, P: float) -> Dict[str, float]:
417
+ """Get thermodynamic properties of a mineral."""
418
+
419
+ # This would look up mineral in database
420
+ # For now, return typical values
421
+
422
+ typical_props = {
423
+ 'quartz': {'G': -856300, 'H': -910700, 'S': 41.5, 'V': 22.7, 'Cp': 44.6},
424
+ 'calcite': {'G': -1128800, 'H': -1207400, 'S': 91.7, 'V': 36.9, 'Cp': 83.5},
425
+ 'pyrite': {'G': -160200, 'H': -171500, 'S': 52.9, 'V': 23.9, 'Cp': 62.2},
426
+ 'hematite': {'G': -744400, 'H': -824200, 'S': 87.4, 'V': 30.3, 'Cp': 103.9}
427
+ }
428
+
429
+ return typical_props.get(mineral.lower(), {
430
+ 'G': -500000, 'H': -600000, 'S': 50, 'V': 30, 'Cp': 80
431
+ })
432
+
433
+
434
+ # Global mineral equilibria calculator
435
+ _mineral_calculator = MineralEquilibria()
436
+
437
+
438
+ def mineral_solubility(mineral: str, pH_range: Tuple[float, float] = (0, 14),
439
+ T: float = 298.15, P: float = 1.0) -> Dict[str, Any]:
440
+ """
441
+ Calculate mineral solubility vs pH.
442
+
443
+ Parameters
444
+ ----------
445
+ mineral : str
446
+ Mineral name
447
+ pH_range : tuple, default (0, 14)
448
+ pH range for calculation
449
+ T : float, default 298.15
450
+ Temperature in Kelvin
451
+ P : float, default 1.0
452
+ Pressure in bar
453
+
454
+ Returns
455
+ -------
456
+ dict
457
+ Solubility data
458
+ """
459
+
460
+ return _mineral_calculator.mineral_solubility(mineral, T, P, pH_range)
461
+
462
+
463
+ def stability_field(minerals: List[str],
464
+ variables: Tuple[str, str],
465
+ ranges: Tuple[Tuple[float, float], Tuple[float, float]],
466
+ T: float = 298.15, P: float = 1.0) -> Dict[str, Any]:
467
+ """
468
+ Calculate mineral stability fields.
469
+
470
+ Parameters
471
+ ----------
472
+ minerals : list of str
473
+ Minerals to consider
474
+ variables : tuple of str
475
+ (x_variable, y_variable) for diagram
476
+ ranges : tuple of tuples
477
+ ((x_min, x_max), (y_min, y_max))
478
+ T : float, default 298.15
479
+ Temperature in Kelvin
480
+ P : float, default 1.0
481
+ Pressure in bar
482
+
483
+ Returns
484
+ -------
485
+ dict
486
+ Stability diagram data
487
+ """
488
+
489
+ x_var, y_var = variables
490
+ x_range, y_range = ranges
491
+
492
+ return _mineral_calculator.stability_diagram(
493
+ minerals, x_var, x_range, y_var, y_range, T, P)
494
+
495
+
496
+ def phase_boundary(reaction: Dict[str, float],
497
+ T_range: Tuple[float, float] = (273.15, 673.15)) -> Dict[str, Any]:
498
+ """
499
+ Calculate phase boundary for a reaction.
500
+
501
+ Parameters
502
+ ----------
503
+ reaction : dict
504
+ Reaction stoichiometry {species: coefficient}
505
+ T_range : tuple, default (273.15, 673.15)
506
+ Temperature range in Kelvin
507
+
508
+ Returns
509
+ -------
510
+ dict
511
+ Phase boundary data
512
+ """
513
+
514
+ return _mineral_calculator.phase_equilibrium(reaction, T_range=T_range)