CEM II/B-V and CEM II/B-M (V-LL), with their CEM I, integrated in time
Before this page
Validation against measured blended pastes, the same four pastes at the extents De Weerdt et al. (2011) measured, and CEM I 52.5 R with slag, fly ash and limestone, integrated in time, the same path on another blend.
De Weerdt et al. (2011) blended a clinker interground with gypsum with 5 % limestone powder, 35 % siliceous fly ash, or 30 % fly ash and 5 % limestone, and followed the pastes at w/b = 0.5 and 20 °C for six months by X-ray diffraction: the clinker phases left, and the portlandite and the ettringite formed (their Table 7). The validation page computes these pastes at the degrees of reaction they measured. Here the clinker reacts under a rate law, and only the fly ash follows the measurement: what the pastes form over six months is then a prediction of the law and of the thermodynamics together, which nothing below was fitted to.
1. The laws
The materials are the templates of the validation page: the clinker by its Rietveld phases and its minor oxides, the fly ash by its crystals, inert, and its glass, the limestone by its calcite. Each clinker phase dissolves under the law of Parrott and Killoh (1984) with the parameters Lavergne et al. (2018) tabulate, at the Blaine fineness of the cement ((De Weerdt et al., 2011; Table 1)) and at the water/clinker ratio of the paste. The glass of the fly ash reacts as De Weerdt et al. (2011) measured it by image analysis: the fit printed on their Fig. 7, in percent of the fly ash at t days,
using ChemistryLab, DynamicQuantities, OptimaSolver, Printf, Plots
include(joinpath(pkgdir(ChemistryLab), "scripts", "deweerdt2011_kinetics.jl"))
a, b, c = dw11_value("fly_ash_fit_a"), dw11_value("fly_ash_fit_b"), dw11_value("fly_ash_fit_c")
@printf("y = %g + %g ln(t + %g)\n", a, b, c)y = -15 + 10 ln(t + 4.5)written on the degree of reaction α of the glass rather than on the time, as the Waller law (Waller, 1999) is: with s the glass's share of the fly ash, y = 100 s α, t + c = exp((y − a)/b), and the rate is dα/dt = b exp(−(y − a)/b) / (100 s) per day (dw11k_fly_ash_law). The limestone, the gypsum and everything else are at equilibrium.
ASSUMED: the minor oxides of the clinker (free lime, the alkalis, magnesia and the sulfate of the clinker itself) enter the equilibrium at the mixing. The validation page releases them with the clinker as a whole.
2. The four pastes
setup = dw11k_setup()
day = 86400.0
# `Any`: the type of a run is long enough that a dictionary specialized on it
# takes minutes to compile.
runs = Dict{String, Any}(mix => dw11k_run(setup, mix) for mix in DW11_MIXES)
for mix in DW11_MIXES
@printf("%-9s %s, %3d steps\n", mix, runs[mix].sol.retcode, length(runs[mix].sol.t))
endOPC Success, 161 steps
OPC-L Success, 162 steps
OPC-FA Success, 147 steps
OPC-FA-L Success, 142 stepsNeither law reads the equilibrium partition, so the trajectory does not depend on it: every number below is computed on the certified replay of each run (speciated_states), in percent of the solids of the paste, every solid with its water and the unreacted part of the binder included.
3. The clinker phases
days = [1, 7, 28, 90, 180]
states = Dict{String, Any}(mix => dw11k_replay(runs[mix], days) for mix in DW11_MIXES)
contents = Dict(mix => dw11k_contents(runs[mix], states[mix]) for mix in DW11_MIXES)
cell(mix, k, d, q) = (m = dw11_phase_content(mix, d, q); m === nothing ?
@sprintf("%5.1f/ – ", contents[mix][k][q]) : @sprintf("%5.1f/%4.1f", contents[mix][k][q], m))
for mix in ("OPC", "OPC-FA")
println(mix, ", wt.% computed/measured")
println(" days C3S C2S C3A C4AF OPC reacted")
for (k, d) in enumerate(days)
println(lpad(d, 6), " ", join((cell(mix, k, d, q) for q in ("C3S", "C2S", "C3A", "C4AF", "opc_reacted")), " "))
end
endOPC, wt.% computed/measured
days C3S C2S C3A C4AF OPC reacted
1 25.3/15.3 13.6/17.8 5.4/ 5.9 5.2/ 5.9 44.2/51.7
7 11.8/ 6.5 8.9/17.1 2.4/ 2.5 2.7/ 3.7 70.9/68.0
28 9.0/ 4.5 6.3/15.5 1.8/ 1.5 1.7/ 2.5 78.8/74.3
90 7.4/ 2.0 4.7/ 8.2 1.5/ 1.5 1.4/ 2.5 83.1/84.7
180 6.7/ 1.4 4.1/ 6.5 1.3/ 1.5 1.2/ 2.7 84.9/87.1
OPC-FA, wt.% computed/measured
days C3S C2S C3A C4AF OPC reacted
1 17.2/ 9.2 9.2/12.2 3.6/ 3.8 3.6/ 4.0 41.7/49.1
7 7.6/ 2.4 6.2/11.6 1.6/ 0.8 1.9/ 2.5 70.0/69.8
28 4.1/ – 4.4/10.6 0.8/ – 1.1/ 1.6 81.9/79.0
90 2.4/ – 3.2/ 7.8 0.5/ – 0.7/ 1.5 88.1/84.0
180 1.8/ – 2.7/ 7.1 0.3/ – 0.5/ – 90.7/87.6meas(mix, d, q) = something(dw11_phase_content(mix, d, q), NaN)
function phase_panel(mix, quantities; title = mix, legend = false, data = contents)
p = plot(; xscale = :log10, xlabel = "age (days)", ylabel = "wt.% of the solids", title, titlefontsize = 10, legend)
for (k, q) in enumerate(quantities)
plot!(p, days, [data[mix][j][q] for j in eachindex(days)]; lw = 2, color = k, marker = :circle, ms = 3, label = q)
scatter!(p, days, [meas(mix, d, q) for d in days]; color = k, marker = :diamond, label = "")
end
# The diamonds take the color of their phase; the legend names them once.
legend === false || scatter!(p, [NaN], [NaN]; color = :black, marker = :diamond, label = "measured (XRD)")
return p
end
plot(phase_panel("OPC", ("C3S", "C2S", "C3A", "C4AF"); legend = :topright, title = "OPC, w/b 0.5, 20 °C"),
phase_panel("OPC-FA", ("C3S", "C2S", "C3A", "C4AF"));
layout = (1, 2), size = (900, 380), left_margin = 6Plots.mm, bottom_margin = 6Plots.mm)
The last column is the table's own measure of the clinker reacted: one minus the sum of the four phases over that sum at the mixing, the same for the model and for the measurement.
4. Portlandite and ettringite
for mix in DW11_MIXES
println(mix, ", wt.% computed/measured")
println(" days portlandite ettringite")
for (k, d) in enumerate(days)
println(lpad(d, 6), " ", join((cell(mix, k, d, q) for q in ("portlandite", "ettringite")), " "))
end
endOPC, wt.% computed/measured
days portlandite ettringite
1 11.1/12.6 12.2/ 9.8
7 17.7/18.9 9.6/10.1
28 19.1/19.0 7.3/ 9.1
90 19.8/21.8 6.3/ 7.4
180 20.1/21.9 5.9/ 8.8
OPC-L, wt.% computed/measured
days portlandite ettringite
1 10.5/11.5 11.7/ 9.0
7 17.1/18.0 12.0/ 9.6
28 18.4/18.3 11.7/10.4
90 19.1/18.8 11.6/ 9.9
180 19.4/21.0 11.5/11.0
OPC-FA, wt.% computed/measured
days portlandite ettringite
1 6.8/ 8.7 8.5/ 9.0
7 9.2/14.0 4.5/ 7.4
28 7.3/13.7 0.0/ 7.5
90 4.3/12.5 0.0/ 6.6
180 2.5/11.3 0.0/ 6.6
OPC-FA-L, wt.% computed/measured
days portlandite ettringite
1 6.9/ 8.8 8.5/ 9.3
7 9.6/13.5 8.7/ 9.2
28 8.3/12.8 8.5/ 9.5
90 6.2/12.2 8.3/10.5
180 4.7/10.8 8.3/10.9plot([phase_panel(mix, ("portlandite", "ettringite"); legend = mix == first(DW11_MIXES) ? :topleft : false,
title = mix == first(DW11_MIXES) ? "$mix, w/b 0.5, 20 °C" : mix) for mix in DW11_MIXES]...;
layout = (1, 4), size = (1100, 330), left_margin = 6Plots.mm, bottom_margin = 7Plots.mm)
Where the aluminum and the sulfate are at six months, in mmol per 100 g of binder:
phases = ("ettringite", "monosulphate12", "C4AH13", "monocarbonate", "hemicarbonate",
"straetlingite", "C3AH6", "hydrotalcite")
println(rpad("", 16), join((lpad(mix, 10) for mix in DW11_MIXES)))
for name in phases
println(rpad(name, 16), join((@sprintf("%10.2f", 1000 * dw11k_amount(states[mix][end], name)) for mix in DW11_MIXES)))
end
xs = collect(eachindex(phases))
w = 0.2
fig = plot(; xticks = (xs, collect(phases)), xrotation = 30, ylabel = "mmol per 100 g of binder", legend = :topright,
size = (900, 400), left_margin = 6Plots.mm, bottom_margin = 12Plots.mm, title = "At six months")
for (k, mix) in enumerate(DW11_MIXES)
bar!(fig, xs .+ (k - 2.5) * w, [1000 * dw11k_amount(states[mix][end], name) for name in phases];
bar_width = w, label = mix)
end
plot!(fig; ylims = (0, 1.1 * ylims(fig)[2]))
5. What the comparison says
The clinker as a whole reacts at about the measured rate, its phases do not. The table's measure, the clinker reacted, is reproduced within five points from seven days to six months in the four pastes, and seven to ten points low at one day. It hides two errors in opposite directions. The law dissolves the alite too slowly at first, 25 wt.% of the solids of the plain cement at one day where 15 are left, and then lets it stall, at 6.7 wt.% at six months where the measurement finds 1.4; and it dissolves the belite from the first day, 8.9 against 17.1 wt.% at seven days, where the pastes leave it barely touched for a month. The parameters are not this clinker's: Section 6 fits those of the alite on the plain paste and tests them on the three others.
Without fly ash the portlandite is the measured one. In the plain and in the limestone cement it is within two points of the measurement at every age, the measurement being good to ±1 wt.%. The ettringite of the limestone cement is within three points, and the limestone holds it as De Weerdt et al. (2011) found, as monocarbonate takes the aluminum; in the plain cement it falls below the measurement after a month, the minimization turning part of it into monosulfate, which the authors also found, but more of it.
With fly ash the portlandite and, without limestone, the ettringite are lost. The glass, at the degree of reaction De Weerdt et al. (2011) measured, consumes the portlandite at equilibrium: at six months a fifth of the measured amount is left in the paste without limestone, under half in the paste with it. This is the finding of the validation page at measured extents, which the kinetics carries in time: the C-S-H of this phase list, CSHQ, takes no aluminum and stays at the calcium-to-silicon ratio of a gel in equilibrium with portlandite, where the authors measured a gel poorer in calcium holding aluminum. Without limestone, the aluminum of the glass then goes to the AFm phases and to hydrogarnet, and the ettringite is gone from 28 days, where the paste keeps 7 wt.%; with limestone it goes to monocarbonate, and the ettringite stays. The two other gels the package ships do not fix it: at measured extents CASH+NK behaves as CSHQ, and CNASH_ss takes the aluminum at a calcium-to-silicon ratio a Portland cement's gel does not have (the validation page). Section 7 finds the same in time.
6. The alite calibrated on the plain cement
Which constants of the law the measured alite determines is first a question of the law: its rate is the least of three terms, nucleation, interaction and diffusion, and only the least acts at a given degree. The law of the alite alone is fitted on the five contents of the plain cement, its interaction and diffusion constants and the critical degree of its water/cement factor free (dw11k_alite_fit), and identifiability reads the singular values of the exact sensitivity at the fit, for a measurement good to ±1 wt.%:
fit = dw11k_alite_fit(runs["OPC"])
println("constant published fitted")
for k in keys(fit.θ)
@printf("%-6s %12.4g %12.4g\n", k, fit.published[k], fit.θ[k])
end
@printf("misfit over the five ages: %.2f wt.%% published, %.2f fitted\n", fit.rms_published, fit.rms)
println("singular values at the fit: ", round.(fit.identifiability.S; sigdigits = 3))constant published fitted
k₂ 0.05 0.6261
k₃ 1.1 7.381
n₃ 3.3 3.718
H 1.333 2.034
misfit over the five ages: 6.40 wt.% published, 0.43 fitted
singular values at the fit: [25.1, 1.97, 0.0, 0.0]The four pastes again, the alite under the fitted constants, the rest as before: the plain cement is the paste the constants were fitted on, the three others test them.
calibrated = Dict{String, Any}(mix => dw11k_run(setup, mix; alite = fit.θ) for mix in DW11_MIXES)
recalc = Dict(mix => dw11k_contents(calibrated[mix], dw11k_replay(calibrated[mix], days)) for mix in DW11_MIXES)
measured(mix, d, q) = something(dw11_phase_content(mix, d, q), NaN)
for mix in DW11_MIXES
println(mix, ", wt.% published/calibrated/measured")
println(" days C3S OPC reacted portlandite")
for (k, d) in enumerate(days)
@printf("%6d %5.1f/%5.1f/%5.1f %5.1f/%5.1f/%5.1f %5.1f/%5.1f/%5.1f\n", d,
contents[mix][k]["C3S"], recalc[mix][k]["C3S"], measured(mix, d, "C3S"),
contents[mix][k]["opc_reacted"], recalc[mix][k]["opc_reacted"], measured(mix, d, "opc_reacted"),
contents[mix][k]["portlandite"], recalc[mix][k]["portlandite"], measured(mix, d, "portlandite"))
end
endOPC, wt.% published/calibrated/measured
days C3S OPC reacted portlandite
1 25.3/ 14.8/ 15.3 44.2/ 56.9/ 51.7 11.1/ 14.9/ 12.6
7 11.8/ 6.4/ 6.5 70.9/ 77.3/ 68.0 17.7/ 19.8/ 18.9
28 9.0/ 3.7/ 4.5 78.8/ 84.9/ 74.3 19.1/ 21.0/ 19.0
90 7.4/ 2.4/ 2.0 83.1/ 88.9/ 84.7 19.8/ 21.7/ 21.8
180 6.7/ 1.8/ 1.4 84.9/ 90.5/ 87.1 20.1/ 21.9/ 21.9
OPC-L, wt.% published/calibrated/measured
days C3S OPC reacted portlandite
1 24.2/ 14.2/ 13.0 43.8/ 56.6/ 54.0 10.5/ 14.3/ 11.5
7 10.7/ 6.1/ 6.1 71.6/ 77.4/ 69.6 17.1/ 18.9/ 18.0
28 7.7/ 3.5/ 2.9 80.0/ 85.2/ 77.3 18.4/ 20.0/ 18.3
90 6.2/ 2.3/ 2.2 84.4/ 89.2/ 84.5 19.1/ 20.6/ 18.8
180 5.5/ 1.7/ 1.6 86.4/ 91.0/ 87.6 19.4/ 20.9/ 21.0
OPC-FA, wt.% published/calibrated/measured
days C3S OPC reacted portlandite
1 17.2/ 10.2/ 9.2 41.7/ 54.5/ 49.1 6.8/ 9.6/ 8.7
7 7.6/ 4.4/ 2.4 70.0/ 75.8/ 69.8 9.2/ 10.5/ 14.0
28 4.1/ 2.6/ NaN 81.9/ 84.6/ 79.0 7.3/ 8.0/ 13.7
90 2.4/ 1.7/ NaN 88.1/ 89.5/ 84.0 4.3/ 4.6/ 12.5
180 1.8/ 1.3/ NaN 90.7/ 91.6/ 87.6 2.5/ 2.7/ 11.3
OPC-FA-L, wt.% published/calibrated/measured
days C3S OPC reacted portlandite
1 17.2/ 10.2/ 8.8 41.7/ 54.5/ 50.8 6.9/ 9.7/ 8.8
7 7.6/ 4.4/ 1.8 70.3/ 76.0/ 71.6 9.6/ 10.9/ 13.5
28 4.1/ 2.6/ 0.6 82.2/ 84.9/ 82.8 8.3/ 8.9/ 12.8
90 2.4/ 1.6/ NaN 88.3/ 89.7/ 87.4 6.2/ 6.5/ 12.2
180 1.8/ 1.2/ NaN 90.9/ 91.8/ 89.5 4.7/ 5.0/ 10.8alite = map(enumerate(DW11_MIXES)) do (k, mix)
p = plot(; xscale = :log10, xlabel = "age (days)", ylabel = "C3S, wt.% of the solids",
title = mix == "OPC" ? "OPC, the paste fitted on" : mix,
titlefontsize = 10, legend = k == 1 ? :topright : false)
plot!(p, days, [contents[mix][j]["C3S"] for j in eachindex(days)]; lw = 2, ls = :dash, color = :grey50, label = "published")
plot!(p, days, [recalc[mix][j]["C3S"] for j in eachindex(days)]; lw = 2, color = :steelblue, label = "calibrated")
scatter!(p, days, [measured(mix, d, "C3S") for d in days]; color = :black, marker = :diamond, label = "measured")
end
plot(alite...; layout = (1, 4), size = (1100, 330), left_margin = 6Plots.mm, bottom_margin = 7Plots.mm)
The fit determines two combinations of the four constants, those of the diffusion term. The interaction constant and the critical degree come out where they no longer act, the two zero singular values: the data say only that they are larger than the published ones, which cap the alite of this clinker where it does not slow down. With a diffusion constant seven times the published one, the alite of the plain cement is the measured one within half a point at every age.
The limestone cement, which nothing was fitted on, follows: its alite is within 1.2 points of the measurement at every age. With fly ash the measured alite is faster still, 2.4 and 1.8 wt.% at seven days against 4.4 computed: the filler effect of the fly ash, which De Weerdt et al. (2011) find, and which a law written for each phase of the cement alone does not carry.
The clinker reacted is now overestimated, by 3 to 11 points, because the belite still reacts as the published law makes it, too fast for this clinker, and nothing compensates it any more. Under this law the rate of the belite is always its diffusion term, greatest at the mixing and decreasing from there, where the belite of these pastes waits a month: no value of the constants holds it. The portlandite of the pastes without fly ash follows the alite, within two points from seven days and two to three too high at one day.
7. The two other gels, in time
The two fly-ash pastes again, with CNASH_ss and then CASH+NK in place of CSHQ, the rest of the phase list and the laws unchanged, the alite under its published constants as in Sections 2 to 5; CASH+NK without the aqueous ion pairs its authors left out when fitting it (Miron et al., 2022; Section 3.2), as on the validation page. The members of CASH+NK are mixed under the compound energy formalism, whose activities depend on the standard Gibbs energies of the members, which the run evaluates at its temperature.
gels = ("CSHQ", "CNASH_ss", "CASH+NK")
fa_mixes = ("OPC-FA", "OPC-FA-L")
gel_days = [1, 7, 28, 90, 140, 180]
gel_states = Dict{Tuple{String, String}, Any}()
gel_contents = Dict{Tuple{String, String}, Any}()
for gel in gels, mix in fa_mixes
r = gel == "CSHQ" ? runs[mix] : dw11k_run(dw11k_setup(; gel), mix)
gel_states[(gel, mix)] = dw11k_replay(r, gel_days)
gel_contents[(gel, mix)] = dw11k_contents(r, gel_states[(gel, mix)][[1, 2, 3, 4, 6]])
end
for mix in fa_mixes
println(mix, ", wt.% portlandite ettringite")
println(" days CSHQ CNASH_ss CASH+NK measured CSHQ CNASH_ss CASH+NK measured")
for (k, d) in enumerate(days)
@printf("%6d %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f %8.1f\n", d,
(gel_contents[(g, mix)][k]["portlandite"] for g in gels)..., measured(mix, d, "portlandite"),
(gel_contents[(g, mix)][k]["ettringite"] for g in gels)..., measured(mix, d, "ettringite"))
end
endOPC-FA, wt.% portlandite ettringite
days CSHQ CNASH_ss CASH+NK measured CSHQ CNASH_ss CASH+NK measured
1 6.8 9.3 7.1 8.7 8.5 8.8 8.3 9.0
7 9.2 15.7 9.7 14.0 4.5 8.6 4.6 7.4
28 7.3 15.8 8.0 13.7 0.0 5.4 0.0 7.5
90 4.3 14.7 4.9 12.5 0.0 3.0 0.0 6.6
180 2.5 13.8 3.1 11.3 0.0 1.8 0.0 6.6
OPC-FA-L, wt.% portlandite ettringite
days CSHQ CNASH_ss CASH+NK measured CSHQ CNASH_ss CASH+NK measured
1 6.9 9.4 7.2 8.8 8.5 8.8 8.3 9.3
7 9.6 15.9 10.2 13.5 8.7 8.9 8.9 9.2
28 8.3 16.3 9.0 12.8 8.5 8.7 8.8 9.5
90 6.2 15.5 6.9 12.2 8.3 8.6 8.7 10.5
180 4.7 14.7 5.5 10.8 8.3 8.5 8.6 10.9gel_panels = map(fa_mixes) do mix
p = plot(; xscale = :log10, xlabel = "age (days)", ylabel = "portlandite, wt.% of the solids", title = mix,
titlefontsize = 10, legend = mix == first(fa_mixes) ? :bottomleft : false)
for g in gels
plot!(p, days, [gel_contents[(g, mix)][k]["portlandite"] for k in eachindex(days)]; lw = 2, marker = :circle,
ms = 3, label = g)
end
scatter!(p, days, [measured(mix, d, "portlandite") for d in days]; color = :black, marker = :diamond, label = "measured")
end
plot(gel_panels...; layout = (1, 2), size = (900, 380), left_margin = 6Plots.mm, bottom_margin = 6Plots.mm)
The C-S-H of each, against the SEM-EDX analyses of De Weerdt et al. (2011), at one day and at 140 days:
println(" Ca/Si, 1 d and 140 d Al/Si, 1 d and 140 d")
@printf("measured %8.2f %8.2f %8.2f %8.2f\n",
dw11_value("csh_ca_si_fly_ash_1d"), dw11_value("csh_ca_si_fly_ash_140d"),
dw11_value("csh_al_si_fly_ash_1d"), dw11_value("csh_al_si_fly_ash_140d"))
for mix in fa_mixes, gel in gels
early, late = dw11k_gel(gel_states[(gel, mix)][1], gel), dw11k_gel(gel_states[(gel, mix)][5], gel)
@printf("%-9s %-9s %8.2f %8.2f %8.3f %8.3f\n", mix, gel, early.Ca_Si, late.Ca_Si, early.Al_Si, late.Al_Si)
end Ca/Si, 1 d and 140 d Al/Si, 1 d and 140 d
measured 1.70 1.40 0.06 0.13
OPC-FA CSHQ 1.58 1.58 0.000 0.000
OPC-FA CNASH_ss 1.12 1.16 0.090 0.107
OPC-FA CASH+NK 1.57 1.55 0.000 0.000
OPC-FA-L CSHQ 1.58 1.58 0.000 0.000
OPC-FA-L CNASH_ss 1.12 1.16 0.090 0.101
OPC-FA-L CASH+NK 1.57 1.55 0.000 0.000In time as at the measured extents, neither gel reproduces the pastes. The gel of CASH+NK is that of CSHQ, a Ca/Si between 1.55 and 1.58 at one day and at 140 days, with no aluminum, and the portlandite with it, within a point. CNASH_ss takes aluminum, an Al/Si rising from 0.09 to 0.10 or 0.11 where De Weerdt et al. (2011) measure 0.06 rising to 0.13, but at a Ca/Si of 1.12 to 1.16 from the first day, where the paper measures 1.7 falling to 1.4. The calcium its gel does not take stays in portlandite, above the measurement by 1.7 to 3.9 points from seven days, and without limestone the ettringite goes more slowly, 1.8 wt.% left at six months against none with CSHQ and 6.6 measured. What the measurement asks of the model is a gel whose Ca/Si falls with time as it takes aluminum, beside portlandite: none of the three does both, and CSHQ stays the gel of these pages.
Where to go next
Validation against measured blended pastes separates the thermodynamics from the kinetics on the same pastes, and Kinetics under partial equilibrium writes the formulation these runs integrate.