Modeling Brine Density vs Concentration Regression Lines with Phreeqc and Aquifer App - Tutorial

Complex geochemical simulations are entirely possible to be performed with Phreeqc coupled with Aquifer App and Python. Brines can be simulated at different concentrations to obtain relations that are input of other variable density flow models. In this case we have model one brine, sodium bicarbonate, with the REACTION keyword with moles values that range from 0.5 to 12 moles. Values of mass, volume, concentration, and density were processed in Python from the dataframes generated from Aquifer App.

Tutorial

Code

Phreeqc input files:

TITLE Brine 1: Dissolved sodium bicarbonate
SOLUTION 2  
        pH      7.0
        temp    25.0
REACTION 1
        NaHCO3		1.0
        0    0.5    1.0    2.0    4.0    6.0    8.0    10.0    12.0 moles  
END

Python code

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
# open solution description dataframe
descDf = pd.read_csv('solutionDescription.csv')
descDf.head()

Simulation Type Number Parameter Value
0 1 initial 1 pH 7.00000
1 1 initial 1 pe 4.00000
2 1 initial 1 Specific Conductance (µS/cm, 25°C) 0.00000
3 1 initial 1 Density (g/cm³) 0.99704
4 1 initial 1 Volume (L) 1.00297
# filter the density row and works only for the batch reactions and not for initial solution
densityDf = descDf[(descDf['Parameter']=='Density (g/cm³)')&(descDf['Type']=='batch')]
densityDf.head()

Simulation Type Number Parameter Value
18 1 batch 1 Density (g/cm³) 0.99704
33 1 batch 2 Density (g/cm³) 1.02698
49 1 batch 3 Density (g/cm³) 1.05628
65 1 batch 4 Density (g/cm³) 1.11283
81 1 batch 5 Density (g/cm³) 1.21767
# filter the volumne row and works only for the batch reactions and not for initial solution
volumeDf = descDf[(descDf['Parameter']=='Volume (L)')&(descDf['Type']=='batch')]
volumeDf.head()

Simulation Type Number Parameter Value
19 1 batch 1 Volume (L) 1.00297
34 1 batch 2 Volume (L) 1.01463
50 1 batch 3 Volume (L) 1.02625
66 1 batch 4 Volume (L) 1.04959
82 1 batch 5 Volume (L) 1.09721
# moles list
molAmount = [0.0, 0.5, 1.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0]
#Working for the brine = 

# brine data
index = 1
molarMass = 84.01 #g/mol

#filter the simulation for given index
brineDenDf = densityDf[densityDf['Simulation'] == index]
brineVolDf = volumeDf[volumeDf['Simulation'] == index]
#set batch reaction number as index
brineDenDf.index  = brineDenDf['Number']
brineVolDf.index  = brineVolDf['Number']
#order the batch in ascending order
brineDenDf = brineDenDf.sort_index(ascending=True)
brineVolDf = brineVolDf.sort_index(ascending=True)
#show the resulting dataframe
print(brineDenDf)
print(brineVolDf)
Simulation   Type  Number        Parameter    Value
Number                                                     
1                1  batch       1  Density (g/cm³)  0.99704
2                1  batch       2  Density (g/cm³)  1.02698
3                1  batch       3  Density (g/cm³)  1.05628
4                1  batch       4  Density (g/cm³)  1.11283
5                1  batch       5  Density (g/cm³)  1.21767
6                1  batch       6  Density (g/cm³)  1.31205
7                1  batch       7  Density (g/cm³)  1.39662
8                1  batch       8  Density (g/cm³)  1.47210
9                1  batch       9  Density (g/cm³)  1.53917
        Simulation   Type  Number   Parameter    Value
Number                                                
1                1  batch       1  Volume (L)  1.00297
2                1  batch       2  Volume (L)  1.01463
3                1  batch       3  Volume (L)  1.02625
4                1  batch       4  Volume (L)  1.04959
5                1  batch       5  Volume (L)  1.09721
6                1  batch       6  Volume (L)  1.14634
7                1  batch       7  Volume (L)  1.19722
8                1  batch       8  Volume (L)  1.24998
9                1  batch       9  Volume (L)  1.30467
#creation of another dataframe with for the concentration and densities
brineConDf = pd.DataFrame(index = brineDenDf.index)
brineConDf['mass(g)'] = np.asarray(molAmount) * molarMass
brineConDf['volume(L)'] = brineVolDf['Value']
brineConDf['concentration(g/L)'] = brineConDf['mass(g)'] / brineConDf['volume(L)']
brineConDf['density(g/cm³)'] = brineDenDf['Value']
print(brineConDf)
mass(g)  volume(L)  concentration(g/L)  density(g/cm³)
Number                                                         
1          0.000    1.00297            0.000000         0.99704
2         42.005    1.01463           41.399328         1.02698
3         84.010    1.02625           81.861145         1.05628
4        168.020    1.04959          160.081556         1.11283
5        336.040    1.09721          306.267715         1.21767
6        504.060    1.14634          439.712476         1.31205
7        672.080    1.19722          561.367167         1.39662
8        840.100    1.24998          672.090753         1.47210
9       1008.120    1.30467          772.701143         1.53917
# Perform linear regression
slope, intercept, r_value, p_value, std_err = stats.linregress(brineConDf['concentration(g/L)'], brineConDf['density(g/cm³)'])

# Create the regression line
regression_line = slope * brineConDf['concentration(g/L)'] + intercept

#Create figure 
fig = plt.figure(figsize=(12,6))

# Plot data points
plt.scatter(brineConDf['concentration(g/L)'], brineConDf['density(g/cm³)'], color='royalblue', label='Data points')

# Plot regression line
plt.plot(brineConDf['concentration(g/L)'], regression_line, color='teal', label='Regression line')

# Add regression formula and R-squared value to the plot
formula = f'y = {slope:.6f}x + {intercept:.2f}\n$R^2 = {r_value**2:.2f}$'
plt.text(300, 1, formula, fontsize=12, color='teal')

# Customize the plot
plt.xlabel('Concentration ((g/L)')
plt.ylabel('Density (g/cm³)')
plt.title('NaHCO3 Concentration vs Density')
plt.legend()
plt.grid(True)

# Show the plot
plt.show()

Input files

You can download the input data from this link.

Comment

Saul Montoya

Saul Montoya es Ingeniero Civil graduado de la Pontificia Universidad Católica del Perú en Lima con estudios de postgrado en Manejo e Ingeniería de Recursos Hídricos (Programa WAREM) de la Universidad de Stuttgart con mención en Ingeniería de Aguas Subterráneas y Hidroinformática.

 

Suscribe to our online newsletter

Subscribe for free newsletter, receive news, interesting facts and dates of our courses in water resources.