Foam Spread

Quick Start

When you want foam to spread to extinguish a fire it's good to have a working model. The model isn't, and can't be, perfect, but it captures the key elements as a starting point for fuller analysis of other factors.

This is only an early prototype - criticisms very much welcome.

Credits

The app is based on the FOAMSPEX project (Large scale Foam Application - Modelling of Foam Spread and Extinguishment) and report produced at what is now RISE in Sweden.

Foam Spread

Flow Q m³/s
Expansion S
Width B m
τfluid Pa
τwall Pa
adrain x1000
tmax s
Premix flow
R
h0
u
Pool
Wall %
R0 m
//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 = {
        V: sliders.SlideV.value,
        S: sliders.SlideS.value,
        B: sliders.SlideB.value,
        tauf: sliders.Slidetauf.value,
        tauw: sliders.Slidetauw.value,
        a: sliders.Slidea.value/1000, //input is milli
        tMax: sliders.SlidetMax.value,
        R0: sliders.SlideR0.value,
        showR: document.getElementById('showR').checked, 
        showh0: document.getElementById('showh0').checked, 
        showu: document.getElementById('showu').checked, 
        isPool: document.getElementById('isPool').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('FFlow').value = result.FFlow;
    // 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({ V,S,B,tauf,tauw,a,tMax,showR,showh0,showu,isPool,R0 }) {

    let RPts = [],f,rho, heLabel,yLabel
    const c=9.81*(1-1/S), rho0=1000/S,tf=tauf,PI2=Math.pow(Math.PI,2)
    if (showu) {RPts.push({x:0,y:Math.pow(rho*V*V*c/(3*B*B*tf),0.333)})} else {RPts.push({x:0,y:0})}
    for (let t=1;t<=tMax;t++){
        rho=rho0/(1+a*t)
        if (showR) {
            if (isPool){
                f=Math.pow(225*rho*c*V*V/(128*PI2*tf),0.4)*Math.pow(t,0.333)
            } else {
                f=Math.pow(9*rho*c*V*V/(8*B*B*tf),0.333)*Math.pow(t,0.667)
            }
            theLabel="R"
            yLabel="R&m"
        }
        if (showh0) {
            if (isPool){
                f=Math.pow(15*tf*tf*V/(2*Math.PI*rho*rho*c*c),0.2)*Math.pow(t,0.2)
            } else {
                f=Math.pow(3*V*tf/(rho*B*c),0.333)*Math.pow(t,0.333)
            }
            f*=100 //to cm
            theLabel="h0"
            yLabel="h0&cm"
       }
        if (showu) {
            if (isPool){
                f=Math.pow(15*tf*tf*V/(2*Math.PI*rho*rho*c*c),0.2)*Math.pow(t,0.2)
                f=V/(2*Math.PI*R0*f)
            } else {
                f=Math.pow(3*V*tf/(rho*B*c),0.333)*Math.pow(t,0.333)
                f=V/(B*f)
            }
            theLabel="u"
            yLabel="u&m/s"
       }
        RPts.push({x:t,y:f})
    }
    const FFlow=V/S*1000*60
    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot
    const prmap = {
        plotData: [RPts], //An array of 1 or more datasets
        lineLabels: [theLabel], //An array of labels for each dataset
        hideLegend: true,
        xLabel: 't&s', //Label for the x axis, with an & to separate the units
        yLabel: 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

    }
    return {
        FFlow: FFlow.toFixed(1)+" L/min",
        plots: [prmap],
        canvas: ['canvas'],
    };
}

            

You pump some foam onto some burning fuel in a channel of width B or into a pool via an initial radius R0. The foam extends a distance R in time t depending on the flow rate of the foam Q, and on core foam properties such as expansion rate S, foam/liquid friction τfl (which also depends on the liquid viscosity), wall friction τwall (which depends strongly on the yield stress of the foam, though not for the pool case) and drainage rate, described here by a factor a. The foam advances with a velocity u and forms a somewhat triangular shape with a height h0 at the start.

Using ρ0 = 1000/S kg/m² and c=g(1-1/S), the core equations start with a correction for density via drainage:

`ρ=ρ_0/(1+at)`

and for channel : pool respectively we have:

`R=((9ρcQ^2)/(8B^2τ_"fl"))^0.333t^0.667` : `R=((225ρcQ^2)/(128π^2τ_"fl"))^0.2t^0.4`

`h_0=((3Qτ_"fl")/(ρBc))^0.333t^0.333` : `h_0=((15τ_"fl"^2Q)/(2πρ^2c^2))^0.2t^0.2`

`u=Q/(h_0B)` : `u=Q/(2πR_0h_0)`

The basic equations are for gentle deposition onto a room temperature pool of fuel. To describe the realities of heat and fire, the factor a which is derived from drainage experiments at room temperature should be increased to emphasise the loss of water by faster drainage, plus evaporation, along with a decrease in τfl as the system warms up.

The app is based on the detailed algorithms and data contained in the FOAMSPEX book, SP Swedish Testing & Research Institute Report 2001:13, with coauthors Bror Persson, Anders Lönnermark, Henry Persson (SP), Dave Mulligan (Angus Fire), Antonio Lancia and Micaela Dermichela (TRI).