Validation against measured blended pastes: CEM II/B-V and CEM II/B-M (V-LL), with their CEM I
Before this page
Validation against a measured paste: a CEM I 42.5 N through its first year, whose method this page follows.
De Weerdt et al. (2011) blended one clinker, interground with gypsum, with a siliceous fly ash and a limestone powder, in four pastes at a water/binder ratio of 0.5 and 20 °C:
| paste | binder | designation |
|---|---|---|
| OPC | clinker and gypsum | CEM I |
| OPC-L | 95 % OPC, 5 % limestone | CEM I, the limestone a minor additional constituent |
| OPC-FA | 65 % OPC, 35 % fly ash | CEM II/B-V |
| OPC-FA-L | 65 % OPC, 30 % fly ash, 5 % limestone | CEM II/B-M (V-LL) |
De Weerdt et al. (2011) measured how much of each clinker phase had reacted (XRD, their Table 7), how much of the fly ash (image analysis, the fit printed on their Fig. 7), the portlandite (Table 7) and the pore solution (Table 8), up to 180 days. With the degrees of reaction taken from the measurement, what is left to compare is the chemistry: what the reacted material becomes, and what stays in solution.
using ChemistryLab
using DynamicQuantities
using JSON
using Printf
using Plots
default(framestyle = :box, grid = false)
# The materials, recipes and system of the four pastes, shared with the test of
# this page and with the generator of the GEMS3K replay.
include(joinpath(pkgdir(ChemistryLab), "scripts", "de_weerdt_2011.jl"))
model = cemdata18_activity_model(:KOH)1. The materials
The clinker is described by its Rietveld phases ((De Weerdt et al., 2011; Table 2)) and by what its oxide analysis (Table 1) holds beyond them, its free lime, alkalis, magnesia and sulfate; the fly ash by its crystals (Table 3), which do not react, and its glass, found by difference from its analysis; the limestone by its calcite. All three are templates of data/recipe_templates.toml:
for m in (dw11_clinker("OPC"), dw11_fly_ash(), dw11_limestone())
println(m.name)
for c in m.constituents
@printf(" %-14s %5.1f %%\n", c.name, 100c.mass_fraction)
end
endclinker (De Weerdt 2011)
C2S 19.0 %
C3S 54.0 %
C3A 11.0 %
C4AF 8.0 %
minor oxides 8.0 %
siliceous fly ash (De Weerdt 2011)
Quartz 12.3 %
Calcite 0.4 %
Hematite 0.6 %
Anhydrite 0.4 %
Mullite 18.3 %
glass 68.0 %
limestone (De Weerdt 2011)
Calcite 81.0 %
minor oxides 19.0 %2. How much has reacted
The degree of reaction of each clinker phase is one minus its content over its content before hydration, both from (De Weerdt et al., 2011; Table 7); the glass of the fly ash reacts at the rate of the fit on their Fig. 7, the fly ash as a whole reaching
clinker, fly_ash = dw11_clinker("OPC-FA"), dw11_fly_ash()
println(" 1 d 28 d 140 d")
for c in vcat(clinker.constituents, [c for c in fly_ash.constituents if c.name == "glass"])
c.name in ("C3S", "C2S", "C3A", "C4AF", "glass") || continue
@printf(" %-8s %6.2f %6.2f %6.2f\n", c.name, (extent(c.extent, t) for t in (1, 28, 140))...)
end 1 d 28 d 140 d
C2S 0.00 0.13 0.39
C3S 0.72 1.00 1.00
C3A 0.46 1.00 1.00
C4AF 0.23 0.69 0.87
glass 0.03 0.29 0.513. The pastes at each age
cs = dw11_system()
days = dw11_days()
# `Any`: the type of a state is long enough that a dictionary specialized on it
# takes minutes to compile.
pastes = Dict{String, Any}(mix => hydrate(dw11_recipe(mix), cs, days; model) for mix in DW11_MIXES)
for mix in DW11_MIXES
@printf("%-9s %d ages, %d certified\n", mix, length(pastes[mix]), count(rs -> rs.certificate.optimal, pastes[mix]))
endOPC 5 ages, 5 certified
OPC-L 5 ages, 5 certified
OPC-FA 5 ages, 5 certified
OPC-FA-L 5 ages, 5 certified4. Portlandite
De Weerdt et al. (2011), Table 7 give the portlandite by XRD in weight percent of the dry paste, to ±1 wt.%; the model's is its mass over the mass of every solid of the paste, the unreacted binder included.
println(" ", join((rpad(m, 16) for m in DW11_MIXES)))
println(" ", join((rpad("model/measured", 16) for _ in DW11_MIXES)))
for (k, d) in enumerate(days)
measured = [dw11_measured_portlandite(mix, d) for mix in DW11_MIXES]
all(isnan, measured) && continue
@printf("%6.0f d ", d)
for (mix, m) in zip(DW11_MIXES, measured)
print(rpad(@sprintf("%.1f/%.1f", dw11_portlandite(pastes[mix][k]), m), 16))
end
println()
end OPC OPC-L OPC-FA OPC-FA-L
model/measured model/measured model/measured model/measured
1 d 16.0/12.6 15.8/11.5 10.0/8.7 10.3/8.8
7 d 19.0/18.9 18.2/18.0 10.3/14.0 11.1/13.5
28 d 19.9/19.0 19.5/18.3 7.6/13.7 9.1/12.8
90 d 21.4/21.8 20.3/18.8 4.5/12.5 6.7/12.25. The pore solution
elements = ["pH", "Na", "K", "S", "Si", "Al", "OH-"]
for mix in DW11_MIXES
println(mix)
println(" age ", join((rpad(e, 14) for e in elements)))
for (k, d) in enumerate(days)
d in (1.0, 28.0, 140.0) || continue
rs = pastes[mix][k]
ps = pore_solution_mmol(rs.state)
@printf("%6.0f d ", d)
for e in elements
cell = e == "pH" ? @sprintf("%.2f/%.1f", pH(rs.state, model), dw11_measured(mix, d, e)) :
@sprintf("%.3g/%.3g", ps[e], dw11_measured(mix, d, e))
print(rpad(cell, 14))
end
println()
end
endOPC
age pH Na K S Si Al OH-
1 d 13.51/13.7 93.9/131 213/315 0.418/4.2 0.0482/0.43 0.289/0.22 290/653
28 d 13.62/13.7 116/244 297/495 0.876/9.3 0.0623/0.33 0.384/0.17 383/675
140 d 13.64/13.7 114/302 318/565 0.983/13 0.0653/0.3 0.4/0.33 399/627
OPC-L
age pH Na K S Si Al OH-
1 d 13.50/13.7 94/258 213/443 1.54/6.3 0.0478/0.37 0.0398/0.21 288/589
28 d 13.63/13.7 117/258 306/515 3.34/17.1 0.0627/0.29 0.0542/0.23 386/631
140 d 13.64/13.7 115/279 320/532 3.6/27.6 0.0647/0.29 0.0558/0.31 396/677
OPC-FA
age pH Na K S Si Al OH-
1 d 13.36/13.6 70.6/223 142/310 0.174/1.2 0.0394/0.22 0.205/0.16 205/459
28 d 13.55/13.6 101/228 242/271 0.0341/1.6 0.0527/0.26 0.992/0.1 322/459
140 d 13.58/13.4 104/151 268/227 0.0417/2.6 0.0567/0.53 1.07/0.27 347/303
OPC-FA-L
age pH Na K S Si Al OH-
1 d 13.37/13.5 72.7/158 147/313 0.7/1.7 0.0399/0.21 0.0288/0.14 211/442
28 d 13.57/13.5 107/193 260/297 2.37/5.9 0.055/0.28 0.0473/0.16 339/426
140 d 13.60/13.3 108/151 283/235 2.76/6.5 0.0582/0.42 0.0503/0.22 359/278colors = Dict("OPC" => :black, "OPC-L" => :gray, "OPC-FA" => :firebrick, "OPC-FA-L" => :darkorange)
p1 = plot(; xlabel = "time (d)", ylabel = "pH", title = "pH", legend = :bottomleft)
p2 = plot(; xlabel = "time (d)", ylabel = "portlandite (wt.%)", title = "Portlandite", legend = false)
for mix in DW11_MIXES
plot!(p1, days, [pH(rs.state, model) for rs in pastes[mix]]; color = colors[mix], label = mix, linewidth = 2)
scatter!(p1, days, [dw11_measured(mix, d, "pH") for d in days]; color = colors[mix], label = "")
plot!(p2, days, [dw11_portlandite(rs) for rs in pastes[mix]]; color = colors[mix], linewidth = 2)
scatter!(p2, days, [dw11_measured_portlandite(mix, d) for d in days]; color = colors[mix])
end
fig = plot(p1, p2; layout = (1, 2), size = (900, 380), left_margin = 6Plots.mm, bottom_margin = 6Plots.mm)Lines are the model, markers the measurement.
The C-S-H itself, against the SEM-EDX analyses of De Weerdt et al. (2011) (Ca/Si 1.8 ± 0.1 without fly ash; with it, 1.7 at 1 day falling to 1.4 at 140 days, the Al/Si rising from 0.06 to 0.13):
println(" Ca/Si of the gel Al/Si")
for mix in DW11_MIXES
gel = solid_solution_totals(pastes[mix][end].state, "CSHQ").elements
@printf(" %-9s %.2f at 140 d %.2f\n", mix, gel[:Ca] / gel[:Si], get(gel, :Al, 0.0) / gel[:Si])
end Ca/Si of the gel Al/Si
OPC 1.58 at 140 d 0.00
OPC-L 1.58 at 140 d 0.00
OPC-FA 1.58 at 140 d 0.00
OPC-FA-L 1.58 at 140 d 0.006. The same budgets through GEMS3K
test/reference/xgems_deweerdt2011.json holds the answer of GEMS3K (Kulik et al., 2013) on the twenty budgets, with the phases of this system; the AFm sulfate and hydroxide are, in both codes, the Guggenheim binary of Cemdata18 (Lothenbach et al., 2019), one composition unless the certificate asks for two.
fixture = JSON.parsefile(joinpath(pkgdir(ChemistryLab), "test", "reference", "xgems_deweerdt2011.json"))
worst = Dict{String, Float64}()
worst_pH = 0.0
for row in fixture["rows"]
rs = pastes[row["mix"]][findfirst(==(row["time_d"]), days)]
ps = pore_solution_mmol(rs.state)
for (e, x) in row["mmol_per_kg_water"]
worst[e] = max(get(worst, e, 0.0), abs(ps[e] / x - 1))
end
global worst_pH = max(worst_pH, abs(pH(rs.state, model) - row["pH"]))
end
@printf("GEMS3K converged on all %d budgets: %s\n", length(fixture["rows"]), all(r -> r["converged"], fixture["rows"]))
@printf("largest difference in pH: %.4f\n", worst_pH)
for (e, w) in sort(collect(worst))
@printf(" %-4s largest relative difference: %.2f %%\n", e, 100w)
endGEMS3K converged on all 20 budgets: true
largest difference in pH: 0.0008
Al largest relative difference: 0.34 %
Ca largest relative difference: 0.24 %
K largest relative difference: 0.10 %
Na largest relative difference: 0.09 %
OH- largest relative difference: 0.09 %
S largest relative difference: 1.08 %
Si largest relative difference: 1.55 %7. A C-S-H that takes aluminum
The package ships two other models of the gel: CNASH_ss (Myers et al., 2014), mixed on the sublattices of its authors, which takes aluminum and the alkalis, and CASH+NK (Miron et al., 2022), whose end members carry the alkalis but no aluminum. The same budgets, with each in place of CSHQ, the rest of the phase list unchanged, and for CASH+NK without the four aqueous ion pairs its authors left out when fitting it, NaOH⁰, KOH⁰, NaHSiO₃⁰ and KHSiO₃⁰ (Miron et al., 2022; Section 3.2):
cashplus = build_species(datapath("cemdata18-cashplus.json"); verbose = false)
gel_systems = (
"CNASH_ss" => phase_list_system(DW11_PHASES, DW11_SUBSTANCES; replace = Dict("CSHQ" => "CNASH_ss")),
"CASH+NK" => phase_list_system(DW11_PHASES, cashplus; replace = Dict("CSHQ" => "CASH+NK"), exclude_aqueous = DW11_CASHPLUS_EXCLUDED),
)
other = Dict{String, Any}("CSHQ" => pastes)
for (gel, cs_g) in gel_systems
other[gel] = Dict{String, Any}(mix => hydrate(dw11_recipe(mix), cs_g, days; model) for mix in DW11_MIXES)
end
k90 = findfirst(==(90.0), days)
ettringite(rs) = (m = phase_masses(rs); 100 * get(m, "ettringite", 0.0) / sum(values(m)))
println("at 90 days portlandite, wt.% ettringite, wt.% gel at 140 days, Ca/Si and Al/Si")
for mix in DW11_MIXES
println(mix, ", measured: portlandite ", dw11_measured_portlandite(mix, 90), ", ettringite ", something(dw11_phase_content(mix, 90, "ettringite"), NaN))
for gel in ("CSHQ", "CNASH_ss", "CASH+NK")
rs = other[gel][mix]
e = solid_solution_totals(rs[end].state, gel).elements
@printf(" %-9s %16.1f %24.1f %22.2f %6.3f\n", gel, dw11_portlandite(rs[k90]), ettringite(rs[k90]),
e[:Ca] / e[:Si], get(e, :Al, 0.0) / e[:Si])
end
endat 90 days portlandite, wt.% ettringite, wt.% gel at 140 days, Ca/Si and Al/Si
OPC, measured: portlandite 21.8, ettringite 7.4
CSHQ 21.4 3.8 1.58 0.000
CNASH_ss 31.7 10.1 1.16 0.107
CASH+NK 23.0 3.9 1.54 0.000
OPC-L, measured: portlandite 18.8, ettringite 9.9
CSHQ 20.3 10.6 1.58 0.000
CNASH_ss 30.1 10.9 1.16 0.101
CASH+NK 21.7 11.0 1.54 0.000
OPC-FA, measured: portlandite 12.5, ettringite 6.6
CSHQ 4.5 0.0 1.58 0.000
CNASH_ss 14.7 0.7 1.16 0.107
CASH+NK 5.0 0.0 1.56 0.000
OPC-FA-L, measured: portlandite 12.2, ettringite 10.5
CSHQ 6.7 7.8 1.58 0.000
CNASH_ss 15.7 8.0 1.16 0.101
CASH+NK 7.3 8.1 1.56 0.000Neither reproduces the four pastes. CASH+NK changes nothing that matters here: it takes no aluminum, its gel is within 0.04 of the Ca/Si of that of CSHQ, and its portlandite within 1.6 points. CNASH_ss takes aluminum, an Al/Si of 0.10 to 0.11 against the 0.13 measured, but its gel sits at a Ca/Si of 1.16 in every paste, the plain cement included; its end members reach 1.5 at most (T2C-CNASHss), where De Weerdt et al. (2011) measure 1.8 without fly ash. The calcium the gel does not take goes to portlandite: 31.7 against 21.8 wt.% at 90 days in the CEM I. With fly ash the same excess brings the portlandite near the measurement, 14.7 against 12.5 and 15.7 against 12.2, and the ettringite of the CEM II/B-V is still lost, 0.7 against 6.6 wt.%.
8. What the comparison says
The two codes agree. On the twenty budgets the pore solutions differ by less than 0.001 in pH and by at most 1.6 % on an element. What separates the model from the pastes is the model's, and it would be found with either code.
Without fly ash, the portlandite is reproduced from the seventh day, within 1.5 wt.%, and within the ±1 wt.% of the measurement in the CEM I (19.0 against 18.9 at 7 days, 21.4 against 21.8 at 90). At one day the model holds 3.4 and 4.3 wt.% more than the two pastes: the clinker is dissolved as measured, and an equilibrium puts its calcium hydroxide in portlandite at once.
With fly ash, the model consumes the portlandite far faster than the pastes: 4.5 against 12.5 wt.% at 90 days in the CEM II/B-V, 6.7 against 12.2 in the CEM II/B-M (V-LL). The fly ash dissolves at the measured rate in both, so the difference is what its silica becomes. The model's C-S-H stays at a Ca/Si of 1.58, the composition in equilibrium with portlandite, in every paste; De Weerdt et al. (2011) measure 1.4 in the fly ash pastes at 140 days, with an Al/Si of 0.13, a gel poorer in calcium beside a portlandite it has not reached equilibrium with, and one that takes less calcium per silicon. CSHQ has no aluminum end-member, and the model's gel holds none.
The pore solution follows from both. The pH is within 0.3 unit at every age. In the CEM I the alkalis are low (sodium 114 against 302 mmol at 140 days, potassium 318 against 565), CSHQ holding them, as for the paste of Lothenbach and Winnefeld (2006) on its page. In the fly ash pastes the measured pH falls after 28 days, 13.6 to 13.4 and 13.5 to 13.3, while the model's rises, 13.55 to 13.58 and 13.57 to 13.60: the pastes lose potassium after 28 days (271 to 227 mmol in the CEM II/B-V) and the model gains it (242 to 268) from the clinker and the glass that go on releasing it. The aluminum the gel does not take stays in solution, 1.07 against 0.27 mmol in the CEM II/B-V at 140 days, where the sulfate is a sixtieth of the measurement (0.042 against 2.6); silicon is 4 to 10 times low throughout.
For a user, the line falls here. On the pastes without fly ash the model gives the portlandite and the pH, with CSHQ or CASH+NK. With a siliceous fly ash, CSHQ overstates the pozzolanic consumption of portlandite and cannot place the aluminum, and CNASH_ss, which takes the aluminum, does so at a Ca/Si a Portland cement's gel does not have (Section 7): the two bracket the measured portlandite, from below and from above, and neither keeps the ettringite of the paste without limestone. What the pastes need is a gel that takes aluminum at the Ca/Si of a Portland cement, which none of the three is.
Where to go next
The method, and a Portland cement followed from its first minute, are on Validation against a measured paste.