Cation exchange competition among Na+, K+ and Ca2+
Cation exchange competition among Na+, K+ and Ca2+#
Written by Svetlana Kyas (ETH Zurich) on Mar 10th, 2022.
Last revised on Mar 16, 2023 by Allan Leal.
Attention
Always make sure you are using the latest version of Reaktoro. Otherwise, some new features documented on this website will not work on your machine and you may receive unintuitive errors. Follow these update instructions to get the latest version of Reaktoro!
In this tutorial, we show how Reaktoro can be used to model ion exchange processes. We use the PHREEQC database phreeqc.dat
for this purpose. We assume that there is an ion exchanger X- on which the aqueous cations Na+, K+ and Ca2+ can bind and form the exchange phases NaX, KX and CaX2 respectively. We are interested in calculating the amounts of these phases as the concentrations of K+ and Ca2+ vary in the aqueous solution.
First, we define a chemical system representative of the process we want to model.
import numpy as np
from reaktoro import *
import pandas as pd
# Initialize the PHREEQC database
db = PhreeqcDatabase("phreeqc.dat")
# Define an aqueous phase
solution = AqueousPhase("H2O Na+ Cl- H+ OH- K+ Ca+2 Mg+2")
solution.set(ActivityModelPhreeqc(db))
# Define an ion exchange phase
exchange = IonExchangePhase("NaX KX CaX2")
exchange.set(ActivityModelIonExchangeGainesThomas())
# Create the chemical system
system = ChemicalSystem(db, solution, exchange)
As we are going to perform a sequence of chemical equilibrium calculations, we define an object of EquilibriumSolver
as well as IonExchangeProps
and AqueousProps
using the chemical system. These objects will be updated after every equilibrium calculation.
# Define the equilibrium solver
solver = EquilibriumSolver(system)
# Define exchange, aqueous, and chemical properties
exprops = IonExchangeProps(system)
aqprops = AqueousProps(system)
Note
Note that at the end of each equilibrium calculation, state.props()
can be used to access a ChemicalProps
object with the chemical properties of the system evaluated in the last iteration of the algorithm.
Next, we define the range of Ca2+ and K+ initial amounts as well as auxiliary pandas.DataFrame
to collect the results of the ion exchange simulations.
# Output dataframe
df = pd.DataFrame(columns=[
"amount_K",
"amount_Ca",
"amount_K_ion",
"amount_Na_ion",
"amount_Ca_ion",
"amount_KX",
"amount_NaX",
"amount_CaX2",
"pH"
])
# Sampling arrays storing the amounts of ions
steps = 21
mols_K = np.linspace(0.1, 0.0, num=steps)
mols_Ca = np.linspace(0.0, 0.5, num=steps)
We’ll now perform a sequence of equilibrium calculations by varying the initial amounts of ions Ca2+ and K+ ions (taken from the arrays mols_Ca
and mols_K
, respectively). At the end of each calculation, we’ll extract the following properties from the computed chemical state:
amount of K,
amount of Ca,
amount of K+,
amount of Na+,
amount of Ca2+,
amount of KX,
amount of NaX,
amount of CaX2,
pH.
for mol_K, mol_Ca in zip(mols_K, mols_Ca):
# Define initial chemical state
state = ChemicalState(system)
state.setTemperature(25, "celsius")
state.setPressure(1, "atm")
state.set("H2O" , 1.0 , "kg")
state.set("NaX" , 0.4 , "mol")
state.set("K+" , mol_K , "mol")
state.set("Ca+2", mol_Ca, "mol")
# Equilibrate the chemical state
res = solver.solve(state)
# Stop if the equilibration did not converge or failed
assert res.succeeded()
# Update exchange and aqueous properties
chemprops = state.props()
exprops.update(chemprops)
aqprops.update(chemprops)
# Update output arrays:
df.loc[len(df)] = [
chemprops.elementAmount('K'), # amount_K
chemprops.elementAmount('Ca'), # amount_Ca
state.speciesAmount('K+'), # amount_K+
state.speciesAmount('Na+'), # amount_Na+
state.speciesAmount('Ca+2'), # amount_Ca+2
state.speciesAmount('KX'), # amount_KX
state.speciesAmount('NaX'), # amount_NaX
state.speciesAmount('CaX2'), # amount_CaX2
aqprops.pH() # pH
]
The data collected in the previous calculations can be inspected by printing the pandas.DataFrame
object df
:
df
amount_K | amount_Ca | amount_K_ion | amount_Na_ion | amount_Ca_ion | amount_KX | amount_NaX | amount_CaX2 | pH | |
---|---|---|---|---|---|---|---|---|---|
0 | 0.1 | 2e-16 | 0.00579097 | 0.094209 | 1e-16 | 0.094209 | 0.305791 | 1e-16 | 6.98671 |
1 | 0.095 | 0.025 | 0.00873692 | 0.134683 | 0.000790228 | 0.0862631 | 0.265317 | 0.0242098 | 6.98361 |
2 | 0.09 | 0.05 | 0.011732 | 0.171596 | 0.00333599 | 0.078268 | 0.228404 | 0.046664 | 6.98079 |
3 | 0.085 | 0.075 | 0.0144722 | 0.202807 | 0.00886037 | 0.0705278 | 0.197193 | 0.0661396 | 6.97806 |
4 | 0.08 | 0.1 | 0.0166446 | 0.227346 | 0.0180048 | 0.0633554 | 0.172654 | 0.0819952 | 6.97538 |
5 | 0.075 | 0.125 | 0.0181137 | 0.245914 | 0.0304859 | 0.0568863 | 0.154086 | 0.0945141 | 6.97277 |
6 | 0.07 | 0.15 | 0.0189176 | 0.259953 | 0.0455647 | 0.0510824 | 0.140047 | 0.104435 | 6.97029 |
7 | 0.065 | 0.175 | 0.0191628 | 0.270776 | 0.0625305 | 0.0458372 | 0.129224 | 0.112469 | 6.96797 |
8 | 0.06 | 0.2 | 0.0189568 | 0.279337 | 0.0808531 | 0.0410432 | 0.120663 | 0.119147 | 6.96583 |
9 | 0.055 | 0.225 | 0.0183882 | 0.286279 | 0.100166 | 0.0366118 | 0.113721 | 0.124834 | 6.96384 |
10 | 0.05 | 0.25 | 0.0175251 | 0.292034 | 0.120221 | 0.0324749 | 0.107966 | 0.129779 | 6.96199 |
11 | 0.045 | 0.275 | 0.0164195 | 0.296895 | 0.140843 | 0.0285805 | 0.103105 | 0.134157 | 6.96028 |
12 | 0.04 | 0.3 | 0.0151111 | 0.301068 | 0.161911 | 0.0248889 | 0.0989323 | 0.138089 | 6.95869 |
13 | 0.035 | 0.325 | 0.0136307 | 0.304698 | 0.183336 | 0.0213693 | 0.0953023 | 0.141664 | 6.95721 |
14 | 0.03 | 0.35 | 0.0120026 | 0.307892 | 0.205053 | 0.0179974 | 0.0921077 | 0.144947 | 6.95582 |
15 | 0.025 | 0.375 | 0.010246 | 0.310732 | 0.227011 | 0.014754 | 0.0892683 | 0.147989 | 6.95452 |
16 | 0.02 | 0.4 | 0.00837679 | 0.313277 | 0.249173 | 0.0116232 | 0.0867231 | 0.150827 | 6.9533 |
17 | 0.015 | 0.425 | 0.00640769 | 0.315575 | 0.271508 | 0.00859231 | 0.0844246 | 0.153492 | 6.95214 |
18 | 0.01 | 0.45 | 0.00434943 | 0.317665 | 0.293993 | 0.00565057 | 0.0823352 | 0.156007 | 6.95105 |
19 | 0.005 | 0.475 | 0.00221099 | 0.319575 | 0.316607 | 0.00278901 | 0.0804249 | 0.158393 | 6.95002 |
20 | 2e-16 | 0.5 | 1e-16 | 0.32133 | 0.339335 | 1e-16 | 0.0786695 | 0.160665 | 6.94904 |
We can now construct an interactive plot from this data:
from reaktplot import *
fig = Figure()
fig.title("AMOUNTS OF EXCHANGE SPECIES KX, CaX<sub>2</sub>, NaX")
fig.xaxisTitle('Ca<sup>2+</sup> [molal]')
fig.yaxisTitle('Amount [mol]')
fig.drawLineWithMarkers(df["amount_Ca"], df["amount_KX"], name="KX")
fig.drawLineWithMarkers(df["amount_Ca"], df["amount_CaX2"], name="CaX<sub>2</sub>")
fig.drawLineWithMarkers(df["amount_Ca"], df["amount_NaX"], name="NaX")
fig.show()
This figure shows that as the concentration of Ca2+ increases from 0.0 to 0.5 molal, while the concentration of K+ decreases from 0.1 to 0.0 molal, there is a tendency for the cations Na+ and K+ to unbind from their respective NaX and KX exchange phases. In the process, the cations Ca2+ tend to bind to the available ion exchange species X-, with the exchange phase CaX2 becoming predominant among the others.
The next figure shows how the concentrations of Na+, K+ and Ca2+ behave in the process.
fig = Figure()
fig.title("CONCENTRATIONS OF IONS K<sup>+</sup>, Ca<sup>2+</sup>, Na<sup>+</sup>")
fig.xaxisTitle('Ca<sup>2+</sup> [molal]')
fig.yaxisTitle('Concentration [molal]')
fig.drawLineWithMarkers(df["amount_Ca"], df["amount_K_ion"], name="K<sup>+</sup>")
fig.drawLineWithMarkers(df["amount_Ca"], df["amount_Ca_ion"], name="Ca<sup>2+</sup>")
fig.drawLineWithMarkers(df["amount_Ca"], df["amount_Na_ion"], name="Na<sup>+</sup>")
fig.show()
Finally, we present how the pH of the solution changes in the process, whose changes are not significant.
fig = Figure()
fig.title("AQUEOUS SOLUTION pH")
fig.xaxisTitle('Ca<sup>2+</sup> [molal]')
fig.yaxisTitle('pH')
fig.drawLineWithMarkers(df["amount_Ca"], df["pH"], name="")
fig.show()