Thermal analysis simulation

In this note we investigate the right implementation to reproduce the kinetics of kaolinite calcination reported by Eskelinen et al. [4]. Neither their model nor their references properly provide the concentration units used in the rate laws, so that becomes an issue when trying to reproduce the results. Here we derive the equations for a complete mass and energy balance to simulate a coupled DSC/TGA analysis of the material in with different concentration units in the rate laws.

using AuChimiste
using CairoMakie
using DifferentialEquations
using LinearAlgebra
using ModelingToolkit
using NumericalIntegration
using Printf
using Symbolics: scalarize

Species properties

In what follows the condensate species will be indexed as the next list, so for simplifying notation species will be referred to solely by their index.

  1. Liquid water
  2. Kaolinite $Al_2Si_2O_5(OH)_4$
  3. Metakaolin $Al_2Si_2O_7$
  4. Spinel $Al_4Si_3O_{12}$
  5. Amorphous silica $SiO_2$

Final conversion of spinel into mullite and cristobalite is neglected here.

Polynomials for specific heat are those of Schieltz and Soliman [7], except for spinel phase for which at the time of the publication was unknown. A rough estimate of its value is provided by Eskelinen et al. [4]. Since this phase is the least relevant in the present study and the order of magnitude seems correct, it is employed in the simulations.

Below we start by loading the database and displaying the species table:

# Use built-in database:
tdb = AuChimisteDatabase(; selected_species = [
    "WATER_L",
    "KAOLINITE",
    "METAKAOLIN",
    "SIO2_GLASS",
    "SPINEL",
])

species_table(tdb)
5×4 DataFrame
Rownamesdisplaysourcestate
StringStringStringString
1KAOLINITEKaoliniteSCHIELTZ1964solid
2METAKAOLINMetakaolinSCHIELTZ1964solid
3SIO2_GLASSSiO2 (glass)SCHIELTZ1964solid
4WATER_LWater (liquid)SCHIELTZ1964liquid
5SPINELSpinelHYPOTHETICALsolid

Because the mechanism assumes a given species indexing, we enforce it below:

# Materials for considered phases as indexed:
const materials = [
    tdb.species.WATER_L
    tdb.species.KAOLINITE
    tdb.species.METAKAOLIN
    tdb.species.SPINEL
    tdb.species.SIO2_GLASS
]

# Molecular masses of considered phases [kg/mol]:
const W = map(molar_mass, materials)

Computing mixture properties (specific heat) makes use of this indexing too:

"Mass weighted mixture specific heat [J/(kg.K)]"
function mixturespecificheat(T, Y)
    # Retrieve specific heat for all species as stated above:
    specificheat(T) = map(m->specific_heat(m, T), materials)

    return scalarize(Y' * specificheat(T))
end

Global mechanism

Here we provide the mechanism discussed by Eskelinen et al. [4]. Individual reaction steps are better described by Holm [8], especially beyond our scope at high temperatures.

\[\begin{aligned} H_2O_{(l)} &\rightarrow H_2O_{(g)} & \Delta{}H>0\\ Al_2Si_2O_5(OH)_4 &\rightarrow Al_2Si_2O_7 + 2H_2O_{(g)} & \Delta{}H>0\\ 2Al_2Si_2O_7 &\rightarrow Al_4Si_3O_{12} + {SiO_2}_{(a)} & \Delta{}H<0 \end{aligned}\]

Be $r_{i}$ the rate of the above reactions in molar units, i.e. $mol\cdotp{}s^{-1}$, then the rate of production of each of the considered species in solid state is given as:

\[\begin{pmatrix} \dot{\omega}_1\\ \dot{\omega}_2\\ \dot{\omega}_3\\ \dot{\omega}_4\\ \dot{\omega}_5\\ \end{pmatrix} = \begin{pmatrix} -1 & 0 & 0\\ 0 & -1 & 0\\ 0 & 1 & -2\\ 0 & 0 & 1\\ 0 & 0 & 1\\ \end{pmatrix} \begin{pmatrix} r_1\\ r_2\\ r_3\\ \end{pmatrix}\]

In matrix notation one can write $\dot{\omega}=\nu\cdotp{}r$, as it will be implemented. By multiplying each element of the resulting array by its molecular mass we get the net production rates in mass units. Constant $\nu$ provides the required coefficients matrix.

Mass loss through evaporation and dehydroxylation is handled separately because it becomes simpler to evaluate the condensate phases specific heat in a later stage. The first two reactions release water to the gas phase, so $\eta$ below provides the stoichiometry for this processes.

Sample mass loss is then simply $\dot{m}=\eta\cdotp{}rM_1$, where $M_1$ is water molecular mass so that the expression is given in $kg\cdotp{}s^{-1}$.

# Solid state stoichiometric coefficients
const ν = [
    -1  0  0;
     0 -1  0;
     0  1 -2;
     0  0  1;
     0  0  1
]

# Gas phase stoichiometric coefficients
const η = [1 2 0]
"Species net production rate [kg/s]"
function netproductionrates(r)
    return diagm(W) * (ν * r)
end
"Sample mass loss rate [kg/s]"
function masslossrate(r)
    return -1 * scalarize(η * r) * W[1]
end

Balance equations

The modeled system - the solid material - is open in the sense it looses water to the environment. Thus, it is necessary to add this contribution to the balance equations so that evaluated mass fractions remain right. From the definitions below

\[\frac{dm}{dt} = \dot{m}\qquad \frac{dm_k}{dt} = \dot{\omega}\qquad Y_k = \frac{m_k}{m}\]

and using the quotient rule of differentiation we can write

\[\frac{dY_k}{dt}=\frac{m\dot{\omega}-\dot{m}m_k}{m^2}\]

that can be simplified to

\[\frac{dY_k}{dt}=\frac{1}{m}\left(\dot{\omega}-\dot{m}Y_k\right)\]

which is the form we will implement here. Notice that the computation of $\dot{m}$ is already provided by masslossrate and $\dot{\omega}$ by netproductionrates so we can use the results of those evaluations in the following balance equation:

"Compute balance equation for species with varying system mass."
function speciesbalance(ṁ, ω̇, m, Y)
    return (1 / m) * (ω̇ - Y .* ṁ)
end

Reaction kinetics

Eskelinen et al. [4] compile a set of pre-exponential factors and activation energies for Arrhenius rate constants for the above reactions, which are provided in A and Eₐ below. Their reported reaction enthalpies are given in $ΔH$.

For such a global approach we do not have the privilege of working with actual concentrations because the mass distribution in the system is unknown and it would be meaningless to work with specific masses. Thus, assume the reaction rates are proportional to the number of moles of the reacting species $n_r$ so we get the required units as exposed above. Then the $r_i$ can be expressed as

\[r_{i} = k_i(T)n_r=k_i(T)\frac{m_r}{M_r}=k_i(T)\frac{Y_r}{M_r}m\]

"Rate constant pre-exponential factor [1/s]."
const A = [5.0000e+07; 1.0000e+07; 5.0000e+33]
"Reaction rate activtation energies [J/(mol.K)]."
const E = [6.1000e+04; 1.4500e+05; 8.5600e+05]
"Reaction enthalpies per unit mass of reactant [J/kg]."
const ΔH = [2.2582e+06; 8.9100e+05; -2.1290e+05]
"Evaluate rate constants [1/s]."
function rateconstants(T)
    return A .* exp.(-E ./ (GAS_CONSTANT * T))
end
"Compute reaction rates [mol/s]."
function reactionrates(m, T, Y)
    k = rateconstants(T)
    r = m * k .* Y[1:3] ./ W[1:3]
    return scalarize(r)
end
"Total heat release rate for reactions [J/kg]."
heatrelease(r) = (r .* W[1:3])' * ΔH

Experimental controls

To wrap-up we provide the programmed thermal cycle and the computation of required heat input to produce a perfect heating curve. It must be emphasized that actual DSC machines use some sort of controllers to reach this, what introduces one source of stochastic behavior to the measurement.

"Thermal cycle to apply to sample."
temperature(t, θ̇; T₀ = 298.15) = T₀ + θ̇ * t

"Required heat input rate to maintain heating rate `θ̇`."
heatinput(m, c, θ̇, ḣ) = m * c * θ̇ + ḣ

Model statement

Below we put together everything that has been developed above.

There are a few different types of quantities here:

  • Independent variables, here only `$t$
  • Dependent variables, $m$ and $Y$
  • Observable derivatives $ṁ$ and $Ẏ$
  • Other observables (partial calculations)

Because of how a DSC analysis is conducted, it was chosen that the only model parameter should be the heating rate $θ̇$. Furthermore, all other quantities were encoded in the developed functions.

"Model creation routine."
function thermal_analysis(; name)
    @independent_variables t
    D = Differential(t)

    state = @variables(begin
        m(t)
        ṁ(t)

        Y(t)[1:5]
        Ẏ(t)[1:5]

        r(t)[1:3]
        ω̇(t)[1:5]

        T(t)
        c(t)
        ḣ(t)
        q̇(t)
    end)

    param = @parameters(begin
        θ̇
    end)

    eqs = [
        D(m) ~ ṁ
        scalarize(D.(Y) .~ Ẏ)

        scalarize(Ẏ .~ speciesbalance(ṁ, ω̇, m, Y))
        scalarize(r .~ reactionrates(m, T, Y))
        scalarize(ω̇ .~ netproductionrates(r))
        ṁ ~ masslossrate(r)

        T ~ temperature(t, θ̇)
        c ~ mixturespecificheat(T, Y)
        ḣ ~ scalarize(heatrelease(r))
        q̇ ~ heatinput(m, c, θ̇, ḣ)
    ]

    return ODESystem(eqs, t, state, param; name)
end

Below we instantiate the model.

We observed the expanded form with all variables and observables:

# @named analysis = ThermalAnalysis()
@named analysis = thermal_analysis()

\[ \begin{align} \frac{\mathrm{d} m\left( t \right)}{\mathrm{d}t} &= \dot{m}\left( t \right) \\ \frac{\mathrm{d} Y\_{1}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{1}\left( t \right) \\ \frac{\mathrm{d} Y\_{2}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{2}\left( t \right) \\ \frac{\mathrm{d} Y\_{3}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{3}\left( t \right) \\ \frac{\mathrm{d} Y\_{4}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{4}\left( t \right) \\ \frac{\mathrm{d} Y\_{5}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{5}\left( t \right) \\ \dot{Y}\_{1}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{1}\left( t \right) - Y\_{1}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{2}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{2}\left( t \right) - Y\_{2}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{3}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{3}\left( t \right) - Y\_{3}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{4}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{4}\left( t \right) - Y\_{4}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{5}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{5}\left( t \right) - Y\_{5}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ r\_{1}\left( t \right) &= 2.7755 \cdot 10^{9} Y\_{1}\left( t \right) e^{\frac{-61000}{8.3145 T\left( t \right)}} m\left( t \right) \\ r\_{2}\left( t \right) &= 3.8736 \cdot 10^{7} Y\_{2}\left( t \right) e^{\frac{-1.45 \cdot 10^{5}}{8.3145 T\left( t \right)}} m\left( t \right) \\ r\_{3}\left( t \right) &= 2.251 \cdot 10^{34} Y\_{3}\left( t \right) e^{\frac{-8.56 \cdot 10^{5}}{8.3145 T\left( t \right)}} m\left( t \right) \\ \mathtt{\dot{\omega}}\_{1}\left( t \right) &= - 0.018015 r\_{1}\left( t \right) \\ \mathtt{\dot{\omega}}\_{2}\left( t \right) &= - 0.25816 r\_{2}\left( t \right) \\ \mathtt{\dot{\omega}}\_{3}\left( t \right) &= 0.22213 \left( r\_{2}\left( t \right) - 2 r\_{3}\left( t \right) \right) \\ \mathtt{\dot{\omega}}\_{4}\left( t \right) &= 0.38417 r\_{3}\left( t \right) \\ \mathtt{\dot{\omega}}\_{5}\left( t \right) &= 0.060083 r\_{3}\left( t \right) \\ \dot{m}\left( t \right) &= \left[ \begin{array}{c} 0.018015 \left( - r\_{1}\left( t \right) - 2 r\_{2}\left( t \right) \right) \\ \end{array} \right] \\ T\left( t \right) &= 298.15 + t \mathtt{\dot{\theta}} \\ c\left( t \right) &= 4187.5 Y\_{1}\left( t \right) + 544.55 Y\_{4}\left( t \right) + 3.8736 Y\_{2}\left( t \right) \left( 240.45 + 0.1477 T\left( t \right) + \frac{-3.2928 \cdot 10^{6}}{\left( T\left( t \right) \right)^{2}} \right) + 4.5019 Y\_{3}\left( t \right) \left( 229.49 + \frac{-1.456 \cdot 10^{6}}{\left( T\left( t \right) \right)^{2}} + 0.036819 T\left( t \right) \right) + 16.644 Y\_{5}\left( t \right) \left( 55.982 + \frac{-1.4435 \cdot 10^{6}}{\left( T\left( t \right) \right)^{2}} + 0.015397 T\left( t \right) \right) \\ \dot{h}\left( t \right) &= 40681 r\_{1}\left( t \right) + 2.3002 \cdot 10^{5} r\_{2}\left( t \right) - 47291 r\_{3}\left( t \right) \\ \mathtt{\dot{q}}\left( t \right) &= \dot{h}\left( t \right) + c\left( t \right) m\left( t \right) \mathtt{\dot{\theta}} \end{align} \]

For solution is is necessary to simplify this system to the equations that really are integrated. Using structural_simplify we reach this goal.

model = structural_simplify(analysis);

\[ \begin{align} \frac{\mathrm{d} m\left( t \right)}{\mathrm{d}t} &= \dot{m}\left( t \right) \\ \frac{\mathrm{d} Y\_{1}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{1}\left( t \right) \\ \frac{\mathrm{d} Y\_{2}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{2}\left( t \right) \\ \frac{\mathrm{d} Y\_{3}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{3}\left( t \right) \\ \frac{\mathrm{d} Y\_{4}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{4}\left( t \right) \\ \frac{\mathrm{d} Y\_{5}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{5}\left( t \right) \end{align} \]

Now we can get the actual equations:

equations(model)

\[ \begin{align} \frac{\mathrm{d} m\left( t \right)}{\mathrm{d}t} &= \dot{m}\left( t \right) \\ \frac{\mathrm{d} Y\_{1}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{1}\left( t \right) \\ \frac{\mathrm{d} Y\_{2}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{2}\left( t \right) \\ \frac{\mathrm{d} Y\_{3}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{3}\left( t \right) \\ \frac{\mathrm{d} Y\_{4}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{4}\left( t \right) \\ \frac{\mathrm{d} Y\_{5}\left( t \right)}{\mathrm{d}t} &= \dot{Y}\_{5}\left( t \right) \end{align} \]

unknowns(model)
6-element Vector{SymbolicUtils.BasicSymbolic{Real}}:
 m(t)
 (Y(t))[1]
 (Y(t))[2]
 (Y(t))[3]
 (Y(t))[4]
 (Y(t))[5]

... and the observed quantities.

observed(model)

\[ \begin{align} T\left( t \right) &= 298.15 + t \mathtt{\dot{\theta}} \\ r\_{2}\left( t \right) &= 3.8736 \cdot 10^{7} Y\_{2}\left( t \right) e^{\frac{-1.45 \cdot 10^{5}}{8.3145 T\left( t \right)}} m\left( t \right) \\ r\_{1}\left( t \right) &= 2.7755 \cdot 10^{9} Y\_{1}\left( t \right) e^{\frac{-61000}{8.3145 T\left( t \right)}} m\left( t \right) \\ r\_{3}\left( t \right) &= 2.251 \cdot 10^{34} Y\_{3}\left( t \right) e^{\frac{-8.56 \cdot 10^{5}}{8.3145 T\left( t \right)}} m\left( t \right) \\ c\left( t \right) &= 4187.5 Y\_{1}\left( t \right) + 544.55 Y\_{4}\left( t \right) + 3.8736 Y\_{2}\left( t \right) \left( 240.45 + 0.1477 T\left( t \right) + \frac{-3.2928 \cdot 10^{6}}{\left( T\left( t \right) \right)^{2}} \right) + 4.5019 Y\_{3}\left( t \right) \left( 229.49 + \frac{-1.456 \cdot 10^{6}}{\left( T\left( t \right) \right)^{2}} + 0.036819 T\left( t \right) \right) + 16.644 Y\_{5}\left( t \right) \left( 55.982 + \frac{-1.4435 \cdot 10^{6}}{\left( T\left( t \right) \right)^{2}} + 0.015397 T\left( t \right) \right) \\ \mathtt{\dot{\omega}}\_{2}\left( t \right) &= - 0.25816 r\_{2}\left( t \right) \\ \dot{m}\left( t \right) &= 0.018015 \left( - r\_{1}\left( t \right) - 2 r\_{2}\left( t \right) \right) \\ \mathtt{\dot{\omega}}\_{1}\left( t \right) &= - 0.018015 r\_{1}\left( t \right) \\ \mathtt{\dot{\omega}}\_{3}\left( t \right) &= 0.22213 \left( r\_{2}\left( t \right) - 2 r\_{3}\left( t \right) \right) \\ \mathtt{\dot{\omega}}\_{4}\left( t \right) &= 0.38417 r\_{3}\left( t \right) \\ \mathtt{\dot{\omega}}\_{5}\left( t \right) &= 0.060083 r\_{3}\left( t \right) \\ \dot{h}\left( t \right) &= 40681 r\_{1}\left( t \right) + 2.3002 \cdot 10^{5} r\_{2}\left( t \right) - 47291 r\_{3}\left( t \right) \\ \dot{Y}\_{2}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{2}\left( t \right) - Y\_{2}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{1}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{1}\left( t \right) - Y\_{1}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{3}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{3}\left( t \right) - Y\_{3}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{4}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{4}\left( t \right) - Y\_{4}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \dot{Y}\_{5}\left( t \right) &= \frac{\mathtt{\dot{\omega}}\_{5}\left( t \right) - Y\_{5}\left( t \right) \dot{m}\left( t \right)}{m\left( t \right)} \\ \mathtt{\dot{q}}\left( t \right) &= \dot{h}\left( t \right) + c\left( t \right) m\left( t \right) \mathtt{\dot{\theta}} \end{align} \]

Solution utilities

To make problem solution and visualization simple we provide the following utilities.

"""
    plotmodel(model, sol)

Standardized plotting of DSC/TGA analyses simulation.
"""
function plotmodel(model, sol)
    tk = sol[:t]
    Tk = sol[model.T] .- 273.15
    mk = sol[model.m]
    Y1 = sol[model.Y[1]]
    Y2 = sol[model.Y[2]] * 100
    Y3 = sol[model.Y[3]] * 100
    Y4 = sol[model.Y[4]] * 100
    Y5 = sol[model.Y[5]] * 100
    q = sol[model.q̇]

    Y1max = maximum(Y1)
    y1 = 100Y1 / Y1max
    label_water = "Water ($(@sprintf("%.2f", 100Y1max))%wt)"

    DSC = 1.0e-03 * (q ./ mk[1])
    TGA = 100mk ./ maximum(mk)

    δH = 1e-06cumul_integrate(tk, 1000DSC)

    f = Figure(size = (700, 700))

    ax1 = Axis(f[1, 1])
    ax2 = Axis(f[2, 1])
    ax3 = Axis(f[3, 1])
    ax4 = Axis(f[3, 1])

    lines!(ax1, Tk, y1; color = :blue, label = label_water)
    lines!(ax1, Tk, Y2; color = :black, label = "Kaolinite")
    lines!(ax1, Tk, Y3; color = :green, label = "Metakaolin")
    lines!(ax1, Tk, Y4; color = :red, label = "Spinel")
    lines!(ax1, Tk, Y5; color = :cyan, label = "Silica (A)")
    lines!(ax2, Tk, TGA; color = :black, label = "TGA")
    l3 = lines!(ax3, Tk, DSC; color = :black)
    l4 = lines!(ax4, Tk, δH; color = :red)

    axislegend(ax1; position = :ct, orientation = :horizontal)
    axislegend(ax2; position = :rt, orientation = :horizontal)
    axislegend(ax3, [l3, l4], ["DSC", "ΔH"], position = :lt, orientation = :horizontal)

    ax1.ylabel = "Mass content [%]"
    ax2.ylabel = "Residual mass [%]"
    ax3.ylabel = "Power input [mW/mg]"
    ax4.ylabel = "Enthalpy change [MJ/kg]"
    ax4.xlabel = "Temperature [°C]"

    xticks = 0:100:1200
    ax1.xticks = xticks
    ax2.xticks = xticks
    ax3.xticks = xticks
    ax4.xticks = xticks

    ax1.yticks = 0:25:100
    ax2.yticks = 80:4:100
    ax4.yticks = 0:0.5:2.5

    xlims!(ax1, (0, 1200))
    xlims!(ax2, (0, 1200))
    xlims!(ax3, (0, 1200))
    xlims!(ax4, (0, 1200))

    ylims!(ax1, (-1, 135))
    ylims!(ax2, (84, 100))
    ylims!(ax4, (0, 2.5))

    ax4.ygridcolor = :transparent
    ax4.yaxisposition = :right
    ax4.ylabelcolor = :red

    return f
end
"""
    solvemodel(model, τ, Θ̇, m₀, Y₀)

Standard interface for solving the `ThermalAnalysis` model.
"""
function solvemodel(model, τ, Θ̇, m, Y, solver = nothing, kwargs...)
	defaults = (abstol = 1.0e-12, reltol = 1.0e-08, dtmax = 0.001τ)
	options = merge(defaults, kwargs)

    u0 = [model.m => m, model.Y => Y]
    pars = [model.θ̇ => Θ̇]

	prob = ODEProblem(model, u0, (0.0, τ), pars)
    return solve(prob, solver; options...)
end

Sensitivity study

Now it is time to play and perform a numerical experiment.

This will insights about the effects of some parameters over the expected results.

Use the variables below to select the value of:

θ̇user = 20.0
huser = 0.5
sol, fig = let
    @info "Computation running here..."

    # Analysis heating rate.
    Θ̇ = θ̇user / 60.0

    # Kaolin humidity level.
    h = huser / 100.0

    # Initial mass (same as Meinhold, 2001).
    m = 16.0e-06

    # Integration interval to simulate problem.
    τ = 1175.0 / Θ̇

    # Assembly array of initial states.
    Y = [h, 1.0-h, 0.0, 0.0, 0.0]

    # Call model solution routine.
    sol = solvemodel(model, τ, Θ̇, m, Y)

    # ... and plot results.
    fig = plotmodel(model, sol)

    sol, fig
end
[ Info: Computation running here...

An advantage of using observables in the model is the post-processing capactities it offers. All observables are stored in memory together with problem solution. If expected solution is too large, it is important to really think about what should be included as an observable for memory reasons.

Below we illustrate the mixture specific heat extracted from the observables.

with_theme() do
    T = sol[model.T] .- 273.15
    c = sol[model.c] ./ 1000

    f = Figure(size = (700, 350))
    ax = Axis(f[1, 1])
    lines!(ax, T, c; color = :black)

    ax.ylabel = "Specific heat [kJ/(kg.K)]"
    ax.xlabel = "Temperature [°C]"

    ax.xticks = 0:100:1200
    ax.yticks = 0.6:0.1:1.4
    xlims!(ax, (0, 1200))
    ylims!(ax, (0.6, 1.4))

    f
end

Hope these notes provided you insights on DSC/TGA methods!