Moisture Sorption

Quick Start

We need to know how much moisture a food powder such as starch, a dried beverage or a sugar absorbs at a given water activity aw. This dependency is described via a water sorption isotherm, conveniently fitted to the GAB (Guggenheim-Anderson-de Boer) model. The amount absorbed affects the glass transition temperature Tg, which in turn affects whether the powder will stick together as calculated via the Powders-Moisture app.


These food science apps are produced in conjunction with expert Prof Emmanuel Purlis, CONICET, Argentina. This app is the first part of a chain of logic1 from a Nestlé & U Sheffield team.

Sorption Isotherms

Measured RH
% Water
//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
//Any global variables go here

//Main is hard wired as THE place to start calculating when input changes
//It does no calculations itself, it merely sets them up, sends off variables, gets results and, if necessary, plots them.
function Main() {

    //Send all the inputs as a structured object
    //If you need to convert to, say, SI units, do it here!
    const inputs = {
        M: sliders.SlideM.value,
        C: sliders.SlideC.value,
        K: sliders.SlideK.value,
        RH: sliders.SlideRH.value,

    //Get all the resonses as a structure
    const result = CalcIt(inputs)

    //Set all the text box outputs
    document.getElementById('PercW').value = result.PercW
    if (result.plots) {
        for (let i = 0; i < result.plots.length; i++) {
            plotIt(result.plots[i], result.canvas[i]);


//Here's the real app calculation
function CalcIt({ M,C,K,RH }) {
    //The structure automatically has the names provided from input
    //By convention the values are provided with the correct units within CalcIt
    let GotRH=false,PercW=0,WPts=[]
    for (let i = 0; i <= 95; i++) {
         if (i>=RH && ! GotRH){

    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot
    const prmap = {
        plotData: [WPts], //An array of 1 or more datasets
        lineLabels: ["%Water"], //An array of labels for each dataset
        hideLegend: true,
        xLabel: 'a_w& ', //Label for the x axis, with an & to separate the units
        yLabel: '%Water& ', //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: 'P3', //These are the sig figs for the Tooltip readout. A wide choice!
        ySigFigs: 'P3', //F for Fixed, P for Precision, E for exponential

    return {
        PercW: PercW.toFixed(1),
        plots: [prmap],
        canvas: ['canvas'],

A water sorption isotherm shows the volume fraction of water absorbed versus the water activity, aw or, if you prefer, %RH/100. To get these isotherms you need to be able to measure the weight increase as the %RH of the air is changed, allowing enough time for equilibrium to be reached before stepping to the next level. There is often hysteresis (not shown) between the values measured going from 0 to 100% and 100% going down to 0, with the descending isotherm showing more sorption that the ascending one.

The GAB model has three parameters: M which is supposed to what would be expected from a monolayer coverage, typically in the 0.05 to 0.2 range, but with foods we have absorption as well as adsorption, then there's C which is in the 1-50 range, and K which is an adjustment parameter, typically in the 0.5 to 1 range. When K=1 the equation is formally equivalent to the well-known BET isotherm. The GAB model calculates the Sorption, S for a given water activity, aw. Because experimental data and GAB both have problems above 0.95 activity, the graph stops at that point.


When you set your actual RH the fraction at that RH is calculated and you can use the value in the Tg-Moisture app.

1Christine I. Haider, Gerhard Niederreiter, Stefan Palzer, Michael J. Hounslow, Agba D. Salman, Unwanted agglomeration of industrial amorphous food powder from a particle perspective, Chem. Eng. Res. & Design, 132, 2018, 1160–1169