Addition of limestone to the cement recipe

Addition of limestone to the cement recipe

Written jountly by Svetlana Kyas (ETH Zurich) and Dan Miron (PSI) on April 4th, 2022

This tutorial shows how Reaktoro can be used for modeling cementitious systems by using the thermodynamic data for cement hydrates from cemdata18 database. The example shows the effects of addition of limestone addition to the cement formulation.

Note

If your main interest is in calculating thermodynamic properties of cement phases rather than modeling chemical equilibria and kinetics, you should check out ThermoFun, an excellent project dedicated to this task.

The model considered in this tutorial a thermofun database cemdata18 based on Cemdata, containing thermodynamic data for hydrated cement phases in the system (CaO-Al2O3-SiO2-CaSO4-CaCO3-Fe2O3-MgO-H2O). We start with the initialization of the chemical system by defining the elements of the aqueous and gas phase. The activity model of the aqueous phase is set to Debye-Hückel, where the parameters å and b are for the KOH background electrolyte (typical for cement systems where KOH is the most abundant electrolyte in the pore solution).

from reaktoro import *

# Define the Thermofun database
db = ThermoFunDatabase("cemdata18")

# Define an aqueous solution phase
solution = AqueousPhase(speciate("H O K Na S Si Ca Mg Al C Cl"))

# Set up a and b parameters for the ionic species (KOH, b = 0.123, a = 3.67) and the Debye-Huckel activity model
params = ActivityModelDebyeHuckelParams()
params.aiondefault = 3.67
params.biondefault = 0.123
params.bneutraldefault = 0.123
solution.setActivityModel(ActivityModelDebyeHuckel(params))

# Define the gas phase
gaseous = GaseousPhase(speciate("H O C"))

We continue with the definition of solid phases as either pure or solid solutions. Some phases included in the cemdata18 database should be added to the chemical system as solid solutions (more details in Lothenbach et al. (2019)). A solid solution contains two or more end-members (species) and can be ideal or non-ideal. In our case, the defined solid solutions are modeled as ideal solid solutions (the activity coefficient of the end-members is equal to 1).

We finish the creation of the chemical system by initializing it with the database cemdata18 (included in thermofun) and the phases we defined.

# Define pure minerals phases
minerals = MineralPhases("Cal hydrotalcite Portlandite hemicarbonate monocarbonate Amor-Sl FeOOHmic Gbs Mag")

# Define the hydrogarnet solid solution
ss_C3AFS084H  = SolidPhase("C3FS0.84H4.32 C3AFS0.84H4.32")
ss_C3AFS084H.setName("ss_C3AFS084H")
# Define the ettrignite solid solution
ss_ettringite = SolidPhase("ettringite ettringite30")
ss_ettringite.setName("ss_Ettrignite")
# Define the monosulfate solid solution
ss_OH_SO4_AFm = SolidPhase("C4AH13 monosulphate12")
ss_OH_SO4_AFm.setName("ss_Monosulfate")
# Define the CSHQ solid solution
ss_CSHQ = SolidPhase("CSHQ-TobD CSHQ-TobH CSHQ-JenH CSHQ-JenD KSiOH NaSiOH")
ss_CSHQ.setName("ss_CSHQ")

# Define the chemical system by providing database, aqueous phase, minerals, and solid solutions
system = ChemicalSystem(db, solution, minerals, gaseous,
                        ss_C3AFS084H,
                        ss_ettringite,
                        ss_OH_SO4_AFm,
                        ss_CSHQ)

Next, we set up the equilibrium specifications, the equilibrium conditions, and the equilibrium solver, all of which are used for the equilibrium calculations:

# Specify conditions that need to be satisfied at chemical equilibrium
specs = EquilibriumSpecs(system)
specs.temperature()
specs.pressure()

# Define the value of the conditions that need to be satisfied at chemical equilibrium
conditions = EquilibriumConditions(specs)
conditions.temperature(20.0, "celsius")
conditions.pressure(1.0, "bar")

# Define chemical and aqueous properties
props = ChemicalProps(system)
aprops = AqueousProps(system)

# Define equilibrium options
opts = EquilibriumOptions()
opts.optima.output.active = False
opts.epsilon = 1e-13

# Define the equilibrium solver
solver = EquilibriumSolver(specs)
solver.setOptions(opts)

Below, we compile the chemical substances corresponding to:

  • 100 g of cement clinker (defined by the oxide composition, usually determined by XRF analysis),

  • 1000 g of water, and

  • 1 g calcite.

# We define the materials for our equilibrium recipe
# Cement clinker composition from XRF as given in Lothenbach et al.(2008) recalculated for 100g
cement_clinker = Material(system)
cement_clinker.add("SiO2" , 20.47, "g")
cement_clinker.add("CaO"  , 65.70, "g")
cement_clinker.add("Al2O3",  4.90, "g")
cement_clinker.add("Fe2O3",  3.20, "g")
cement_clinker.add("K2O"  ,  0.79, "g")
cement_clinker.add("Na2O" ,  0.42, "g")
cement_clinker.add("MgO"  ,  1.80, "g")
cement_clinker.add("SO3"  ,  2.29, "g")
cement_clinker.add("CO2"  ,  0.26, "g")
cement_clinker.add("O2"   ,  5, "g")

# Define water
water = Material(system)
water.add("H2O", 1000, "g")

# Define calcite
calcite = Material(system)
calcite.add("CaCO3", 0.1, "g")

Next, we specify the list of phases whose volume we want to track. This list of phases is used to define the columns of the pandas.DataFrame instance where the volume of the phases is stored as a percentage of the total volume of the system.

# Create list of species and phases names, list of Species objects, and auxiliary amounts array
import numpy as np
phases_list_str = "ss_C3AFS084H ss_Ettrignite ss_Monosulfate ss_CSHQ " \
                  "Cal hydrotalcite Portlandite hemicarbonate monocarbonate Amor-Sl FeOOHmic Gbs Mag".split()
volume = np.zeros(len(phases_list_str))

# Define dataframe to collect amount of the selected species
import pandas as pd
columns = ["CaCO3"] + phases_list_str
df = pd.DataFrame(columns=columns)

In the following loop, we simulate the addition of calcite at the expense of clinker in the cement mixture (starting from 0 g and incrementing 0.5 g at each step to reach 10 g at the end). In these sequential calculations, the phase volume (cm3) of the selected phases is collected.

# Number of steps
steps_num = 21
step_size = 0.5

for i in range(steps_num):

    # Define a cement mix of 0.5 water/binder at each step calcite is added at the expense of clinker
    cement_mix = Material(system)
    cement_mix = cement_clinker(100.0-i*step_size, "g") + calcite(i*step_size, "g") + water(50.0, "g")

    # Equilibrate cement mix
    state = cement_mix.equilibrate(20.0, "celsius", 1.0, "bar", opts)
    res = cement_mix.result()
    
    if not res.optima.succeeded:

        # Equilibrate the resulting chemical state with equilibrium solver
        res = solver.solve(state, conditions)
        if not res.optima.succeeded: continue

    # Update chemical and aqueous properties
    props.update(state)
    aprops.update(state)

    for j in range(0, len(phases_list_str)):
        # Collecting the volume of specified phase
        volume[j] = float(props.phaseProps(phases_list_str[j]).volume())

    # Update dataframe with obtained values
    df.loc[len(df)] = np.concatenate([[i*step_size], volume*1e6])

To inspect the content of the pandas.DataFrame, we can just output it in the code cell:

df
CaCO3 ss_C3AFS084H ss_Ettrignite ss_Monosulfate ss_CSHQ Cal hydrotalcite Portlandite hemicarbonate monocarbonate Amor-Sl FeOOHmic Gbs Mag
0 0.5 5.233442 6.382255 5.845700e-11 26.316837 0.055807 2.333538 15.053923 2.845150e-11 2.380886e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
1 1.0 5.206176 6.350096 5.845700e-11 26.186315 0.240706 2.321812 14.976234 2.845150e-11 2.370730e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
2 1.5 2.796476 6.333802 5.845700e-11 29.637789 0.283831 2.310085 12.221938 7.313854e+00 3.752730e-10 2.900000e-12 8.238681e-12 3.195600e-12 4.452400e-12
3 2.0 5.151660 6.285788 5.845700e-11 25.925237 0.610511 2.298359 14.820897 2.845150e-11 2.350385e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
4 2.5 5.124410 6.253638 5.845700e-11 25.794681 0.795415 2.286633 14.743249 2.845150e-11 2.340196e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
5 3.0 5.097166 6.221492 5.845700e-11 25.664113 0.980321 2.274906 14.665614 2.845150e-11 2.329996e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
6 3.5 5.069927 6.189349 5.845700e-11 25.533533 1.165229 2.263180 14.587992 2.845150e-11 2.319785e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
7 4.0 5.042693 6.157210 5.845700e-11 25.402942 1.350138 2.251454 14.510385 2.845150e-11 2.309563e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
8 4.5 5.015465 6.125073 5.845700e-11 25.272340 1.535049 2.239727 14.432791 2.845150e-11 2.299329e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
9 5.0 4.988242 6.092939 5.845700e-11 25.141725 1.719961 2.228001 14.355211 2.845150e-11 2.289084e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
10 5.5 4.961025 6.060808 5.845700e-11 25.011099 1.904875 2.216275 14.277645 2.845150e-11 2.278828e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
11 6.0 4.933814 6.028680 5.845700e-11 24.880461 2.089791 2.204548 14.200093 2.845150e-11 2.268561e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
12 6.5 4.906608 5.996555 5.845700e-11 24.749811 2.274708 2.192822 14.122555 2.845150e-11 2.258282e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
13 7.0 4.879408 5.964433 5.845700e-11 24.619148 2.459627 2.181096 14.045030 2.845150e-11 2.247991e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
14 7.5 4.852213 5.932314 5.845700e-11 24.488474 2.644548 2.169369 13.967521 2.845150e-11 2.237689e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
15 8.0 4.825025 5.900197 5.845700e-11 24.357788 2.829470 2.157643 13.890025 2.845150e-11 2.227375e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
16 8.5 4.797842 5.868084 5.845700e-11 24.227089 3.014394 2.145917 13.812544 2.845150e-11 2.217050e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
17 9.0 4.770666 5.835972 5.845700e-11 24.096378 3.199320 2.134191 13.735077 2.845150e-11 2.206712e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
18 9.5 4.743495 5.803864 5.845700e-11 23.965655 3.384247 2.122464 13.657624 2.845150e-11 2.196363e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12
19 10.0 4.716330 5.771758 5.845700e-11 23.834920 3.569176 2.110738 13.580186 2.845150e-11 2.186002e+00 2.900000e-12 3.430550e-12 3.195600e-12 4.452400e-12

To visualize the distribution of different minerals in the cement recipe while the limestone addition, we us bokeh plotting library.

from bokeh.plotting import figure, show
from bokeh.palettes import brewer
from bokeh.io import output_notebook

output_notebook()

p = figure(
    title="EFFECT OF LIMESTONE",
    x_axis_label='CaCO3 [%]',
    y_axis_label='PHASE VOLUME [cm3]',
    sizing_mode="scale_width",
    plot_height=300)

volume_names = ["Cal", "hydrotalcite", "Portlandite", "ss_CSHQ", "ss_C3AFS084H", "ss_Ettrignite", "monocarbonate"]
p.varea_stack(stackers=volume_names, x='CaCO3', color=brewer['Spectral'][len(volume_names)], legend_label=volume_names, source=df)
p.legend[0].items.reverse()

show(p)
Loading BokehJS ...

In this stacked area plot, we see that the volume of calcite is increasing (we add CaCO3 to the cement mix) and the overall volume of the solids in our hydrated cement mix is decreasing by about 2 cm3. This is because the molar volume of calcite is smaller than that of the hydrates it replaces.