#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Created on Wed Mar 17 16:02:22 2021
@author: Adedapo Awolayo and Ben Tutolo, University of Calgary
Copyright (c) 2020 - 2021, Adedapo Awolayo and Ben Tutolo, University of Calgary
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from .read_db import db_reader, dbaccess_modify
from .water_eos import iapws95, ZhangDuan, water_dielec, readIAPWS95data, convert_temperature
from .water_eos import Driesner_NaCl, concentration_converter, Dummy
from .species_eos import heatcap, supcrtaq
from .solid_solution import solidsolution_thermo
from .clay_thermocalc import calclogKclays
import warnings
import re
import os
import json
import numpy as np
import pandas as pd
import time
from scipy.optimize import fsolve, curve_fit
from scipy.linalg import lu_factor, lu_solve
from scipy.interpolate import splev, splrep, RBFInterpolator
import inspect, itertools
import math
from fractions import Fraction
from collections import OrderedDict
np.random.seed(4321)
warnings.filterwarnings("ignore", message="divide by zero encountered")
warnings.filterwarnings("ignore", message="invalid value encountered")
eps = 2.220446049250313e-16
J_to_cal = 4.184
IAPWS95_COEFFS = readIAPWS95data()
[docs]
def calc_elem_count_molewt(formula, **kwargs):
"""
This function calculates the molecular mass and the elemental composition of a substance given
by its chemical formula. This is modified from https://github.com/cgohlke/molmass/blob/master/molmass/molmass.py
Parameters
----------
formula : string
chemical formula
Elementdic : dict
dictionary, containing the atomic mass of element database
Returns
----------
elements : dict
dictionary of elemental composition and their respective number of atoms
molewt : float
Calculated Molecular Weights [g/mol]
Usage:
----------
[elements, molewt] = calc_elem_count_molewt(formula)
Examples of valid formulas are "H2O", "[2H]2O", "CH3COOH", "EtOH", "CuSO4:5H2O", "(COOH)2",
"AgCuRu4(H)2[CO]12{PPh3}2", "CGCGAATTCGCG", and, "MDRGEQGLLK" .
Examples
--------
>>> elements, molewt = calc_elem_count_molewt("CuSO4:5H2O")
>>> elements, molewt
{'O': 9.0, 'H': 10.0, 'S': 1, 'Cu': 1}, 249.6773
"""
kwargs = dict({"Elementdic": None }, **kwargs)
Elementdic = kwargs['Elementdic']
if Elementdic is None:
periodic_table = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'PeriodicTableJSON.json'), encoding='utf8')
data = json.load(periodic_table)
Elementdic = {data['elements'][x]['symbol'] : pd.DataFrame([data['elements'][x]['name'],
data['elements'][x]['atomic_mass']],
index = ['name', 'mass']).T
for x in range(len(data['elements']))}
periodic_table.close()
validchars = set('([{<123456789ABCDEFGHIKLMNOPRSTUVWXYZ')
validchars |= set(']})>0abcdefghiklmnoprstuy')
elements = {}
ele = '' # parsed element
num = 0 # number
level = 0 # parenthesis level
counts = [1] # parenthesis level multiplication
i = len(formula)
while i:
i -= 1
char = formula[i]
if char in '([{<':
level -= 1
elif char in ')]}>':
if num == 0:
num = 1
level += 1
if level > len(counts) - 1:
counts.append(0)
counts[level] = num * counts[level - 1]
num = 0
elif char.isdigit():
j = i
while i and (formula[i - 1].isdigit() or formula[i - 1] == '.'):
i -= 1
num = float(formula[i : j + 1])
elif char.islower():
ele = char
elif char.isupper():
ele = char + ele
if num == 0:
num = 1
j = i
number = num * counts[level]
if ele in elements.keys():
elements[ele] = number + elements[ele]
else:
elements[ele] = number
ele = ''
num = 0
elif char == ':':
if num == 0:
num = 1
for k in elements.keys():
elements[k] = elements[k]*num
molewt = 0
for symbol in elements:
molewt += Elementdic[symbol].mass[0] * elements[symbol]
return elements, molewt
[docs]
def importconfile(filename, *Rows):
"""
This function imports numeric data from a text file as column vectors.
[Var1, Var2] = importconfile(filename) Reads data from text file
filename for the default selection.
[Var1, Var2] = importconfile(filename, StartRow, EndRow) Reads data
from rows StartRow through EndRow of text file filename.
Example:
[Var1, Var2] = importconfile('100bar.con', 6, 13);
"""
# %% Initialize variables.
if (len(Rows) == 0):
startRow = 5
endRow = np.inf
else:
startRow, endRow = Rows
# %% Open the text file.
fileID = open(filename,'r');
df=fileID.readlines()
Var1 = []
Var2 = []
for block in range(startRow,len(df)):
if not df[block].startswith('\n'):
dataArray = df[block].split()
Var1.append(float(dataArray[0]))
Var2.append(float(dataArray[1]))
Var1 = np.asarray(Var1)
Var2 = np.asarray(Var2)
# %% Close the text file.
fileID.close()
# %%
return Var1, Var2
[docs]
def var_name(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
print(str([k for k, v in callers_local_vars if v is var][0])+': '+str(var))
[docs]
def feval(funcName, *args):
return eval(funcName)(*args)
[docs]
def roundup_tenth(x):
return int(math.ceil(x / 10.0)) * 10
[docs]
def roundup_hundredth(x):
return int(math.ceil(x / 100)) * 100
[docs]
def read_specific_lines(file, lines_to_read):
lines = set(lines_to_read)
last = max(lines)
for n, line in enumerate(file):
if n + 1 in lines:
yield line
if n + 1 > last:
return
if not line:
continue
[docs]
def denormalize_phreeqc_species_charge(j):
"""
Helper function to reverse the normalization of PHREEQC species charge notation.
- Convert expanded charges back into compact notation (e.g., 'Ca++' -> 'Ca+2').
- Remove '(aq)' from neutral species.
- Keep integers or floats unchanged.
Examples:
'Ca++' -> 'Ca+2'
'Fe---' -> 'Fe-3'
'H2O(aq)' -> 'H2O'
'CH4(aq)' -> 'CH4'
'-1' -> '-1'
'3.5' -> '3.5'
"""
# If it's just an integer or float, return as is
try:
float(j)
return j
except ValueError:
pass
# Remove (aq) if present
if j.endswith("(aq)"):
j = j[:-4]
# Collapse repeated charges back into +n / -n
def repl(match):
sign = match.group(1)[0] # '+' or '-'
num = len(match.group(1)) # number of signs
return f"{sign}{num}"
out = re.sub(r'([+-]{2,})', repl, j) # match ++, ---, etc.
return out
[docs]
def normalize_species_charges(species):
# 'Ca++' -> 'Ca+2', 'Fe---' -> 'Fe-3'
species = re.sub(r'^([A-Za-z0-9()]+)([+-]+)$',
lambda m: m.group(1) + m.group(2)[0] + str(len(m.group(2))),
species)
# 'Ca(+2)' -> 'Ca+2', 'Fe(-3)' -> 'Fe-3'
species = re.sub(r'\(([\+\-]\d+)\)$', r'\1', species)
# 'SrCl(+)' -> 'SrCl+', 'HNiO2(-)' -> 'HNiO2-'
species = re.sub(r'\(([\+\-])\)$', r'\1', species)
# 'NaCl(0)' -> 'NaCl'
species = re.sub(r'\(0\)$', '', species)
# 'CdOH+1' -> 'CdOH+'
species = re.sub(r'\+1$', '+', species)
species = re.sub(r'-1$', '-', species)
return species
[docs]
def normalize_phreeqc_species_charge(j):
"""
Helper function to normalize PHREEQC species charge notation.
- Replace charge notation like +2, -3, +4 etc. with expanded '+' or '-' symbols.
- Skip cases where the entire string is just an integer (e.g., '1', '-1', '+2').
- If the species has no explicit charge, append '(aq)'.
Examples:
'Ca+2' -> 'Ca++'
'Fe-3' -> 'Fe---'
'-1' -> '-1'
'H2O' -> 'H2O(aq)'
'CH4' -> 'CH4(aq)'
"""
def repl(match):
sign, num = match.groups()
return sign * int(num)
# If it's just an integer or float, return as is
try:
float(j)
return j
except ValueError:
pass
# Expand charges like +2 -> ++, -3 -> ---
out = re.sub(r'([+-])(\d+)', repl, j)
# Append (aq) if no charge, except for "" and "H2O"
if out not in ("", "H2O") and not re.search(r'[+-]', out):
out += "(aq)"
return out
[docs]
def contains_missing_species(line, missing_spx):
# Helper function to check if a line contains missing species
parts = line.replace("=", " ").replace("+", " ").split()
for p in parts:
if p.strip() in missing_spx:
return True
return False
[docs]
def build_side_regex(side):
"""
Build a regex to match one side of a chemical reaction.
- Optional numeric coefficients (integer, decimal, scientific notation)
- Flexible spacing around '+' signs
- Species with charges, e.g., CO3-2, Eu+3, H+, e-
"""
species = [x.strip() for x in side.split('+')]
species_regex = []
for sp in species:
# Optional numeric coefficient in front of species
coeff_pattern = r'(?:\d*\.?\d+(?:[eE][+-]?\d+)?)?'
# Escape the species exactly (keep +, -, numbers for charges)
species_regex.append(rf'{coeff_pattern}\s*{re.escape(sp)}')
# Join species with optional spaces around '+'
return r'\s*\+\s*'.join(species_regex)
[docs]
def derivative(f, a, method = 'central', h = 0.001):
'''Compute the derivative of f, f'(a) with step size h.
Parameters
----------
f : function
Vectorized function of one variable
a : number
Compute derivative at x = a
method : string
Difference formula: 'forward', 'backward' or 'central'
h : number
Step size in difference formula
Returns
-------
float
Difference formula:
central: f(a + h) - f(a - h))/2h
forward: f(a + h) - f(a))/h
backward: f(a) - f(a-h))/h
'''
if method == 'central':
return (f(a + h) - f(a - h))/(2*h)
elif method == 'forward':
return (f(a + h) - f(a))/h
elif method == 'backward':
return (f(a) - f(a - h))/h
else:
raise ValueError("Method must be 'central', 'forward' or 'backward'.")
[docs]
def info(name, dic):
"""
This function checks for naming convention of the species in the direct-access or source database
Parameters
----------
name : string
species name
dic : dict
dictionary of species from direct-access or source database
Returns
----------
lst : list
resulting search of all species with the input name
Examples
--------
>>> from pygcc.pygcc_utils import db_reader
>>> ps = db_reader() # utilizes the default direct-access database, speq21
>>> info('ss_', ps.dbaccessdic)
['ss_Anorthite', 'ss_Albite_high', 'ss_K-feldspar', 'ss_Ferrosilite',
'ss_Enstatite', 'ss_Clinoenstatite', 'ss_Hedenbergite', 'ss_Diopside',
'ss_Forsterite', 'ss_Fayalite', 'ss_Annite', 'ss_Phlogopite',
'ss_Anorthite', 'ss_Albite_high', 'ss_K-feldspar', 'ss_Ferrosilite',
'ss_Enstatite', 'ss_Clinoenstatite', 'ss_Hedenbergite', 'ss_Diopside',
'ss_Forsterite', 'ss_Fayalite', 'ss_Annite', 'ss_Phlogopite']
"""
lst = [i for i in list(dic.keys()) if i.startswith(name)==True] # starts with name
lst = lst + [i for i in list(dic.keys()) if i.__contains__(name)] # contains name
return lst
#%%------------------------------------------------------------------------------------
[docs]
def drummondgamma(TK, I):
"""
This function models solubility of CO2 gas in brine using Drummond equation
Parameters
----------
TK : float, vector
Temperature [K]
I : float, vector
Ionic strength
Returns
----------
log10_gamma : float, vector
co2 aqueous activity coefficients in log10
Examples
--------
>>> log10_gamma = drummondgamma( 500, 0.5)
>>> log10_gamma
0.0781512920184902
"""
# A=20.244
# B=-0.016323
C=-1.0312
# D=-3629.7
E=0.4445
F=0.0012806
G=255.9
H=-0.001606
#gamma=(((C + F*TK + (G/TK))*I - (E + H*TK)*(I / (I + 1)))/np.log(10))
log10_gamma = np.log10(np.exp((C + F*TK + (G/TK))*I - (E + H*TK)*(I / (I + 1))))
return log10_gamma
[docs]
def Henry_duan_sun(TK, P, I):
"""
This function evaluates the solubility of CO2 gase in brine using Duan_Sun Formulation
Parameters
----------
TK : float, vector
Temperature [K]
P : float, vector
Pressure [bar]
I : float
Ionic strength
Returns
----------
log10_co2_gamma : float, vector
co2 aqueous activity coefficients in log10
mco2 : float, vector
co2 aqueous molalities
Usage
----------
log10_co2_gamma, mco2 = Henry_duan_sun( TK, P, I)
Examples
--------
>>> log10_co2_gamma, mco2 = Henry_duan_sun( 500, 250, 0.5)
>>> log10_co2_gamma, mco2
0.06202532, 1.6062557
References
----------
(1) Duan, Z. and Sun, R., 2003. An improved model calculating CO2 solubility in
pure water and aqueous NaCl solutions from 273 to 533 K and from 0 to 2000 bar.
Chemical geology, 193(3-4), pp.257-271.
"""
if (np.ndim(TK) == 0):
TK = np.array(TK).ravel()
if (np.ndim(P) == 0):
P = np.array(P).ravel()
# Always flatten I to 1-D — callers may pass a column vector (shape (N, 1))
# from gamma_correlation, which would cause mNaCl[j] to be shape (1,) instead
# of a scalar and raise ValueError when assigning into lngamco2[j, i].
I = np.asarray(I).ravel()
if np.size(TK) < np.size(P):
TK = TK*np.ones_like(P)
if np.size(P) < np.size(TK):
P = P*np.ones_like(TK)
# calculate CO2 fugacity and partial pressure
# vco2fugacity = np.vectorize(co2fugacity)
[fCO2, denCO2, SI, pCO2] = co2fugacity(TK, P)
# Equation B1 of Duan & Sun model to calculate vapor pressure of water
c1 = -38.640844
c2 = 5.894842
c3 = 59.876516
c4 = 26.654627
c5 = 10.637097
Pc = 220.85 ## bar
Tc = 647.29 ## K
t = (TK - Tc) / Tc
PH2O = Pc * TK/Tc*(1 + c1*(-t)**1.9 + c2*t + c3*t**2 + c4*t**3 + c5*t**4)
yCO2 = (P - PH2O) / P ## gas phase molar fraction of CO2
mNaCl = 2 * I/ ((1)**2 + (-1)**2)
par_mu = np.array([28.9447706, -0.0354581768, -4770.67077, 0.0000102782768,
33.8126098, 0.0090403714, -0.00114934031, -0.307405726,
-0.0907301486, 0.000932713393, 0])
par_lambda = np.array([-0.411370585, 0.000607632013, 97.5347708, 0, 0, 0, 0,
-0.0237622469, 0.0170656236, 0, 0.0000141335834])
par_xi = np.array([0.000336389723, -0.000019829898, 0, 0, 0, 0, 0,
0.0021222083, -0.00524873303, 0, 0])
fTP = [1*np.ones([1, len(TK)]), TK, 1/TK, TK**2, 1/(630-TK), P, P*np.log(TK),
P/TK, P/(630-TK), (P/(630-TK))**2, TK*np.log(P)]
fTP = np.vstack(fTP)
muCO2 = np.sum(par_mu.reshape(-1,1) * fTP, 0) # mu0/RT
lambdaCO2Na = np.sum(par_lambda.reshape(-1,1) * fTP, 0) # lambda_CO2-Na Pitzer 2nd order int. param.
xiCO2NaCl = np.sum(par_xi.reshape(-1,1) * fTP, 0) # zeta_CO2-Na-Cl Pitzer 3rd order int. param.
# activity coef. aqueous co2
# Honoring limits for Duan and Sun - P-T-X range (0– 2000 bar, 0–260°C, 0–4.3 m NaCl)
lngamco2 = np.zeros([len(I), len(TK)]); mco2 = np.zeros([len(I), len(TK)])
for j in range(len(I)):
for i in range(len(TK)):
if (TK[i] <= convert_temperature( 260, Out_Unit = 'K' ) ) and (I[j] <= 4.3) and (P[i] <= 2000):
lngamco2[j, i] = 2*lambdaCO2Na[i]*mNaCl[j] + xiCO2NaCl[i]*mNaCl[j]**2
mco2[j, i] = P[i] * yCO2[i]/np.exp(muCO2[i] - np.log(fCO2[i]/P[i]) + \
2*lambdaCO2Na[i]*mNaCl[j] + xiCO2NaCl[i]*mNaCl[j]**2)
else:
lngamco2[j, i] = 0
mco2[j, i] = 0
log10_co2_gamma = np.log10(np.exp(lngamco2)) # ((lngamco2)/np.log(10)) #
return log10_co2_gamma, mco2
[docs]
def co2fugacity(TK, P, poy = True):
"""
This function computes the fugacity and density of CO2 by Duan and Sun 2003
Also Calculate the Saturation Index SI and partial pressure of CO2(g) at any given T, P
using the Duan equation of state. A Poynting correction factor is also applied.
Parameters
----------
TK : float, vector
Temperature [K]
P : float, vector
pressure [bar]
poy : bool
Option to apply a Poynting correction factor [True is the default]
Returns
----------
fCO2 : float, vector
co2 fugacity [bar]
denCO2 : float, vector
co2 density [g/cm3]
SI : float, vector
co2 saturation index
pCO2 : float, vector
co2 partial pressure [bar]
Usage
----------
[fCO2, denCO2, SI, pCO2] = co2fugacity( TK, P)
Examples
--------
>>> fCO2, denCO2, SI, pCO2 = co2fugacity( np.array([400,420]), np.array([150, 200]))
>>> fCO2
array([114.50387423, 150.87672517])
>>> denCO2
array([2.6741242 , 3.33134397])
>>> SI
array([1.73291553, 1.84597591])
>>> pCO2
array([54.78127571, 71.0710152])
"""
if np.ndim(TK) == 0 :
TK = np.array(TK).ravel()
else:
TK = np.ravel(TK)
if np.ndim(P) == 0 :
P = np.array(P).ravel()
else:
P = np.ravel(P)
Rgas = 0.0831446261815324 #bar*L/mol/K
PcCO2 = 73.825 ## bar
TcCO2 = 31.05 + 273.15 ## K
VcCO2 = Rgas * TcCO2 / PcCO2 # L/mol
Pr = P / PcCO2 # unitless
Tr = TK / TcCO2 # unitless
xmwc = 44.0098 # g/mol
a1 = 0.0899288497
a2 = -0.494783127
a3 = 0.0477922245
a4 = 0.0103808883
a5 = -0.0282516861
a6 = 0.0949887563
a7 = 0.00052060088
a8 = -0.000293540971
a9 = -0.00177265112
a10 = -0.0000251101973
a11 = 0.0000893353441
a12 = 0.0000788998563
a13 = -0.0166727022
a14 = 1.398
a15 = 0.0296
# Equation A1
Z = np.zeros([len(TK), 1]).ravel()
for k in range(len(TK)):
zfun = lambda Z : -Z + (1 + (a1 + a2 / Tr[k]**2 + a3 / Tr[k]**3) / (Z * Tr[k]) * Pr[k] +
(a4 + a5 / Tr[k]**2 + a6 / Tr[k]**3) / (Z * Tr[k])**2 * Pr[k]**2 +
(a7 + a8 / Tr[k]**2 + a9 / Tr[k]**3) / (Z * Tr[k])**4 * Pr[k]**4 +
(a10 + a11 / Tr[k]**2 + a12 / Tr[k] ** 3) / (Z * Tr[k])**5 * Pr[k]**5 +
a13 / Tr[k]**3 / (Z * Tr[k])**2 * Pr[k]**2 * (a14 + a15 / (Z * Tr[k])**2 * Pr[k]**2)*
np.exp(-a15 / (Z * Tr[k])**2 * Pr[k]**2))
# set initial guess from ideal gas law
Videal = Rgas*TK[k]/P[k]
Zguess = Videal/VcCO2*Pr[k]/Tr[k]
Z[k] = fsolve(zfun, Zguess, xtol=1.0e-10)[0]
Vr = Z * Tr / Pr
V = Vr * VcCO2 ## L / mol
phi = np.exp(Z - 1 - np.log(Z) + (a1 + a2 / (Tr ** 2) + a3 / (Tr ** 3)) / Vr + \
(a4 + a5 / (Tr ** 2) + a6 / (Tr ** 3)) / (2 * Vr ** 2) + \
((a7 + a8 / (Tr ** 2) + a9 / (Tr ** 3)) / (4 * Vr ** 4) + \
(a10 + a11 / (Tr ** 2) + a12 / (Tr ** 3)) / (5 * Vr ** 5) + \
a13 / (2 * Tr ** 3 * a15) * (a14 + 1 - (a14 + 1 + a15 / Vr ** 2) * \
np.exp(-a15 / Vr ** 2))))
# print(phi)
#-----fugacity
fCO2 = phi * P #bar
#-----density
denCO2 = xmwc*1e-3 / V # g/cm^3
Poy = 1
Patm = P / 1.01325
if (poy):
R = 0.082057366080960 ## L atm /K/mol
Vm = np.where(V < 0, 32.0e-3, V) ## L / mol
Poy = np.exp(-(Patm - 1)*Vm/R/TK)
pCO2 = phi*Patm*Poy # atm
SI = np.log10(pCO2)
pCO2 = pCO2 * 1.01325 # bar
return fCO2, denCO2, SI, pCO2
[docs]
def gamma_correlation(TC, P, method = None):
"""
This function calculates the CO2 activity correlation coefficients at
given temperature T and pressure P
Parameters
----------
TC : float, vector
Temperature [°C]
P : float, vector
Pressure [bar]
method : string
specify the activity model [``'Duan_Sun'`` or ``'Drummond'``]
Returns
----------
cco2 : float, vector
co2 correlation coefficients
Usage
----------
cco2 = gamma_correlation( TC, P)
Examples
--------
>>> log10_co2_gamma, mco2 = Henry_duan_sun( 500, 250, 0.5)
>>> log10_co2_gamma, mco2
0.06202532, 1.6062557
References
----------
(1) Segal Edward Drummond, 1981, Boiling and Mixing of Hydrothermal
Fluids: Chemical Effects on Mineral Precipitation, page 19
(2) Wolery, T. J., Lawrence Livermore National Laboratory, United States Dept.
of Energy, 1992. EQ3/6: A software package for geochemical modeling of
aqueous systems: package overview and installation guide (version 7.0)
"""
if np.ndim(TC) == 0:
TC = np.array(TC).ravel()
if np.ndim(P) == 0:
P = np.array(P).ravel()
TK = convert_temperature( TC, Out_Unit = 'K' )
# assign ionic strength from 0 to 3
N = 100
I = np.zeros([N, 1])
for i in range(N):
I[i] = 3 * i /(N - 1)
A = np.zeros([3, 3])
B = np.zeros([3, 1])
cco2 = np.zeros([4, len(TK)])
if method is not None:
if method.lower() == 'duan_sun':
ccoef = Henry_duan_sun(TK, P, I)[0]
elif method.lower() == 'drummond':
ccoef = drummondgamma(TK, I)
else:
ccoef = drummondgamma(TK, I)
for i in range(len(TK)):
for ii in range(3):
for jj in range(3):
A[ii, jj] = np.sum(I**((ii+1) + (jj+1)))
B[ii] = np.sum(ccoef[:, i]*I.ravel()**(ii+1))
Coef = lu_solve(lu_factor(A), B)
cco2[:, i] = np.concatenate([Coef.ravel(),np.array([0])])
return cco2
[docs]
def Helgeson_activity(TC, P, I, Dielec_method = None, **rhoEDB):
"""
This function calculates the solute activity coefficient, solvent osmotic coefficient,
and solvent activity at given temperature and pressure using equations 298, 190 and 106 in
Helgeson, Kirkham and Flowers, 1981, A.J.S. p.1249-1516
Parameters:
----------
TC : Temperature [°C]
P : pressure [bar]
I : ionic strength
Dielec_method : specify either 'FGL97' or 'JN91' or 'DEW' as the method to calculate dielectric
constant (optional), if not specified default - 'JN91'
rhoEDB : dictionary of water properties like density (rho), dielectric factor (E) and
Debye–Hückel coefficients (optional)
Returns:
----------
aw : solvent activity
phi : solvent osmotic coefficient
mean_act : solute activity coefficient
Usage
-------
[aw, phi, mean_act] = Helgeson_activity( TC, P, I)
"""
if np.ndim(TC) == 0:
TC = np.array(TC).ravel()
if np.ndim(P) == 0:
P = np.array(P).ravel()
if np.ndim(I) == 0 :
I = np.array(I).ravel()
if np.size(TC) < np.size(P):
TC = TC*np.ones_like(P)
if np.size(P) < np.size(TC):
P = P*np.ones_like(TC)
I = I.reshape(-1,1)
Dielec_method = 'JN91' if Dielec_method is None else Dielec_method
if rhoEDB.__len__() != 0:
Ah = rhoEDB['Ah'].ravel()
Bh = rhoEDB['Bh'].ravel()
else:
water = water_dielec(T = TC, P = P, Dielec_method = Dielec_method)
Ah, Bh = water.Ah, water.Bh
z = [-1.0, 1.0]
mwH2O = 18.01528/1000 # kg/mol
R = 1.9872041 # cal/mol/K
mstar = 2*I # total solute in solution
mtj = I # total concentration of ion j
mchr= 2*I # total solute excluding neutral species
loggamma = [0]*2; summt = [0]*2
rej = [1.810, 1.910] # Rej of Cl- and Na+
bijl = np.zeros(np.size(TC)); bi = np.zeros(np.size(TC))
# bil and bihat correlation
for k in range(len(TC)):
if TC[k] < 350:
Psat = iapws95(T = TC[k], P = 'T').P
else:
Psat = [0]
if P[k] == Psat: #%% P = Psat Region
x = np.array([ 25., 50., 75., 100., 125., 150., 175., 200., 225.,
250., 275., 300., 325.])
y = np.array([ 2.47, 2.15, 1.79, 1.39, 0.93, 0.41, -0.18, -0.85, -1.64,
-2.57, -3.71, -5.21, -7.32])
fun = splrep(x, y)
bhat = splev(TC[k], fun)
y = np.array([-9.77, -5.59, -2.43, 0.23, 2.44, 4.51, 6.38, 8.11, 9.73, 11.29,
11.71, 14.15, 15.49])
epsilon = (x.max() - x.min()) / len(x)
fun = RBFInterpolator(x.reshape(-1, 1), y, kernel='multiquadric', epsilon=epsilon)
bil = fun(np.array([[TC[k]]]))[0]
elif P[k] != Psat and P[k] < 1000:
isoline = P[k]
x = np.array([ 25., 50., 75., 100., 125., 150., 175., 200., 225., 250., 275., 300., 325.])
y = np.array([ 2.47, 2.15, 1.79, 1.39, 0.93, 0.41, -0.18,
-0.85, -1.64, -2.57, -3.71, -5.21, -7.32])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
#ypred1 = np.where(ypred1 < -17.12, -17.12, ypred1)
x = np.arange(25,525,25)
y = np.array([ 2.58, 2.28, 1.95, 1.58, 1.18, 0.73, 0.25, 0.28,
-0.86, -1.5 , -2.2 , -2.99, -3.88, -4.91, -6.11, -7.56,
-9.31, -11.43, -14.01, -17.12])
mask = np.arange(len(x)) !=np.where(x == 200)[0][0]
fun = splrep(x[mask], y[mask])
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bhat = splev(TC[k], fun)
x = np.array([ 25., 50., 75., 100., 125., 150., 175., 200., 225., 250., 275., 300., 325.])
y = np.array([-9.77, -5.59, -2.43, 0.23, 2.44, 4.51, 6.38, 8.11, 9.73, 11.29,
11.71, 14.15, 15.49])
epsilon = (x.max() - x.min()) / len(x)
fun = RBFInterpolator(x.reshape(-1, 1), y, kernel='multiquadric', epsilon=epsilon)
xpred = np.linspace(0, 500, 100)
ypred1 = fun(xpred.reshape(-1, 1))
ypred1 = np.where(ypred1 > 24.03, 24.03, ypred1)
x = np.arange(25,525,25)
y = np.array([ -9.19, -5.07, -2.63, -0.07, 1.71, 4.13, 5.91, 7.48, 9.27,
10.8 , 12.24, 13.68, 15.16, 16.48, 17.8 , 19.12, 20.33, 21.55,
22.86, 24.03])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bil = splev(TC[k], fun)
elif P[k] >= 1000 and P[k] <= 2000:
isoline = P[k] - 1000
x = np.arange(25,525,25)
y = np.array([ 2.58, 2.28, 1.95, 1.58, 1.18, 0.73, 0.25, 0.28,
-0.86, -1.5 , -2.2 , -2.99, -3.88, -4.91, -6.11, -7.56,
-9.31, -11.43, -14.01, -17.12])
mask = np.arange(len(x)) !=np.where(x == 200)[0][0]
fun = splrep(x[mask], y[mask])
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([ 2.66, 2.37, 2.06, 1.72, 1.35, 0.95, 0.51, 0.04, -0.46,
-1. , -1.57, -2.19, -2.85, -3.58, -4.38, -5.26, -6.24, -7.34,
-8.56, -9.88])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bhat = splev(TC[k], fun)
y = np.array([ -9.19, -5.07, -2.63, -0.07, 1.71, 4.13, 5.91, 7.48, 9.27,
10.80, 12.24, 13.68, 15.16, 16.48, 17.8 , 19.12, 20.33, 21.55,
22.86, 24.03])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([ -9.12, -5.52, -2.65, -0.1 , 1.83, 4.12, 5.88, 7.66, 9.2 ,
10.73, 12.19, 13.66, 15.1 , 16.42, 17.79, 19.11, 20.35, 21.61,
22.84, 23.95])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bil = splev(TC[k], fun)
elif P[k] > 2000 and P[k] <= 3000: #%% P = 3000 Region
isoline = P[k] - 2000
x = np.arange(25,525,25)
y = np.array([ 2.66, 2.37, 2.06, 1.72, 1.35, 0.95, 0.51, 0.04, -0.46,
-1. , -1.57, -2.19, -2.85, -3.58, -4.38, -5.26, -6.24, -7.34,
-8.56, -9.88])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([ 2.72, 2.45, 2.15, 1.83, 1.48, 1.11, 0.72, 0.29, -0.15,
-0.63, -1.13, -1.67, -2.24, -2.86, -3.52, -4.24, -5.03, -5.87,
-6.78, -7.73])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bhat = splev(TC[k], fun)
y = np.array([ -9.19, -5.07, -2.63, -0.07, 1.71, 4.13, 5.91, 7.48, 9.27,
10.80, 12.24, 13.68, 15.16, 16.48, 17.8 , 19.12, 20.33, 21.55,
22.86, 24.03])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([ -9.51, -5.6 , -2.46, 0.13, 2.14, 4.28, 6.16, 8.18, 9.59,
11.15, 12.62, 13.99, 15.41, 16.76, 18.1 , 19.4 , 20.66, 21.85,
23.09, 24.32])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bil = splev(TC[k], fun)
elif P[k] > 3000 and P[k] <= 4000: #%% P = 4000 Region
isoline = P[k] - 3000
x = np.arange(25,525,25)
y = np.array([ 2.72, 2.45, 2.15, 1.83, 1.48, 1.11, 0.72, 0.29, -0.15,
-0.63, -1.13, -1.67, -2.24, -2.86, -3.52, -4.24, -5.03, -5.87,
-6.78, -7.73])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([ 2.77, 2.51, 2.22, 1.91, 1.58, 1.23, 0.86, 0.47, 0.05,
-0.38, -0.84, -1.33, -1.85, -2.4 , -2.99, -3.63, -4.32, -5.06,
-5.84, -6.63])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bhat = splev(TC[k], fun)
y = np.array([ -9.51, -5.6 , -2.46, 0.13, 2.14, 4.28, 6.16, 8.18, 9.59,
11.15, 12.62, 13.99, 15.41, 16.76, 18.1 , 19.4 , 20.66, 21.85,
23.09, 24.32])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([-10.44, -5.68, -2.15, 0.71, 3.05, 5.13, 7.05, 9.05,
10.4 , 11.92, 13.39, 14.83, 16.23, 17.52, 18.81, 20.13,
21.38, 22.56, 23.74, 24.94])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bil = splev(TC[k], fun)
elif P[k] > 4000 and P[k] <= 5000: #%% P = 4000 Region
isoline = P[k] - 4000
x = np.arange(25,525,25)
y = np.array([ 2.77, 2.51, 2.22, 1.91, 1.58, 1.23, 0.86, 0.47, 0.05,
-0.38, -0.84, -1.33, -1.85, -2.4 , -2.99, -3.63, -4.32, -5.06,
-5.84, -6.63])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([ 2.82, 2.56, 2.28, 1.99, 1.67, 1.33, 0.98, 0.6 , 0.21,
-0.2 , -0.63, -1.09, -1.58, -2.1 , -2.65, -3.25, -3.89, -4.57,
-5.28, -6. ])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bhat = splev(TC[k], fun)
y = np.array([-10.44, -5.68, -2.15, 0.71, 3.05, 5.13, 7.05, 9.05,
10.4 , 11.92, 13.39, 14.83, 16.23, 17.52, 18.81, 20.13,
21.38, 22.56, 23.74, 24.94])
fun = splrep(x, y)
xpred = np.linspace(0, 500, 100)
ypred1 = splev(xpred, fun)
y = np.array([-11.76, -5.87, -1.68, 1.39, 4.21, 6.26, 8.13, 10.3,
11.58, 13.15, 14.54, 16.01, 17.36, 18.65, 19.83, 21.23,
22.38, 23.64, 24.78, 25.95])
fun = splrep(x, y)
ypred2 = splev(xpred, fun)
newy = (1000-isoline)/1000*ypred1 + (isoline/1000)*ypred2
fun = splrep(xpred, newy)
bil = splev(TC[k], fun)
elif P[k] >= 5000: #%% P = 5000 Region and beyond
x = np.arange(25,525,25)
y = np.array([ 2.82, 2.56, 2.28, 1.99, 1.67, 1.33, 0.98, 0.6 , 0.21,
-0.2 , -0.63, -1.09, -1.58, -2.1 , -2.65, -3.25, -3.89, -4.57,
-5.28, -6. ])
fun = splrep(x, y)
bhat = splev(TC[k], fun)
y = np.array([-11.76, -5.87, -1.68, 1.39, 4.21, 6.26, 8.13, 10.3,
11.58, 13.15, 14.54, 16.01, 17.36, 18.65, 19.83, 21.23,
22.38, 23.64, 24.78, 25.95])
fun = splrep(x, y)
bil = splev(TC[k], fun)
# bihat NaCl (b NaCl = bhat/(2.303RT)) from Table 29 (bi here) in kg/mol * 1e+3
# b Na+Cl- from Table 30 (bil here) in kg/mol * 1e+2
bihat = bhat*1e-3 # [kg/mol]
bi[k] = bihat/(np.log(10)*R*convert_temperature( TC[k], Out_Unit = 'K' )) #[kg/cal]
bijl[k] = bil*1e-2
# activity and osmotic coefficients
# for j in range(len(I)):
for i in range(2):
zabsi = np.abs(z[i])
if i < 1: rex = 1.81
else: rex = 1.91
azero = 2*(rej[i] + zabsi*rex)/(zabsi + 1)
omega = 1.66027e5*z[i]**2/rej[i]
lambdaa = 1 + Bh*azero*I**0.5
loggamma[i] = - Ah * z[i]**2 * I**0.5 / lambdaa - np.log10(1 + mwH2O * mstar) + \
(omega*bi + bijl - 0.19*(np.abs(z[i]) - 1))*I
summt[i] = mtj * ( (Ah*z[i]**2/((azero*Bh)**3*I)) *\
(lambdaa - 1/lambdaa - 2*np.log(lambdaa)) + \
(-np.log10(1 + mwH2O * mstar)/(mwH2O*mstar)) - \
0.5*(omega*bi*I + (bijl - 0.19 *(np.abs(z[i]) - 1)) * \
mchr*0.5) )
mean_act = 10**((loggamma[0]+loggamma[1])/2)
phi = -np.log(10)*(summt[0]+summt[1])/mstar
phi = np.where(np.isnan(phi), 0, phi)
aw = np.exp(-phi*mstar*mwH2O)
return aw, phi, mean_act
[docs]
def aw_correlation(TC, P, Dielec_method = None, **rhoEDB):
"""
Calculates the water activity correlation coefficients at given temperature and pressure
Parameters:
----------
TC : Temperature [°C]
P : pressure [bar]
Dielec_method : specify either 'FGL97' or 'JN91' or 'DEW' as the method to calculate dielectric
constant (optional), if not specified default - 'JN91'
rhoEDB : dictionary of water properties like density (rho), dielectric factor (E) and
Debye-Hückel coefficients (optional)
Returns:
----------
ch20 : water activity correlation coefficients
Usage
-------
[ch20] = aw_correlation( TC, P)
"""
Dielec_method = 'JN91' if Dielec_method is None else Dielec_method
if rhoEDB.__len__() != 0:
rho = rhoEDB['rho'].ravel()
E = rhoEDB['E'].ravel()
Ah = rhoEDB['Ah'].ravel()
Bh = rhoEDB['Bh'].ravel()
else:
if Dielec_method.upper() == 'DEW':
rho = ZhangDuan(T = TC, P = P).rho
else:
rho = iapws95(T = TC, P = P).rho
water = water_dielec(T = TC, P = P, Dielec_method = Dielec_method)
E, Ah, Bh = water.E, water.Ah, water.Bh
rhoEDB = {'rho': rho, 'E': E, 'Ah': Ah, 'Bh': Bh}
if np.ndim(TC) == 0 | np.ndim(P) == 0:
TC = np.array(TC).ravel()
P = np.array(P).ravel()
# assign ionic strength from 0 to 3
N = 100
Is = np.linspace(0, 6, N).reshape(-1,1)
mwH2O = 18.01528/1000 # kg/mol
# Water Activity
aw = Helgeson_activity(TC, P, Is, Dielec_method = Dielec_method, **rhoEDB)[0]
ch20 = np.zeros([4, len(TC)])
x0 = [1.454, 0.02236, 9.380e-3, -5.362e-4]
for i in range(len(TC)):
if np.sum(np.isnan(aw[:, i])) == 0:
Is_0 = Is [1:] # avoid zero values
Ahi = Ah[i]
ch20func = lambda Is_0, *x: (-2*Is_0*mwH2O * \
(1 - (np.log(10)*Ahi/(x[0]**3*Is_0)) * \
((1 + x[0]*np.sqrt(Is_0)) - 2*np.log(1 + x[0]*np.sqrt(Is_0)) - \
( 1/(1 + x[0]*np.sqrt(Is_0)) ) ) + \
(x[1]*Is_0/2) + (2/3*x[2]*Is_0**2) + (3/4*x[3]*Is_0**3) ))
ch20[:, i], pcov = curve_fit(ch20func, Is_0.ravel(), np.log(aw[1:, i]).ravel(),
p0=x0, maxfev = 1000000)
else:
ch20[:, i] = [500]*4
return ch20
#%%------------------------------------------------------------------------------------
[docs]
class calcRxnlogK():
"""
This class implemetation calculates logK values for any reaction with the option for extrapolation where rho < 350kg/m3 \n
Parameters
----------
T : float, vector
Temperature [°C]
P : float, vector
Pressure [bar]
Specie : string
specify the species for logK calculation, either the Product species of any reaction or solid solutions or clay like 'AnAb' or 'AbOr' or 'FoFa' or 'EnFe' or 'DiHedEnFe' or 'clay' \n
Specie_class : string, optional
specify the class of species like 'aqueous', 'minerals', 'liquids', or 'gases'
elem : list
list containing nine or ten parameters with clay names and elements compositions with the following format ['Montmorillonite_Lc_MgK', 'Si', 'Al', 'FeIII', 'FeII', 'Mg', 'K', 'Na', 'Ca', 'Li', 'H3O'] \n
dbaccessdic : dict
direct-acess database dictionary
rhoEGextrap : dict
dictionary of water properties like density (rho), dielectric factor (E) and Gibbs Energy for density region 350-550kg/m3
group : string
specify the structural layering of the phyllosilicate, for layers composed of ``1 tetrahedral + 1 octahedral sheet (1:1 layer)`` - specify ``'7A'``, ``2 tetrahedral + 1 octahedral sheet (2:1 layer)`` - specify ``'10A'``, or the latter with ``a brucitic sheet in the interlayer (2:1:1 layer)`` - specify ``'14A'`` (optional), if not specified, default is '10A' for smectites, micas, et cetera \n
ClayMintype : string
specify either 'Smectite' or 'Chlorite' or 'Mica' as the clay type, if not specified default - 'Smectites'
X : float
volume fractions of any (Anorthite, Albite, Forsterite, Enstatite) or mole fraction of Mg or Fraction of supplied Fe2O3 to convert to FeO when no FeO is supplied
cpx_Ca : float
number of moles of Ca in formula unit (=1 for Di, Hed), must be greater than zero
sourcedic : dict
source database reactions dictionary
specielist : list of list, optional
source database species grouped into categories [element, basis, redox, aqueous, minerals, gases, oxides]
Dielec_method : string
specify either 'FGL97' or 'JN91' or 'DEW' as the method to calculate dielectric constant, default is 'JN91' \n
heatcap_method : string
specify either 'SUPCRT' or 'Berman88' or 'HP11' or 'HF76' as the method to calculate thermodynamic properties of any mineral or gas, default is 'SUPCRT' \n
ThermoInUnit : string
specify either 'cal' or 'KJ' as the input units for species properties (optional), particularly used to covert KJ data to cal by supcrtaq function if not specified default - 'cal'
Al_Si : string
specify either 'pygcc' or 'Arnórsson_Stefánsson' as the input to express Al and Si species in solid solution (optional), 'Arnórsson_Stefánsson' expresses them as 'Al(OH)4-' and 'H4SiO4(aq)', respectively while pygcc uses 'Al3+' and 'SiO2(aq)', respectively if not specified default - 'pygcc'
Int_Mg_fract : string
specify the fraction of Mg to partition into Interlayer sheet and the remainder will be partitioned into Octahedral sheet, if not specified, default is 1
Int_Li_fract : string
specify the fraction of Li to partition into Interlayer sheet and the remainder will be partitioned into Octahedral sheet, if not specified, default is 1
heatcap_approx : string
specify either 'Maier-Kelley' or 'constant' as the approximation method for clay minerals' specific heat capacity calculation, default is 'constant', based on ClayTherm's definition for specific cations (Octahedral sites: Li+, Mn2+, Cr3+, Ni2+, Co2+, Zn2+; Interlayer sites: Cs+, Rb+, Li+, Ba2+, Sr2+, Mg2+, Cu2+, Co2+, Zn2+, H3O+) \n
sourceformat : string
specify the source database format, either 'GWB', 'EQ36' or 'PHREEQC'
densityextrap : float, vector
specify the extrapolation option for density-logK, 'Yes'/True or 'No'/False
oxide_wt : dict
Oxide composition in weight percent. Recognized oxides:
'SiO2', 'Al2O3', 'Fe2O3', 'FeO', 'MgO', 'CaO', 'Na2O', 'K2O'.
Other oxides (TiO2, MnO, P2O5, ...) are dropped from the calculation; only the recognized oxides contribute to the mass
normalization.
ideal_oxygens : int, optional
Oxygens per formula unit prior to per-Si normalization.
Default 4 (Techer 2001 convention).
Returns
-------
log_K : float, vector
logarithmic K value(s) \n
dGrxn : float, vector
Total reaction Gibbs energy [cal/mol] \n
Usage
----------
The general usage of water_dielec is as follows: \n
(1) For water dielectric properties at any Temperature and Pressure: \n
calclogK = calcRxnlogK(T = T, P = P, Dielec_method = 'JN91', **kwargs), \n
where T is temperature in celsius and P is pressure in bar
(2) For water dielectric properties at any Temperature and density : \n
calclogK = calcRxnlogK(T = T, rho = rho, Dielec_method = 'JN91', **kwargs), \n
where T is temperature in celsius and rho is density in kg/m³
(3) For water dielectric properties at any Temperature and Pressure on steam saturation curve: \n
calclogK = calcRxnlogK(T = T, P = 'T', Dielec_method = 'JN91', **kwargs), \n
where T is temperature in celsius, followed with a quoted character 'T' to reflect steam saturation pressure \n
calclogK = calcRxnlogK(P = P, T = 'P', Dielec_method = 'JN91', **kwargs), \n
where P is pressure in bar, followed with a quoted character 'P' to reflect steam saturation temperature
Examples
--------
>>> ps = db_reader(sourcedb = './default_db/thermo.com.dat',
sourceformat = 'gwb', dbaccess = './default_db/speq21.dat')
>>> calclogK = calcRxnlogK(T = 100, P = 50, Specie = 'H2S(aq)',
Specie_class = 'aqueous',
dbaccessdic = ps.dbaccessdic,
sourcedic = ps.sourcedic, specielist = ps.specielist)
>>> calclogK.logK, calclogK.dGrxn
-6.4713, 11049.3168
"""
kwargs = {"T": None, "Specie": None, "Specie_class": None, "ThermoInUnit": 'cal',
"P": None, "group": None, "X": None, "cpx_Ca": None, "elem": None,
'rhoEGextrap': None, "sourcedic": None, "specielist": None,
'dbaccessdic': None, "Dielec_method": None, "sourceformat": None,
'heatcap_method': None, "densityextrap": None, 'rhoEG': None,
'Int_Mg_fract': None, 'Int_Li_fract': None, 'heatcap_approx': None,
'ClayMintype': 'Smectite', "Al_Si": 'pygcc',
# Basalt-glass options (solidsolution_type='glass')
"oxide_wt": None, "ideal_oxygens": 4}
def __init__(self, **kwargs):
self.kwargs = calcRxnlogK.kwargs.copy()
self.__calc__(**kwargs)
[docs]
def __calc__(self, **kwargs):
self.kwargs.update(kwargs)
"""initialization """
self.TC = self.kwargs["T"]; self.P = self.kwargs["P"]
self.sourceformat = self.kwargs['sourceformat']; self.specielist = self.kwargs['specielist'];
self.sourcedic = self.kwargs['sourcedic']; self.Dielec_method = self.kwargs['Dielec_method'];
self.rhoEG = self.kwargs['rhoEG']; self.ThermoInUnit = self.kwargs['ThermoInUnit']
self.rhoEGextrap = self.kwargs['rhoEGextrap']; self.Specie = self.kwargs['Specie']
self.Specie_class = self.kwargs['Specie_class']; self.elem = self.kwargs['elem']
self.cpx_Ca = self.kwargs['cpx_Ca']; self.group = self.kwargs['group']
self.X = self.kwargs['X']; self.heatcap_method = self.kwargs['heatcap_method']
self.ClayMintype = self.kwargs['ClayMintype']; self.Al_Si = self.kwargs["Al_Si"]
self.Int_Mg_fract = self.kwargs['Int_Mg_fract']; self.Int_Li_fract = self.kwargs["Int_Li_fract"]
self.heatcap_approx = self.kwargs["heatcap_approx"]
self.oxide_wt = self.kwargs["oxide_wt"]
self.ideal_oxygens = self.kwargs["ideal_oxygens"]
self.densityextrap = 'No' if (self.kwargs['densityextrap'] is None or self.kwargs['densityextrap'] is False) else 'Yes' if self.kwargs['densityextrap'] is True else self.kwargs['densityextrap']
self.Dielec_method = 'JN91' if self.Dielec_method is None else self.Dielec_method
self.heatcap_method = 'SUPCRT' if self.heatcap_method is None else self.heatcap_method
self.sourceformat = 'GWB' if self.sourceformat is None else self.sourceformat
if self.kwargs['dbaccessdic'] == None:
self.dbaccess_dir = './default_db/speq21.dat'
self.dbaccess_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.dbaccess_dir)
self.dbaccessdic = db_reader(dbaccess = self.dbaccess_dir).dbaccessdic
else:
self.dbaccessdic = self.kwargs['dbaccessdic']
if isinstance(self.P, str) or isinstance(self.TC, str):
if self.P == 'T':
self.P = iapws95(T = self.TC, P = self.P).P
self.P = np.asarray(self.P, dtype=float)
# self.P[np.isnan(self.P) | np.isinf(self.P) | (self.P < 1)] = 1.0133
elif self.TC == 'P':
self.TC = iapws95(P = self.P).TC
if np.ndim(self.TC) == 0 :
self.TC = np.array(self.TC).ravel()
elif np.size(self.TC) == 2:
self.TC = np.array([roundup_tenth(j) if j != 0 else 0.01
for j in np.linspace(self.TC[0], self.TC[-1], 8)])
if np.size(self.P) <= 2:
self.P = np.ravel(self.P)
self.P = self.P[0]*np.ones(np.size(self.TC))
if self.rhoEG is None:
if self.Dielec_method.upper() == 'DEW':
water = ZhangDuan(T = self.TC, P = self.P)
else:
water = iapws95(T = self.TC, P = self.P)
rho, dGH2O = water.rho, water.G
E = water_dielec(T = self.TC, P = self.P, Dielec_method = self.Dielec_method).E
self.rhoEG = {'rho': rho, 'E': E, 'dGH2O': dGH2O}
if self.densityextrap.lower() == 'yes':
subBornptrs = self.rhoEG['rho'] < 350
self.nonsubBornptrs = self.rhoEG['rho'] >= 350
rhoEG_nonsubBornptrs = {'rho': self.rhoEG['rho'][self.nonsubBornptrs], 'E': self.rhoEG['E'][self.nonsubBornptrs],
'dGH2O': self.rhoEG['dGH2O'][self.nonsubBornptrs]}
if self.rhoEGextrap is None:
# Calculate the rho E G for density extrapolation method here so we have it below
self.rhoEGextrap = {}
if any(subBornptrs):
for i, j in enumerate(zip(self.TC[subBornptrs], self.P[subBornptrs])):
rhoextrap = np.linspace(350, 550, 3)
Pextrap = iapws95(T = j[0], rho = rhoextrap).P if self.Dielec_method.upper() != 'DEW' else ZhangDuan(T = j[0], rho = rhoextrap).P
Textrap = j[0]*np.ones(np.size(Pextrap))
dGH2O = iapws95(T = Textrap, P = Pextrap).G if self.Dielec_method.upper() != 'DEW' else ZhangDuan(T = Textrap, P = Pextrap).G
E = water_dielec(T = Textrap, P = Pextrap, Dielec_method = self.Dielec_method).E
rhoextrap = np.around(rhoextrap, 3)
self.rhoEGextrap['%d_%d' % (j[0], j[1])]= {'rho': rhoextrap,'E': E, 'dGH2O': dGH2O,
'Textrap': Textrap, 'Pextrap': Pextrap}
if self.densityextrap.lower() == 'no':
if self.Specie.lower().startswith(('plagio', 'oliv', 'pyroxe', 'alk-')) or self.Specie.lower() in ['cpx', 'clay', 'glass', 'basalt_glass', 'basaltglass']:
self.logK, _, _, _, self.Rxn = self.AllRxnslogK( self.TC, self.P, self.rhoEG)
else:
self.logK, self.dGrxn, dGP, dGRs, _ = self.AllRxnslogK( self.TC, self.P, self.rhoEG)
self.nonsubBornptrs = [False]*len(self.TC) # Required to shut off density extrapolation prompt
elif self.densityextrap.lower() == 'yes':
self.logK = np.nan*np.zeros(len(self.TC))
self.dGrxn = np.nan*np.zeros(len(self.TC))
if any(subBornptrs):
self.logK[subBornptrs] = self.densitylogKextrap( self.TC[subBornptrs], self.P[subBornptrs],
self.rhoEGextrap)
if any(self.nonsubBornptrs):
if self.Specie.lower().startswith(('plagio', 'oliv', 'pyroxe', 'alk-')) or self.Specie.lower() in ['cpx', 'clay', 'glass', 'basalt_glass', 'basaltglass']:
self.logK[self.nonsubBornptrs], _, _, _, self.Rxn = self.AllRxnslogK( self.TC[self.nonsubBornptrs], self.P[self.nonsubBornptrs], rhoEG_nonsubBornptrs)
else:
self.logK[self.nonsubBornptrs], self.dGrxn[self.nonsubBornptrs], dGP, dGRs, _ = self.AllRxnslogK( self.TC[self.nonsubBornptrs], self.P[self.nonsubBornptrs], rhoEG_nonsubBornptrs)
[docs]
def AllRxnslogK( self, TC, P, rhoEG):
"""
This function calculates logK values of all reactions including solid-solution and clay minerals \n
Parameters
----------
TC : temperature [°C] \n
P : pressure [bar] \n
rhoEG : dictionary of water properties like density (rho),
dielectric factor (E) and Gibbs Energy (optional) \n
Returns
-------
logK : logarithmic K value(s) \n
dGrxn : Total reaction Gibbs energy [cal/mol] \n
dGP : Product specie Gibbs energy [cal/mol] \n
dGRs : Reactant species Gibbs energy [cal/mol] \n
Rxn : dict
The calculated dictionary of reaction thermodynamic properties has the following properties:
* type: solid-solution mineral type, [-]
* name: solid-solution name, [K]
* formula: solid-solution mineral formula, [-]
* MW: Molecular weight, [g/mol]
* min: solid-solution mineral properties, ['formula', 'source date', dG[cal/ml], dH[cal/mol], S[cal/mol-K], V[cm3/mol], a[cal/mol-K], b[10^3 cal/mol/K^2], c[10^-5 cal/mol/K]]
* spec: list of species, [-]
* coeff: list of corresponding coefficients of species above, [-]
* nSpec: Total number of species, [-]
* V: Molar volume, [cm3/mol]
* source: Source of thermo data, [kJ/kg·K]
* elements: list of elements and their total numbers, [-]
Usage
-------
The general usage of calcRxnlogK without the optional argument is as follows: \n
(1) Not on steam saturation curve: \n
[logK, dGrxn, dGP, dGRs] = AllRxnslogK(TC, P, Prod, dbaccessdic, sourcedic, specielist), \n
where TC is temperature in celsius and P is pressure in bar;
(2) On steam saturation curve: \n
[logK, dGrxn, dGP, dGRs] = AllRxnslogK(TC, 'T', Prod, dbaccessdic, sourcedic, specielist), \n
where TC is temperature in celsius, followed with a quoted char 'T' \n
[logK, dGrxn, dGP, dGRs] = AllRxnslogK(P, 'P', Prod, dbaccessdic, sourcedic, specielist), \n
where P is pressure in bar, followed with a quoted char 'P'.
(3) Meanwhile, usage with any specific dielectric constant method ('FGL97') for
condition not on steam saturation curve is as follows. Default method is 'JN91' \n
[logK, dGrxn, dGP, dGRs] = AllRxnslogK( TC, P, Prod, dbaccessdic, sourcedic, specielist, Dielec_method = 'FGL97')
"""
# Always returns a 5-tuple (logK, dGrxn, dGP, dGRs, Rxn).
# Solid-solution / clay branches populate `Rxn` and leave dGrxn/dGP/dGRs as None.
# Regular species branch populates dGrxn/dGP/dGRs and leaves Rxn as None.
if self.Specie.lower().startswith(('plagio', 'oliv', 'pyroxe', 'alk-')):
ss = solidsolution_thermo(X = self.X, T = TC, P = P, Dielec_method = self.Dielec_method,
dbaccessdic = self.dbaccessdic, solidsolution_type = self.Specie,
ThermoInUnit = self.ThermoInUnit, rhoEG = rhoEG, Al_Si = self.Al_Si)
return ss.logK, None, None, None, ss.Rxn
elif self.Specie.lower() == 'cpx':
ss = solidsolution_thermo(cpx_Ca = self.cpx_Ca, X = self.X, T = TC, P = P,
dbaccessdic = self.dbaccessdic, Dielec_method = self.Dielec_method,
solidsolution_type = 'cpx', ThermoInUnit = self.ThermoInUnit,
rhoEG = rhoEG, Al_Si = self.Al_Si)
return ss.logK, None, None, None, ss.Rxn
elif self.Specie.lower()in ('glass', 'basalt_glass', 'basaltglass'):
ss = solidsolution_thermo(glass_oxide_wt = self.oxide_wt, FeO_split = self.X, T = TC, P = P,
dbaccessdic = self.dbaccessdic, sourcedic = self.sourcedic, specielist = self.specielist,
Dielec_method = self.Dielec_method, solidsolution_type = 'basalt_glass',
rhoEG = rhoEG, ideal_oxygens = self.ideal_oxygens)
return ss.logK, None, None, None, ss.Rxn
elif self.Specie.lower() == 'clay':
logK, Rxn = calclogKclays(TC, P, *self.elem, dbaccessdic = self.dbaccessdic,
group = self.group, Dielec_method = self.Dielec_method,
Int_Mg_fract = self.Int_Mg_fract, Int_Li_fract = self.Int_Li_fract,
ThermoInUnit = self.ThermoInUnit, ClayMintype = self.ClayMintype,
heatcap_approx = self.heatcap_approx, **rhoEG)
return logK, None, None, None, Rxn
else:
logK, dGrxn, dGP, dGRs = self.RxnlogK( TC, P, self.Specie, rhoEG)
return logK, dGrxn, dGP, dGRs, None
[docs]
def RxnlogK( self, TC, P, Prod, rhoEG):
"""
This function calculates logK values of any reaction \n
Parameters
----------
TC : temperature [°C] \n
P : pressure [bar] \n
Prod : Product species of the reaction \n
dbaccessdic : direct-acess database dictionary \n
sourcedic : source database reactions dictionary \n
specielist : source database species grouped into
[element, basis, redox, aqueous, minerals, gases, oxides] \n
Dielec_method : specify either 'FGL97' or 'JN91' or 'DEW' as the method
to calculate dielectric constant, default is 'JN91' \n
sourceformat: source database format, either 'GWB' or 'EQ36', default is 'GWB'
heatcap_method : specify either 'SUPCRT' or 'Berman88' or 'HP11' or 'HF76' as the method to calculate thermodynamic properties
of any mineral, default is 'SUPCRT' \n
rhoEG : dictionary of water properties like density (rho),
dielectric factor (E) and Gibbs Energy (optional) \n
Returns
-------
logK : logarithmic K value(s) \n
dGrxn : Total reaction Gibbs energy [cal/mol] \n
dGP : Product specie Gibbs energy [cal/mol] \n
dGRs : Reactant species Gibbs energy [cal/mol] \n
Usage
-------
The general usage of calcRxnlogK without the optional argument is as follows: \n
(1) Not on steam saturation curve: \n
[logK, dGrxn, dGP, dGRs] = RxnlogK(TC, P, Prod, dbaccessdic, sourcedic, specielist), \n
where TC is temperature in celsius and P is pressure in bar;
(2) On steam saturation curve: \n
[logK, dGrxn, dGP, dGRs] = RxnlogK(TC, 'T', Prod, dbaccessdic, sourcedic, specielist), \n
where TC is temperature in celsius, followed with a quoted char 'T' \n
[logK, dGrxn, dGP, dGRs] = RxnlogK(P, 'P', Prod, dbaccessdic, sourcedic, specielist), \n
where P is pressure in bar, followed with a quoted char 'P'.
(3) Meanwhile, usage with any specific dielectric constant method ('FGL97') for
condition not on steam saturation curve is as follows. Default method is 'JN91' \n
[logK, dGrxn, dGP, dGRs] = RxnlogK( TC, P, Prod, dbaccessdic, sourcedic, specielist, Dielec_method = 'FGL97')
"""
R = 1.9872041 # cal/mol/K
dGH2O = rhoEG['dGH2O'].ravel()
TK = convert_temperature( TC, Out_Unit = 'K' )
if self.sourceformat.upper() == 'EQ36':
rxnspecies = [j for k,j in enumerate(self.sourcedic[Prod]) if k not in [2,3]]
elif self.sourceformat.upper() == 'GWB' or self.sourceformat.upper() == 'PHREEQC':
rxnspecies = self.sourcedic[Prod]
method = 'SUPCRT' if (self.heatcap_method != 'HP11' and (Prod.endswith(('(g)', ',g')) or
self.Specie_class == 'gases')) else self.heatcap_method
# print(self.heatcap_method, Prod, method)
if Prod == 'e-' or Prod == 'eh':
dGP = 0
elif Prod == 'H2O':
dGP = dGH2O
elif Prod in ['Hydroxyapatite', 'Fluorapatite', 'Ankerite', 'Acmite', 'Molybdenite', 'Molybdite'] or Prod.startswith('ss_'):
if self.heatcap_method != 'HP11': #and self.dbaccessdic[Prod][1].split()[0] in ['H&P2011', 'R&H95']
dGP = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[Prod], method = 'HF76').dG
else:
dGP = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[Prod], Species = Prod, method = 'HP11').dG
elif self.specielist is None:
if (Prod.endswith(('(aq)','+','-')) or Prod[-1].isdigit()):
dGP = supcrtaq(TC, P, self.dbaccessdic[Prod.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)').replace('CH4(aq)', 'Methane(aq)')],
Dielec_method = self.Dielec_method, ThermoInUnit = self.ThermoInUnit, **rhoEG)
else:
dGP = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[Prod], Species = Prod,
method = method).dG
elif (Prod in self.specielist[4] + self.specielist[5] + self.specielist[6]) or (self.Specie_class in ['minerals', 'liquids', 'gases']): #
dGP = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[Prod], Species = Prod,
method = method).dG
else:
dGP = supcrtaq(TC, P, self.dbaccessdic[Prod.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)').replace('CH4(aq)', 'Methane(aq)')],
Dielec_method = self.Dielec_method, ThermoInUnit = self.ThermoInUnit, **rhoEG)
total_reactants = int(len(rxnspecies[2:])/2)
dGRs = 0
for i in range(total_reactants):
R_coeff = float(rxnspecies[2 + 2*i])
R_specie = rxnspecies[4 + 2*i - 1]
method = 'SUPCRT' if (self.heatcap_method != 'HP11' and R_specie.endswith(('(g)', ',g')) ) else self.heatcap_method
# print(self.heatcap_method, R_specie, method)
if R_specie == 'e-' or R_specie == 'eh':
dGR = 0
elif R_specie == 'H2O':
dGR = dGH2O
elif R_specie in ['Hydroxyapatite', 'Fluorapatite', 'Ankerite', 'Acmite', 'Molybdenite', 'Molybdite'] or R_specie.startswith('ss_'):
if self.heatcap_method != 'HP11':
dGR = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[R_specie], method = 'HF76').delG
else:
dGP = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[R_specie], Species = R_specie, method = 'HP11').dG
elif self.specielist is None:
if (R_specie.endswith(('(aq)','+','-')) or R_specie[-1].isdigit()):
dGR = supcrtaq(TC, P, self.dbaccessdic[R_specie.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)').replace('CH4(aq)', 'Methane(aq)')],
Dielec_method = self.Dielec_method, ThermoInUnit = self.ThermoInUnit, **rhoEG)
else:
dGR = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[R_specie], Species = R_specie,
method = method).dG
elif (R_specie in self.specielist[4] + self.specielist[5] + self.specielist[6]) or R_specie.endswith(('(g)', ',g')):
dGR = heatcap( T = TC, P = P, Species_ppt = self.dbaccessdic[R_specie], Species = R_specie,
method = method).dG
else:
dGR = supcrtaq(TC, P, self.dbaccessdic[R_specie.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)').replace('CH4(aq)', 'Methane(aq)')],
Dielec_method = self.Dielec_method, ThermoInUnit = self.ThermoInUnit, **rhoEG)
dGRs = dGRs + R_coeff*dGR
dGrxn = - dGP + dGRs
logK = (-dGrxn/R/(TK)/np.log(10))
return logK, dGrxn, dGP, dGRs
[docs]
def outputfmt(fid, logK, Rxn, *T, dataset = None, logK_form = None):
"""
This function writes logK and Rxn data to any file using GWB, EQ36, PHREEQC, Pflotran and ToughReact format
Parameters
----------
fid : string
file ID
logK : float, vector
logarithmic K value(s)
Rxn : dict
dictionary of reaction thermodynamic properties
T : float, vector
Temperature value(s), optional, required when 'polycoeffs' is specified for logK_form
dataset : string
specify the dataset format, either 'GWB', 'EQ36', 'PHREEQC', 'Pflotran' or 'ToughReact'
logK_form : string
specify the format of logK either as a set of eight values one for each of the dataset's principal temperatures, or blocks of polynomial coefficients, [values, polycoeffs], default is 'a set of eight values' (optional)
Returns
-------
Output data to the file with filename described in fid with any format mentioned above.
Examples
--------
>>> fid = open('./logK_details.txt', 'w')
>>> logKRxn = calcRxnlogK(T = 100, P = 'T', X = 0.634,
Specie = 'Plagioclase', densityextrap = True)
>>> # output in EQ36 format
>>> outputfmt(fid, logKRxn.logK, logKRxn.Rxn, dataset = 'EQ36')
>>> fid.close()
"""
if len(T) == 0:
TK = 25*np.ones(np.size(logK))
else:
TK = np.asarray(T).ravel()
logK_form = 'values' if logK_form is None else 'polycoeffs' if dataset.lower() == 'phreeqc' else logK_form.lower()
# Warn when temperature is absent but the output format requires it for
# polynomial fitting (polycoeffs) or the PHREEQC analytic expression.
# In this case TK defaults to 298.15 K for every logK point, which gives
# a degenerate fit. When T is supplied the temperature grid is embedded in
# TminK/TmaxK (GWB polycoeffs) or used directly for the analytic fit.
if len(T) == 0 and (logK_form == 'polycoeffs' or dataset.lower() == 'phreeqc'):
warnings.warn(
"outputfmt: no temperature data supplied (*T is empty). "
"Arbitrary values of 298.15 K will be used for all logK points. "
"Pass the temperature grid in Kelvin via *TK_range to obtain a "
"meaningful polynomial fit and correct TminK/TmaxK bounds.",
UserWarning, stacklevel=2
)
# Open the text file.
if dataset.lower() == 'gwb':
fid.writelines("%s " % Rxn['name'])
fid.writelines( "%s= " % list(Rxn.keys())[0])
if Rxn['type'].find('plag') == 0:
fid.writelines( "plagioclase\n")
else:
fid.writelines( "%s\n" % Rxn['type'])
fid.writelines( " %s= " % list(Rxn.keys())[2])
fid.writelines( "%s\n" % Rxn['formula'])
fid.writelines( " mole vol.= %1.3f cc" % Rxn['V'])
fid.writelines( " mole wt.= %1.4f g\n" % Rxn['MW'])
fid.writelines( " %s species in reaction\n" % Rxn['nSpec'])
for i in range(len(Rxn['spec'])):
i = i + 1
fid.writelines( "%9.4f " % Rxn['coeff'][i-1])
fid.writelines( "%-9s " % Rxn['spec'][i-1])
if (i % 3 == 0) | (i % 6 == 0) | (i == len(Rxn['spec'])):
fid.writelines( "\n")
if logK_form.lower() == 'polycoeffs':
Tr = 298.15
logKfunc = lambda TK, *x: x[0] + x[1]*(TK - Tr) + x[2]*(TK**2 - Tr**2) + x[3]*((1/TK) - (1/Tr)) + \
x[4]*((1/TK**2) - (1/Tr**2)) + x[5]*np.log(TK/Tr)
x0 = [-31.9605, 20.6576, 3.73497e-2, -9.01862, 6.0111, 2.5]
valid = np.asarray(logK) != 500
n_valid_gwb = int(np.sum(valid))
if n_valid_gwb >= 6:
logKcorr = curve_fit(logKfunc, TK[valid].ravel(), np.asarray(logK)[valid].ravel(),
p0=x0, maxfev=1000000)[0]
else:
# Fewer valid logK points than the 6 polynomial parameters — use a
# constant approximation (a = mean logK at valid T, all other coeffs = 0).
mean_val = float(np.mean(np.asarray(logK)[valid])) if n_valid_gwb > 0 else 500.0
logKcorr = np.array([mean_val, 0.0, 0.0, 0.0, 0.0, 0.0])
fid.writelines(' a= %15.9f ' % logKcorr[0] + 'b= %15.9f ' % logKcorr[1] + \
'c= %15.6e\n' % logKcorr[2])
fid.writelines(' d= %15.6f ' % logKcorr[3] + 'e= %15.5f ' % logKcorr[4] + \
'f= %15.8f \n' % logKcorr[5])
# Report the temperature range where logK is actually defined (non-placeholder),
# rather than the full grid extent which may include 500-padded endpoints.
valid_TK = TK[valid].ravel()
TminK_val = np.min(valid_TK) if valid_TK.size > 0 else np.min(TK)
TmaxK_val = np.max(valid_TK) if valid_TK.size > 0 else np.max(TK)
fid.writelines(' TminK= %-15.2f ' % TminK_val + 'TmaxK= %-7.2f\n' % TmaxK_val)
else:
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9) | (i == 13) | (i == 17):
fid.writelines(" %9.4f" % logK[i-1])
else:
fid.writelines(" %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == 8) | (i == len(logK)):
fid.writelines( "\n")
fid.writelines( "* gflag = 1 [reported delG0f used]\n" )
if Rxn['type'].find('plag') == 0:
fid.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
else:
fid.writelines( "* extrapolation algorithm: supcrt92/water95\n" )
if Rxn['type'].find('serp') == 0:
fid.writelines( "* reference-state data source = Blanc et al 2015\n" )
elif 'source' in Rxn:
fid.writelines( "* reference-state data source = %s\n" % Rxn['source'])
else:
fid.writelines( "* reference-state data source = supcrt92?\n" )
fid.write( "* delG0f = %8.3f kcal/mol\n" % (Rxn['min'][2]/1000) )
if Rxn['type'] == 'Smectites':
fid.writelines( "* delH0f = %8.3f kcal/mol\n" % (Rxn['min'][3]/1000) )
else:
fid.writelines( "* delH0f = NaN kcal/mol\n")
fid.writelines( "* S0PrTr = %8.3f cal/mol\n" % Rxn['min'][4])
fid.writelines( "\n")
elif dataset.lower() == 'eq36':
fid.writelines('%-25s %s \n' % (Rxn['name'], Rxn['formula']))
fid.writelines(' sp.type = solid\n')
fid.writelines('* EQ3/6 = com, alt, sup\n')
fid.writelines(' revised = 01-Jan-2020\n')
fid.writelines('* mol.wt. =%8.3f g/mol\n' % Rxn['MW'])
fid.writelines(' V0PrTr = %8.3f cm**3/mol [source: %s ]\n' % (Rxn['V'], Rxn['source']))
fid.writelines('****\n')
fid.writelines( " %s element(s):\n" % int(len(Rxn['elements'])/2))
for i in range(len(Rxn['elements'])):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fid.writelines( " %9.4f " % float(Rxn['elements'][i - 1]))
elif i % 2 != 0:
fid.writelines( "%9.4f " % float(Rxn['elements'][i - 1]))
else:
fid.writelines( "%-9s " % (Rxn['elements'][i - 1]))
if (i % 6 == 0) | (i == len(Rxn['elements'])):
fid.writelines( "\n")
fid.writelines('****\n')
fid.writelines( " %s species in reaction:\n" % (Rxn['nSpec'] + 1))
fid.writelines( " %9.4f " % (-1))
fid.writelines( " %-21s " % Rxn['name'])
for i in range(len(Rxn['spec'])):
i = i + 1
fid.writelines( " %9.4f " % Rxn['coeff'][i-1])
fid.writelines( " %-21s " % Rxn['spec'][i-1])
if (i % 2 != 0) | (i == len(Rxn['spec'])):
fid.writelines( "\n")
fid.writelines('*\n')
fid.writelines('**** logK grid [T, P @ Miscellaneous parameters]\n')
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9) | (i == 13) | (i == 17):
fid.writelines( " %9.4f" % logK[i-1])
else:
fid.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fid.writelines( "\n")
fid.writelines( "* gflag = 1 [reported delG0f used]\n" )
if Rxn['type'].find('plag') == 0:
fid.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
else:
fid.writelines( "* extrapolation algorithm: supcrt92/water95\n" )
if Rxn['type'].find('serp') == 0:
fid.writelines( "* ref-state data [source: Blanc et al 2015 ]\n" )
elif 'source' in Rxn:
fid.writelines( "* ref-state data [source: %s ]\n" % Rxn['source'])
else:
fid.writelines( "* ref-state data [source: supcrt92? ]\n" )
fid.writelines( "* delG0f = %8.3f kcal/mol\n" % (Rxn['min'][2]/1000) )
if Rxn['type'] == 'Smectites':
fid.writelines( "* delH0f = %8.3f kcal/mol\n" % (Rxn['min'][3]/1000) )
else:
fid.writelines( "* delH0f = NaN kcal/mol\n")
fid.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % Rxn['min'][4])
fid.writelines( "* Cp coefficients [source: %s ]\n" % Rxn['source'])
fid.writelines( "* T**0 = %11.8e \n" % (Rxn['min'][6]) )
fid.writelines( "* T**1 = %11.8e \n" % (Rxn['min'][7]))
if Rxn['min'][8] < 1:
fid.writelines( "* T**-2 = %12.8e \n" % (Rxn['min'][8]))
else:
fid.writelines( "* T**-2 = %11.8e \n" % (Rxn['min'][8]))
if len(Rxn['min']) > 10:
fid.writelines( "* T**-0.5 = %11.8e \n" % (Rxn['min'][9]))
fid.writelines( "* T**2 = %11.8e \n" % (Rxn['min'][10]))
fid.writelines( "+" + "-"*68 + "\n")
elif dataset.lower() == 'phreeqc':
fid.writelines("%s " % Rxn['name'] + "\n")
lhs_terms = [Rxn['formula'].replace('FeIII', 'Fe')] # start with the mineral formula
rhs_terms = []
for n in range(len(Rxn['coeff'])):
coeff = Rxn['coeff'][n]
specie = denormalize_phreeqc_species_charge(Rxn['spec'][n])
# Absolute value of coefficient for display
abs_coeff = abs(coeff)
# Format term (skip coefficient if it's 1)
if abs_coeff == 1.0:
term = f"{specie}"
else:
term = f"{abs_coeff:.4f}{specie}"
# Negative coeffs go to LHS, positive coeffs go to RHS
if coeff < 0:
lhs_terms.append(term)
else:
rhs_terms.append(term)
# Join LHS and RHS with " + "
to_write = " + ".join(lhs_terms) + " = " + " + ".join(rhs_terms)
fid.writelines(to_write + "\n")
logKfunc = lambda TK, *x: x[0] + x[1]*TK + x[2]*TK**(-1) + x[3]*np.log10(TK) + x[4]*TK**(-2) #+ x[5]*TK**(2)
x0 = [2.06576e2, 3.73497e-2, -9.01862e3, -3.19605e1, 6.0111e5]
valid_phreeqc = np.asarray(logK) != 500
n_valid_phreeqc_out = int(np.sum(valid_phreeqc))
if n_valid_phreeqc_out >= 5:
logKcorr = curve_fit(logKfunc, TK[valid_phreeqc].ravel(),
np.asarray(logK)[valid_phreeqc].ravel(), p0=x0, maxfev=1000000)[0]
info = " " + " ".join("%9.5f" % e for e in logKcorr)
logK25 = logKfunc(convert_temperature( 25, Out_Unit = 'K' ), *logKcorr)
fid.writelines(f" -log_k {logK25:.3f}\n")
fid.writelines(f" -analytic {info}\n")
else:
# Not enough valid logK points for the 5-parameter analytic fit.
# Write log_k only, using the value at the temperature closest to 25 °C.
_vals_out = np.asarray(logK)[valid_phreeqc]
_tks_out = TK[valid_phreeqc]
if _vals_out.size > 0:
_idx25 = int(np.argmin(np.abs(_tks_out - 298.15)))
logK25 = float(_vals_out[_idx25])
else:
logK25 = 500.0
fid.writelines(f" -log_k {logK25:.3f}\n")
if Rxn['type'].find('plag') == 0:
fid.writelines( " # extrapolation algorithm: supcrt92 [92joh/oel]\n" )
else:
fid.writelines( " # extrapolation algorithm: supcrt92/water95\n" )
if Rxn['type'].find('serp') == 0:
fid.writelines( " # reference-state data source = Blanc et al 2015\n" )
elif 'source' in Rxn:
fid.writelines( " # reference-state data source = %s\n" % Rxn['source'])
else:
fid.writelines( " # reference-state data source = supcrt92?\n" )
fid.writelines( "\n")
elif dataset.lower() == 'pflotran':
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
Rxns_lst = ' '.join([ "%8.4f" % Rxn['coeff'][i]+' ' + "'%s'" % Rxn['spec'][i]
for i in range(len(Rxn['spec']))])
info = "'%s'" % Rxn['name'] + ' ' + "%7.3f" % Rxn['V'] + ' ' + str(Rxn['nSpec']) + ' ' + \
Rxns_lst + ' ' + list_logk + ' ' + "%8.4f" % Rxn['MW']
fid.writelines('%s\n' % info)
elif dataset.lower() == 'toughreact':
logKfunc = lambda TK, *x: x[0]*np.log(TK) + x[1] + x[2]*TK + x[3]*TK**(-1) + x[4]*TK**(-2)
x0 = [-3.19605e1, 2.06576e2, 3.73497e-2, -9.01862e3, 6.0111e5]
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
list_logKcorr = ' '.join(str("%.5e" % e) for e in list(logKcorr))
Rxns_spec = [j.replace('++++', '+4') if j.endswith('++++',0)
else j.replace('+++', '+3') if j.endswith('+++',0)
else j.replace('++', '+2') if j.endswith('++',0)
else j.replace('----', '-4') if j.endswith('----',0)
else j.replace('---', '-3') if j.endswith('---',0)
else j.replace('--', '-2') if j.endswith('--',0) else j for j in Rxn['spec']]
Rxn_lst = ' '.join([ "%8.4f" % Rxn['coeff'][i]+' ' + "'%s'" % Rxns_spec[i] for i in range(len(Rxn['spec']))])
info = "%-32s" % Rxn['name'] + "%8.3f" % Rxn['MW'] + " %7.3f" % Rxn['V'] +\
' ' + str(Rxn['nSpec']) + ' ' + Rxn_lst
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fid.writelines('%s\n' % info)
info = '%-35s' % Rxn['name'] + ' ' + list_logk
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fid.writelines('%s\n' % info)
info = '%-35s' % Rxn['name'] + ' ' + list_logKcorr.replace('e','E')
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fid.writelines('%s\n' % info)
return
[docs]
class write_database():
r"""
Class to write the new database for either GWB, EQ3/6, ToughReact, Pflotran or PHREEQC into
a new folder called "output" \n
Parameters
----------
T : float, vector
Temperature [°C] \n
P : float, vector
Pressure [bar] \n
cpx_Ca : string
number of moles of Ca in solid solution of clinopyroxene (optional) if it is ommitted solid solution of clinopyroxene will not be included, 0 < nCa >=1 \n
solid_solution : string, bool
specify the inclusion of solid-solution [Yes/True or No/False], default is 'No' \n
clay_thermo : float, vector
specify the inclusion of clay thermodynamic properties [Yes/True or No/False], default is 'No' \n
logK_form : float, vector
specify the format of logK either as a set of eight values one for each of the dataset’s principal temperatures, or blocks of polynomial coefficients, [values, polycoeffs] default is 'a set of eight values' \n
densityextrap : float, vector
specify the utilization of density extrapolation [Yes/True or No/False], default is 'Yes' \n
dbaccess : string
direct-access database filename and location (optional) \n
dbBerman_dir : string
filename and location of the Berman mineral database (optional) \n
dbHP_dir : string
filename and location of the supcrtbl mineral and gas database, optional
dbaccessformat : string, optional
specify the direct-access/sequential-access database format, either 'speq' or 'supcrtbl', default is 'speq'
sourcedb : string
source database filename and location (optional) \n
sourceformat : string
source database format, either 'GWB', 'EQ36' or 'PHREEQC', default is 'GWB'
sourcedb_codecs : string
specify the name of the encoding used to decode or encode the sourcedb file, optional
objdb : string
new database filename and location (optional) \n
co2actmodel : string
co2 activity model equation [Duan_Sun or Drummond] (optional), if not specified, default is 'Drummond' \n
Dielec_method : string
specify either 'FGL97' or 'JN91' or 'DEW' as the method to calculate dielectric constant, default is 'JN91' (optional) \n
heatcap_method : string
specify either 'SUPCRT' or 'Berman88' or 'HP11' or 'HF76' the method to calculate thermodynamic properties of any mineral, default is 'SUPCRT' \n
ThermoInUnit : string
specify either 'cal' or 'KJ' as the input units for species properties (optional), particularly used to covert KJ data to cal by supcrtaq function if not specified default - 'cal'
dataset : string
specify the dataset format, either 'GWB', 'EQ36', 'PHREEQC', 'Pflotran' or 'ToughReact', default is old GWB database ['GWB'] (optional) \n
print_msg : string, bool
print debug message [True or False], default is False \n
Returns
-------
Output the new database to an ASCII file with filename described in 'objdb' if specified.
Usage
-------
With any Temperature and Pressure:
(1) General format with default dielectric constant and CO2 activity model and exclusions
of solid solutions for GWB \n
write_database(T = T, P = P, cpx_Ca = nCa, dataset = 'GWB', sourceformat = 'GWB') \n
(2) Inclusion of solid solutions and exclusion of solid solution of clinopyroxene and clay thermo \n
write_database(T = T, P = P, solid_solution = 'Yes', clay_thermo = 'Yes', dataset = 'GWB', sourceformat = 'GWB') \n
(3) Inclusion of all solid solutions and clay thermo with \\emph{'Duan_Sun'} CO2 activity model and
'FGL97' dielectric constant calculation \n
write_database(T = T, P = P, cpx_Ca = nCa, solid_solution = 'Yes', clay_thermo = 'Yes', co2actmodel = 'Duan_Sun', Dielec_method = 'FGL97', dataset = 'GWB', sourceformat = 'GWB') \n
With any Temperature or Pressure on the steam saturation curve:
(4) General format with default dielectric constant and CO2 activity model \n
write_database(T = T, P = 'T', cpx_Ca = nCa, dataset = 'GWB', sourceformat = 'GWB'), \n
where T is temperature in celsius and P is assigned a quoted character 'T' to reflect steam saturation pressure \n
write_database(P = P, T = 'P', cpx_Ca = nCa, dataset = 'GWB', sourceformat = 'GWB'), \n
where P is pressure in bar and T is assigned a quoted character 'P' to reflect steam saturation temperature
Examples
--------
>>> write_database(T=np.array([ 0.010, 25., 60., 100., 150., 200.,
250., 300.]), P=200, cpx_Ca = 0.5,
sourceformat = 'GWB', solid_solution = True,
clay_thermo = True, dataset = 'GWB') \n
>>> write_database(T=np.array([ 0.010, 25., 60., 100., 150., 200., 250.,
300.]), P=200, cpx_Ca = 0.5,
sourceformat = 'GWB', solid_solution = True,
clay_thermo = True, dataset = 'Pflotran',
sourcedb = './pygcc/default_db/thermo.2021.dat') \n
"""
kwargs = {
"T": None, # float/array - temperature
"P": None, # float/array - pressure
"cpx_Ca": None, # float - number of moles of Ca in formula unit
"solid_solution": None, # bool - is solid solution
"clay_thermo": None, # bool/vector? - include clay thermodynamic properties
"logK_form": None, # float, vector - format of logK
'dbBerman_dir': None, # string opt - filename and location of berman mineral database
'dbHP_dir': None, # string opt - filename and location of the supcrtbl mineral and gas database
"dbaccess": None, # string - direct-access database filename and location
"sourcedb": None, # string opt - source database filename and location
"objdb": None, # string opt - new database filename and location
"ThermoInUnit": 'cal',
"co2actmodel": None, # string opt - co2 activity model equation (Duan-Sun or Drummond), defaults to Drummond
"Dielec_method": None, # string opt - way to calculate dielectric constant
"heatcap_method": None, # string opt - specifiy method to calculate thermo properties of any mineral (‘SUPCRT’ or ‘Berman88’ or ‘HP11’ or ‘HF76’)
"dataset": None, # string opt - dataset format (‘GWB’, ‘EQ36’, ‘PHREEQC‘, ‘Pflotran’ or ‘ToughReact’)
"sourceformat": None, # string opt - (‘GWB’ or ‘EQ36’ or ‘PHREEQC‘)
'densityextrap': None, # bool opt - utilization of density extrapolation, default is 'Yes'
"sourcedb_codecs": None, # string opt - name of encoding used to decode/encode sourcedb file
"dbaccessformat": 'speq',
"print_msg": True # string, bool - print debugging message
}
def __init__(self, **kwargs):
self.kwargs = write_database.kwargs.copy()
self.kwargs.update(kwargs)
# Required args
self.T = self.kwargs["T"]
self.P = self.kwargs["P"]
self.dbaccessformat = self.kwargs.get("dbaccessformat")
self.dbBerman_dir = self.kwargs.get("dbBerman_dir")
self.dbHP_dir = self.kwargs.get("dbHP_dir")
self.dataset = self.kwargs.get("dataset", "") #.upper()
self.objdb = self.kwargs.get("objdb")
self.co2actmodel = self.kwargs.get("co2actmodel")
self.ThermoInUnit = self.kwargs.get("ThermoInUnit")
self.sourcedb_codecs = self.kwargs.get("sourcedb_codecs")
self.Dielec_method = self.kwargs.get("Dielec_method")
self.cpx_Ca = self.kwargs.get("cpx_Ca")
self.logK_form = self.kwargs.get("logK_form")
self.heatcap_method = self.kwargs.get("heatcap_method")
self.dbaccess = self.kwargs.get("dbaccess")
self.sourceformat = self.kwargs.get("sourceformat")
self.__calc__(**kwargs)
[docs]
def __calc__(self, **kwargs):
# Direct-access database
if self.dbaccess is None:
self.dbaccess = os.path.join(os.path.dirname(os.path.abspath(__file__)), "default_db/speq23.dat")
# Source format
if self.sourceformat is None:
if self.dataset == "GWB":
self.sourceformat = "GWB"
elif self.dataset == "EQ36":
self.sourceformat = "EQ36"
elif self.dataset == "PHREEQC":
self.sourceformat = "PHREEQC"
else:
self.sourceformat = ""
# Source DB selection
self.sourcedb = self._resolve_sourcedb(self.sourceformat, self.kwargs.get("sourcedb") )
# Options
self.solid_solution = self._flag_to_yesno(self.kwargs.get("solid_solution"), default="No")
self.clay_thermo = self._flag_to_yesno(self.kwargs.get("clay_thermo"), default="No")
self.densityextrap = self._flag_to_yesno(self.kwargs.get("densityextrap"), default="Yes")
self.Dielec_method = 'JN91' if self.Dielec_method is None else self.Dielec_method
self.heatcap_method = 'HP11' if self.dbHP_dir is not None else 'Berman88' if self.dbBerman_dir is not None else 'SUPCRT' if self.heatcap_method is None else self.heatcap_method
self.logK_form = 'values' if self.logK_form is None else self.logK_form
self.cpx_Ca = 0 if self.cpx_Ca is None else self.cpx_Ca
# Read DB
self.dbr = db_reader(dbaccess = self.dbaccess, dbBerman_dir = self.dbBerman_dir,
dbHP_dir = self.dbHP_dir, dbaccessformat = self.dbaccessformat,
sourcedb = self.sourcedb, sourceformat = self.sourceformat,
sourcedb_codecs = self.sourcedb_codecs)
# condition to add Dimer from Sverjensky et al. 2014 to SPEQ database
# if self.dbHP_dir is None or self.dbBerman_dir is None:
# print("print database name", self.dbHP_dir)
# Check for Sverjensky Si2O4 requirement
# if self.dbr.header_ref[self.dbr.dbaccessdic['SiO2(aq)'][1].strip(' ref:').split(' ')[0]].startswith('Sverjensky, D. A., Harrison, B., & Azzolini, D., 2014'):
# if 'Si2O4(aq)' not in self.dbr.dbaccessdic.keys() or 'Si2O4(aq)' not in self.dbr.sourcedic.keys():
# warnings.warn('Warning: you are using SiO2(aq) data from Sverjensky et al. (2014) GCA. This thermodynamic model requires that you also add data for Si2O4(aq) to achieve accurate solubility calculations. Please ensure thermodynamic data for Si2O4(aq) are added to both your source GWB or EQ3/6 database AND your source direct-access database.')
si_entry = self.dbr.dbaccessdic.get("SiO2(aq)")
if si_entry:
ref_key = si_entry[1].strip(" ref:").split(" ")[0]
if self.dbr.header_ref[ref_key].startswith("Sverjensky, D. A., Harrison, B., & Azzolini, D., 2014"):
if "Si2O4(aq)" not in self.dbr.dbaccessdic.keys() and "Si2O4(aq)" not in self.dbr.sourcedic.keys():
warnings.warn(
"Warning: Using SiO2(aq) data from Sverjensky et al. (2014) GCA. "
"This thermodynamic model requires that you also add data for Si2O4(aq) to achieve accurate solubility calculations."
"Please ensure thermodynamic data for Si2O4(aq) are added to both your source GWB or EQ3/6 database AND your source direct-access database."
)
# Handle P/T arrays
self._process_PT()
# Write dataset-specific DB
if self.dataset.upper() == 'GWB':
self.write_GWBdb(self.T, self.P)
elif self.dataset.upper() == 'EQ36':
self.write_EQ36db(self.T, self.P )
elif self.dataset.upper() == 'PHREEQC':
self.write_PHREEQCdb(self.T, self.P )
elif self.dataset.lower() == 'pflotran':
self.write_pflotrandb(self.T, self.P )
elif self.dataset.lower() == 'toughreact':
self.write_ToughReactdb(self.T, self.P )
# Build message
self._build_message()
# --------------------------------------------------- Helpers --------------------------------------------------- #
[docs]
def _resolve_sourcedb(self, sourceformat, sourcedb):
"""
Resolve default source database based on format and user input.
"""
base_dir = os.path.dirname(os.path.abspath(__file__))
if sourceformat.lower() == "gwb":
mapping = {
"thermo.com": "thermo.com.dat",
"thermo.2021": "thermo.2021.dat",
"thermo_latest": "thermo_latest.tdat",
"thermo_cemdata_mar": "thermo_cemdata_mar.tdat",
None: "thermo.com.tdat", # fallback
}
fname = mapping.get(sourcedb, sourcedb)
elif sourceformat.lower() == "eq36":
fname = "data0.dat" if sourcedb in (None, "data0") else sourcedb
elif sourceformat.lower() == "phreeqc":
fname = "phreeqc.dat" if sourcedb in (None, "phreeqc") else sourcedb
else:
fname = sourcedb
return os.path.join(base_dir, "default_db", fname)
[docs]
def _flag_to_yesno(self, value, default="No"):
"""
Convert boolean/None/custom flag to Yes/No string.
"""
if value is True:
return "Yes"
elif value is False or value is None:
return default
return value
[docs]
def _process_PT(self):
"""
Process temperature and pressure arrays safely.
Accepted string sentinels:
P == 'T' -> P is computed along the saturation curve for the given T
T == 'P' -> T is computed along the saturation curve for the given P
Any other string raises ValueError. All `==` against the sentinels is
gated on ``isinstance(..., str)`` so an ndarray is never compared to a
string (which produces NumPy FutureWarning / breaks under NumPy 2.x).
Saturation properties are computed directly via
``Dummy().vcalcsatpropT`` / ``Dummy().vcalcsatpropP`` instead of going
through a full ``iapws95(...)`` call.
"""
# --- Sentinel validation -------------------------------------------
if isinstance(self.P, str) and self.P != 'T':
raise ValueError(
f"_process_PT: P={self.P!r} is not a valid sentinel. "
"Use P = 'T' to request the saturation pressure for the given "
"temperature, or pass a numeric pressure."
)
if isinstance(self.T, str) and self.T != 'P':
raise ValueError(
f"_process_PT: T={self.T!r} is not a valid sentinel. "
"Use T = 'P' to request the saturation temperature for the given "
"pressure, or pass a numeric temperature."
)
if isinstance(self.P, str) and isinstance(self.T, str):
raise ValueError(
"_process_PT: P and T cannot both be string sentinels."
)
# --- Saturation-curve handling -------------------------------------
if isinstance(self.P, str): # P == 'T'
self.T = self._expand_temperature(self.T)
TK = convert_temperature(self.T, Out_Unit='K')
_Tc_K = 647.096 # IAPWS95 critical temperature in K
_Pc_bar = 220.64 # critical pressure of water in bar
_TK_arr = np.asarray(TK, dtype=float)
_supercrit = _TK_arr >= _Tc_K
if np.any(_supercrit):
_TC_bad = np.asarray(self.T, dtype=float).ravel()[np.ravel(_supercrit)]
warnings.warn(
f"write_database: {_TC_bad.size} temperature(s) at or above the critical "
f"point ({_TC_bad} °C >= {_Tc_K - 273.15:.2f} °C). "
f"Saturation pressure is undefined; pressure clamped to the critical "
f"pressure ({_Pc_bar} bar). Computed values at these points may be NaN.",
UserWarning, stacklevel=3
)
_TK_clipped = np.minimum(_TK_arr, _Tc_K * (1.0 - 1e-8))
sat_P = Dummy().vcalcsatpropT(_TK_clipped)[0]
self.P = np.asarray(sat_P, dtype=float)
# The temperature clipping above prevents nan/inf from calcsatpropT, so the
# nan/inf guards here are now defensive. The (P < 1) floor is intentional:
# database entries must not be generated below 1.0133 bar (1 atm), since the
# saturation pressure at very low temperatures falls below that threshold.
self.P[np.isnan(self.P) | np.isinf(self.P) | (self.P < 1)] = 1.0133
elif isinstance(self.T, str): # T == 'P'
self.P = np.ravel(np.asarray(self.P, dtype=float))
sat_TK = Dummy().vcalcsatpropP(self.P)[0]
self.T = convert_temperature(sat_TK, Out_Unit='C')
self.T = self._expand_temperature(self.T)
if np.size(self.P) <= 2:
if np.ndim(self.P) == 0:
self.P = np.ravel(self.P)
self.P = self.P[0]*np.ones(np.size(self.T))
[docs]
def _expand_temperature(self, T):
"""
Expand temperature input into proper array.
"""
if np.ndim(T) == 0:
return np.ravel(T)
elif np.size(T) == 2:
if T[-1] > 400:
# for critical region of water (350 - 400C)
return np.array([0.01 if (x == 0)&(T[0] == 0)
else T[0] if (x == 0)&(T[0] != 0)
else roundup_hundredth(T[0] + x*(T[-1] - T[0])/(8 - 1)) if 350 <= (T[0] + x*(T[-1] - T[0])/(8 - 1)) <= 400
else round(T[-1]) if x == 7
else roundup_tenth(T[0] + x*(T[-1] - T[0])/(8 - 1))
for x in range(8)])
elif T[-1] > 350:
return np.array([0.01 if (x == 0)&(T[0] == 0)
else T[0] if (x == 0)&(T[0] != 0)
else roundup_hundredth(T[0] + x*(T[-1] - T[0])/(8 - 1)) if 350 <= (T[0] + x*(T[-1] - T[0])/(8 - 1)) <= 400
else roundup_tenth(T[0] + x*(T[-1] - T[0])/(8 - 1))
for x in range(8)])
else:
return np.array([roundup_tenth(j) if j != 0 else 0.01 for j in np.linspace(T[0], T[-1], 8)])
else:
return np.ravel(T).astype(float)
[docs]
def _build_message(self):
"""Construct final success message."""
# Use basename instead of regex splitting
fname = os.path.basename(self.sourcedb)
self.msg = (
"Database for %s generated successfully using %s dielectric constant, "
"%s %s source database"
% (
self.dataset.upper(),
self.Dielec_method,
fname,
self.sourceformat,
)
)
if self.cpx_Ca != 0:
self.msg += ", full solid solution included"
elif self.solid_solution == "Yes":
self.msg += ", solid solution included with cpx excluded"
if self.clay_thermo == "Yes":
self.msg += ", clay thermodynamics included"
# Report any supplemental minerals that were hard-coded in read_db.py.
# These minerals are injected into dbaccessdic when absent from the user's
# direct access database; their provenance is stored in entry index [1].
_BUILTIN_MINERALS = [
'Fluorapatite', 'Hydroxyapatite', 'Ankerite', 'Acmite',
'ss_Annite', 'ss_Phlogopite', 'Molybdenite', 'Molybdite',
]
dbacc = self.dbr.dbaccessdic
dbsource = self.dbr.sourcedic
present = [m for m in _BUILTIN_MINERALS if m in dbsource]
if present:
# Group minerals by their reference string (index [1], stripped).
by_ref = {}
for name in present:
ref = str(dbacc[name][1]).strip().rstrip('\n').strip()
by_ref.setdefault(ref, []).append(name)
ref_notes = '; '.join(
"%s [%s]" % (', '.join(names), ref)
for ref, names in sorted(by_ref.items())
)
self.msg += "\n Supplemental built-in thermodynamic data: " + ref_notes
if self.kwargs.get("print_msg", False):
print(self.msg)
[docs]
def write_GWBdb(self, T, P ):
r"""
This function writes the new GWB database into a new folder called "output" \n
Parameters
----------
T : temperature [°C] \n
P : pressure [bar] \n
Returns
-------
Outputs the new database to an ASCII file with filename described in 'objdb'. \n
Usage
-------
Example:
(1) General format with default dielectric constant and CO2 activity model and exclusions
of solid solutions \n
write_GWBdb(T, P) \n
(2) Inclusion of solid solutions and clay thermo and exclusion of solid solution of clinopyroxene \n
write_GWBdb(T, P) \n
(3) Inclusion of all solid solutions and clay thermo with \\emph{'Duan_Sun'} CO2 activity model and 'JN91'
dielectric constant calculation \n
write_GWBdb(T, P) \n
"""
nCa_cpx = self.cpx_Ca; logK_form = self.logK_form
solid_solution = self.solid_solution; clay_thermo = self.clay_thermo
sourcedb = self.sourcedb
objdb = self.objdb; Dielec_method = self.Dielec_method
co2actmodel = self.co2actmodel; sourceformat = self.sourceformat
heatcap_method = self.heatcap_method;
densityextrap = self.densityextrap
dbaccessdic, dbname, sourcedic, specielist = self.dbr.dbaccessdic, self.dbr.dbaccess, self.dbr.sourcedic, self.dbr.specielist
MWdic, act_param, chargedic = self.dbr.MWdic, self.dbr.act_param, self.dbr.chargedic
sourcedb_codecs = self.dbr.sourcedb_codecs if self.sourcedb_codecs is None else self.sourcedb_codecs
activity_model = act_param['activity_model']
if sourceformat.upper() == 'GWB':
Mineraltype, fugacity_info = self.dbr.Mineraltype, self.dbr.fugacity_info
# fugacity_model = fugacity_info['fugacity_model']
elif sourceformat.upper() == 'EQ36':
block_info, Elemlist = self.dbr.block_info, self.dbr.Elemlist
if sourceformat.upper() == 'EQ36':
dataset = 'tdat'
dataset_format = 'apr20'
else:
dataset = sourcedb.split('.')[-1]
dataset_format = act_param['dataset_format']
logK_form = 'values' if (logK_form is None) | (dataset_format in ['oct94', 'jul17']) else logK_form.lower()
if Dielec_method.upper() == 'DEW':
water = ZhangDuan(T = T, P = P)
rho, dGH2O, dHH2O, SH2O = water.rho, water.G, np.nan*np.ones(len(T)), np.nan*np.ones(len(T))
else:
water = iapws95(T = T, P = P)
rho, dGH2O, dHH2O, SH2O = water.rho, water.G, water.H, water.S
TK = convert_temperature( T, Out_Unit = 'K' )
if dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26'] and logK_form.lower() == 'polycoeffs':
Tr = 298.15
logKfunc = lambda TK, *x: x[0] + x[1]*(TK - Tr) + x[2]*(TK**2 - Tr**2) + x[3]*((1/TK) - (1/Tr)) + \
x[4]*((1/TK**2) - (1/Tr**2)) + x[5]*np.log(TK/Tr)
x0 = [-31.9605, 20.6576, 3.73497e-2, -9.01862, 6.0111, 2.5]
if os.path.exists(os.path.join(os.getcwd(), 'output/GWB')) == False:
os.makedirs(os.path.join(os.getcwd(), 'output/GWB'))
periodic_table = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'PeriodicTableJSON.json'), encoding='utf8')
data = json.load(periodic_table)
Element = {data['elements'][x]['symbol'] : pd.DataFrame([data['elements'][x]['name'],
data['elements'][x]['atomic_mass']],
index = ['name', 'mass']).T
for x in range(len(data['elements']))}
periodic_table.close()
fid = open(sourcedb, 'r', encoding = sourcedb_codecs)
missing_species = []
elemspeclist = [ symbol for x in specielist[0] for symbol, item in Element.items()
if item.name[0][:5] == x[:5] ] if sourceformat.upper() == 'GWB' else specielist[0]
form_del = [1] if sourceformat.upper() == 'GWB' else [1, 3, 4]
all_species_source = [[i]+k for i, k in sourcedic.items()
if i not in (['eh', 'e-', 'H2O']) or (dataset_format in ['mar21', 'jul22', 'jan26'] and i not in specielist[6])]
# all_species_source = [x.split()[0] if (dataset_format in ['mar21', 'jul22', 'jan26'] and k in specielist[6])
# else [i]+k
# for i, k in sourcedic.items() for x in k if i not in (['eh', 'e-', 'H2O']) or (dataset_format in ['mar21', 'jul22', 'jan26'] and k in specielist[6] and x.strip('\n') and x.split()[0] not in ['a0', '*'] ) ]
all_species_source = [[k for j, k in enumerate(all_species_source[i])
if (j not in form_del and k not in elemspeclist and str(k).strip('0123456789.- ') != '') ]
if (i <= len(specielist[0]))
else [k for j, k in enumerate(all_species_source[i])
if (j not in form_del and str(k).strip('0123456789.- ') != '') ]
for i in range(len(all_species_source)) ]
for num in range(len(all_species_source)): #
if num < len(all_species_source):
if dataset_format in ['mar21', 'jul22', 'jan26']:
lst = [v for v in all_species_source[num] if v not in (specielist[6] + ['eh', 'e-', 'H2O']) ]
else:
lst = [v for v in all_species_source[num] if v not in (['eh', 'e-', 'H2O']) ]
bool_miss = [x.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
not in dbaccessdic.keys() for x in lst ]
if any(bool_miss):
sublist = [i for (i, v) in zip(lst, bool_miss) if v ]
if lst[0] not in sublist:
missing_species.append([lst[0]] + sublist)
else:
missing_species.append(sublist)
missingfile = open(os.path.join(os.path.abspath("."), 'output', 'GWB', 'spxNotFound.txt'), 'w+')
#missing_species = [i for i in missing_species if len(i)<1]
for line in missing_species:
if len(line) > 0:
missingfile.writelines(line[0])
missingfile.writelines('\n')
for i in range(len(line)):
missingfile.writelines(' %s' % line[i])
missingfile.writelines('\n')
missingfile.close()
missing_species = [item for sublist in missing_species for item in sublist]
missing_species = [i for n, i in enumerate(missing_species) if i not in missing_species[:n]]
elem_avail = list(np.unique([k for i, j in enumerate([[i] + k for i, k in sourcedic.items()
if i not in missing_species])
for l, k in enumerate(j) if i < len(specielist[1])
and str(k).strip('0123456789.- ') != ''
and not str(k).endswith(("+", "-", '(aq)', '(g)'))
and k not in ['O2', 'H2O'] and len(k) < 3]))
if objdb == None:
objdb = 'thermo.%sbars' % int(P[0])
logKnan_alert = False
# timestr = '.' + time.strftime("%d%b%y_%H%M")
fout = open(os.path.join(os.path.abspath("."),'output', 'GWB', objdb + '.' + dataset), 'w+') # + timestr
dbname2 = 'and supcrtbl.dat' if self.dbHP_dir is not None else 'and berman.dat' if self.dbBerman_dir is not None else ''
if sourceformat.upper() != 'EQ36':
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s[:16] + dataset_format + '\n')
if dataset_format in ['jul17', 'jan19', 'apr20', 'mar21', 'jul22', 'jan26']:
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
if dataset_format == 'jan26':
fout.writelines(s)
if s.strip(' \n*') != '':
if s[:27].strip(' \n*:') == 'THERMODYNAMIC DATABASE':
fout.writelines(s[:27] + dbname + '\n')
else:
fout.writelines('* THERMODYNAMIC DATABASE: ' + dbname + '\n')
else:
fout.writelines('* THERMODYNAMIC DATABASE: ' + dbname + '\n')
s = fid.readline()
if s[:15].strip(' \n*:') == 'generated by':
fout.writelines(s[:15] + ': pyGeochemCalc, ' + time.ctime() + '\n')
else:
fout.writelines('* generated by' + ': pyGeochemCalc, ' + time.ctime() + '\n')
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
else:
fout.writelines('dataset of thermodynamic data for gwb programs \n' + \
'dataset format: ' + dataset_format + '\n' + \
'activity model: %s \n' % activity_model + \
'fugacity model: tsonopoulos \n' + \
'* THERMODYNAMIC DATABASE: ' + dbname + ' ' + dbname2 + '\n' +\
'* generated by: pyGeochemCalc, ' + time.ctime() + '\n' +\
'* Output package: gwb \n' + \
'* Data set: com \n')
if dataset_format in ['oct94', 'jul17']:
fout.writelines('* Note: coefficients for calculating the activity coefficients \n' + \
'* for CO2 are based on Ref:\n' + \
'* S.E.Drummond,1981. Boiling and Mixing of Hydrothermal\n' + \
'* Fluids: Chemical Effects on Mineral Precipitation.\n')
elif dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26'] and logK_form.lower() == 'polycoeffs':
fout.writelines('* \n' + \
'* This thermo data file uses the polynomial expression of the logK values: \n' + \
'* \n' + \
'* log10 K(TK) = a + b*(TK-Tr) + c*(TK^2-Tr^2) + d*(1/TK-1/Tr) + e*(1/TK^2-1/Tr^2) + f*ln(TK/Tr) \n' + \
'* TK is the Temperature in Kelvin, Tr is the relative temperature (T = 298.15 K). \n')
#skip lines till temperature rows
for i in range(1000) :
s = fid.readline()
# Accept both 'temperatures (degC)' (standard) and 'temperatures(degC)'
# (no-space variant written by earlier pygcc versions) so that a
# pygcc-generated database can be used as a source without header garbling.
if s.strip('\n').strip('* ') in ['temperatures', 'temperatures (degC)',
'temperatures(degC)', 'Temperature grid (degC)']:
break
fout.writelines( "\n")
if s.strip('\n').strip('* ') in ('temperatures', 'temperatures(degC)'):
if sourceformat.upper() != 'EQ36':
fout.writelines('* temperatures (degC)\n')
else:
fout.writelines('* temperatures (degC)\n')
elif s.strip('\n').strip('* ') == 'Temperature grid (degC)':
fout.writelines('* temperatures (degC)\n')
else:
fout.writelines(s)
for i in range(len(T)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % T[i-1])
else:
fout.writelines( " %9.4f" % T[i-1])
if (i % 4 == 0) | (i == len(T)):
fout.writelines( "\n")
#skip lines till pressure rows
for i in range(1000) :
s = fid.readline()
# Accept both 'pressures (bar)' and the no-space 'pressures(bar)' variant.
if s.strip('\n').strip('* ') in ['pressures', 'pressures (bar)',
'pressures(bar)', 'Pressure grid (bars)']:
break
if s.strip('\n').strip('* ') in ('pressures', 'pressures(bar)'):
if sourceformat.upper() != 'EQ36':
fout.writelines('* pressures (bar)\n')
else:
fout.writelines('* pressures (bar)\n')
elif s.strip('\n').strip('* ') == 'Pressure grid (bars)':
fout.writelines('* pressures (bar)\n')
else:
fout.writelines(s)
for i in range(len(P)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % P[i-1])
else:
fout.writelines( " %9.4f" % P[i-1])
if (i % 4 == 0) | (i == len(P)):
fout.writelines( "\n")
#% Calculation for debye huckel and bdot and water properties
waterdielc = water_dielec(T = T, P = P, Dielec_method = Dielec_method)
E, Adh, Bdh, bdot = waterdielc.E, waterdielc.Ah, waterdielc.Bh, waterdielc.bdot
rhoEG = {'rho': rho, 'E': E, 'dGH2O': dGH2O}
rhoEDB = {'rho': rho, 'E': E, 'Ah': Adh, 'Bh': Bdh}
# Calculate the rho E G for density extrapolation method here so we have it below
rhoEGextrap = {}
if any(rhoEG['rho'] < 350):
subBornptrs = rhoEG['rho'] < 350
for i, j in enumerate(zip(T[subBornptrs], P[subBornptrs])):
rhoextrap = np.linspace(350, 550, 3)
Pextrap = iapws95(T = j[0], rho = rhoextrap).P if Dielec_method.upper() != 'DEW' else ZhangDuan(T = j[0], rho = rhoextrap).P
Textrap = j[0]*np.ones(np.size(Pextrap))
dGH2O = iapws95(T = Textrap, P = Pextrap).G if Dielec_method.upper() != 'DEW' else ZhangDuan(T = Textrap, P = Pextrap).G
E = water_dielec(T = Textrap, P = Pextrap, Dielec_method = Dielec_method).E
rhoextrap = np.around(rhoextrap, 3)
rhoEGextrap['%d_%d' % (j[0], j[1])]= {'rho': rhoextrap,'E': E, 'dGH2O': dGH2O,
'Textrap': Textrap, 'Pextrap': Pextrap}
#skip lines till adh rows
# 'cco2' in s.strip('\n') 'log k for eh reaction' or 'Eh reaction: logKr' in s.strip('\n')
for i in range(100) :
s = fid.readline()
if any(re.findall(r'|'.join(('(adh)', 'Debye-Huckel A_gamma')), s.strip('\n'), re.IGNORECASE)):
break
if sourceformat.upper() != 'EQ36':
fout.writelines(s)
else:
fout.writelines('* debye huckel a (adh)\n')
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
Adhcorr = curve_fit(logKfunc, TK[Adh!=500].ravel(), Adh[Adh!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % Adhcorr[0] + 'b= %15.9f ' % Adhcorr[1] + \
'c= %15.6e\n' % Adhcorr[2])
fout.writelines(' d= %15.6f ' % Adhcorr[3] + 'e= %15.5f ' % Adhcorr[4] + \
'f= %15.8f \n' % Adhcorr[5])
else:
for i in range(len(Adh)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Adh[i-1])
else:
fout.writelines( " %9.4f" % Adh[i-1])
if (i % 4 == 0) | (i == len(Adh)):
fout.writelines( "\n")
#skip lines till bdh rows
for i in range(100):
s = fid.readline()
if any(re.findall(r'|'.join(('(bdh)', 'Debye-Huckel B_gamma')), s.strip('\n'), re.IGNORECASE)):
break
if sourceformat.upper() != 'EQ36':
fout.writelines(s)
else:
fout.writelines('* debye huckel b (bdh)\n')
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
Bdhcorr = curve_fit(logKfunc, TK[Bdh!=500].ravel(), Bdh[Bdh!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % Bdhcorr[0] + 'b= %15.9f ' % Bdhcorr[1] + \
'c= %15.6e\n' % Bdhcorr[2])
fout.writelines(' d= %15.6f ' % Bdhcorr[3] + 'e= %15.5f ' % Bdhcorr[4] + \
'f= %15.8f \n' % Bdhcorr[5])
else:
for i in range(len(Bdh)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Bdh[i-1])
else:
fout.writelines( " %9.4f" % Bdh[i-1])
if (i % 4 == 0) | (i == len(Bdh)):
fout.writelines( "\n")
#skip lines till bdot rows
if activity_model in ['debye-huckel', 'b-dot']:
for i in range(100) :
s = fid.readline()
if any(re.findall(r'|'.join(('bdot', 'B-dot')), s.strip('\n'), re.IGNORECASE)):
break
if sourceformat.upper() != 'EQ36':
if activity_model in ['debye-huckel', 'b-dot']:
fout.writelines(s)
if activity_model in ['h-m-w'] and dataset_format in ['jul17']:
fout.writelines('* bdot\n')
bdot = 500*np.ones(np.size(T))
for i in range(len(bdot)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % bdot[i-1])
else:
fout.writelines( " %9.4f" % bdot[i-1])
if (i % 4 == 0) | (i == len(bdot)):
fout.writelines( "\n")
cco2 = 500 * np.ones((np.size(T), 4))
for j in range(4):
fout.writelines('* c co2 %s\n' % (j+1))
for i in range(len(cco2)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % cco2[i-1, j])
else:
fout.writelines( " %9.4f" % cco2[i-1, j])
if (i % 4 == 0) | (i == len(cco2)):
fout.writelines( "\n")
ch2o = 500 * np.ones((np.size(T), 4))
for j in range(4):
fout.writelines('* c h2o %s\n' % (j+1))
for i in range(len(cco2)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % ch2o[i-1, j])
else:
fout.writelines( " %9.4f" % ch2o[i-1, j])
if (i % 4 == 0) | (i == len(ch2o)):
fout.writelines( "\n")
else:
if activity_model in ['debye-huckel', 'b-dot']:
fout.writelines('* bdot\n')
if activity_model in ['debye-huckel', 'b-dot']:
for i in range(len(bdot)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % bdot[i-1])
else:
fout.writelines( " %9.4f" % bdot[i-1])
if (i % 4 == 0) | (i == len(bdot)):
fout.writelines( "\n")
#% Calculation for co2 fitting coefs
cco2 = gamma_correlation(T, P, co2actmodel)
cco2 = cco2.T #.ravel()
for j in range(4):
fout.writelines('* c co2 %s\n' % (j+1))
for i in range(len(cco2)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % cco2[i-1, j])
else:
fout.writelines( " %9.4f" % cco2[i-1, j])
if (i % 4 == 0) | (i == len(cco2)):
fout.writelines( "\n")
#skip lines till c h2o rows
if sourceformat.upper() != 'EQ36':
for i in range(100) :
s = fid.readline()
# print(s)
if s[:7] == '* c h2o':
break
#% Calculation for h2o fitting coefs
ch2o = aw_correlation(T, P, Dielec_method = Dielec_method, **rhoEDB)
ch2o = ch2o.T #.ravel()
for j in range(4):
fout.writelines('* c h2o %s\n' % (j+1))
for i in range(len(cco2)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % ch2o[i-1, j])
else:
fout.writelines( " %9.4f" % ch2o[i-1, j])
if (i % 4 == 0) | (i == len(ch2o)):
fout.writelines( "\n")
if dataset_format == 'oct94':
# #Copy and replace c h2o values
for i in range(100) :
s = fid.readline()
if s[:7] == '* log k':
break
# fout.writelines(s)
#% Calculations for "log k for eh" rows
fout.writelines(s)
logK = calcRxnlogK( T = T, P = P, Specie = 'eh', dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap,
rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
#% Calculations for "log k for o2"
#skip lines till "log k for o2" rows
for i in range(50) :
s = fid.readline()
# print(s)
if s[:14] == '* log k for o2':
break
fout.writelines(s)
logK = calcRxnlogK( T = T, P = P, Specie = 'O2(g)', dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
#% Calculations for "log k for h2"
#skip lines till "log k for h2" rows
for i in range(50) :
s = fid.readline()
# print(s)
if s[:14] == '* log k for h2':
break
fout.writelines(s)
logK = calcRxnlogK( T = T, P = P, Specie = 'H2(g)', dbaccessdic = dbaccessdic, sourcedic = sourcedic,
Specie_class = 'gases', specielist = specielist, Dielec_method = Dielec_method,
sourceformat = sourceformat, densityextrap = densityextrap, rhoEG = rhoEG,
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
#% Calculations for "log k for n2"
#skip lines till "log k for n2" rows
for i in range(50) :
s = fid.readline()
# print(s)
if s[:14] == '* log k for n2':
break
fout.writelines(s)
logK = calcRxnlogK( T = T, P = P, Specie = 'N2(g)', dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "\n")
else:
fout.writelines('\n')
if sourceformat.upper() == 'EQ36':
fout.writelines( "\n")
f_ionsize = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ion_size.txt'), 'r')
Rd = f_ionsize.readlines()
Rd = Rd[1:]
ion_sizedic = {Rd[x].split()[0] : Rd[x].split()[1] for x in range(len(Rd))}
f_ionsize.close()
# Needed for rebalancing aqueous and mineral reactions in terms of O2(aq) instead of O2(g)
dic = {'O2(g)' : ['', 1, '1.0000', 'O2(aq)']}
logK_rebal = calcRxnlogK( T = T, P = P, Specie = 'O2(g)', dbaccessdic = dbaccessdic, sourcedic = dic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK_rebal = np.where(np.isnan(logK_rebal), 500, logK_rebal) # set abitrary 500 to nan values
#% Elements
#skip lines till "elements" rows
for i in range(5000) :
s = fid.readline()
if s.rstrip('\n').lstrip('0123456789.- ') == 'elements':
break
fout.writelines( " %s elements" % len(elem_avail))
fout.writelines( "\n\n")
#copy and paste lines till "basis" rows
for i in range(1000) :
s = fid.readline()
s_mod = s.split()[0] if s.strip('\n') != '' else s
if s.rstrip('\n').lstrip('0123456789.- ') == 'basis species':
break
if sourceformat.upper() != 'EQ36':
if s_mod[:5] in [Element[j].name[0][:5] for j in elem_avail]:
fout.writelines(s)
else:
if s_mod in elem_avail and not s.startswith('+---'):
fout.writelines('%-15s (%-2s) mole wt.= %8.4f g\n'
% (Element[s_mod].name[0], s_mod, float(s.split()[1])))
else:
continue
fout.writelines( "\n-end-\n\n")
#only used for counter
counter = 0
for j in specielist[1]:
if (j not in missing_species):
counter +=1
fout.writelines( " %s basis species\n\n" % counter)
#% Basis reactions
#skip lines till "redox couples" rows
for i in range(2000):
s = fid.readline()
if s.rstrip('\n').lstrip('0123456789.- ') in ['redox couples', 'auxiliary basis species']:
break
for j in specielist[1]:
if (j not in missing_species):
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
if sourceformat.upper() != 'EQ36':
fout.writelines('%s\n' % j)
else:
fout.writelines('%s\n' % j.replace('O2(g)', 'O2(aq)'))
if sourceformat.upper() != 'EQ36':
fout.writelines('%s\n' % chargedic[j])
else:
if j == 'O2(g)':
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info['O2(g)_b'] if x.strip('* ').startswith('DHazero')]
else:
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info[j] if x.strip('* ').startswith('DHazero')]
if any(ionsize) and any([MWdic[j]]):
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (float(chargedic[j].split()[-1]), ionsize[0], MWdic[j]))
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(g)')
ionsize = 3 if j.endswith('(aq)') else float(ion_sizedic[j]) if j in ion_sizedic.keys() else 500
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (float(chargedic[j].split()[-1]), ionsize,
calc_elem_count_molewt(formula, Elementdic = Element)[-1]) )
if sourceformat.upper() != 'EQ36':
fout.writelines( " %s elements in species\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
else:
fout.writelines( " %s elements in species\n" % int(len(Elemlist[j])/2) )
Rxn = Elemlist[j]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
if sourceformat.upper() != 'EQ36':
fout.writelines( "%-9s " % (Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]).replace('O2(g)', 'O2(aq)'))
if (i % 6 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: [92joh/oel]\n" )
if j != 'H2O':
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method if Dielec_method in ['FGL97', 'JN91'] else 'DEW'
fout.writelines( "* reference-state data source = %s\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "\n")
else:
continue
fout.writelines( "-end-\n\n")
#only used for counter
counter = 0
for j in specielist[2]:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species]) == len(rxnlst)):
counter +=1
fout.writelines( " %s redox couples\n\n" % counter)
#% Redox reactions
#skip lines till "aqueous species" rows
for i in range(2000):
s = fid.readline()
# print(s)
if s.rstrip('\n').lstrip('0123456789.- ') == 'aqueous species':
break
if sourceformat.upper() != 'EQ36':
speclst = specielist[2]
else:
speclst = [ x for x in specielist[2] if x != 'O2(aq)']
for j in speclst:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([i for i in rxnlst if i not in missing_species]) == len(rxnlst)):
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
if dataset_format == 'oct94':
fout.writelines('%s\n' % j)
if sourcedic[j][0] != '':
fout.writelines('* formula= %s\n' % sourcedic[j][0])
else:
fout.writelines('* formula= %s\n' % dbaccessdic[k][0])
elif dataset_format in ['jul17', 'jan19', 'apr20', 'mar21', 'jul22', 'jan26']:
if sourcedic[j][0] != '':
fout.writelines('%-30s %s %s\n' % (j, 'formula=', sourcedic[j][0]))
else:
fout.writelines('%-30s %s %s\n' % (j, 'formula=', dbaccessdic[k][0]))
if sourceformat.upper() != 'EQ36':
fout.writelines('%s\n' % chargedic[j])
else:
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info[j] if x.strip('* ').startswith('DHazero')]
if any(ionsize) and any([MWdic[j]]):
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (float(chargedic[j].split()[-1]), ionsize[0], MWdic[j]))
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)')
ionsize = 3 if j.endswith('(aq)') else float(ion_sizedic[j]) if j in ion_sizedic.keys() else 500
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (float(chargedic[j].split()[-1]), ionsize,
calc_elem_count_molewt(formula, Elementdic = Element)[-1]) )
if sourceformat.upper() != 'EQ36':
fout.writelines( " %s species in reaction\n" % sourcedic[j][1])
else:
fout.writelines( " %s species in reaction\n" % (sourcedic[j][1] - 1) )
Rxn = sourcedic[j][2:] if sourceformat.upper() != 'EQ36' else sourcedic[j][4:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]))
if (i % 6 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit)
if densityextrap.lower() == 'yes': #any(np.isnan(logK)):
if all(logK.nonsubBornptrs) == True: # if all densities are >= 350
logKnan_alert = False # turn off the prompts for using Density extrapolation
else:
logKnan_alert = True
else:
logKnan_alert = False # turn off the prompts for using Density extrapolation
logK = logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % logKcorr[0] + 'b= %15.9f ' % logKcorr[1] + \
'c= %15.6e\n' % logKcorr[2])
fout.writelines(' d= %15.6f ' % logKcorr[3] + 'e= %15.5f ' % logKcorr[4] + \
'f= %15.8f \n' % logKcorr[5])
fout.writelines(' TminK= %-15.2f ' % np.min(TK) + 'TmaxK= %-7.2f\n' % np.max(TK))
else:
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method if Dielec_method in ['FGL97', 'JN91'] else 'DEW'
fout.writelines( "* reference-state data source = %s\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "\n")
else:
continue
fout.writelines( "-end-\n\n")
if logKnan_alert == True:
warnings.warn('Some temperature and pressure points are out of aqueous species HKF eqns regions of applicability, hence, density extrapolation has been applied')
#only used for counter
counter = 0
for j in specielist[3]:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
counter +=1
fout.writelines( " %s aqueous species\n\n" % counter)
#% Aqueous reactions
# skip lines till "minerals" rows
for i in range(20000):
s = fid.readline()
if s.rstrip('\n').lstrip('0123456789.- ') in ['minerals', 'solids']:
break
for j in specielist[3]:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([i for i in rxnlst if i not in missing_species]) == len(rxnlst)):
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
# print(k)
if dataset_format == 'oct94':
fout.writelines('%s\n' % j)
if sourcedic[j][0] != '':
fout.writelines('* formula= %s\n' % sourcedic[j][0])
else:
fout.writelines('* formula= %s\n' % dbaccessdic[k][0])
elif dataset_format in ['jul17', 'jan19', 'apr20', 'mar21', 'jul22', 'jan26']:
if sourcedic[j][0] != '':
fout.writelines('%-30s %s %s\n' % (j, 'formula=', sourcedic[j][0]))
else:
fout.writelines('%-30s %s %s\n' % (j, 'formula=', dbaccessdic[k][0]))
if sourceformat.upper() != 'EQ36':
fout.writelines('%s\n' % chargedic[j])
else:
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info[j] if x.strip('* ').startswith('DHazero')]
if any(ionsize) and any([MWdic[j]]):
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (float(chargedic[j].split()[-1]), ionsize[0], MWdic[j]))
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)')
ionsize = 3 if j.endswith('(aq)') else float(ion_sizedic[j]) if j in ion_sizedic.keys() else 500
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (float(chargedic[j].split()[-1]), ionsize,
calc_elem_count_molewt(formula, Elementdic = Element)[-1] ) )
if sourceformat.upper() != 'EQ36':
fout.writelines( " %s species in reaction\n" % sourcedic[j][1])
else:
fout.writelines( " %s species in reaction\n" % (sourcedic[j][1] - 1) )
Rxn = sourcedic[j][2:] if sourceformat.upper() != 'EQ36' else sourcedic[j][4:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
if sourceformat.upper() != 'EQ36':
fout.writelines( "%-9s " % (Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]).replace('O2(g)', 'O2(aq)'))
if (i % 6 == 0) | (i % 12 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
if sourceformat.upper() == 'EQ36' and 'O2(g)' in Rxn:
coeff_O2 = float(Rxn[Rxn.index('O2(g)') - 1])
logK = np.where(logK != 500, logK + coeff_O2*logK_rebal, logK)
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % logKcorr[0] + 'b= %15.9f ' % logKcorr[1] + \
'c= %15.6e\n' % logKcorr[2])
fout.writelines(' d= %15.6f ' % logKcorr[3] + 'e= %15.5f ' % logKcorr[4] + \
'f= %15.8f \n' % logKcorr[5])
fout.writelines(' TminK= %-15.2f ' % np.min(TK) + 'TmaxK= %-7.2f\n' % np.max(TK))
else:
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method if Dielec_method in ['FGL97', 'JN91'] else 'DEW'
fout.writelines( "* reference-state data source = %s\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "\n")
else:
continue
fout.writelines( "-end-\n\n")
#% free electron for tdat dataset format
if dataset_format in ['jul17', 'jan19', 'apr20', 'mar21', 'jul22', 'jan26']:
if sourceformat.upper() != 'EQ36':
speclst = specielist[4]
else:
speclst = ['e-']
fout.writelines( " %s free electron\n\n" % len(speclst))
for j in speclst:
fout.writelines('%s\n' % j)
if sourceformat.upper() != 'EQ36':
fout.writelines('%s\n' % chargedic[j])
fout.writelines( " %s species in reaction\n" % sourcedic[j][1])
else:
fout.writelines(' charge= %d ion size= %.1f A mole wt.= %8.4f g\n'
% (-1, 0, 0) )
fout.writelines( " %s species in reaction\n" % (sourcedic[j][1] - 1) )
Rxn = sourcedic[j][2:] if sourceformat.upper() != 'EQ36' else sourcedic[j][4:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]))
if (i % 6 == 0) | (i % 12 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
speclist = specielist #None if dataset_format == 'mar21' else specielist
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = speclist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap,
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % logKcorr[0] + 'b= %15.9f ' % logKcorr[1] + \
'c= %15.6e\n' % logKcorr[2])
fout.writelines(' d= %15.6f ' % logKcorr[3] + 'e= %15.5f ' % logKcorr[4] + \
'f= %15.8f \n' % logKcorr[5])
fout.writelines(' TminK= %-15.2f ' % np.min(TK) + 'TmaxK= %-7.2f\n' % np.max(TK))
else:
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "-end-\n\n")
#only used for counter
if clay_thermo is not None:
if clay_thermo.lower() == 'yes':
fclay = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'clay_elements.dat'), 'r')
Rd = fclay.readlines()
Rd = [j.replace('-','_').strip('\n') for j in Rd]
counter = len(Rd)
else:
counter = 0
else: # default no clay thermo calculation
clay_thermo = 'no'
counter = 0
solidsolution_no = 11
if solid_solution is not None:
if solid_solution.lower() == 'yes':
if nCa_cpx is None:
nCa = 0
counter = counter + 3*solidsolution_no # no cpx
else:
nCa = nCa_cpx
if nCa > 0:
counter = counter + 4*solidsolution_no
else:
counter = counter + 3*solidsolution_no # no cpx
else:
counter = counter + 0
else: # default no solid solution
counter = counter + 0
solid_solution = 'no'
if sourceformat.upper() != 'EQ36':
speclst = specielist[5]
else:
speclst = specielist[4] + specielist[5]
for j in speclst:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species]) == len(rxnlst)):
if (solid_solution.lower() == 'no') & (clay_thermo.lower() == 'no'):
counter += 1
elif (solid_solution.lower() == 'yes') & (clay_thermo.lower() == 'no'):
if j not in ['Anorthite', 'Albite', 'Forsterite', 'Fayalite', 'Enstatite', 'Ferrosilite']:
counter += 1
else:
continue
elif (solid_solution.lower() == 'no') & (clay_thermo.lower() == 'yes'):
if j not in [ Rd[h].split(',')[0] for h in range(len(Rd))]:
counter += 1
else:
continue
else:
if j not in ['Anorthite', 'Albite', 'Forsterite', 'Fayalite', 'Enstatite', 'Ferrosilite'] + [ Rd[h].split(',')[0] for h in range(len(Rd))]:
counter += 1
else:
continue
if (solid_solution.lower() == 'yes') and (nCa == 1):
fout.writelines( " %s minerals\n\n" % (counter - 2))
else:
fout.writelines( " %s minerals\n\n" % (counter))
# print('-> Processing mineral output')
#% Mineral reactions
#skip lines till "gases" rows
for i in range(20000):
s = fid.readline()
if (dataset_format in ['mar21', 'jul22', 'jan26']) and (s.rstrip('\n').lstrip('0123456789.- ') == 'solid solutions'):
break
elif dataset_format not in ['mar21', 'jul22', 'jan26'] and s.rstrip('\n').lstrip('0123456789.- ') == 'gases':
break
if solid_solution.lower() == 'yes':
mineralcount = 0
fnlist = ['plagio', 'olivine', 'pyroxene', 'cpx'] if nCa > 0 else ['plagio', 'olivine', 'pyroxene']
for fn in fnlist:
for nX in np.round(np.linspace(1, 0, solidsolution_no), 1):
if fn != 'cpx':
ss = calcRxnlogK(X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
else:
ss = calcRxnlogK(cpx_Ca = nCa, X = nX, T = T, P = P, Dielec_method = Dielec_method,
rhoEG = rhoEG, dbaccessdic = dbaccessdic, Specie = fn,
densityextrap = densityextrap, ThermoInUnit = self.ThermoInUnit,
rhoEGextrap = rhoEGextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, TK, dataset = 'GWB', logK_form = logK_form)
mineralcount += 1
# clay minerals
if clay_thermo.lower() == 'yes':
for i in range(len(Rd)):
ss = calcRxnlogK(T = T, P = P, Specie = 'Clay', elem = Rd[i].split(','),
dbaccessdic = dbaccessdic, ThermoInUnit = self.ThermoInUnit, rhoEG = rhoEG,
rhoEGextrap = rhoEGextrap, densityextrap = densityextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, TK, dataset = 'GWB', logK_form = logK_form)
for j in speclst:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
if solid_solution.lower() == 'yes' and j in ['Anorthite', 'Albite', 'Forsterite', 'Fayalite', 'Enstatite', 'Ferrosilite']:
continue
elif solid_solution.lower() == 'yes' and (nCa == 1) and j in ['Diopside', 'Hedenbergite']:
continue
elif clay_thermo.lower() == 'yes' and j in [ Rd[h].split(',')[0] for h in range(len(Rd))]:
continue
else:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
fout.writelines("%s " % j)
if sourceformat.upper() != 'EQ36':
fout.writelines( "type= %s\n" % Mineraltype[j])
else:
fout.writelines( "\n" )
if sourcedic[j][0] != '':
fout.writelines(' formula= %s\n' % sourcedic[j][0])
else:
fout.writelines(' formula= %s\n' % dbaccessdic[k][0])
fout.writelines(" mole vol.= %1.3f cc" % dbaccessdic[k][5])
if MWdic[j] != []:
fout.writelines(" mole wt.= %1.4f g\n" % MWdic[j])
else:
formula = k if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(am)')#.rstrip('+2').rstrip('+3').rstrip('+4')
fout.writelines(" mole wt.= %1.4f g\n" % calc_elem_count_molewt(formula, Elementdic = Element)[-1] )
if sourceformat.upper() != 'EQ36':
fout.writelines( " %s species in reaction\n" % sourcedic[j][1])
else:
fout.writelines( " %s species in reaction\n" % (sourcedic[j][1] - 1) )
Rxn = sourcedic[j][2:] if sourceformat.upper() != 'EQ36' else sourcedic[j][4:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines("%9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines("%9.4f " % float(Rxn[i - 1]))
else:
if sourceformat.upper() != 'EQ36':
fout.writelines( "%-9s " % (Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]).replace('O2(g)', 'O2(aq)'))
if (i % 6 == 0) | (i % 12 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'minerals',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
if sourceformat.upper() == 'EQ36' and 'O2(g)' in Rxn:
coeff_O2 = float(Rxn[Rxn.index('O2(g)') - 1])
logK = np.where(logK != 500, logK + coeff_O2*logK_rebal, logK)
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % logKcorr[0] + 'b= %15.9f ' % logKcorr[1] + \
'c= %15.6e\n' % logKcorr[2])
fout.writelines(' d= %15.6f ' % logKcorr[3] + 'e= %15.5f ' % logKcorr[4] + \
'f= %15.8f \n' % logKcorr[5])
fout.writelines(' TminK= %-15.2f ' % np.min(TK) + 'TmaxK= %-7.2f\n' % np.max(TK))
else:
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9):
fout.writelines(" %9.4f" % logK[i-1])
else:
fout.writelines(" %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
if dbaccessdic[k][0] == 'nan':
fout.writelines( "* extrapolation algorithm: supcrt92/water95\n" )
else:
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
fout.writelines( "* reference-state data source = %s\n" % ref )
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dbaccessdic[k][2]/1000/J_to_cal if heatcap_method.lower() == 'berman88' else dbaccessdic[k][2]/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dbaccessdic[k][3]/1000/J_to_cal if heatcap_method.lower() == 'berman88' else dbaccessdic[k][3]/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % (dbaccessdic[k][4]/J_to_cal if heatcap_method.lower() == 'berman88' else dbaccessdic[k][4]))
fout.writelines( "\n")
else:
continue
fout.writelines( "-end-\n\n")
#% Solid solutions for GWB internal calculation
#only used for counter
counter = 0; ss_list = []
if dataset_format in ['mar21', 'jul22', 'jan26']:
for j in specielist[6]:
# print(j)
lst = [x for x in sourcedic[j] if x.strip('\n')]
minlst = [x.split()[0] for x in lst if x.split()[0] not in ['a0', '*']]
# print(minlst)
if all(x not in missing_species for x in minlst):
counter +=1
ss_list.append(j)
fout.writelines( " %s solid solutions\n\n" % counter )
for i in range(20000):
s = fid.readline()
if s.rstrip('\n').lstrip('0123456789.- ') == 'gases':
break
for j in ss_list:
for k in range(len(sourcedic[j])):
fout.writelines( sourcedic[j][k] )
if sourcedic[j][-1] != '\n':
fout.writelines( "\n")
fout.writelines( "-end-\n\n")
#only used for counter
counter = 0
spec = specielist[6] if dataset_format not in ['mar21', 'jul22', 'jan26'] else specielist[7]
for j in spec:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
counter +=1
fout.writelines( " %s gases\n\n" % counter )
#% Gas reactions
#skip lines till "oxides" rows
for i in range(20000):
s = fid.readline()
# print(s)
if s.rstrip('\n').lstrip('0123456789.- ') in ['oxides', 'solid solutions']:
break
for j in spec:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
fout.writelines("%s\n" % j)
if MWdic[j] != []:
fout.writelines( " mole wt.= %1.4f g\n" % MWdic[j])
else:
formula = j.rstrip('(g)')
fout.writelines(" mole wt.= %1.4f g\n" % calc_elem_count_molewt(formula, Elementdic = Element)[-1] )
if sourceformat.upper() == 'EQ36':
fugacity_info = {'fugacity_chi': {'CH4(g)': ' chi= -537.779 1.54946 -.000927827 1.20861 -.00370814 3.33804e-6\n',
'CO2(g)': ' chi= -1430.87 3.598 -.00227376 3.47644 -.0104247 8.46271e-6\n',
'H2(g)': ' chi= -12.5908 .259789 -7.2473e-5 .00471947 -2.69962e-5 2.15622e-8\n',
'H2O(g)': ' chi= -6191.41 14.8528 -.00914267 -66.3326 .18277 -.00013274\n'},
'fugacity_Pcrit': {'Ar(g)': ' Pcrit= 48.7 bar Tcrit= 150.8 K omega= .001\n',
'CH4(g)': ' Pcrit= 46.0 bar Tcrit= 190.4 K omega= .011\n',
'CO2(g)': ' Pcrit= 73.8 bar Tcrit= 304.1 K omega= .239\n',
'H2(g)': ' Pcrit= 13.0 bar Tcrit= 33.2 K omega= -.218\n',
'H2O(g)': ' Pcrit= 221.2 bar Tcrit= 647.3 K omega= .344 a=-.0109 b= 0.0\n',
'H2S(g)': ' Pcrit= 89.4 bar Tcrit= 373.2 K omega= .097\n',
'He(g)': ' Pcrit= 2.27 bar Tcrit= 5.19 K omega= -.365\n',
'N2(g)': ' Pcrit= 33.9 bar Tcrit= 126.2 K omega= .039\n',
'NH3(g)': ' Pcrit= 113.5 bar Tcrit= 405.5 K omega= .250\n',
'O2(g)': ' Pcrit= 50.4 bar Tcrit= 154.6 K omega= .025\n',
'SO2(g)': ' Pcrit= 78.8 bar Tcrit= 430.8 K omega= .256\n'}}
if dataset_format in ['jul17', 'jan19', 'apr20', 'mar21', 'jul22', 'jan26']:
if j in fugacity_info['fugacity_chi'].keys():
fout.writelines("%s" % fugacity_info['fugacity_chi'][j])
if j in fugacity_info['fugacity_Pcrit'].keys():
fout.writelines("%s" % fugacity_info['fugacity_Pcrit'][j])
if sourceformat.upper() != 'EQ36':
fout.writelines( " %s species in reaction\n" % sourcedic[j][1])
else:
fout.writelines( " %s species in reaction\n" % (sourcedic[j][1] - 1) )
Rxn = sourcedic[j][2:] if sourceformat.upper() != 'EQ36' else sourcedic[j][4:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
if sourceformat.upper() != 'EQ36':
fout.writelines( "%-9s " % (Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]).replace('O2(g)', 'O2(aq)'))
if (i % 6 == 0) | (i % 12 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
if sourceformat.upper() == 'EQ36' and 'O2(g)' in Rxn:
coeff_O2 = float(Rxn[Rxn.index('O2(g)') - 1])
logK = np.where(logK != 500, logK + coeff_O2*logK_rebal, logK)
if (dataset_format in ['jan19', 'apr20', 'mar21', 'jul22', 'jan26']) & (logK_form.lower() == 'polycoeffs'):
TK = convert_temperature( T, Out_Unit = 'K' )
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
fout.writelines(' a= %15.9f ' % logKcorr[0] + 'b= %15.9f ' % logKcorr[1] + \
'c= %15.6e\n' % logKcorr[2])
fout.writelines(' d= %15.6f ' % logKcorr[3] + 'e= %15.5f ' % logKcorr[4] + \
'f= %15.8f \n' % logKcorr[5])
fout.writelines(' TminK= %-15.2f ' % np.min(TK) + 'TmaxK= %-7.2f\n' % np.max(TK))
else:
for i in range(len(logK)):
i = i + 1
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
fout.writelines( "* reference-state data source = %s\n" % ref )
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dbaccessdic[k][2]/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dbaccessdic[k][3]/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % dbaccessdic[k][4])
fout.writelines( "\n")
else:
continue
fout.writelines( "-end-\n\n")
if sourceformat.upper() != 'EQ36':
#only used for counter
counter = 0
spec = specielist[7] if dataset_format not in ['mar21', 'jul22', 'jan26'] else specielist[8]
for j in spec:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
counter +=1
fout.writelines( " %s oxides\n\n" % counter )
#% Oxides reactions
#skip lines till "references" rows
for i in range(20000):
s = fid.readline()
# print(s)
if s.strip(' \n*').lstrip('0123456789.- ').startswith(("references", 'virial coefficients', 'Virial coefficients', 'SIT epsilon coefficients', 'Pitzer parameters')):
break
for j in spec:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (len([k for k in rxnlst if k not in missing_species]) == len(rxnlst)):
fout.writelines("%s\n" % j)
fout.writelines( " mole wt.= %1.4f g\n" % MWdic[j])
fout.writelines( " %s species in reaction\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]))
if (i % 6 == 0) | (i % 12 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines( "\n")
else:
continue
else:
fout.writelines( " 0 oxides\n\n" )
#% Pitzer parameters
if activity_model != 'h-m-w':
fout.writelines( "-end-\n\n")
elif activity_model == 'h-m-w':
fout.writelines( "-end-\n*\n")
if sourceformat.upper() == 'GWB':
fout.writelines(s)
for i in range(15000):
s = fid.readline()
fout.writelines(s)
if not s.strip(' \n').startswith("*"):
break
for i in range(15000):
s = fid.readline()
if any(s.lstrip().rstrip('\n').startswith(x) for x in act_param['act_list']):
rowlst = []; rowlst.append(s)
for i in range(150):
s = fid.readline()
if s.lstrip().rstrip('\n').startswith(""):
break
rowlst.append(s)
if all([x not in missing_species for x in rowlst[0].rstrip('\n').split()]):
fout.writelines(rowlst)
fout.writelines( "\n")
else:
fout.writelines(s)
elif sourceformat.upper() == 'EQ36':
fout.writelines("* Pitzer parameters are represented by the 25C-centric four-term temperature \n" + \
"* function given by: \n" + "* \n" + \
"* x(T) = a1 + a2*(1/T - 1/298.15) + a3*ln(T/298.15) + a4*(T - 298.15) \n" + "* \n" + \
"* where T is temperature in Kelvin and a1 through a4 denote the temperature \n" + \
"* function fitting coefficients for the temperature-dependent Pitzer \n" + \
"* parameters. The conversion of non-standard or expanded forms of Pitzer \n" + \
"* interaction parameters recently adopted by several workers for highly \n" + \
"* soluble salts to the standard form currently embedded in EQ3/6 Version 8.0 \n" + \
"* was conducted using the approach described in 02Rard/Wij. This conversion \n" + \
"* imposes usage limits on these parameters within a valid range of temperature \n" + \
"* and ionic strength. \n" + "* \n" + \
"* In GWB (Version 9.0.1 or lower) Pitzer parameters are represented by the \n" + \
"* 25C-centric five-term temperature function given by: \n" + "* \n" + \
"* val = val25 + c1*(Tk-Tr) + c2*(1/Tk-1/Tr) + c3*ln(Tk/Tr) + c4(Tk^2-Tr^2) \n" + "* \n" + \
"* So the last temperature term (c4(Tk^2-Tr^2)) will be set to zero \n" + \
"* since no such term is available in the data0.ypf.R0. \n" + "* \n" + \
"* In GWB (Version 9.0.2 or higher) Pitzer parameters are represented by the \n" + \
"* 25C-centric six-term temperature function given by: \n" + \
"* val = val25 + c1*(Tk-Tr) + c2*(1/Tk-1/Tr) + c3*ln(Tk/Tr) + c4(Tk^2-Tr^2) + c5(1/Tk^2-1/Tr^2) \n" + \
"* So the last two temperature terms (c4(Tk^2-Tr^2) and c5(1/Tk^2-1/Tr^2)) \n" + \
"* will be set to zero (or left blank) since no such term is available. \n\n")
for k in act_param['alpha_beta'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-8s %-8s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
lst = ['beta0', 'beta1', 'beta2', 'cphi', 'alpha1', 'alpha2']
for order in lst:
fout.writelines(' %-6s = %s \n' % (order, act_param[order][k]))
fout.writelines('\n')
fout.writelines('-end- end of beta set, begin with theta set of 2nd virial coefficients \n\n')
for k in act_param['theta'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-8s %-8s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s = %s \n' % ('theta', act_param['theta'][k]))
fout.writelines('\n')
fout.writelines('-end- end of theta set, begin with lambda set \n\n')
for k in act_param['lambda'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-8s %-8s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s = %s \n' % ('lambda', act_param['lambda'][k]))
fout.writelines('\n')
fout.writelines('-end- end of lambda set, begin with psi set \n\n')
for k in act_param['psi'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-8s %-8s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s = %s \n' % ('psi', act_param['psi'][k]))
fout.writelines('\n')
fout.writelines('-end- end of psi set \n')
fout.writelines('* references\n')
fout.writelines('** Please copy references to here from the corresponding\n')
fout.writelines('** sequential-access version of the direct-access SUPCRT database.\n* stop.\n\n')
#% close all files
fid.close()
fout.close()
if clay_thermo.lower() == 'yes':
fclay.close()
return print('Success, your new GWB database is ready for download')
[docs]
def write_EQ36db(self, T, P ):
r"""
This function writes the new EQ3/6 database into a new folder called "output" \n
Parameters
----------
T : temperature [°C] \n
P : pressure [bar] \n
Returns
-------
Outputs the new database to an ASCII file with filename described in 'objdb'. \n
Usage
-------
Example:
(1) General format with default dielectric constant and CO2 activity model and exclusions
of solid solutions \n
write_EQ36db(T, P ) \n
(2) Inclusion of solid solutions and clay thermo and exclusion of solid solution of clinopyroxene \n
write_EQ36db(T, P ) \n
(3) Inclusion of all solid solutions and clay thermo with \\emph{'Duan_Sun'} CO2 activity model and 'FGL97'
dielectric constant calculation \n
write_EQ36db(T, P ) \n
"""
nCa_cpx = self.cpx_Ca;
solid_solution = self.solid_solution; clay_thermo = self.clay_thermo
sourcedb = self.sourcedb
objdb = self.objdb; Dielec_method = self.Dielec_method
heatcap_method = self.heatcap_method; sourceformat = self.sourceformat
densityextrap = self.densityextrap
dbaccessdic, dbname, sourcedic, specielist = self.dbr.dbaccessdic, self.dbr.dbaccess, self.dbr.sourcedic, self.dbr.specielist
if sourceformat.upper() == 'GWB':
MWdic, act_param, chargedic = self.dbr.MWdic, self.dbr.act_param, self.dbr.chargedic
dataset = '1kbu'
periodic_table = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'PeriodicTableJSON.json'), encoding='utf8')
data = json.load(periodic_table)
Element = {data['elements'][x]['symbol'] : pd.DataFrame([data['elements'][x]['name'],
data['elements'][x]['atomic_mass']],
index = ['name', 'mass']).T
for x in range(len(data['elements']))}
periodic_table.close()
specielist[0] = [ symbol for x in specielist[0] for symbol, item in Element.items()
if item.name[0][:4] == x[:4] ]
elif sourceformat.upper() == 'EQ36':
block_info, Elemlist, act_param = self.dbr.block_info, self.dbr.Elemlist, self.dbr.act_param
dataset = sourcedb.split('.')[-1]
if os.path.exists(os.path.join(os.getcwd(), 'output/EQ36')) == False:
os.makedirs(os.path.join(os.getcwd(), 'output/EQ36'))
logKnan_alert = False
missing_species = []
all_species_source = [[i]+k for i, k in sourcedic.items() if i not in (['eh', 'e-', 'H2O']) ]
all_species_source = [[k for j, k in enumerate(all_species_source[i])
if (j not in [1, 3] and k not in all_species_source[i][:j]
and k not in specielist[0] and str(k).strip('0123456789.- ') != '') ]
if (i <= len(specielist[0]))
else [k for j, k in enumerate(all_species_source[i])
if (j not in [1, 3] and k not in all_species_source[i][:j] and str(k).strip('0123456789.- ') != '') ]
for i in range(len(all_species_source)) ]
for num in range(len(all_species_source)): #
if num < len(all_species_source):
lst = [v for v in all_species_source[num] if v not in (specielist[7] + ['eh', 'e-', 'H2O']) ]
bool_miss = [x.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
not in dbaccessdic.keys() for x in lst ]
if any(bool_miss):
sublist = [i for (i, v) in zip(lst, bool_miss) if v ]
if lst[0] not in sublist:
missing_species.append([lst[0]] + sublist)
else:
missing_species.append(sublist)
missingfile = open(os.path.join(os.path.abspath("."),'output', 'EQ36', 'spxNotFound.txt'), 'w')
#missing_species = [i for i in missing_species if len(i)<1]
for line in missing_species:
if len(line) > 0:
missingfile.writelines(line[0])
missingfile.writelines('\n')
for i in range(len(line)):
missingfile.writelines(' %s' % line[i])
missingfile.writelines('\n')
missingfile.close()
missing_species = [item for sublist in missing_species for item in sublist]
elem_avail = list(set([k for i, j in enumerate([[i] + k for i, k in sourcedic.items()
if i not in missing_species])
for l, k in enumerate(j) if i < len(specielist[1]) and str(k).strip('0123456789.- ') != '' and
not str(k).endswith(("+", "-", '(aq)', '(g)')) and k not in ['O2', 'H2O'] and len(k) < 3]))
all_species_source = [k for j in all_species_source for i, k in enumerate(j) ]
all_species_source = list(set(all_species_source))
all_species_avail = [j for j in all_species_source if j not in missing_species]
if objdb == None:
objdb = 'data0.%s' % (int(P[0]))
fout = open(os.path.join(os.path.abspath("."),'output', 'EQ36', objdb + '.%s' % dataset), 'w+') # + timestr
fid = open(sourcedb, 'r')
dbname2 = 'and supcrtbl.dat' if self.dbHP_dir is not None else 'and berman.dat' if self.dbBerman_dir is not None else ''
if sourceformat.upper() != 'GWB':
s = fid.readline()
fout.writelines(s)
for i in range(500) :
s = fid.readline()
if s.startswith('Generated', 0) | s.startswith('Data', 0) | (s.rstrip('\n') == ""):
break
fout.writelines('CII: ' + ' pyGeochemCalc.2021' + '\n')
fout.writelines('Generated by: ' + ' pyGeochemCalc, ' + time.ctime() + '\n')
fout.writelines('Output package: eq3\n' + 'Data set: ' + dbname + ' ' + dbname2 + '\n')
else:
fout.writelines('data0.com.RX ! dataset converted from gwb database \n' + \
'CII: ' + ' pyGeochemCalc.2021' + '\n' + \
'Generated by: ' + ' pyGeochemCalc, ' + time.ctime() + '\n' + \
'Output package: eq3\n' + 'Data set: ' + dbname + ' ' + dbname2 + '\n')
if Dielec_method.upper() == 'DEW':
water = ZhangDuan(T = T, P = P)
rho, dGH2O, dHH2O, SH2O = water.rho, water.G, np.nan*np.ones(len(T)), np.nan*np.ones(len(T))
else:
water = iapws95(T = T, P = P)
rho, dGH2O, dHH2O, SH2O = water.rho, water.G, water.H, water.S
#copy and paste lines till temperature rows
for i in range(2500):
s = fid.readline()
if sourceformat.upper() != 'GWB':
fout.writelines(s)
if s.startswith('+', 0):
break
else:
if s.strip('\n').strip('* ') in ['temperatures', 'temperatures (degC)',
'temperatures(degC)', 'Temperature grid (degC)']:
break
if sourceformat.upper() != 'GWB':
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
else:
fout.write( "+" + "-"*68 + "\n")
fout.write("Miscellaneous parameters\n")
fout.write( "+" + "-"*68 + "\n")
fout.write("Temperature limits (degC)\n")
fout.writelines(' %9.4f %9.4f\n' % (T[0], T[-1]))
if sourceformat.upper() != 'GWB':
for i in range(50) :
s = fid.readline()
if s.strip('\n').strip('* ') in ['temperatures', 'temperatures (degC)', 'Temperature grid (degC)']:
break
fout.writelines(s) if sourceformat.upper() != 'GWB' else fout.writelines(s.strip('* ').split('(')[0] + '\n')
for i in range(len(T)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % T[i-1])
else:
fout.writelines( " %9.4f" % T[i-1])
if (i % 4 == 0) | (i == len(T)):
fout.writelines( "\n")
for i in range(50) :
s = fid.readline()
if s.strip('\n').strip('* ') in ['pressures', 'Pressure grid (bars)',
'pressures (bar)', 'pressures(bar)']:
break
fout.writelines(s) if sourceformat.upper() != 'GWB' else fout.writelines(s.strip('* ').split('(')[0] + '\n')
for i in range(len(P)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % P[i-1])
else:
fout.writelines( " %9.4f" % P[i-1])
if (i % 4 == 0) | (i == len(P)):
fout.writelines( "\n")
#% Calculation for debye huckel and bdot and water properties
waterdielc = water_dielec(T = T, P = P, Dielec_method = Dielec_method)
E, Adh, Bdh, bdot = waterdielc.E, waterdielc.Ah, waterdielc.Bh, waterdielc.bdot
rhoEG = {'rho': rho, 'E': E, 'dGH2O': dGH2O}
# Calculate the rho E G for density extrapolation method here so we have it below
rhoEGextrap = {}
if any(rhoEG['rho'] < 350):
subBornptrs = rhoEG['rho'] < 350
for i, j in enumerate(zip(T[subBornptrs], P[subBornptrs])):
rhoextrap = np.linspace(350, 550, 3)
Pextrap = iapws95(T = j[0], rho = rhoextrap).P if Dielec_method.upper() != 'DEW' else ZhangDuan(T = j[0], rho = rhoextrap).P
Textrap = j[0]*np.ones(np.size(Pextrap))
dGH2O = iapws95(T = Textrap, P = Pextrap).G if Dielec_method.upper() != 'DEW' else ZhangDuan(T = Textrap, P = Pextrap).G
E = water_dielec(T = Textrap, P = Pextrap, Dielec_method = Dielec_method).E
rhoextrap = np.around(rhoextrap, 3)
rhoEGextrap['%d_%d' % (j[0], j[1])]= {'rho': rhoextrap,'E': E, 'dGH2O': dGH2O,
'Textrap': Textrap, 'Pextrap': Pextrap}
#skip lines till adh rows
if act_param['activity_model'] == 'debye-huckel':
for i in range(50) :
s = fid.readline()
if '(adh)' in s.strip('\n') or 'Debye-Huckel A_gamma' in s.strip('\n'):
break
fout.writelines(s) if sourceformat.upper() != 'GWB' else fout.writelines(s.strip('* '))
for i in range(len(Adh)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Adh[i-1])
else:
fout.writelines( " %9.4f" % Adh[i-1])
if (i % 4 == 0) | (i == len(Adh)):
fout.writelines( "\n")
#skip lines till bdh rows
for i in range(50) :
s = fid.readline()
if '(bdh)' in s.strip('\n') or 'Debye-Huckel B_gamma' in s.strip('\n'):
break
fout.writelines(s) if sourceformat.upper() != 'GWB' else fout.writelines(s.strip('* '))
for i in range(len(Bdh)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Bdh[i-1])
else:
fout.writelines( " %9.4f" % Bdh[i-1])
if (i % 4 == 0) | (i == len(Bdh)):
fout.writelines( "\n")
#skip lines till bdot rows
for i in range(50) :
s = fid.readline()
if 'bdot' in s.strip('\n') or 'B-dot' in s.strip('\n'):
break
fout.writelines(s) if sourceformat.upper() != 'GWB' else fout.writelines(s.strip('* '))
for i in range(len(bdot)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % bdot[i-1])
else:
fout.writelines( " %9.4f" % bdot[i-1])
if (i % 4 == 0) | (i == len(bdot)):
fout.writelines( "\n")
#skip lines till cco2 rows
for i in range(50) :
s = fid.readline()
if 'cco2' in s.strip('\n') or 'c co2' in s.strip('\n'):
break
if sourceformat.upper() != 'GWB':
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
else:
fout.writelines('cco2 (coefficients for the Drummond (1981) polynomial) \n' + \
' -1.0312 0.0012806 \n' + \
' 255.9 0.4445 \n' + \
' -0.001606 \n')
elif act_param['activity_model'] == 'h-m-w':
for i in range(50) :
s = fid.readline()
if any(re.findall(r'|'.join(('aphi', 'debye huckel')), s.strip('\n'), re.IGNORECASE)):
break
fout.writelines(s)
Aphi = Adh*np.log(10)/3
for i in range(len(Aphi)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Aphi[i-1])
else:
fout.writelines( " %9.4f" % Aphi[i-1])
if (i % 4 == 0) | (i == len(Aphi)):
fout.writelines( "\n")
for i in range(50) :
s = fid.readline()
if any(re.findall(r'|'.join(('log k for eh reaction', 'Eh reaction: logKr')), s.strip('\n').strip('*'), re.IGNORECASE)):
break
fout.writelines(s) if sourceformat.upper() != 'GWB' else fout.writelines(s.strip('* '))
#% Calculations for "log k for eh" rows
logK = calcRxnlogK( T = T, P = P, Specie = 'eh', dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap,
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
if sourceformat.upper() != 'GWB':
for i in range(50) :
s = fid.readline()
if s.startswith('+', 0):
break
if act_param['activity_model'] == 'debye-huckel':
if sourceformat.upper() != 'GWB':
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
# copy and paste bdot parameters
for i in range(3000) :
s = fid.readline()
s = s.replace(' acid','_acid').replace(' high','_high').replace(' low','_low') if 'acid' in s else s
s_mod = s.split()[0]
if s.startswith('+--', 0):
break
if s_mod in all_species_avail:
fout.writelines(s)
else:
missing_species.append(s_mod)
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
else:
fout.write( "+" + "-"*68 + "\n")
fout.write("bdot parameters \n")
fout.write( "+" + "-"*68 + "\n")
fout.write("* species name azer0 neutral ion type \n")
f_ionsize = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ion_size.txt'), 'r')
Rd = f_ionsize.readlines()
Rd = Rd[1:]
Rd = list(OrderedDict.fromkeys(Rd))
Rd_dup = [x for n, x in enumerate(Rd) if x.split()[0] in [l.split()[0] for l in Rd[:n]]]
Rd = [x for x in Rd if x not in Rd_dup]
Rd = [x if x.split()[0] != 'O2(aq)' else x.replace('O2(aq)', 'O2(g) ') for x in Rd if x.split()[0] in chargedic.keys() ]
f_ionsize.close() #
for i in range(len(Rd)):
fout.writelines(Rd[i])
fout.write( "+" + "-"*68 + "\n")
elif act_param['activity_model'] == 'h-m-w':
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
for i in range(2000):
s = fid.readline()
if (s.rstrip('\n') == "elements"):
break
if any(s.lstrip().rstrip('\n').startswith(x) for x in act_param['act_list']):
rowlst = []; rowlst.append(s)
for i in range(50):
s = fid.readline()
if s.lstrip().rstrip('\n').startswith("+" + "-"*30):
break
rowlst.append(s)
if all([x in all_species_avail for x in rowlst[0].rstrip('\n').split()]):
fout.writelines(rowlst)
fout.writelines( "+" + "-"*64 + "\n")
else:
#break
fout.writelines(s)
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
# copy and paste elements
if sourceformat.upper() != 'GWB':
counter = 0
for i in range(2000) :
s = fid.readline()
s_mod = s.split()[0]
if s_mod in elem_avail:
fout.writelines(s)
counter = counter + 1
if s.startswith('+', 0):
break
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
s = fid.readline()
fout.writelines(s)
else:
#skip lines till "elements" rows
for i in range(5000) :
s = fid.readline()
if s.rstrip('\n').lstrip('0123456789.- ') == 'elements':
break
fout.writelines( "%s \n" % s.rstrip('\n').lstrip('0123456789.- '))
fout.writelines( "+" + "-"*68 + "\n")
#copy and paste lines till "basis" rows
for i in range(1000) :
s = fid.readline()
s_mod = s.split() if s.strip('\n') != '' else s.strip('\n')
if s.rstrip('\n').lstrip('0123456789.- ') == 'basis species':
break
if len(s_mod) > 1:
if s_mod[1].strip('()') in elem_avail:
fout.writelines('%-2s %14s \n' % (s_mod[1].strip('()'), s_mod[-1]))
fout.writelines( "+" + "-"*68 + "\n")
fout.writelines( "%s \n" % s.rstrip('\n').lstrip('0123456789.- '))
fout.writelines( "+" + "-"*68 + "\n")
#% Basis reactions
counter = 0
for j in specielist[1]:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
if j not in missing_species:
fout.writelines('%s\n' % j.replace('O2(aq)', 'O2(g)')) if j == 'O2(aq)' else fout.writelines('%s\n' % j)
if sourceformat.upper() != 'GWB':
if j == 'O2(g)':
fout.writelines(block_info['O2(g)_b'])
else:
fout.writelines(block_info[j])
else:
filler = re.sub(r'[^-0123456789\.]', ' ', chargedic[j]).split()
fout.writelines(' sp.type = basis \n') if j != 'O2(aq)' else fout.writelines(' sp.type = gas refstate \n')
fout.writelines('* EQ3/6 = com, alt, sup, pit\n' +\
' revised = - \n' +\
'* mol.wt. = %s g/mol\n' % filler[-1] +\
'* DHazero = %s \n' % filler[1] +\
' charge = %s \n' % filler[0])
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s element(s):\n" % int(len(Elemlist[j])/2))
Rxn = Elemlist[j]
else:
Rxn = [[v,k] for k,v in dict( sorted(calc_elem_count_molewt(j.rstrip('(aq)(g)') )[0].items(),
key=lambda x: x[0].lower()) ).items()]
Rxn = [item for sublist in Rxn for item in sublist]
fout.writelines( " %s element(s):\n" % int(len(Rxn)/2))
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 7):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( "%-9s " % (Rxn[i - 1]))
if (i % 6 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines('****\n')
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method
fout.writelines( "* ref-state data [source: %s ]\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "+" + "-"*68 + "\n")
counter = counter + 1
else:
continue
fout.writelines( "auxiliary basis species\n")
fout.writelines( "+" + "-"*68 + "\n")
#% Auxiliary Basis reactions
specielist[2] = specielist[2] + ['O2(aq)'] if sourceformat.upper() == 'GWB' else specielist[2]
for j in specielist[2]:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([i for i in rxnlst if i not in missing_species]) == len(rxnlst)):
if sourcedic[j][0] != '':
fout.writelines('%-25s %s \n' % (j, sourcedic[j][0]))
else:
fout.writelines('%-25s %s \n' % (j, dbaccessdic[k][0]))
if sourceformat.upper() != 'GWB':
fout.writelines(block_info[j])
else:
filler = re.sub(r'[^-0123456789\.]', ' ', chargedic[j]).split()
fout.writelines(' sp.type = aux\n' +\
'* EQ3/6 = com, alt, sup \n' +\
' revised = - \n' +\
'* mol.wt. = %s g/mol\n' % filler[-1] +\
'* DHazero = %s \n' % filler[1] +\
' charge = %s \n' % filler[0])
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s element(s):\n" % int(len(Elemlist[j])/2))
Elem = Elemlist[j]
else:
# print(j)
formula = j.rstrip('(aq)(g)') if sourcedic[j][0] == '' else j if not j.endswith('(aq)') else dbaccessdic[k][0].rstrip('(aq)(g)') #.rstrip('(+0123456789)')
Elem = [[v,k] for k,v in dict( sorted(calc_elem_count_molewt(formula)[0].items(),
key=lambda x: x[0].lower()) ).items()]
Elem = [item for sublist in Elem for item in sublist]
fout.writelines( " %s element(s):\n" % int(len(Elem)/2))
for i in range(len(Elem)):
i = i + 1
if (i == 1) | (i == 7):
fout.writelines( " %9.4f " % float(Elem[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Elem[i - 1]))
else:
fout.writelines( "%-9s " % (Elem[i - 1]))
if (i % 6 == 0) | (i == len(Elem)):
fout.writelines( "\n")
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s species in aqueous dissociation reaction:\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
else:
fout.writelines( " %s species in aqueous dissociation reaction:\n" % (sourcedic[j][1] + 1))
sourcedic[j] = [k if k != 'O2(aq)' else k.replace('O2(aq)', 'O2(g)') for k in sourcedic[j]]
Rxn = ['-1.0000', 'O2(aq)', '1.0000', 'O2(g)'] if sourceformat.upper() == 'GWB' and j == 'O2(aq)' else ['-1.0000', '%s' % j] + sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( " %-21s " % (Rxn[i - 1]))
if (i % 4 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines('*\n')
fout.writelines('**** logK grid [T, P @ Miscellaneous parameters]\n')
sourcedic[j] = ['', 1, '1.0000', 'O2(g)'] if sourceformat.upper() == 'GWB' and j == 'O2(aq)' else sourcedic[j]
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit)
if densityextrap.lower() == 'yes':
if all(logK.nonsubBornptrs) == True: # if all densities are >= 350
logKnan_alert = False # turn off the prompts for using Density extrapolation
else:
logKnan_alert = True
else:
logKnan_alert = False # turn off the prompts for using Density extrapolation
logK = logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method
fout.writelines( "* ref-state data [source: %s ]\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "+" + "-"*68 + "\n")
else:
continue
fout.writelines( "aqueous species\n")
fout.writelines( "+" + "-"*68 + "\n")
if logKnan_alert == True:
warnings.warn('Some temperature and pressure points are out of aqueous species HKF eqns regions of applicability, hence, density extrapolation has been applied')
#% Aqueous reactions
for j in specielist[3]:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([i for i in rxnlst if i not in missing_species]) == len(rxnlst)):
if sourcedic[j][0] != '':
fout.writelines('%-25s %s \n' % (j, sourcedic[j][0]))
else:
fout.writelines('%-25s %s \n' % (j, dbaccessdic[k][0]))
if sourceformat.upper() != 'GWB':
fout.writelines(block_info[j])
else:
filler = re.sub(r'[^-0123456789\.]', ' ', chargedic[j]).split()
fout.writelines(' sp.type = aqueous\n' +\
'* EQ3/6 = com, alt, sup \n' +\
' revised = - \n' +\
'* mol.wt. = %s g/mol\n' % filler[-1] +\
'* DHazero = %s \n' % filler[1] +\
' charge = %s \n' % filler[0])
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s element(s):\n" % int(len(Elemlist[j])/2))
Elem = Elemlist[j]
else:
# print(j)
filler = ('(aq)', '(But)', '(Prop)', '(Pent)', '(For)', '(Gly)', '(Glyc)', '(Lac)', 'Acetate')
formula = j.rstrip('(aq)(g)') if sourcedic[j][0] == '' else j if not any([l in j for l in filler]) else re.sub('(aq)', '', dbaccessdic[k][0]) #.rstrip('(+0123456789)')
Elem = [[v,k] for k,v in dict( sorted(calc_elem_count_molewt(formula)[0].items(),
key=lambda x: x[0].lower()) ).items()]
Elem = [item for sublist in Elem for item in sublist]
fout.writelines( " %s element(s):\n" % int(len(Elem)/2))
for i in range(len(Elem)):
i = i + 1
if (i == 1) | (i == 7):
fout.writelines( " %9.4f " % float(Elem[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Elem[i - 1]))
else:
fout.writelines( "%-9s " % (Elem[i - 1]))
if (i % 6 == 0) | (i == len(Elem)):
fout.writelines( "\n")
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s species in aqueous dissociation reaction:\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
else:
fout.writelines( " %s species in aqueous dissociation reaction:\n" % (sourcedic[j][1] + 1))
sourcedic[j] = [k if k != 'O2(aq)' else k.replace('O2(aq)', 'O2(g)') for k in sourcedic[j]]
Rxn = ['-1.0000', '%s' % j] + sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( " %-21s " % (Rxn[i - 1]))
if (i % 4 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines('*\n')
fout.writelines('**** logK grid [T, P @ Miscellaneous parameters]\n')
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
ref = dbaccessdic[k][1].split(' ')[0]
ref = ref.split(':')[1] if ('ref' in ref) or ('REF' in ref) else ref
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method
fout.writelines( "* ref-state data [source: %s ]\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "+" + "-"*68 + "\n")
else:
continue
fout.writelines( "solids\n")
fout.writelines( "+" + "-"*68 + "\n")
#% Mineral reactions
solid_solution = 'no' if solid_solution is None else solid_solution
clay_thermo = 'no' if clay_thermo is None else clay_thermo
if solid_solution.lower() == 'yes':
if nCa_cpx is None:
nCa = 0
else:
nCa = nCa_cpx
solidsolution_no = 11
fnlist = ['plagio', 'olivine', 'pyroxene', 'cpx'] if nCa > 0 else ['plagio', 'olivine', 'pyroxene']
for fn in fnlist:
for nX in np.round(np.linspace(1, 0, solidsolution_no), 1):
if fn != 'cpx':
ss = calcRxnlogK(X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
else:
ss = calcRxnlogK(cpx_Ca = nCa, X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, dataset = 'EQ36')
# clay minerals
if clay_thermo.lower() == 'yes':
fclay = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'clay_elements.dat'), 'r')
Rd = fclay.readlines()
Rd = [j.replace('-','_').strip('\n') for j in Rd]
for i in range(len(Rd)):
ss = calcRxnlogK(T = T, P = P, Specie = 'Clay', elem = Rd[i].split(','),
dbaccessdic = dbaccessdic, ThermoInUnit = self.ThermoInUnit, rhoEG = rhoEG,
rhoEGextrap = rhoEGextrap, densityextrap = densityextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, dataset = 'EQ36')
# other minerals in the source database
minlst = specielist[4] if sourceformat.upper() != 'GWB' else specielist[5]
for j in minlst:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species]) == len(rxnlst)):
if solid_solution.lower() == 'yes' and j in ['Anorthite', 'Albite', 'Forsterite',
'Fayalite', 'Enstatite', 'Ferrosilite']:
continue
elif solid_solution.lower() == 'yes' and (nCa == 1) and j in ['Diopside', 'Hedenbergite']:
continue
elif clay_thermo.lower() == 'yes' and j in [ Rd[h].split(',')[0] for h in range(len(Rd))]:
continue
else:
if sourcedic[j][0] != '':
fout.writelines('%-25s %s \n' % (j, sourcedic[j][0]))
else:
fout.writelines('%-25s %s \n' % (j, dbaccessdic[k][0]))
if sourceformat.upper() != 'GWB':
fout.writelines(block_info[j])
else:
fout.writelines(' sp.type = solid \n' +\
'* EQ3/6 = com, alt, sup \n' +\
' revised = - \n' +\
'* mol.wt. = %s g/mol\n' % MWdic[j] +\
' V0PrTr = %s cm**3/mol \n' % dbaccessdic[j][5] )
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s element(s):\n" % int(len(Elemlist[j])/2))
Elem = Elemlist[j]
else:
formula = re.sub('(s)', '', j) if sourcedic[j][0] == '' else re.sub('(s)', '', dbaccessdic[k][0])
Elem = [[v,k] for k,v in dict( sorted(calc_elem_count_molewt(formula)[0].items(),
key=lambda x: x[0].lower()) ).items()]
Elem = [item for sublist in Elem for item in sublist]
fout.writelines( " %s element(s):\n" % int(len(Elem)/2))
for i in range(len(Elem)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Elem[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Elem[i - 1]))
else:
fout.writelines( "%-9s " % (Elem[i - 1]))
if (i % 6 == 0) | (i == len(Elem)):
fout.writelines( "\n")
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s species in reaction:\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
else:
fout.writelines( " %s species in reaction:\n" % (sourcedic[j][1] + 1))
sourcedic[j] = [k if k != 'O2(aq)' else k.replace('O2(aq)', 'O2(g)') for k in sourcedic[j]]
Rxn = ['-1.0000', '%s' % j] + sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9) | (i == 13) | (i == 17) | (i == 21):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( " %-21s " % (Rxn[i - 1]))
if (i % 4 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines('*\n')
fout.writelines('**** logK grid [T, P @ Miscellaneous parameters]\n')
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'minerals',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method
fout.writelines( "* ref-state data [source: %s ]\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "* Cp coefficients [source: %s ]\n" % ref)
fout.writelines( "* T**0 = %11.8e \n" % (dbaccessdic[k][6]) )
fout.writelines( "* T**1 = %11.8e \n" % (dbaccessdic[k][7]*10**-3))
if dbaccessdic[j][8] < 1:
fout.writelines( "* T**-2 = %12.8e \n" % (dbaccessdic[k][8]*10**5))
else:
fout.writelines( "* T**-2 = %11.8e \n" % (dbaccessdic[k][8]*10**5))
fout.writelines( "* Tlimit = %7.2fC \n" % (dbaccessdic[k][9]))
fout.writelines( "+" + "-"*68 + "\n")
else:
continue
fout.writelines( "liquids\n")
fout.writelines( "+" + "-"*68 + "\n")
#% Liquids reactions
if sourceformat.upper() != 'GWB':
for j in specielist[5]:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
if sourcedic[j][0] != '':
fout.writelines('%-25s %s \n' % (j, sourcedic[j][0]))
else:
fout.writelines('%-25s %s \n' % (j, dbaccessdic[k][0]))
fout.writelines(block_info[j])
fout.writelines('****\n')
fout.writelines( " %s element(s):\n" % int(len(Elemlist[j])/2))
Elem = Elemlist[j]
for i in range(len(Elem)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Elem[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Elem[i - 1]))
else:
fout.writelines( "%-9s " % (Elem[i - 1]))
if (i % 6 == 0) | (i == len(Elem)):
fout.writelines( "\n")
fout.writelines('****\n')
fout.writelines( " %s species in reaction:\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( " %-21s " % (Rxn[i - 1]))
if (i % 4 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines('*\n')
fout.writelines('**** logK grid [T, P @ Miscellaneous parameters]\n')
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = 'EQ36', densityextrap = densityextrap, Specie_class = 'liquids',
rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j == 'Quicksilver':
fout.writelines( "* alternate name = Quicksilver\n" )
if j != 'H2O':
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method
fout.writelines( "* ref-state data [source: %s ]\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "* Cp coefficients [source: %s ]\n" % ref)
fout.writelines( "* T**0 = %11.8e \n" % (dbaccessdic[k][6]) )
fout.writelines( "* T**1 = %11.8e \n" % (dbaccessdic[k][7]*10**-3))
if dbaccessdic[k][8] < 1:
fout.writelines( "* T**-2 = %12.8e \n" % (dbaccessdic[k][8]*10**5))
else:
fout.writelines( "* T**-2 = %11.8e \n" % (dbaccessdic[k][8]*10**5))
fout.writelines( "* Tlimit = %7.2fC \n" % (dbaccessdic[k][9]))
fout.writelines( "+" + "-"*68 + "\n")
else:
continue
fout.writelines( "gases\n")
fout.writelines( "+" + "-"*68 + "\n")
#% Gases reactions
for j in specielist[6]:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
if sourcedic[j][0] != '':
fout.writelines('%-25s %s \n' % (j, sourcedic[j][0]))
else:
fout.writelines('%-25s %s \n' % (j, dbaccessdic[k][0]))
if sourceformat.upper() != 'GWB':
fout.writelines(block_info[j])
else:
fout.writelines(' sp.type = gas \n' +\
'* EQ3/6 = com, alt, sup \n' +\
' revised = - \n' +\
'* mol.wt. = %s g/mol\n' % MWdic[j] +\
' V0PrTr = %s cm**3/mol \n' % dbaccessdic[j][5] )
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s element(s):\n" % int(len(Elemlist[j])/2))
Elem = Elemlist[j]
else:
formula = j.rstrip('(g)') if sourcedic[j][0] == '' else sourcedic[j][0].rstrip('(g)')
Elem = [[v,k] for k,v in dict( sorted(calc_elem_count_molewt(formula)[0].items(),
key=lambda x: x[0].lower()) ).items()]
Elem = [item for sublist in Elem for item in sublist]
fout.writelines( " %s element(s):\n" % int(len(Elem)/2))
for i in range(len(Elem)):
i = i + 1
if (i == 1) | (i == 7) | (i == 13):
fout.writelines( " %9.4f " % float(Elem[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Elem[i - 1]))
else:
fout.writelines( "%-9s " % (Elem[i - 1]))
if (i % 6 == 0) | (i == len(Elem)):
fout.writelines( "\n")
fout.writelines('****\n')
if sourceformat.upper() != 'GWB':
fout.writelines( " %s species in reaction:\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
else:
fout.writelines( " %s species in reaction:\n" % (sourcedic[j][1] + 1))
sourcedic[j] = [k if k != 'O2(aq)' else k.replace('O2(aq)', 'O2(g)') for k in sourcedic[j]]
Rxn = ['-1.0000', '%s' % j] + sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( " %-21s " % (Rxn[i - 1]))
if (i % 4 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines('*\n')
fout.writelines('**** logK grid [T, P @ Miscellaneous parameters]\n')
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
for i in range(len(logK)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % logK[i-1])
else:
fout.writelines( " %9.4f" % logK[i-1])
if (i % 4 == 0) | (i == len(logK)):
fout.writelines( "\n")
fout.writelines( "* gflag = 1 [reported delG0f used]\n" )
fout.writelines( "* extrapolation algorithm: supcrt92 [92joh/oel]\n" )
if j != 'H2O':
dG, dH, S = dbaccessdic[k][2], dbaccessdic[k][3], dbaccessdic[k][4]
else:
dG, dH, S, ref = dGH2O[0], dHH2O[0], SH2O[0], 'iapws95/' + Dielec_method
fout.writelines( "* ref-state data [source: %s ]\n" % ref)
fout.writelines( "* delG0f = %8.3f kcal/mol\n" % (dG/1000) )
fout.writelines( "* delH0f = %8.3f kcal/mol\n" % (dH/1000))
fout.writelines( "* S0PrTr = %8.3f cal/(mol*K)\n" % S)
fout.writelines( "* Cp coefficients [source: %s ]\n" % ref)
fout.writelines( "* T**0 = %11.8e \n" % (dbaccessdic[k][6]) )
fout.writelines( "* T**1 = %11.8e \n" % (dbaccessdic[k][7]*10**-3))
if dbaccessdic[j][8] < 1:
fout.writelines( "* T**-2 = %12.8e \n" % (dbaccessdic[k][8]*10**5))
else:
fout.writelines( "* T**-2 = %11.8e \n" % (dbaccessdic[k][8]*10**5))
fout.writelines( "* Tlimit = %7.2fC \n" % (dbaccessdic[k][9]))
fout.writelines( "+" + "-"*68 + "\n")
else:
continue
fout.writelines( "solid solutions\n")
fout.writelines( "+" + "-"*68 + "\n")
#% Solid solution reactions
if sourceformat.upper() != 'GWB':
for j in specielist[7]:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (len([k for k in rxnlst if k not in missing_species]) == len(rxnlst)):
if sourcedic[j][0] != '':
fout.writelines('%-25s %s \n' % (j, sourcedic[j][0]))
else:
fout.writelines('%-25s %s \n' % (j, dbaccessdic[j][0]))
fout.writelines(block_info[j][0])
fout.writelines( " %s components\n" % sourcedic[j][1])
Rxn = sourcedic[j][2:]
for i in range(len(Rxn)):
i = i + 1
if (i == 1) | (i == 5) | (i == 9) | (i == 13):
fout.writelines( " %9.4f " % float(Rxn[i - 1]))
elif i % 2 != 0:
fout.writelines( "%9.4f " % float(Rxn[i - 1]))
else:
fout.writelines( " %-21s " % (Rxn[i - 1]))
if (i % 4 == 0) | (i == len(Rxn)):
fout.writelines( "\n")
fout.writelines(block_info[j][1])
fout.writelines( "+" + "-"*68 + "\n")
else:
continue
fout.writelines( "references\n")
fout.writelines( "+" + "-"*68 + "\n")
fout.writelines('** Please copy references to here from the corresponding\n')
fout.writelines('** sequential-access version of the direct-access SUPCRT database.\n stop.\n\n')
#% close all files
fid.close()
fout.close()
if clay_thermo.lower() == 'yes':
fclay.close()
return print('Success, your new EQ3/6 database is ready for download')
[docs]
def write_PHREEQCdb(self, T, P):
r"""
This function writes the new PHREEQC database into a new folder called "output" \n
Parameters
----------
T : temperature [°C] \n
P : pressure [bar] \n
Returns
-------
Outputs the new database to an ASCII file with filename described in 'objdb'. \n
Usage
-------
Example:
(1) General format with default dielectric constant and CO2 activity model and exclusions
of solid solutions \n
write_PHREEQCdb(T, P ) \n
(2) Inclusion of solid solutions and clay thermo and exclusion of solid solution of clinopyroxene \n
write_PHREEQCdb(T, P ) \n
(3) Inclusion of all solid solutions and clay thermo with \\emph{'Duan_Sun'} CO2 activity model and 'FGL97'
dielectric constant calculation \n
write_PHREEQCdb(T, P ) \n
"""
nCa_cpx = self.cpx_Ca
solid_solution = self.solid_solution
clay_thermo = self.clay_thermo
# sourcedb = self.sourcedb
objdb = self.objdb # new database file path
Dielec_method = self.Dielec_method
heatcap_method = self.heatcap_method
sourceformat = self.sourceformat # source database format
densityextrap = self.densityextrap
dbaccessdic, sourcedic, specielist, Rd, d = self.dbr.dbaccessdic, self.dbr.sourcedic, self.dbr.specielist, self.dbr.Rd, self.dbr.d
act_param = self.dbr.act_param
dataset_format = act_param['dataset_format']
# sourcedb_codecs = getattr(self, "sourcedb_codecs", None) or getattr(self.dbr, "sourcedb_codecs", {})
logK_form = self.logK_form
sourcedic_logK = {(normalize_phreeqc_species_charge(k) if k in specielist[1] + specielist[3] else k):
[normalize_phreeqc_species_charge(x) if isinstance(x, str) else x for x in v]
for k, v in sourcedic.items()
}
# Some databases (e.g. llnl.dat) list the same species in both
# SOLUTION_SPECIES (aqueous) and PHASES (mineral). The dict
# comprehension above normalises the key for the aqueous entry
# (e.g. 'AlF3' → 'AlF3(aq)'), which causes a KeyError when
# write_PHREEQCdb's mineral loop looks up the original name.
# Ensure the original key is also present so both lookups succeed.
# Include basis species (specielist[1]) that also appear as minerals —
# Sb(OH)3 is an example: it is the Sb master species AND a PHASES entry.
_dual = (set(specielist[1]) | set(specielist[3])) & set(specielist[4])
for k, v in sourcedic.items():
if k in _dual and k not in sourcedic_logK:
sourcedic_logK[k] = [normalize_phreeqc_species_charge(x) if isinstance(x, str) else x for x in v]
specielist_logK = [[normalize_phreeqc_species_charge(x) for x in k] if idx in (1, 3) else k for idx, k in enumerate(specielist)]
# make dir and file to write data to
phreeqc_path = os.path.join(os.getcwd(), 'output', 'PHREEQC')
os.makedirs(phreeqc_path, exist_ok=True)
# extract periodic table symbols into a set safely
try:
periodic_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PeriodicTableJSON.json")
except NameError:
periodic_file = os.path.join(os.getcwd(), "PeriodicTableJSON.json")
with open(periodic_file, encoding="utf8") as f:
periodic_data = json.load(f)
element_symbols = {element["symbol"] for element in periodic_data["elements"]}
# missing species list
missing_species = [] # list of all species in sourcedb that are not in dbaccessdic
# missing species - start
for key, values in sourcedic.items():
normalized_key = normalize_phreeqc_species_charge(key) if key in specielist[1] + specielist[3] else key
normalized_key = normalized_key + "(s)" if not normalized_key.endswith('(aq)') and key in specielist[3] and key in specielist[4] else normalized_key
key_found = True
normalized_key = normalized_key.replace('CH4(aq)', 'Methane(aq)')
if normalized_key not in dbaccessdic.keys() and normalized_key not in (['eh', 'e-', 'H2O']):
key_found = False
# go through keys reactants
temp_missing = []
for i in range(3, len(values), 2):
curr = values[i]
curr_norm = normalize_phreeqc_species_charge(curr).replace('CH4(aq)', 'Methane(aq)')
# check for each reaction if all the species in that reaction exist in dbaccess
if curr_norm not in dbaccessdic.keys() and curr_norm not in (['eh', 'e-', 'H2O']) and curr_norm not in element_symbols:
temp_missing.append(curr)
if temp_missing != [] or not key_found:
missing_species.append([key] + temp_missing)
missingfile = open(os.path.join(os.path.abspath("."), 'output', 'PHREEQC', 'spxNotFound.txt'), 'w+')
for line in missing_species:
if len(line) > 0:
missingfile.writelines(line[0])
missingfile.writelines('\n')
for i in range(len(line)):
missingfile.writelines(' %s' % line[i])
missingfile.writelines('\n')
missingfile.close()
# write all info from source unless bases species was not found in direct-access db ?
flattened_missing_spx = list(itertools.chain(*missing_species))
missing_species = [item for sublist in missing_species for item in sublist]
missing_species = [i for n, i in enumerate(missing_species) if i not in missing_species[:n]]
# make new file
if objdb == None:
objdb = 'phreeqc_%sbars' % int(P[0])
fout = open(os.path.join(os.path.abspath("."),'output', 'PHREEQC', objdb + '.dat'), 'w+')
# write header - TODO - add comments from sourcedb?
fout.writelines(["# Dataset for thermodynamic data for PHREEQC programs \n",
"# Dataset format: " + dataset_format + "\n",
"# Generated by: pyGeochemCalc" + time.ctime() + '\n',
"# Output Package: PHREEQC \n",
"\n"])
# moved this block outside of debye huckel if statement because of errors when we needed rho later
if Dielec_method.upper() == 'DEW':
water = ZhangDuan(T = T, P = P)
rho, dGH2O, dHH2O, SH2O = water.rho, water.G, np.nan*np.ones(len(T)), np.nan*np.ones(len(T))
else:
water = iapws95(T = T, P = P)
rho, dGH2O, dHH2O, SH2O = water.rho, water.G, water.H, water.S
waterdielc = water_dielec(T = T, P = P, Dielec_method = Dielec_method)
E, Adh, Bdh, bdot = waterdielc.E, waterdielc.Ah, waterdielc.Bh, waterdielc.bdot
rhoEG = {'rho': rho, 'E': E, 'dGH2O': dGH2O}
rhoEDB = {'rho': rho, 'E': E, 'Ah': Adh, 'Bh': Bdh}
rhoEGextrap = {}
if any(rhoEG['rho'] < 350):
subBornptrs = rhoEG['rho'] < 350
for i, j in enumerate(zip(T[subBornptrs], P[subBornptrs])):
rhoextrap = np.linspace(350, 550, 3)
Pextrap = iapws95(T = j[0], rho = rhoextrap).P if Dielec_method.upper() != 'DEW' else ZhangDuan(T = j[0], rho = rhoextrap).P
Textrap = j[0]*np.ones(np.size(Pextrap))
dGH2O = iapws95(T = Textrap, P = Pextrap).G if Dielec_method.upper() != 'DEW' else ZhangDuan(T = Textrap, P = Pextrap).G
E = water_dielec(T = Textrap, P = Pextrap, Dielec_method = Dielec_method).E
rhoextrap = np.around(rhoextrap, 3)
rhoEGextrap['%d_%d' % (j[0], j[1])]= {'rho': rhoextrap,'E': E, 'dGH2O': dGH2O,
'Textrap': Textrap, 'Pextrap': Pextrap}
TK = convert_temperature( T, Out_Unit = 'K' )
logKfunc = lambda TK, *x: x[0] + x[1]*TK + x[2]*TK**(-1) + x[3]*np.log10(TK) + x[4]*TK**(-2) #+ x[5]*TK**(2)
x0 = [2.06576e2, 3.73497e-2, -9.01862e3, -3.19605e1, 6.0111e5]
if act_param['activity_model'].lower() == 'debye huckel':
if any("-llnl_gamma" in k for k in Rd[d['SOLUTION_SPECIES']:d['PHASES']]):
fout.writelines(["LLNL_AQUEOUS_MODEL_PARAMETERS \n", "-temperatures\n"])
# write temperatures
for i in range(len(T)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % T[i-1])
else:
fout.writelines( " %9.4f" % T[i-1])
if (i % 4 == 0) | (i == len(T)):
fout.writelines( "\n")
# Calculation for debye huckel and bdot and water properties
fout.writelines(["#debye huckel a (adh) \n", " -dh_a \n"])
for i in range(len(Adh)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Adh[i-1])
else:
fout.writelines( " %9.4f" % Adh[i-1])
if (i % 4 == 0) | (i == len(Adh)):
fout.writelines( "\n")
fout.writelines(["#debye huckel b (bdh) \n", " -dh_b \n"])
for i in range(len(Bdh)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Bdh[i-1])
else:
fout.writelines( " %9.4f" % Bdh[i-1])
if (i % 4 == 0) | (i == len(Bdh)):
fout.writelines("\n")
fout.writelines(["#bdot (bdot) \n", " -bdot \n"])
for i in range(len(bdot)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % bdot[i-1])
else:
fout.writelines( " %9.4f" % bdot[i-1])
if (i % 4 == 0) | (i == len(bdot)):
fout.writelines( "\n")
fout.writelines(["#cco2 (coefficients for the Drummond (1981) polynomial) \n", " -co2_coefs \n"])
# Define Drummond equation coefficients
C = -1.0312
F = 0.0012806
G = 255.9
E = 0.4445
H = -0.001606
# First row: C and F
fout.writelines(f" {C:9.4f} {F:12.7f}\n")
# Second row: G and E
fout.writelines(f" {G:9.4f} {E:12.4f}\n")
# Third row: H alone
fout.writelines(f" {H:9.5f}\n")
fout.writelines([" \n NAMED_EXPRESSIONS\n\n #\n # formation of O2 from H2O\n # 2H2O = O2 + 4H+ + 4e-\n #\n Log_K_O2\n "])
# print(sourcedic_logK['O2(aq)'])
logK = calcRxnlogK( T = T, P = P, Specie = 'O2(aq)', dbaccessdic = dbaccessdic, sourcedic = sourcedic_logK,
specielist = specielist_logK, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit)
logK = - logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
_valid_o2 = logK != 500
if int(np.sum(_valid_o2)) >= 5:
logKcorr = curve_fit(logKfunc, TK[_valid_o2].ravel(), logK[_valid_o2].ravel(), p0 = x0, maxfev = 1000000)[0]
info = " " + " ".join("%9.5f" % e for e in logKcorr)
logK25 = logKfunc(convert_temperature( 25, Out_Unit = 'K' ), *logKcorr)
fout.writelines(f" -log_k {logK25:.3f}\n")
fout.writelines(f" -analytic {info}\n")
else:
_vals_o2 = logK[_valid_o2]; _tks_o2 = TK[_valid_o2]
logK25 = float(_vals_o2[int(np.argmin(np.abs(_tks_o2 - 298.15)))]) if _vals_o2.size > 0 else 500.0
fout.writelines(f" -log_k {logK25:.3f}\n")
else:
fout.writelines(["\n"])
else:
fout.writelines(["\n"])
fout.writelines(["\n",
"SOLUTION_MASTER_SPECIES",
"\n\n",
"#"
+ "element".ljust(14)
+ "species".ljust(14)
+ "alk".ljust(14)
+ "gfw_formula".ljust(14)
+ "element_gfw".ljust(14)
+ "\n"
])
solid_solution = 'no' if solid_solution is None else solid_solution
clay_thermo = 'no' if clay_thermo is None else clay_thermo
master_sp = []
# Elements
# print(flattened_missing_spx)
for i in range(d['SOLUTION_MASTER_SPECIES'], d['SOLUTION_SPECIES']):
s = Rd[i]
if (not s.startswith('#')) and (len(s.split()) > 2 and s.split()[1] not in flattened_missing_spx):
fout.writelines(s)
master_sp.append(s.split()[1])
if solid_solution.lower() == 'yes' or clay_thermo.lower() == 'yes':
if 'Al+3' not in master_sp:
fout.writelines('Al Al+3 0 Al 26.982\n')
if 'Fe+3' not in master_sp:
fout.writelines('Fe(2) Fe+2 0 Fe 55.847\n')
fout.writelines('Fe(3) Fe+3 -2 Fe 55.847\n')
if 'O2' not in master_sp:
fout.writelines('O(0) O2 0 O 15.999\n')
if 'H4SiO4' not in master_sp:
fout.writelines('Si H4SiO4 0 Si 28.086\n')
# print(master_sp)
# SOLUTION_SPECIES
fout.writelines(["\nSOLUTION_SPECIES\n\n"])
last_num = 0
if solid_solution.lower() == 'yes' or clay_thermo.lower() == 'yes':
if 'Al+3' not in master_sp:
fout.writelines(['Al+3 = Al+3\n', ' -log_k 0.000 \n\n'])
if 'Fe+3' not in master_sp:
fout.writelines(['Fe+3 = Fe+3\n', ' -log_k 0.000 \n\n'])
sourcedic['Fe+3'] = ['', 4, '0.250', 'O2', '1.000', 'Fe+2', '1.000', 'H+', '-0.500', 'H2O']
sourcedic['O2'] = ['', 3, '2.000', 'H2O', '-4.000', 'e-', '-4.000', 'H+']
specielist[3] += ['Fe+3', 'O2']
sourcedic_logK['Fe+++'] = ['', 4, '0.250', 'O2(aq)', '1.000', 'Fe++', '1.000', 'H+', '-0.500', 'H2O']
sourcedic_logK['O2(aq)'] = ['', 3, '2.000', 'H2O', '-4.000', 'e-', '-4.000', 'H+']
specielist_logK[3] += ['Fe+++', 'O2(aq)']
if 'H4SiO4' not in master_sp:
fout.writelines(['H4SiO4 = H4SiO4\n', ' -log_k 0.000 \n\n'])
sourcedic['Al(OH)4-'] = ['', 3, '4.000', 'H2O', '1.000', 'Al+3', '-4.000', 'H+']
sourcedic['SiO2'] = ['', 3, '1.000', 'H4SiO4', '-2.000', 'H2O']
specielist[3] += ['Al(OH)4-', 'SiO2']
sourcedic_logK['Al(OH)4-'] = ['', 3, '4.000', 'H2O', '1.000', 'Al+++', '-4.000', 'H+']
sourcedic_logK['SiO2(aq)'] = ['', 3, '1.000', 'H4SiO4(aq)', '-2.000', 'H2O']
specielist_logK[3] += ['Al(OH)4-', 'SiO2(aq)']
for j in specielist[1] + specielist[3]: #
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 == 0 ]
if (j not in flattened_missing_spx) and all(sp not in flattened_missing_spx for sp in rxnlst):
lhs_terms = []
rhs_terms = [j]
for n in range(2, len(sourcedic[j]), 2):
coeff = sourcedic[j][n]
specie = sourcedic[j][n+1]
# Absolute value of coefficient for display
abs_coeff = abs(float(coeff))
# Try to detect if it's a rational with small denominator
frac = Fraction(abs_coeff).limit_denominator(1000)
if abs(abs_coeff - float(frac)) < 1e-12: # effectively rational
coeff_str = f"{abs_coeff:.15f}".rstrip("0").rstrip(".")
else:
coeff_str = f"{abs_coeff:.4f}".rstrip("0").rstrip(".")
# Format term (skip coefficient if it's 1)
if coeff_str == "1":
term = f"{specie}"
else:
term = f"{coeff_str}{specie}"
# Sort into LHS (positive coeffs) or RHS (negative coeffs)
if float(coeff) >= 0:
lhs_terms.append(term)
else:
rhs_terms.append(term)
# Join LHS and RHS with " + "
to_write = " + ".join(lhs_terms) + " = " + " + ".join(rhs_terms)
fout.writelines(to_write + "\n")
lhs_species, rhs_species = [x.strip() for x in to_write.split('=')]
pattern = re.compile(rf'^\s*{build_side_regex(lhs_species)}\s*=\s*{build_side_regex(rhs_species)}\s*$')
# pattern = re.compile(rf'{build_side_regex(lhs_species)}\s*=\s*{build_side_regex(rhs_species)}')
species_line = re.compile(r'^\s*[\w\+\-\(\)]+.*=\s*[\w\+\-\(\)]+.*$')
block = []
capture = False
for line in Rd[d['SOLUTION_SPECIES']:d['PHASES']]:
stripped = line
if stripped.startswith('#'):
continue # skip comments
# Start capturing when we find the species of interest
if pattern.match(stripped):
capture = True
block.append(line)
continue
# Stop if a new species starts
if capture and species_line.match(stripped):
break
if capture:
block.append(line)
fout.writelines([k for k in block if ("llnl_gamma" in k or "gamma" in k) and not k.lstrip().startswith('#')])
# if this is 1 : 1 - no need to calculate logk
if len(sourcedic[j]) > 4:
curr_species_w_normalized_charge = normalize_phreeqc_species_charge(j)
logK = calcRxnlogK( T = T, P = P, Specie = curr_species_w_normalized_charge, dbaccessdic = dbaccessdic, sourcedic = sourcedic_logK,
specielist = specielist_logK, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit)
if densityextrap.lower() == 'yes':
if all(logK.nonsubBornptrs) == True: # if all densities are >= 350
logKnan_alert = False # turn off the prompts for using Density extrapolation
else:
logKnan_alert = True
else:
logKnan_alert = False # turn off the prompts for using Density extrapolation
logK = - logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
_valid_aq = logK != 500
if int(np.sum(_valid_aq)) >= 5:
logKcorr = curve_fit(logKfunc, TK[_valid_aq].ravel(), logK[_valid_aq].ravel(), p0 = x0, maxfev = 1000000)[0]
info = " " + " ".join("%9.5f" % e for e in logKcorr)
logK25 = logKfunc(convert_temperature( 25, Out_Unit = 'K' ), *logKcorr)
fout.writelines(f" -log_k {logK25:.3f}\n")
fout.writelines(f" -analytic {info}\n")
else:
_vals_aq = logK[_valid_aq]; _tks_aq = TK[_valid_aq]
logK25 = float(_vals_aq[int(np.argmin(np.abs(_tks_aq - 298.15)))]) if _vals_aq.size > 0 else 0.0
fout.writelines(f" -log_k {logK25:.3f}\n")
else:
fout.writelines(f" -log_k {0.000}\n")
# fout.writelines([k for _, k in block if ("llnl_gamma" in k or "gamma" in k) and not k.lstrip().startswith('#')])
fout.writelines([k for k in block if "dw" in k and not k.lstrip().startswith('#')])
fout.writelines([k for k in block if "millero" in k and not k.lstrip().startswith('#')])
fout.writelines([k for k in block if "Vm" in k and not k.lstrip().startswith('#')])
fout.writelines(f"\n")
if logKnan_alert == True:
warnings.warn('Some temperature and pressure points are out of aqueous species HKF eqns regions of applicability, hence, density extrapolation has been applied')
# PHASES
if d['PHASES'] != -1:
fout.writelines(["PHASES\n\n"])
if nCa_cpx is None:
nCa = 0
else:
nCa = nCa_cpx
if solid_solution.lower() == 'yes':
solidsolution_no = 11
fnlist = ['plagio', 'olivine', 'pyroxene', 'cpx'] if nCa_cpx > 0 else ['plagio', 'olivine', 'pyroxene']
for fn in fnlist:
for nX in np.round(np.linspace(1, 0, solidsolution_no), 1):
if fn != 'cpx':
ss = calcRxnlogK(X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap, Al_Si = 'Arnórsson_Stefánsson')
else:
ss = calcRxnlogK(cpx_Ca = nCa, X = nX, T = T, P = P, Dielec_method = Dielec_method,
rhoEG = rhoEG, dbaccessdic = dbaccessdic, Specie = fn,
densityextrap = densityextrap, ThermoInUnit = self.ThermoInUnit,
rhoEGextrap = rhoEGextrap, Al_Si = 'Arnórsson_Stefánsson')
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, TK, dataset = 'PHREEQC', logK_form = logK_form)
# clay minerals
if clay_thermo.lower() == 'yes':
# fclay = open(os.path.join(folder, 'clay_elements.dat'), 'r')
fclay = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'clay_elements.dat'), 'r')
Rd_clay = fclay.readlines()
Rd_clay = [j.replace('-','_').strip('\n') for j in Rd_clay]
for i in range(len(Rd_clay)):
ss = calcRxnlogK(T = T, P = P, Specie = 'Clay', elem = Rd_clay[i].split(','),
dbaccessdic = dbaccessdic, ThermoInUnit = self.ThermoInUnit, rhoEG = rhoEG,
rhoEGextrap = rhoEGextrap, densityextrap = densityextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, TK, dataset = 'PHREEQC', logK_form = logK_form)
for j in specielist[4] + specielist[5]: #
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2]]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 == 0 ]
if (j not in flattened_missing_spx) and all(sp not in flattened_missing_spx for sp in rxnlst):
lhs_terms = []
rhs_terms = [sourcedic[j][0]]
for n in range(2, len(sourcedic[j]), 2):
coeff = sourcedic[j][n]
specie = sourcedic[j][n+1]
# Absolute value of coefficient for display
abs_coeff = abs(float(coeff))
# Format term (skip coefficient if it's 1)
if abs_coeff == 1.0:
term = f"{specie}"
else:
term = f"{abs_coeff:.3f}{specie}"
# Sort into LHS (positive coeffs) or RHS (negative coeffs)
if float(coeff) >= 0:
lhs_terms.append(term)
else:
rhs_terms.append(term)
# Join LHS and RHS with " + "
to_write = " + ".join(rhs_terms) + " = " + " + ".join(lhs_terms)
# Read the source-database block for this mineral first — needed
# for Vm / T_c copy-through regardless of whether logK computation
# succeeds.
block = []
_capture_blk = False
for line in Rd[d['PHASES']:]:
stripped = line.strip()
if stripped.startswith('#'):
continue # skip comments
if stripped == j:
_capture_blk = True
elif _capture_blk and stripped and not stripped.startswith('-') and ' ' not in stripped:
break
if _capture_blk:
block.append(line)
try:
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic_logK,
specielist = specielist_logK, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'minerals',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap)
logK = logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
_valid_min = logK != 500
if int(np.sum(_valid_min)) >= 5:
logKcorr = curve_fit(logKfunc, TK[_valid_min].ravel(), logK[_valid_min].ravel(), p0 = x0, maxfev = 1000000)[0]
info = " " + " ".join("%9.5f" % e for e in logKcorr)
logK25 = logKfunc(convert_temperature( 25, Out_Unit = 'K' ), *logKcorr)
_analytic_line = f" -analytic {info}\n"
else:
_vals_min = logK[_valid_min]; _tks_min = TK[_valid_min]
logK25 = float(_vals_min[int(np.argmin(np.abs(_tks_min - 298.15)))]) if _vals_min.size > 0 else 500.0
_analytic_line = None
# Write mineral entry only after logK computation succeeded.
fout.writelines(j + "\n" + " " + to_write + "\n")
fout.writelines(f" -log_k {logK25:.3f}\n")
if _analytic_line:
fout.writelines(_analytic_line)
except KeyError:
# Mineral absent from direct-access database — skip entirely.
block = [] # suppress Vm/T_c copy-through for skipped species
fout.writelines([k for k in block if "Vm" in k and not k.lstrip().startswith('#')])
fout.writelines([k for k in block if "T_c" in k and not k.lstrip().startswith('#')])
fout.writelines([k for k in block if "P_c" in k and "T_c" not in k and not k.lstrip().startswith('#')])
fout.writelines([k for k in block if "Omega" in k and "T_c" not in k and not k.lstrip().startswith('#')])
fout.writelines(f"\n")
# PITZER
if act_param['activity_model'].lower() == 'h-m-w':
# fout.writelines(["PITZER\n\n"])
for i in range(d['PITZER'], d['length']):
s = Rd[i]
if s.startswith(('EXCHANGE_MASTER_SPECIES', 'SURFACE_MASTER_SPECIES', 'RATES')) or i == d['length']:
break
else:
if s.startswith(('-B0', '-B1', '-B2', '-C0', '-THETA', '-LAMDA', '-ZETA', '-PSI')):
fout.writelines(s)
else:
s_out = [p for p in s.split() if not re.match(r'^[0-9.eE+-]+$', p)]
if all(item not in flattened_missing_spx for item in s_out):
fout.writelines(s)
# EXCHANGE_SPECIES
if d['EXCHANGE_MASTER_SPECIES'] != -1:
# fout.writelines(["EXCHANGE_MASTER_SPECIES\n\n"])
# Collect filtered block
filtered_block = []
skip_block = False
for i in range(d['EXCHANGE_MASTER_SPECIES'], d['length']):
line = Rd[i]
if line.startswith(('SURFACE_MASTER_SPECIES', 'RATES')) or i == d['length']:
break
if line.strip().startswith("#"): # reset skip check per block header
skip_block = False
if contains_missing_species(line, flattened_missing_spx):
skip_block = True
if not skip_block:
filtered_block.append(line)
fout.writelines(filtered_block)
# for i in range(d['EXCHANGE_MASTER_SPECIES'], d['length']):
# s = Rd[i]
# if s.startswith(('SURFACE_MASTER_SPECIES', 'RATES')) or i == d['length']:
# break
# else:
# fout.writelines(s)
# SURFACE_SPECIES
if d['SURFACE_MASTER_SPECIES'] != -1:
filtered_block = []
skip_block = False
for i in range(d['SURFACE_MASTER_SPECIES'], d['length']):
line = Rd[i]
if line.startswith('RATES') or i == d['length']:
break
if line.strip().startswith("#"): # reset skip check per block header
skip_block = False
if contains_missing_species(line, flattened_missing_spx):
skip_block = True
if not skip_block:
filtered_block.append(line)
fout.writelines(filtered_block)
# for i in range(d['SURFACE_MASTER_SPECIES'], d['length']):
# s = Rd[i]
# if s.startswith('RATES') or i == d['length']:
# break
# else:
# fout.writelines(s)
# RATES
if d['RATES'] != -1:
fout.writelines(["RATES\n\n"])
for j in specielist[6]: #
if (j in specielist[4] + specielist[5]):
block = []
capture = False
for k in Rd[d['RATES']:]:
line = k.strip()
if line.startswith('#'):
continue # skip comments
if line.startswith(j):
capture = True
if capture:
block.append(k)
if capture and line == "-end":
break
# block now contains all lines from "-start" to "-end" inclusive
fout.writelines(block)
fout.writelines("\n")
fout.close()
if clay_thermo.lower() == 'yes':
fclay.close()
return print('Success, your new PHREEQC database is ready for download')
[docs]
def write_pflotrandb(self, T, P ):
r"""
This function writes the new pflotran database into a new folder called "output" \n
Parameters
----------
T : temperature [°C] \n
P : pressure [bar] \n
Returns
-------
Outputs the new database to an ASCII file with filename described in 'objdb'. \n
Usage
-------
Example:
(1) General format with default dielectric constant and CO2 activity model and exclusions
of solid solutions \n
write_pflotrandb(T, P ) \n
(2) Inclusion of solid solutions and clay thermo and exclusion of solid solution of clinopyroxene \n
write_pflotrandb(T, P ) \n
(3) Inclusion of all solid solutions and clay thermo with \\emph{'Duan_Sun'} CO2 activity model and 'FGL97'
dielectric constant calculation \n
write_pflotrandb(T, P ) \n
"""
nCa_cpx = self.cpx_Ca; sourcedb = self.sourcedb;
solid_solution = self.solid_solution; clay_thermo = self.clay_thermo
objdb = self.objdb; Dielec_method = self.Dielec_method
sourceformat = self.sourceformat; heatcap_method = self.heatcap_method;
densityextrap = self.densityextrap
dbaccessdic, sourcedic, specielist = self.dbr.dbaccessdic, self.dbr.sourcedic, self.dbr.specielist
MWdic, chargedic = self.dbr.MWdic, self.dbr.chargedic
if sourceformat.upper() == 'EQ36':
block_info = self.dbr.block_info
if sourceformat.upper() == 'EQ36':
dataset = 'tdat'
dataset_format = 'apr20'
else:
dataset = sourcedb.split('.')[-1]
dataset_format = self.dbr.act_param['dataset_format']
if os.path.exists(os.path.join(os.getcwd(), 'output/Pflotran')) == False:
os.makedirs(os.path.join(os.getcwd(), 'output/Pflotran'))
periodic_table = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'PeriodicTableJSON.json'), encoding='utf8')
data = json.load(periodic_table)
Element = {data['elements'][x]['symbol'] : pd.DataFrame([data['elements'][x]['name'],
data['elements'][x]['atomic_mass']],
index = ['name', 'mass']).T
for x in range(len(data['elements']))}
periodic_table.close()
missing_species = []
elemspeclist = [ symbol for x in specielist[0] for symbol, item in Element.items()
if item.name[0][:5] == x[:5] ] if sourceformat.upper() == 'GWB' else specielist[0]
form_del = [1] if sourceformat.upper() == 'GWB' else [1, 3, 4]
all_species_source = [[i]+k for i, k in sourcedic.items() if i not in (['eh', 'e-', 'H2O']) ]
all_species_source = [[k for j, k in enumerate(all_species_source[i])
if (j not in form_del and k not in elemspeclist and str(k).strip('0123456789.- ') != '') ]
if (i <= len(specielist[0]))
else [k for j, k in enumerate(all_species_source[i])
if (j not in form_del and str(k).strip('0123456789.- ') != '') ]
for i in range(len(all_species_source)) ]
for num in range(len(all_species_source)): #
if num < len(all_species_source):
lst = [v for v in all_species_source[num] if v not in (['eh', 'e-', 'H2O']) ]
bool_miss = [x.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
not in dbaccessdic.keys() for x in lst ]
if any(bool_miss):
sublist = [i for (i, v) in zip(lst, bool_miss) if v ]
if lst[0] not in sublist:
missing_species.append([lst[0]] + sublist)
else:
missing_species.append(sublist)
missingfile = open(os.path.join(os.path.abspath("."),'output', 'Pflotran', 'spxNotFound.txt'), 'w')
for line in missing_species:
if len(line) > 0:
missingfile.writelines(line[0])
missingfile.writelines('\n')
for i in range(len(line)):
missingfile.writelines(' %s' % line[i])
missingfile.writelines('\n')
missingfile.close()
missing_species = [item for sublist in missing_species for item in sublist]
missing_species = [i for n, i in enumerate(missing_species) if i not in missing_species[:n]]
logKnan_alert = False
if objdb == None:
objdb = 'thermo_%sbars' % int(P[0])
# timestr = '.' + time.strftime("%d%b%y_%H%M")
fout = open(os.path.join(os.path.abspath("."),'output', 'Pflotran', objdb + '.dat'), 'w+') # + timestr
if np.ndim(T) == 0 | np.ndim(P) == 0:
T = np.ravel(T)
P = np.ravel(P)
if Dielec_method.upper() == 'DEW':
water = ZhangDuan(T = T, P = P)
else:
water = iapws95(T = T, P = P)
rho, dGH2O = water.rho, water.G
#% Calculation for debye huckel and bdot and water properties
E = water_dielec(T = T, P = P, Dielec_method = Dielec_method).E
rhoEG = {'rho': rho, 'E': E, 'dGH2O': dGH2O}
# Calculate the rho E G for density extrapolation method here so we have it below
rhoEGextrap = {}
if any(rhoEG['rho'] < 350):
subBornptrs = rhoEG['rho'] < 350
for i, j in enumerate(zip(T[subBornptrs], P[subBornptrs])):
rhoextrap = np.linspace(350, 550, 3)
Pextrap = iapws95(T = j[0], rho = rhoextrap).P if Dielec_method.upper() != 'DEW' else ZhangDuan(T = j[0], rho = rhoextrap).P
Textrap = j[0]*np.ones(np.size(Pextrap))
dGH2O = iapws95(T = Textrap, P = Pextrap).G if Dielec_method.upper() != 'DEW' else ZhangDuan(T = Textrap, P = Pextrap).G
E = water_dielec(T = Textrap, P = Pextrap, Dielec_method = Dielec_method).E
rhoextrap = np.around(rhoextrap, 3)
rhoEGextrap['%d_%d' % (j[0], j[1])]= {'rho': rhoextrap,'E': E, 'dGH2O': dGH2O,
'Textrap': Textrap, 'Pextrap': Pextrap}
fout.writelines("'temperatures(degC) points' %s" % len(T))
for i in range(len(T)):
fout.writelines( " %6.1f" % T[i])
fout.writelines( "\n")
fout.write('!:database is isobaric, at %d bars\n' % P[0])
fout.write('!:basis_species a0 valence formula weight [g]\n')
if sourceformat.upper() == 'EQ36':
f_ionsize = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'ion_size.txt'), 'r')
Rd = f_ionsize.readlines()
Rd = Rd[1:]
ion_sizedic = {Rd[x].split()[0] : Rd[x].split()[1] for x in range(len(Rd))}
f_ionsize.close()
#% Basis reactions
for j in specielist[1]:
if j not in missing_species:
if sourceformat.upper() == 'GWB':
charge = chargedic[j].lstrip().split()[1]
ionsize = chargedic[j].lstrip().split()[4]
MW = MWdic[j]
elif sourceformat.upper() == 'EQ36':
charge = chargedic[j].split()[-1]
if j == 'O2(g)':
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info['O2(g)_b'] if x.strip('* ').startswith('DHazero')]
else:
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info[j] if x.strip('* ').startswith('DHazero')]
if any(ionsize) and any([MWdic[j]]):
ionsize, MW = ionsize[0], MWdic[j]
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(g)')
ionsize = 3 if j.endswith('(aq)') else float(ion_sizedic[j]) if j in ion_sizedic.keys() else 500 #
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
info = "'%s'" % j + ' ' + str(ionsize) + ' ' + charge +' ' + str(MW)
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 0 0 0\n")
fout.write("!:species_name num (n_i A_i, i=1,num) log K (1:8) a0 valence formula weight [g]\n")
#% Redox and Aqueous reactions
for j in specielist[2] + specielist[3]:
if sourceformat.upper() == 'GWB':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
charge = chargedic[j].lstrip().split()[1]
ionsize = chargedic[j].lstrip().split()[4]
MW, source_rxns = MWdic[j], sourcedic[j][2:]
elif sourceformat.upper() == 'EQ36':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
charge = chargedic[j].split()[-1]
source_rxns = sourcedic[j][4:]
ionsize = [float(re.sub(r'[^0123456789\.]', '', x)) for x in block_info[j] if x.strip('* ').startswith('DHazero')]
if any(ionsize) and any([MWdic[j]]):
ionsize, MW = ionsize[0], MWdic[j]
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)')
ionsize = 3 if j.endswith('(aq)') else float(ion_sizedic[j]) if j in ion_sizedic.keys() else 500
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([i for i in rxnlst if i not in missing_species])==len(rxnlst)):
name, species = j, sourcedic[j][1]
Rxn = [row if i%2==0 else "'%s'" %row for i,row in enumerate(source_rxns)]
Rxn = ' '.join(Rxn)
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit)
if densityextrap.lower() == 'yes':
if all(logK.nonsubBornptrs) == True: # if all densities are >= 350
logKnan_alert = False # turn off the prompts for using Density extrapolation
else:
logKnan_alert = True
else:
logKnan_alert = False # turn off the prompts for using Density extrapolation
logK = logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
info = "'%s'" % name + ' ' + str(species) + ' ' + Rxn + ' ' + list_logk + ' ' + str(ionsize) +\
' ' + charge +' ' + str(MW)
fout.writelines('%s\n' % info)
else:
continue
fout.writelines("'null' 1 0. '0' 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n")
if logKnan_alert == True:
warnings.warn('Some temperature and pressure points are out of aqueous species HKF eqns regions of applicability, hence, density extrapolation has been applied')
fout.write("!:gas_name molar_vol num (n_i A_i, i=1,num) log K (1:8) formula weight [g]\n")
#% Gas reactions
if dataset_format not in ['mar21', 'jul22', 'jan26']:
speclst = specielist[6]
else:
speclst = specielist[7]
#print(speclst)
for j in speclst:
if sourceformat.upper() == 'GWB':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
source_rxns = sourcedic[j][2:]
elif sourceformat.upper() == 'EQ36':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
source_rxns = sourcedic[j][4:]
#print(j)
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species]) == len(rxnlst)):
name, species = j, sourcedic[j][1]
if MWdic[j] != [] or MWdic[j] != '':
MW = MWdic[j]
else:
formula = j.rstrip('(g)')
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
Rxn = [row if i%2 == 0 else "'%s'" %row for i,row in enumerate(source_rxns)]
Rxn = ' '.join(Rxn)
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
info = "'%s'" % name + ' ' + '0.000' + ' ' + str(species) + ' ' + Rxn +\
' ' + list_logk + ' ' + str(MW)
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 0. 1 1. '0' 0. 0. 0. 0. 0. 0. 0. 0. 0.\n")
fout.write("!:mineral_name molar_vol num (n_i A_i, i=1,num) log K (1:8) formula weight [g]\n")
#% Mineral reactions
solid_solution = 'no' if solid_solution is None else solid_solution
clay_thermo = 'no' if clay_thermo is None else clay_thermo
if nCa_cpx is None:
nCa = 0
else:
nCa = nCa_cpx
if solid_solution.lower() == 'yes':
solidsolution_no = 11
fnlist = ['plagio', 'olivine', 'pyroxene', 'cpx'] if nCa > 0 else ['plagio', 'olivine', 'pyroxene']
for fn in fnlist:
for nX in np.round(np.linspace(1, 0, solidsolution_no), 1):
if fn != 'cpx':
ss = calcRxnlogK(X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
else:
ss = calcRxnlogK(cpx_Ca = nCa, X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, dataset = 'Pflotran')
# clay minerals
if clay_thermo.lower() == 'yes':
fclay = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'clay_elements.dat'), 'r')
Rd = fclay.readlines()
Rd = [j.replace('-','_').strip('\n') for j in Rd]
for i in range(len(Rd)):
ss = calcRxnlogK(T = T, P = P, Specie = 'Clay', elem = Rd[i].split(','),
dbaccessdic = dbaccessdic, ThermoInUnit = self.ThermoInUnit, rhoEG = rhoEG,
rhoEGextrap = rhoEGextrap, densityextrap = densityextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, dataset = 'Pflotran')
if sourceformat.upper() == 'GWB':
speclst = specielist[5]
elif sourceformat.upper() == 'EQ36':
speclst = specielist[4]+specielist[5]
for j in speclst:
if sourceformat.upper() == 'GWB':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
source_rxns = sourcedic[j][2:]
elif sourceformat.upper() == 'EQ36':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
source_rxns = sourcedic[j][4:]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
if solid_solution.lower() == 'yes' and j in ['Anorthite', 'Albite', 'Forsterite',
'Fayalite', 'Enstatite', 'Ferrosilite']:
continue
elif solid_solution.lower() == 'yes' and (nCa == 1) and j in ['Diopside', 'Hedenbergite']:
continue
elif clay_thermo.lower() == 'yes' and j in [ Rd[h].split(',')[0] for h in range(len(Rd))]:
continue
else:
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
MW, MV, species = MWdic[j], dbaccessdic[k][5], sourcedic[j][1]
Rxn = [row if i%2 == 0 else "'%s'" %row for i, row in enumerate(source_rxns)]
Rxn = ' '.join(Rxn)
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'minerals',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
info = "'%s'" % j + ' ' + "%7.3f" % (MV) + ' ' + str(species) + ' ' + \
Rxn + ' ' + list_logk + ' ' + "%8.4f" % (MW)
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 1 0. '0' 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.\n")
fout.write("!:oxide_name molar_vol num (n_i A_i, i=1,num) log K (1:8) formula weight [g]\n")
#% Oxides reactions
if sourceformat.upper() == 'GWB':
if dataset_format not in ['mar21', 'jul22', 'jan26']:
speclst = specielist[7]
else:
speclst = specielist[8]
for j in speclst:
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]] # remove formula and specie number
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ] # remove all coefficients
if (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
MW, species = MWdic[j], sourcedic[j][1]
Rxn = [row if i%2 == 0 else "'%s'" %row for i,row in enumerate(sourcedic[j][2:])]
Rxn = ' '.join(Rxn)
list_logk = ' '.join(['500.00']*len(T))
info = "'%s'" % j + ' ' + '0.000' + ' ' + str(species) + ' ' + Rxn +\
' ' + list_logk + ' ' + str(MW)
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 0. 1 1. '0' 0. 0. 0. 0. 0. 0. 0. 0. 0.\n")
#% close all files
fout.close()
if clay_thermo.lower() == 'yes':
fclay.close()
return print('Success, your new Pflotran database is ready for download')
[docs]
def write_ToughReactdb(self, T, P ):
r"""
This function writes the new ToughReact database into a new folder called "output" \n
Parameters
----------
T : temperature [°C] \n
P : pressure [bar] \n
Returns
-------
Outputs the new database to an ASCII file with filename described in 'objdb'. \n
Usage
-------
Example:
(1) General format with default dielectric constant and CO2 activity model and exclusions
of solid solutions \n
write_ToughReactdb(T, P, dbaccess = 'location', sourcedb = 'location',
objdb = 'location', sourceformat = 'GWB') \n
(2) Inclusion of solid solutions and clay thermo and exclusion of solid solution of clinopyroxene \n
write_ToughReactdb(T, P, solid_solution = 'Yes', clay_thermo = 'Yes', dbaccess = 'location',
sourcedb = 'location', objdb = 'location', sourceformat = 'GWB') \n
(3) Inclusion of all solid solutions and clay thermo with \\emph{'Duan_Sun'} CO2 activity model and 'FGL97'
dielectric constant calculation \n
write_ToughReactdb(T, P, nCa, solid_solution = 'Yes', clay_thermo = 'Yes', dbaccess = 'location',
sourcedb = 'location', objdb = 'location', co2actmodel = 'Duan_Sun',
Dielec_method = 'FGL97', sourceformat = 'GWB') \n
"""
nCa_cpx = self.cpx_Ca;
solid_solution = self.solid_solution; clay_thermo = self.clay_thermo
objdb = self.objdb; Dielec_method = self.Dielec_method
sourceformat = self.sourceformat; heatcap_method = self.heatcap_method;
densityextrap = self.densityextrap
dbaccessdic, sourcedic, specielist = self.dbr.dbaccessdic, self.dbr.sourcedic, self.dbr.specielist
act_param, MWdic, chargedic = self.dbr.act_param, self.dbr.MWdic, self.dbr.chargedic
# from Table 3 of Helgeson, H.C., Kirkham, D.H., Flowers, G.C., 1981. Theoretical prediction of the
# thermodynamic behavior of aqueous electrolytes at high pressures and temperatures: IV.
# Calculation of activity coefficients, osmotic coefficients, and apparent molal and standard and relative partial molal properties to 600oC. Am. J. Sci
rej = {'H+' : 3.08, 'Li+' : 1.64, 'Na+': 1.910, 'K+' : 2.27, 'Rb+' : 2.41, 'Cs+' : 2.61,
'NH4+' : 2.31, 'Ag+' : 2.20, 'Au+' : 2.31, 'Cu+' : 1.90, 'Mg++' : 2.54, 'Sr++' : 3.0,
'Ca++' : 2.87, 'Ba++' : 3.22, 'Pb++' : 3.08, 'Zn++' : 2.62, 'Cu++': 2.60, 'Cd++' : 2.85,
'Hg++' : 2.98, 'Fe++' : 2.62, 'Mn++' : 2.68, 'Fe+++' : 3.46, 'Al+++' : 3.33, 'Au+++' : 3.72,
'La+++' : 3.96, 'Gd+++' : 3.79, 'In+++' : 3.63, 'Ga+++' : 3.44, 'Tl+++' : 3.77, 'F-' : 1.33,
'Cl-' : 1.810, 'Br-' : 1.96, 'I-' : 2.20, 'OH-' : 1.40, 'HS-' : 1.84, 'NO3-' : 2.81,
'HCO3-' : 2.10, 'HSO4-' : 2.37, 'ClO4-' : 3.59, 'ReO4--' : 4.230, 'SO4--' : 3.15,
'CO3--' : 2.810}
if os.path.exists(os.path.join(os.getcwd(), 'output/ToughReact')) == False:
os.makedirs(os.path.join(os.getcwd(), 'output/ToughReact'))
periodic_table = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
'PeriodicTableJSON.json'), encoding='utf8')
data = json.load(periodic_table)
Element = {data['elements'][x]['symbol'] : pd.DataFrame([data['elements'][x]['name'],
data['elements'][x]['atomic_mass']],
index = ['name', 'mass']).T
for x in range(len(data['elements']))}
periodic_table.close()
missing_species = []
elemspeclist = [ symbol for x in specielist[0] for symbol, item in Element.items()
if item.name[0][:5] == x[:5] ] if sourceformat.upper() == 'GWB' else specielist[0]
form_del = [1] if sourceformat.upper() == 'GWB' else [1, 3, 4]
all_species_source = [[i]+k for i, k in sourcedic.items() if i not in (['eh', 'e-', 'H2O']) ]
all_species_source = [[k for j, k in enumerate(all_species_source[i])
if (j not in form_del and k not in elemspeclist and str(k).strip('0123456789.- ') != '') ]
if (i <= len(specielist[0]))
else [k for j, k in enumerate(all_species_source[i])
if (j not in form_del and str(k).strip('0123456789.- ') != '') ]
for i in range(len(all_species_source)) ]
for num in range(len(all_species_source)): #
if num < len(all_species_source):
lst = [v for v in all_species_source[num] if v not in (['eh', 'e-', 'H2O']) ]
bool_miss = [x.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
not in dbaccessdic.keys() for x in lst ]
if any(bool_miss):
sublist = [i for (i, v) in zip(lst, bool_miss) if v ]
if lst[0] not in sublist:
missing_species.append([lst[0]] + sublist)
else:
missing_species.append(sublist)
missingfile = open(os.path.join(os.path.abspath("."),'output', 'ToughReact', 'spxNotFound.txt'), 'w')
for line in missing_species:
if len(line) > 0:
missingfile.writelines(line[0])
missingfile.writelines('\n')
for i in range(len(line)):
missingfile.writelines(' %s' % line[i])
missingfile.writelines('\n')
missingfile.close()
missing_species = [item for sublist in missing_species for item in sublist]
missing_species = [i for n, i in enumerate(missing_species) if i not in missing_species[:n]]
if objdb == None:
objdb = './thermo%sbars' % int(P[0])
else:
objdb = objdb
# timestr = '.' + time.strftime("%d%b%Y_%H%M")
fout = open(os.path.join(os.path.abspath("."),'output', 'ToughReact', objdb + '.dat'), 'w+') # + timestr
Dielec_method = 'JN91' if Dielec_method is None else Dielec_method
heatcap_method = 'SUPCRT' if heatcap_method is None else heatcap_method
if Dielec_method.upper() == 'DEW':
water = ZhangDuan(T = T, P = P)
else:
water = iapws95(T = T, P = P)
rho, dGH2O = water.rho, water.G
#% Calculation for debye huckel and bdot and water properties
waterdielc = water_dielec(T = T, P = P, Dielec_method = Dielec_method)
E, Adh = waterdielc.E, waterdielc.Ah
rhoEG = {'rho': rho, 'E': E, 'dGH2O': dGH2O}
# Calculate the rho E G for density extrapolation method here so we have it below
rhoEGextrap = {}
if any(rhoEG['rho'] < 350):
subBornptrs = rhoEG['rho'] < 350
for i, j in enumerate(zip(T[subBornptrs], P[subBornptrs])):
rhoextrap = np.linspace(350, 550, 3)
Pextrap = iapws95(T = j[0], rho = rhoextrap).P if Dielec_method.upper() != 'DEW' else ZhangDuan(T = j[0], rho = rhoextrap).P
Textrap = j[0]*np.ones(np.size(Pextrap))
dGH2O = iapws95(T = Textrap, P = Pextrap).G if Dielec_method.upper() != 'DEW' else ZhangDuan(T = Textrap, P = Pextrap).G
E = water_dielec(T = Textrap, P = Pextrap, Dielec_method = Dielec_method).E
rhoextrap = np.around(rhoextrap, 3)
rhoEGextrap['%d_%d' % (j[0], j[1])]= {'rho': rhoextrap,'E': E, 'dGH2O': dGH2O,
'Textrap': Textrap, 'Pextrap': Pextrap}
fout.writelines('The file format of this thermodynamic database is suitable for TOUGHREACT\n')
fout.writelines('Generated by pyGeochemCalc.2021, ' + time.ctime() + '\n')
fout.writelines('\n!end-of-header Do not remove this record!\n')
fout.writelines("'temperature points' %6s" % len(T))
for i in range(len(T)):
fout.writelines( " %6.1f" % T[i])
fout.writelines( "\n")
logKfunc = lambda TK, *x: x[0]*np.log(TK) + x[1] + x[2]*TK + x[3]*TK**(-1) + x[4]*TK**(-2)
TK = convert_temperature( T, Out_Unit = 'K' )
x0 = [-3.19605e1, 2.06576e2, 3.73497e-2, -9.01862e3, 6.0111e5]
#% Basis reactions
for j in specielist[1]:
if j not in missing_species:
if any([MWdic[j]]):
MW = MWdic[j]
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(g)')
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
name = j.replace('++++', '+4') if j.endswith('++++',0) else j.replace('+++', '+3') if j.endswith('+++',0) else j.replace('++', '+2') if j.endswith('++',0) else j.replace('----', '-4') if j.endswith('----',0) else j.replace('---', '-3') if j.endswith('---',0) else j.replace('--', '-2') if j.endswith('--',0) else j
if sourceformat.upper() == 'GWB':
charge = float(chargedic[j].lstrip().split()[1])
elif sourceformat.upper() == 'EQ36':
charge = float(chargedic[j].split()[-1])
# ToughReact implementation
if j in rej.keys():
ionrad = rej[j]
elif charge == -1 :
ionrad = rej['Cl-']
elif charge == -2 :
ionrad = round(np.mean([rej['SO4--'], rej['CO3--']]))
elif charge <= -3 :
ionrad = charge*4.2/3
elif charge == 1 :
ionrad = rej['NH4+']
elif charge == 2 :
ionrad = np.mean([rej[j] for j in rej.keys()
if j.endswith('++',0) and not j.endswith('+++',0)])
elif charge == 3 :
ionrad = np.mean([ rej[j] for j in rej.keys() if j.endswith('+++',0)])
elif charge == 4 :
ionrad = 4.5
elif charge > 3 :
ionrad = charge*4.5/4
else:
ionrad = 0
info = "%-34s" % name + '%5.2f' % ionrad + ' %5.2f' % charge + ' %8.3f' % MW
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 0. 0. 0.\n\n")
fout.write("#****************************** \n")
fout.write("#* Aqueous Species \n")
fout.write("#****************************** \n\n")
#% Redox and Aqueous reactions
for j in specielist[2] + specielist[3]:
if sourceformat.upper() == 'GWB':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
charge = float(chargedic[j].lstrip().split()[1])
source_rxns = sourcedic[j][2:]
elif sourceformat.upper() == 'EQ36':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
charge = float(chargedic[j].split()[-1])
source_rxns = sourcedic[j][4:]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([i for i in rxnlst if i not in missing_species])==len(rxnlst)):
name = j.replace('++++', '+4') if j.endswith('++++',0) else j.replace('+++', '+3') if j.endswith('+++',0) else j.replace('++', '+2') if j.endswith('++',0) else j.replace('----', '-4') if j.endswith('----',0) else j.replace('---', '-3') if j.endswith('---',0) else j.replace('--', '-2') if j.endswith('--',0) else j
species = sourcedic[j][1]
if any([MWdic[j]]):
MW = MWdic[j]
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(g)')
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
# ToughReact implementation
if j in rej.keys():
ionrad = rej[j]
elif charge == -1 :
ionrad = rej['Cl-']
elif charge == -2 :
ionrad = round(np.mean([rej['SO4--'], rej['CO3--']]))
elif charge <= -3 :
ionrad = charge*4.2/3
elif charge == 1 :
ionrad = rej['NH4+']
elif charge == 2 :
ionrad = np.mean([rej[j] for j in rej.keys()
if j.endswith('++',0) and not j.endswith('+++',0)])
elif charge == 3 :
ionrad = np.mean([ rej[j] for j in rej.keys() if j.endswith('+++',0)])
elif charge == 4 :
ionrad = 4.5
elif charge > 3 :
ionrad = charge*4.5/4
else:
ionrad = 0
Rxn = [row if i%2==0 else "'%s'" % (row.replace('++++', '+4') if row.endswith('++++',0)
else row.replace('+++', '+3') if row.endswith('+++',0)
else row.replace('++', '+2') if row.endswith('++',0)
else row.replace('----', '-4') if row.endswith('----',0)
else row.replace('---', '-3') if row.endswith('---',0)
else row.replace('--', '-2') if row.endswith('--',0)
else row) for i,row in enumerate(source_rxns)]
Rxn = ' '.join(Rxn)
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'aqueous',
rhoEGextrap = rhoEGextrap, ThermoInUnit = self.ThermoInUnit)
if densityextrap.lower() == 'yes':
if all(logK.nonsubBornptrs) == True: # if all densities are >= 350
logKnan_alert = False # turn off the prompts for using Density extrapolation
else:
logKnan_alert = True
else:
logKnan_alert = False # turn off the prompts for using Density extrapolation
logK = logK.logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
logKcorr = curve_fit(logKfunc, TK[logK != 500].ravel(), logK[logK != 500].ravel(), p0 = x0, maxfev = 1000000)[0]
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
list_logKcorr = ' '.join(str("%.5e" % e) for e in list(logKcorr))
info = "%-32s" % name + ' %7.3f' % MW + ' %3.1f' % ionrad + ' %5.2f' % charge +\
' ' + str(species) + ' ' + Rxn
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
info = '%-35s' % name + ' ' + list_logk
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
info = '%-35s' % name + ' ' + list_logKcorr.replace('e','E')
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
else:
continue
fout.writelines("'null' 0. 0. 0. 0 \n\n")
fout.write("#****************************** \n")
fout.write("#* Minerals \n")
fout.write("#****************************** \n\n")
if logKnan_alert == True:
warnings.warn('Some temperature and pressure points are out of aqueous species HKF eqns regions of applicability, hence, density extrapolation has been applied')
#% Mineral reactions
solid_solution = 'no' if solid_solution is None else solid_solution
clay_thermo = 'no' if clay_thermo is None else clay_thermo
if nCa_cpx == 0:
nCa = 0
else:
nCa = nCa_cpx
if solid_solution.lower() == 'yes':
solidsolution_no = 11
fnlist = ['plagio', 'olivine', 'pyroxene', 'cpx'] if nCa > 0 else ['plagio', 'olivine', 'pyroxene']
for fn in fnlist:
for nX in np.round(np.linspace(1, 0, solidsolution_no), 1):
if fn != 'cpx':
ss = calcRxnlogK(X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
else:
ss = calcRxnlogK(cpx_Ca = nCa, X = nX, T = T, P = P, Dielec_method = Dielec_method, rhoEG = rhoEG,
dbaccessdic = dbaccessdic, Specie = fn, densityextrap = densityextrap,
ThermoInUnit = self.ThermoInUnit, rhoEGextrap = rhoEGextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, dataset = 'ToughReact')
# clay minerals
if clay_thermo.lower() == 'yes':
fclay = open(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'clay_elements.dat'), 'r')
Rd = fclay.readlines()
Rd = [j.replace('-','_').strip('\n') for j in Rd]
for i in range(len(Rd)):
ss = calcRxnlogK(T = T, P = P, Specie = 'Clay', elem = Rd[i].split(','),
dbaccessdic = dbaccessdic, ThermoInUnit = self.ThermoInUnit, rhoEG = rhoEG, #group = layering,
rhoEGextrap = rhoEGextrap, densityextrap = densityextrap)
logK, Rxn = ss.logK, ss.Rxn
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
outputfmt(fout, logK, Rxn, TK, dataset = 'ToughReact')
if sourceformat.upper() == 'GWB':
speclst = specielist[5]
elif sourceformat.upper() == 'EQ36':
speclst = specielist[4]+specielist[5]
for j in speclst:
if sourceformat.upper() == 'GWB':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
source_rxns = sourcedic[j][2:]
elif sourceformat.upper() == 'EQ36':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
source_rxns = sourcedic[j][4:]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
if solid_solution.lower() == 'yes' and j in ['Anorthite', 'Albite', 'Forsterite',
'Fayalite', 'Enstatite', 'Ferrosilite']:
continue
elif solid_solution.lower() == 'yes' and (nCa == 1) and j in ['Diopside', 'Hedenbergite']:
continue
elif clay_thermo.lower() == 'yes' and j in [ Rd[h].split(',')[0] for h in range(len(Rd))]:
continue
else:
name = j
k = j.replace('(CH3COO)', '(Ac)').replace('CH3COO', '(Ac)')
MV, species = dbaccessdic[k][5], sourcedic[j][1]
if any([MWdic[j]]):
MW = MWdic[j]
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(g)(am)')
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
Rxn = [row if i%2 == 0 else "'%s'" % (row.replace('++++', '+4') if row.endswith('++++',0)
else row.replace('+++', '+3') if row.endswith('+++',0)
else row.replace('++', '+2') if row.endswith('++',0)
else row.replace('----', '-4') if row.endswith('----',0)
else row.replace('---', '-3') if row.endswith('---',0)
else row.replace('--', '-2') if row.endswith('--',0)
else row) for i, row in enumerate(source_rxns)]
Rxn = ' '.join(Rxn)
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'minerals',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
list_logKcorr = ' '.join(str("%.5e" % e) for e in list(logKcorr))
info = "%-32s" % name + "%8.3f" % MW + " %7.2f" % MV +\
' ' + str(species) + ' ' + Rxn
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
info = '%-35s' % name + ' ' + list_logk
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
info = '%-35s' % name + ' ' + list_logKcorr.replace('e','E')
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 0. 0. 0 ! end of mineral\n\n")
fout.write("#****************************** \n")
fout.write("#* Gases \n")
fout.write("#****************************** \n\n")
#% Gas reactions
for j in specielist[6]:
if sourceformat.upper() == 'GWB':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1]]
source_rxns = sourcedic[j][2:]
elif sourceformat.upper() == 'EQ36':
rxnlst = [b for a, b in enumerate(sourcedic[j]) if a not in [0, 1, 2, 3]]
source_rxns = sourcedic[j][4:]
rxnlst = [v for x, v in enumerate(rxnlst) if x % 2 != 0 ]
if (j not in missing_species) and (len([k for k in rxnlst if k not in missing_species])==len(rxnlst)):
name, species = j, sourcedic[j][1]
if any([MWdic[j]]):
MW = MWdic[j]
else:
formula = j if sourcedic[j][0] == '' else sourcedic[j][0]
formula = formula.rstrip('(aq)(g)')
MW = calc_elem_count_molewt(formula, Elementdic = Element)[-1]
Rxn = [row if i%2 == 0 else "'%s'" %(row.replace('++++', '+4') if row.endswith('++++',0)
else row.replace('+++', '+3') if row.endswith('+++',0)
else row.replace('++', '+2') if row.endswith('++',0)
else row.replace('----', '-4') if row.endswith('----',0)
else row.replace('---', '-3') if row.endswith('---',0)
else row.replace('--', '-2') if row.endswith('--',0)
else row) for i,row in enumerate(source_rxns)]
Rxn = ' '.join(Rxn)
logK = calcRxnlogK( T = T, P = P, Specie = j, dbaccessdic = dbaccessdic, sourcedic = sourcedic,
specielist = specielist, Dielec_method = Dielec_method, rhoEG = rhoEG,
sourceformat = sourceformat, densityextrap = densityextrap, Specie_class = 'gases',
heatcap_method = heatcap_method, rhoEGextrap = rhoEGextrap).logK
logK = np.where(np.isnan(logK), 500, logK) # set abitrary 500 to nan values
logKcorr = curve_fit(logKfunc, TK[logK!=500].ravel(), logK[logK!=500].ravel(), p0 = x0, maxfev = 1000000)[0]
list_logk = ' '.join(str("%9.4f" % e) for e in list(logK))
list_logKcorr = ' '.join(str("%.5e" % e) for e in list(logKcorr))
info = "%-32s" % name + "%8.3f" % MW + ' ' + '0.100E-09' +\
' ' + str(species) + ' ' + Rxn
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
info = '%-35s' % name + ' ' + list_logk
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
info = '%-35s' % name + ' ' + list_logKcorr.replace('e','E')
info = "'%s'" % info[:len(info.split()[0])] + info[len(info.split()[0]) + 2:]
fout.writelines('%s\n' % info)
else:
continue
fout.write("'null' 0. 0. 0 ! end of gas\n\n")
fout.write("#****************************** \n")
fout.write("#* Surface complexes \n")
fout.write("#****************************** \n\n")
fout.write("'null' 0. 0 ! surface complex\n\n")
#% Pitzer database
if act_param['activity_model'] == 'h-m-w' and sourceformat.lower() == 'eq36':
fout.write("StartPitzerParams !this is a needed keyword!!!\n\n")
fout.write("#************************************************************\n")
delimiters = "/", "\\"
patterns = '|'.join('(?<={})'.format(re.escape(delim)) for delim in delimiters)
fout.write("#* Pitzer ion interaction parameters from EQ3/6 %s\n" % re.split(patterns, self.sourcedb)[-1])
fout.write("#************************************************************\n")
fout.write("#!!! Note: 'Miscellaneous' below is a needed flag !!!!\n\n")
fout.write("+--------------------------------------------------------------------\n")
fout.write("Miscellaneous parameters\n")
fout.write("+--------------------------------------------------------------------\n")
fout.write("Temperature limits (degC)\n")
fout.writelines(' %9.4f %9.4f\n' % (T[0], T[-1]))
fout.write("temperatures\n")
for i in range(len(T)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % T[i-1])
else:
fout.writelines( " %9.4f" % T[i-1])
if (i % 4 == 0) | (i == len(T)):
fout.writelines( "\n")
fout.write("debye huckel aphi \n")
Aphi = Adh*np.log(10)/3
for i in range(len(Aphi)):
i = i + 1
if (i == 1) | (i == 5):
fout.writelines( " %9.4f" % Aphi[i-1])
else:
fout.writelines( " %9.4f" % Aphi[i-1])
if (i % 4 == 0) | (i == len(Aphi)):
fout.writelines( "\n")
fout.write("\n+--------------------------------------------------------------------\n")
fout.write("ca combinations: beta(n)(ca) and Cphi(ca) [optional: alpha(n)(ca)]\n")
fout.write("+--------------------------------------------------------------------\n")
for k in act_param['alpha_beta'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
lst = ['alpha1', 'alpha2', 'beta0', 'beta1', 'beta2', 'cphi']
app_lst = ['alpha(1)', 'alpha(2)', 'beta(0)', 'beta(1)', 'beta(2)', 'Cphi']
for l, order in enumerate(lst):
if l < 2:
fout.writelines(' %-6s = %s \n' % (app_lst[l], act_param[order][k]))
else:
fout.writelines(' %-6s: \n' % app_lst[l])
if type(act_param[order][k]) == float:
fout.writelines(' a1 = %s \n' % act_param[order][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param[order][k][0])
fout.writelines(' a2 = %s \n' % act_param[order][k][1])
fout.writelines(' a3 = %s \n' % act_param[order][k][2])
fout.writelines(' a4 = %s \n' % act_param[order][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("cc' and aa' combinations: theta(cc') and theta(aa')\n")
fout.write("+--------------------------------------------------------------------\n")
for k in act_param['theta'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'theta')
if type(act_param['theta'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['theta'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['theta'][k][0])
fout.writelines(' a2 = %s \n' % act_param['theta'][k][1])
fout.writelines(' a3 = %s \n' % act_param['theta'][k][2])
fout.writelines(' a4 = %s \n' % act_param['theta'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("nc and na combinations: lambda(nc) and lambda(na)\n")
fout.write("+--------------------------------------------------------------------\n")
if act_param['lambda'].keys(): # checks if dictionary is not empty
ions_exmpt = [j for j, k in enumerate(act_param['lambda'].keys()) if len(k.rstrip('\n').split()) <= 1][0]
ions_exmpt = list(act_param['lambda'].keys())[ions_exmpt:]
for k in act_param['lambda'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]) and k not in ions_exmpt:
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'lambda')
if type(act_param['lambda'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['lambda'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['lambda'][k][0])
fout.writelines(' a2 = %s \n' % act_param['lambda'][k][1])
fout.writelines(' a3 = %s \n' % act_param['lambda'][k][2])
fout.writelines(' a4 = %s \n' % act_param['lambda'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("nn combinations: lambda(nn) and mu(nnn) \n")
fout.write("+--------------------------------------------------------------------\n")
if act_param['mu'].keys(): # checks if dictionary is empty
ions_exmpt = [j for j, k in enumerate(act_param['mu'].keys()) if len(k.rstrip('\n').split()) <= 1][0]
ions_exmpt = list(act_param['mu'].keys())[ions_exmpt:]
for k in act_param['mu'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
if len(ks) <= 1:
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'lambda')
if type(act_param['lambda'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['lambda'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['lambda'][k][0])
fout.writelines(' a2 = %s \n' % act_param['lambda'][k][1])
fout.writelines(' a3 = %s \n' % act_param['lambda'][k][2])
fout.writelines(' a4 = %s \n' % act_param['lambda'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines(' %-6s: \n' % 'mu')
if type(act_param['mu'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['mu'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['mu'][k][0])
fout.writelines(' a2 = %s \n' % act_param['mu'][k][1])
fout.writelines(' a3 = %s \n' % act_param['mu'][k][2])
fout.writelines(' a4 = %s \n' % act_param['mu'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("nn' combinations: lambda(nn') \n")
fout.write("+--------------------------------------------------------------------\n")
for k in [j for j in ions_exmpt if j not in act_param['mu'].keys()]:
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'lambda')
if type(act_param['lambda'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['lambda'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['lambda'][k][0])
fout.writelines(' a2 = %s \n' % act_param['lambda'][k][1])
fout.writelines(' a3 = %s \n' % act_param['lambda'][k][2])
fout.writelines(' a4 = %s \n' % act_param['lambda'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("cc'a and aa'c combinations: psi(cc'a) and psi(aa'c) \n")
fout.write("+--------------------------------------------------------------------\n")
for k in act_param['psi'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'psi')
if type(act_param['psi'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['psi'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['psi'][k][0])
fout.writelines(' a2 = %s \n' % act_param['psi'][k][1])
fout.writelines(' a3 = %s \n' % act_param['psi'][k][2])
fout.writelines(' a4 = %s \n' % act_param['psi'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("nca combinations: zeta(nca) \n")
fout.write("+--------------------------------------------------------------------\n")
for k in act_param['zeta'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'zeta')
if type(act_param['zeta'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['zeta'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['zeta'][k][0])
fout.writelines(' a2 = %s \n' % act_param['zeta'][k][1])
fout.writelines(' a3 = %s \n' % act_param['zeta'][k][2])
fout.writelines(' a4 = %s \n' % act_param['zeta'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("nnn' combinations: mu(nnn') \n")
fout.write("+--------------------------------------------------------------------\n")
for k in act_param['mu'].keys():
if all([x not in missing_species for x in k.rstrip('\n').split()]):
ks = k.rstrip('\n').split()
if len(ks) > 1:
fout.writelines('%-8s\n' % ks[0]) if len(ks) == 1 else fout.writelines('%-24s %-24s\n' % (ks[0], ks[1])) if len(ks) == 2 else fout.writelines('%-8s %-8s %-8s\n' % (ks[0], ks[1], ks[2]))
fout.writelines(' %-6s: \n' % 'mu')
if type(act_param['mu'][k]) == float:
fout.writelines(' a1 = %s \n' % act_param['mu'][k])
fout.writelines(' a2 = 0. \n a3 = 0. \n a4 = 0. \n a5 = 0. \n a6 = 0. \n' )
else:
fout.writelines(' a1 = %s \n' % act_param['mu'][k][0])
fout.writelines(' a2 = %s \n' % act_param['mu'][k][1])
fout.writelines(' a3 = %s \n' % act_param['mu'][k][2])
fout.writelines(' a4 = %s \n' % act_param['mu'][k][3])
fout.writelines(' a5 = 0. \n a6 = 0. \n' )
fout.writelines('+---------------------------------------------------------------\n')
fout.write("'null' 0. 0 !this is needed here to end the DB\n")
#% close all files
fout.close()
if clay_thermo.lower() == 'yes':
fclay.close()
return print('Success, your new ToughReact database is ready for download')
[docs]
def main_function_name(module):
"""
Return the names of essential public functions and classes in the given module.
Filters out imported third-party names, stdlib helpers, internal constants,
and private utility functions so that only the user-facing API is listed.
"""
_exclude = {
# --- scipy imports ---
'splev', 'splrep', 'RBFInterpolator',
'lu_factor', 'lu_solve',
'fsolve', 'curve_fit', 'root_scalar', 'newton', 'brentq',
# --- numpy / pandas aliases ---
'np', 'pd',
# --- stdlib imports ---
'os', 're', 'json', 'math', 'time', 'warnings', 'inspect',
'itertools', 'textwrap', 'functools', 'timer',
# --- stdlib types brought in via from-imports ---
'Fraction', 'OrderedDict',
# --- internal data / constants ---
'IAPWS95_COEFFS', 'J_to_cal', 'MW', 'eps', 'theta',
# --- internal helper functions ---
'var_name', 'feval', 'read_specific_lines',
'roundup_tenth', 'roundup_hundredth',
'derivative', 'info',
'denormalize_phreeqc_species_charge',
'normalize_species_charges',
'normalize_phreeqc_species_charge',
'contains_missing_species',
'build_side_regex',
'calcRxnlogKonly',
'main_function_name',
# --- internal helper classes ---
'Dummy',
}
return [
i for i in dir(module)
if not i.startswith(('Delta', 'Psi', 'phir', 'phi0', '__'))
and i not in _exclude
]
# pipreqs --encoding=utf8 --debug . # creates requirements.txt