Coffee Extraction

Quick Start

Immersion coffee extraction can be modelled to see how extraction varies over time given your choice of brew temperature and particle size. The model is especially relevant for the newer controlled Uniform Extraction (UX) brew technique described below.

Credits

Papers from the Moroney group in Limerick1 and Wang & Lim at Guelph2 provide the validated theory.

Coffee Extraction

D μm
T °C
Dfines μm
Fines %
TDSmax %
tmax min
TDSend
 Show Fines
//One universal basic required here to get things going once loaded
window.onload = function () {
    //restoreDefaultValues(); //Un-comment this if you want to start with defaults

    Main();
};

//Main() is hard wired as THE place to start calculating when inputs change
//It does no calculations itself, it merely sets them up, sends off variables, gets results and, if necessary, plots them.
function Main() {
    //Save settings every time you calculate, so they're always ready on a reload
    saveSettings();

    //Send all the inputs as a structured object
    //If you need to convert to, say, SI units, do it here!
    const inputs = {
        T: sliders.SlideT.value + 273, //C to K
        D: sliders.SlideD.value,
        Dfines: sliders.SlideDfines.value,
        FPerc: sliders.SlideFPerc.value/100, //% to fraction
        TDS: sliders.SlideTDS.value,
        tmax: sliders.Slidetmax.value*60, //min to s
        showFines: document.getElementById("showFines").checked
    };

    //Send inputs off to CalcIt where the names are instantly available
    //Get all the resonses as an object, result


    const result = CalcIt(inputs);

    //Set all the text box outputs
    document.getElementById('TDSend').value = result.TDSend;

    //Do all relevant plots by calling plotIt - if there's no plot, nothing happens
    //plotIt is part of the app infrastructure in app.new.js
    if (result.plots) {
        for (let i = 0; i < result.plots.length; i++) {
            plotIt(result.plots[i], result.canvas[i]);
        }
    }

    //You might have some other stuff to do here, but for most apps that's it for Main!
}

//Here's the app calculation
function CalcIt({ T, D, Dfines, FPerc, TDS, tmax, showFines}) {
    let Bulk = [], Fines=[], Total=[], x,xf
    const beta = 0.7 //Varies from 0.6 to 0.8 in Wang, but not critical
    const Ea = 16000 //J/mol
    const Tref = 93 + 273, R = 8.314
    const Dcorr = Math.pow(643 / D, 2) //D^2 dependency on diffusion
    if (!showFines) FPerc=0
    const BPerc=1-FPerc
    const k = Dcorr * Math.exp(-16000 / (R * T)) / Math.exp(-16000 / (R * Tref)) //D + Arrhenius correction
    const Dcorrf = Math.pow(643 / Dfines, 2) //D^2 dependency on diffusion
    const kf = Dcorrf * Math.exp(-16000 / (R * T)) / Math.exp(-16000 / (R * Tref)) //D + Arrhenius correction
    let tinc=0.1
    for (let t = 0; t <= tmax; t+=tinc) {
        x = BPerc*TDS * (1 - Math.exp(-((k * Math.sqrt(t)) ** beta)))
        Bulk.push({ x: t / 60, y: x })
        xf = FPerc*TDS * (1 - Math.exp(-((kf * Math.sqrt(t)) ** beta)))
        if (showFines) Fines.push({ x: t / 60, y: xf })
        if (showFines) Total.push({ x: t / 60, y: x+xf })
        if (t>9.9) tinc=1
    }
    const TDSend = x
    let plotData = [Bulk,Fines,Total]
    if (!showFines) plotData=[Bulk]
    const hideLegend=!showFines
    const lineLabels = ["Bulk","Fines","Total"]
    const myColors = ["green","orange", "blue"]
    const borderWidth = [2,2,2]

    //Now set up all the graphing data detail by detail.
    const prmap = {
        plotData: plotData, //An array of 1 or more datasets
        lineLabels: lineLabels, //An array of labels for each dataset
        colors: myColors, //An array of colors for each dataset
        hideLegend: hideLegend,
        borderWidth: borderWidth,
        xLabel: 't&min', //Label for the x axis, with an & to separate the units
        yLabel: 'TDS&%', //Label for the y axis, with an & to separate the units
        y2Label: null, //Label for the y2 axis, null if not needed
        yAxisL1R2: [], //Array to say which axis each dataset goes on. Blank=Left=1
        logX: false, //Is the x-axis in log form?
        xTicks: undefined, //We can define a tick function if we're being fancy
        logY: false, //Is the y-axis in log form?
        yTicks: undefined, //We can define a tick function if we're being fancy
        legendPosition: 'top', //Where we want the legend - top, bottom, left, right
        xMinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        yMinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        y2MinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        xSigFigs: 'F2', //These are the sig figs for the Tooltip readout. A wide choice!
        ySigFigs: 'F2', //F for Fixed, P for Precision, E for exponential
    };

    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot
    return {
        plots: [prmap],
        canvas: ['canvas'],
        TDSend: TDSend.toFixed(2)
    };
}
            

Uniform Extraction brew

Common immersion brews such as French press, Aeropress or cupping, as well as pourovers such a cone filters, are "out of control" systems because the grinds are not exposed to a constant extraction environment. They settle or float (via CO2 bubbles), they get trapped beneath other particles, and temperature, T, varies, flow varies from place to place. You also have plenty of convection currents which add extra variation.

However, using a double-walled vessel for good T control and a magnetic stirrer to keep everything well-dispersed, an in control, Uniform Extraction (UX) brew is possible, up to your desired TDS, Total Dissolved Solid.

You are in control

For the moment, don't select the Show Fines option. It is explained below.

With such a setup you can choose grind size, T and stirring/extraction time. When stirring stops, some extraction continues but the bulk of the coffee settles quickly so this post-stirring non-uniformity is, as you will see from the extraction curve, not too serious.

So how does extraction depend on T and diameter D? The two cited papers agree on the basics, with good theory backed up by experiments, and the Wang paper allows you to calculate the rate for different T and D values, using their Fine grind as a reference point.

Extraction, up to the full extraction TDSmax follows a Weibull curve based on parameters k and β:

`TDS=(TDS)_(max)[1-exp[-(kt^0.5)^β]]`

The parameter k is effectively a rate constant, while β controls the overall shape of the curve. For our purposes, β is a constant set to 0.7, the middle of the range found by Wang et al. If we have a known k at a reference temperature T (in °K) (366 from the 93 °C value in the Wang paper) then we use the average Arrhenius activation energy of 16 kJ/mol to calculate rates at other T values (see the code for this). Although the Wang paper builds the D-dependence into an overall equation, a good fit can be found using the standard diffusion law that k depends on `(D_(ref)/D)^2`, choosing the 650 μm Fine grind as the reference value.

Your choice

If you know the fully-extracted TDS from your coffee dose and amount of water, then you enter this as TDSmax. Or just enter some typical value such as 1.4. Either way you see how close you are to full extraction at your chosen brew time, tmax. The TDSend output gives you this value, though you can read out values anywhere on the curve using your mouse. Whether you want full extraction is your choice. If you are worried about the lack of precision between stopping stirring and effective end of extraction, then check the curve to see how much extraction takes place in the, say, 0.5min after stirring stops.

Extraction is fast!

What is observed in just about every systematic brewing study is that over 50% of the extraction is reached in the very early stages. In addition to the essentially instant fines extraction discussed below, extraction is also rapid from the rather large surface area and relative volume of the fractured surface of the larger particles. Let's say that the fractures go 20 μm deep in a 650 μm particle. The volume in that 20 μm is, surprisingly, 17% of the total which can be rapidly extracted. If it's 30 μm deep then that's 25% of the total!

In principle, a full particle size analysis plus some sophisticated estimate of the true surface area of the large particles would allow you (as in the Moroney papers) to take into account these factors. But many of use don't know our "D" values, which are complicated because they are some weighted average of all the particles from fines to boulders. In the Wang paper, the Volume Mean Diameter is used, sometimes called the DeBroukere mean. In others you might have the Sauter average or the D50. Confused? So is everyone; look at the Size Distribution app to create and understand the values as you create various grind+fines combinations.

Fines

On a good grinder a typical "medium" grind might have 5% of fines in the 100μm range. As you go to espresso sizes, you can easily get 20% of fines. Selecting the Show Fines option lets you see how they impact the overall extraction.

You can enter your fine size and the % fines - and will in general get an orange Fines "graph" which goes from 0 to its % of total extraction instantly. This is exactly what the physics says should happen. As mentioned above, diffusion goes as 1/D², so a 100 μm fine will extract 42x faster than a 650 μm particle. The impact on the total extraction is shown in the blue Total curve, the sum of the Fines and Bulk curves. Extraction from fines is essentially instantaneous compared to the bulk of the coffee, and if fines over-extraction is a concern, there's nothing you can do about it.

Cold brews

The Wang paper includes cold brew extraction. The T effect is small (those familiar with Arrhenius will have realised this from the 16 kJ/mol value): compared to their 93 °C reference, the rate at 50 ° is 50% and at 23 ° is 30%. Of course, this is only a measure of TDS; the impact on aroma is not (yet) appable. Basically, the reason for the modest T effect is that caffeine and similar compounds are readily soluble even at low T, and the "kick" from the fines/surface effect gives you a lot of extraction very quickly.

1

Kevin M Moroney et al, Coffee extraction kinetics in a well mixed system, J. Ind. Math., (2017) 7:3, DOI 10.1186/s13362-016-0024-6

2

Xiuju Wang and Loong-Tak Lim, Modeling study of coffee extraction at different temperature and grind size conditions to better understand the cold and hot brewing process, J Food Process Eng. 2021;44:e13748, doi.org/10.1111/jfpe.13748