Metastable Zone

Quick Start

When you cool a solution (or evaporate or add anti-solvent) to create supersaturation it is, by definition, unstable. But in the absence of triggers the solution can remain indefinitely stable till you go through the metastable zone after which it is unconditionally unstable. What does "metastable" mean? There's a thermodynamic explanation, that the app brings to life


This is part of my series of apps trying to wrestle with the complexities of crystallization.

Metastable Zone

P1 P2 P3 P4 X
//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 = {
        P1: sliders.SlideP1.value,
        P2: sliders.SlideP2.value,
        P3: sliders.SlideP3.value,
        P4: sliders.SlideP4.value,
        X: sliders.SlideX.value,

    //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('Comments').value = result.Comments
    //Do all relevant plots by calling plotIt - if there's no plot, nothing happens
    //plotIt is part of the app infrastructure in
    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 CalcIt!

//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({ P1, P2, P3, P4, X }) {
    let Comments="Unsaturated"
    let MCurve = [], ABLine = [], CDLine = [], XLine = [], ACLine = [], XtraLine = []
    const C1 = -1 - 3 * P1
    const C2 = 0 + 2 * P2
    const C3 = 33 + 10 * P3
    const C4 = -65 - 10 * P4
    const C5 = -(C1 + C2 + C3 + C4)
    let oldD2 = 1, oldD1 = -1
    let y, D2Vals = [], D1Vals = []
    let XY = 0, yMin = 999
    for (let x = 0; x <= 1.01; x += 0.01) {
        y = C1 * x + C2 * x * x + C3 * x * x * x + C4 * x * x * x * x + C5 * x * x * x * x * x
        yMin = Math.min(yMin, y)
        MCurve.push({ x: x, y: y })
        D1 = C1 + 2 * C2 * x + 3 * C3 * x * x + 4 * C4 * x * x * x + 5 * C5 * x * x * x * x
        if (Math.sign(D1) != Math.sign(oldD1)) D1Vals.push({ x: x, y: y })
        D2 = 2 * C2 + 6 * C3 * x + 12 * C4 * x * x + 20 * C5 * x * x * x
        if (x>0.1 && Math.sign(D2) != Math.sign(oldD2)) D2Vals.push({ x: x, y: y })
        if (XY == 0 && x >= X) XY = y
        oldD1 = D1
        oldD2 = D2
    if (D1Vals.length < 3) D1Vals.push({ x: 1, y: 0 })
    ABLine.push(D1Vals[0], D1Vals[2])
    CDLine.push(D2Vals[0], D2Vals[1])
    ACLine.push(ABLine[0], CDLine[0])
    let yCross = 0

    if (X >= ABLine[0].x && X <= CDLine[0].x) {
        yCross = ABLine[0].y + (CDLine[0].y - ABLine[0].y) * (X - ABLine[0].x) / (CDLine[0].x - ABLine[0].x)
        XtraLine.push({ x: X, y: XY }, { x: X, y: yCross })
        XLine.push({ x: X, y: -0.5 }, { x: X, y: XY })
        Comments="In the binodal region, +ve ΔG for local split (small fluctuation), so metastable"
    } else {
        if (X > CDLine[0].x && X < CDLine[1].x) {
            yCross = CDLine[0].y + (CDLine[1].y - CDLine[0].y) * (X - CDLine[0].x) / (CDLine[1].x - CDLine[0].x)
            XtraLine.push({ x: X, y: XY }, { x: X, y: yCross })
            XLine.push({ x: X, y: -0.5 }, { x: X, y: yCross })
            Comments="In the spinodal region, -ve ΔG for local split, so unstable"
        else {
            XLine.push({ x: X, y: -0.5 }, { x: X, y: XY })
            if (X>CDLine[1].x && X<=ABLine[1].x) {Comments="Binodal unstable"} else{if (X>ABLine[1].x) Comments="Stable"}
    let inText = []
    //yp=0 is bottom, 100 is top
    inText.push({ txt: "A", xp: 100*ABLine[0].x, yp: 95*(ABLine[0].y+0.5), fontSize: 15 })
    inText.push({ txt: "B", xp: 100*ABLine[1].x, yp: 95*(ABLine[1].y+0.5), fontSize: 15 })
    inText.push({ txt: "C", xp: 100*CDLine[0].x, yp: 95*(CDLine[0].y+0.5), fontSize: 15 })
    inText.push({ txt: "D", xp: 100*CDLine[1].x, yp: 95*(CDLine[1].y+0.5), fontSize: 15 })

    //Now set up all the graphing data detail by detail.
    let plotData=[MCurve, ABLine, CDLine, ACLine, XLine]
    let lineLabels= ["ΔG", "A-B", "C-D", "A-C", "X"] //An array of labels for each dataset
    let dottedLine= [false, true, true, true, false]
    if (yCross!=0){
    const prmap = {
        plotData: plotData,
        lineLabels: lineLabels,
        dottedLine: dottedLine,
        //colors: ['#edc240', '#edc240', '#afd8f8', '#afd8f8', "red"],
        xLabel: "Solute Concentration X& ", //Label for the x axis, with an & to separate the units
        yLabel: "ΔG& ", //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: [, 1], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        yMinMax: [-0.5,0.5], //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: 'F3', //These are the sig figs for the Tooltip readout. A wide choice!
        ySigFigs: 'F3', //F for Fixed, P for Precision, E for exponential
        inText: inText,

    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot

    return {
        Comments: Comments,
        plots: [prmap],
        canvas: ['canvas'],



We know that when you have a supersaturated solution then thermodynamically the excess solute must fall out of solution, and yet the solutions can sit for days, months, years with nothing happening. However, if you supersaturate a bit more you can find it impossible to avoid the solute falling out. The zone between the saturation point and unstable point is called the metastable zone.

At first, the metastable zone seems a lawless land. After all, we know that in practice its width can vary depending on details of the setup. But even though there is variability, it's a rather reproducible variability in that some solutes in some solvents always have a wide zone and others a narrow one.

Here we see (in a highly stylised and exaggerated manner) the explanation for this. The graph shows the free energy of the system relative to "standard states" of 0 for pure solvent, X=0 (left) and pure solute X=1 (right). As you start adding solute, i.e. increasing X, the free energy goes down - the solute likes to dissolve. But at point A, the first minimum, we reach saturation - the free energy starts to increase as you try to add more. If you try to add even more you reach a point C where the second derivative of the curve, `(δ^2ΔG)/(δx^2)`, goes to 0. After reaching a maximum, the free energy goes down (in essence you are now trying to dissolve solvent in the solute, not so hard to do) till you reach point D where again `(δ^2ΔG)/(δx^2)` = 0. Eventually you reach point B, another minimum. If that's at slighly less than X=1 then we know that you really can dissolve some solvent in this solute. However, we're not interested in that, and it's ignored for the rest of the app.

You control the shape with 4 sliders. They happen to be controlling the 1st, 2nd ... order polynomial (the 5th is automatically fitted to ensure a return to 0) but just play around to get whatever shape (within reason) interests you.

Binodals and Spinodals

A and B are the binodal points. In the long term, any value of X between these points must split (see below) into a solvent rich portion, with concentration XA and a solute-rich portion with XB. As we aren't much interested in B, our focus is on point A which is the solubility limit.

However, set up the X slider to give a value between A and C. If you have a small, local fluctuation in the conditions, the system will tend to split along the line A-C. But ΔG of this line is greater than that of the solution, as you can see from the dotted part of the X line, so this small-scale fluctuation is stable. Give it a bigger fluctuation, so there are some regions of concentration above C, then we have a different effect.

The C-D region is the Spinodal. Spinodals are special. Any system that finds itself in a spinodal is unstable to the slightest fluctuation, as you can see from the dotted part of the X line, because its ΔG is higher than the C-D split. Spinodals aren't metastable, they just split.

So if you are in the A-C region, you are metastable - you can survive small fluctuations. As soon as you're above C, either by a large fluctuation or with a solution that starts with a concentration greater than C, you have no choice but to split into a solution at concentration XC and one with XD, which must then slide all the way down to A and B.

Note that these ΔG-style arguments should really be expressed in the language of convexity or concavity. The approach here is less rigorous, but captures the essence.

Metastable zone width

Now we can see that under careful control, with only mild fluctuations, the metastable zone width is fixed by the thermodyanmics. Yet if you start stirring, firing lasers, sending in ultrasound, throwing in seeds or junk, and if you are nearer C than A, then that fluctuation can force the system into the spinodal and instant phase separation.

A fluctuation is simply a term meaning an area of higher or lower concentration than the average. All solutions have fluctuations, but they're generally rather small as it's hard to get a zone with a substantially higher or lower concentration. As you get nearer to phase separations, clusters get larger so fluctuations can be larger. The natural language for all this is fluctuation theory, often implemented using the Kirkwood-Buff approach discussed on this site. That's why I believe that KB (and similar) will eventually prove to be a more reliable language than is currently the norm for crystallization.


What does it mean to "split" between A and B? That's really two questions - why A & B and why is there a split. In the diagram, ΔG of A and B are different and yet the system splits to those points because their chemical potentials are equal. By definition, if you have two phases in contact their chemical potentials must be equal. We know that they are equal at A and B because chemical potential is `(δΔG)/(δX)` and at these two minima the values are both 0. Splitting means that we get one part with composition A and another with composition B. Let's assume the B is at X=1, so it's pure solid. If we had A at X=0.5, B at 1 and started with a solution at X=0.6, what is the result? By the lever rule we get (1-0.6)/(1-0.5) of the solution at A and (0.6-0.5)/(1-0.5) of solute at B, an 80:20 split from the original, so if we started with 60g/100ml we'd end up with 48g of X=0.5 solution and 12g of solid.