Foam from Solvents

Quick Start

Who would have thought that you can get foam just by mixing a pair of solvents? If you think that's not likely, then you're right for many pairs, but those that show a significant non-linearity in surface tension versus concentration can, and do, foam. This app shows the effect and helps us think through this fascinating phenomenon.

Credits

The app implements the ideas of a joint lab between ESPCI, CNRS, Sorbonne Universite and Total, in the paper H.-P. Tran et al, Understanding Frothing of Liquid Mixtures: A Surfactantlike Effect at the Origin of Enhanced Liquid Film Lifetimes, Phys Rev Lets 125, 178002 (2020).

Foam from Solvents

γ1 mN/m
γ2 mN/m
σfit
v/σ1
v/σ2
//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 = {
        gamma1: sliders.Slidegamma1.value/1000, //N/m
        gamma2: sliders.Slidegamma2.value/1000,
        sigma: sliders.Slidesigma.value,
        vs1: sliders.Slidevs1.value,
        vs2: sliders.Slidevs2.value,
    //     isFan: document.getElementById('isFan').checked,
    //     isDry: document.getElementById('isDry').checked,
    //     isFilledMap: document.getElementById('isFilledMap').checked,
    };

    //Send inputs off to CalcIt where the names are instantly available
    //Get all the resonses as an object, result
    console.time("time")

    const result = CalcIt(inputs);

    //Set all the text box outputs
    //document.getElementById('toBake').value = result.toBake;
    // document.getElementById('Solid').value = result.sInfo;
    //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]);
        }
    }

    console.timeEnd("time")
    //You might have some other stuff to do here, but for most apps that's it for Main!
}

//Here's the app calculation
//The inputs are just the names provided - their order in the curly brackets is unimportant!
//By convention the input values are provided with the correct units within Main
function CalcIt({ gamma1,gamma2,sigma,vs1,vs2 }) {

    if (gamma1>gamma2){
        tmp=gamma1; gamma1=gamma2; gamma2=tmp
        tmp=vs1; vs1=vs2; vs2=tmp
    }
    const gPlt=[], aPlt=[]
    const RT=8.33*300
    sigma=RT*(sigma*1000)
    for (let x=0;x<=1;x+=0.01){
         ginf=-RT/sigma*Math.log(x*Math.exp(-sigma*gamma1/RT)+(1-x)*Math.exp(-sigma*gamma2/RT))
        gPlt.push({x:x,y:1000*ginf})
        dg=-RT/sigma*(Math.exp(-sigma*gamma1/RT)-Math.exp(-sigma*gamma2/RT))/(x*Math.exp(-sigma*gamma1/RT)+(1-x)*Math.exp(-sigma*gamma2/RT)) //The derivative
        alpha=2*dg*vs1/(gamma2-gamma1)*((1-gamma2/ginf)*(1-x)+vs2/vs1*x*(1-gamma1/ginf))
        aPlt.push({x:x,y:10*alpha})
    }
  
    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot
    const prmap = {
        plotData: [aPlt], //An array of 1 or more datasets
        lineLabels: ["α"], //An array of labels for each dataset
        hideLegend: true,
       xLabel: 'x&mole fraction', //Label for the x axis, with an & to separate the units
        yLabel: 'α&Å', //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
    }
    const prmasigma = {
        plotData: [gPlt], //An array of 1 or more datasets
        lineLabels: ["Surface tension"], //An array of labels for each dataset
        hideLegend: true,
        xLabel: 'x1&mole fraction', //Label for the x axis, with an & to separate the units
        yLabel: 'γ&mN/m', //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 {
        plots: [prmap, prmasigma],
        canvas: ['canvas', 'canvas1'],
    };
}
            

We all know that you need a surfactant for a system to foam. That's not technically correct; what you need is some sort of Marangoni effect that provides some local stabilization to your foam in the places that matter - the Plateau borders. Sure, if you want long-lasting foam when the foam walls become thin you need some disjoining pressure (see the Foam DLVO app), but stability when the foam walls are very thick (~100nm) is all about Marangoni, which in turn is all about non-linear dependencies of surface tension on surface concentration.

It's well known that if you mix very compatible solvents such as alkane with alkane or simple alcohol with simple alcohol, you cannot get foaming. The core reason is that the change of surface tension with concentration is nearly linear. However, if you mix an odd alcohol such as cyclopentanol with ethanol, or an aromatic like toluene with a linear alkane such as decane, then there's a significant non-linearity in the curve of surface tension versus mole fraction x1 of the solvent with the lowest surface tension. The app's default curve of surface tension is that of toluene and decane, going from 28 mN/m for pure toluene down to 23 for pure decane.

Foamability

Going from the curvature of the surface tension graph to a measure of foamability is a bit complex. But basically the surface tension of a film of thickness h depends on a parameter α via the formula:

`γ(h)=γ_∞(1+α/h)`

Here γ is just the curve as shown in the graph, measured in the bulk (so h is large and the α/h term becomes irrelevant). In turn out that α gives a measure of an intermediate film thickness that can keep a foam stable long enough to be noticable. For the record (it's not explicit in the paper), if we have vs1 being the ratio of molecular volume to molecular surface area for 1 (and similarly for 2), then we find that α can be calculated from the derivative δ of γ with χ1 via:

`α=(2*δ*vs_1)/(γ2-γ1)*((1-(γ_2)/γ_∞)*(1-x_1)+(vs_2)/(vs_1)*x_1*(1-(γ_1)/γ_∞))`

From the plot of α - which when "large" is of a typical molecular dimension of 1-2Å - we see at which fraction of 1 we have the largest chance of seeing a foam. The default settings have x1=0.1, which is close to the experimental value. If you know the relative molecular volumes and areas then you can tweak the calculation; basically the relative ability of the molecules to be at the surface depends on their ability to fit at the surface. Fortunately, leaving them at their default values of 1 (think of the units as being nm) is good enough for most purposes.

The non-linearity of γ

The surface tension plot is set by a convenient function that depends on a value σfit which you slide to get the best approximation to your experimental data. Obviously for accurate calculations you would need a suitable fitting function to your experimental data, but this one-parameter model illustrates the principle. As you slide σfit towards 0 (the α graph become meaningless at 0) the γ plot becomes a straight line and α values become very small. These are the conditions of alkane-alkane or simple alcohol-alcohol mixes and the small α tells us that there will be no foaming.

Who cares?

In addition to being a lovely bit of science, the work tells us that even in super-simple systems such as two solvents being mixed, it's possible to get foaming. Most of us aren't going to mix toluene with decane and get worried about foam. But many of us will have seen foaming in systems without surfactants, and the principle described here - a non-linear dependence of surface tension on concentration, is likely to be behind that foam.

Tran Hoai-Phuong and Dr Talini kindly sorted out some errors in my early attempts to appify their paper. I thank them for their kind help, and stress that all remaining errors are mine.