Roll Diffusion Calculator

Quick Start

This calculates the diffusion of heat or humidity into a roll over time. It allows multiple options covering different diffusion scenarios.

Credits

This is taken from the old AbbottApps which are based on inputs from web-handling experts Dr David Roisum, Dr Dilwyn Jones and Tim Walker.

Roll Diffusion Calculator

Diff cm²/s e-4
Width mm
Core OD mm
Roll D mm
From
To
Time hr
Open
Sealed
Fixed
Full
//One universal basic required here to get things going once loaded
let DoTheCalc=false
window.onload = function () {
    //restoreDefaultValues(); //Un-comment this if you want to start with defaults
    Main();
};
// document.getElementById("Calc").addEventListener('click', DoIt, false);
// function DoIt(){
//     DoTheCalc=true
//     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 = {
        D:sliders.SlideD.value,
        Width:sliders.SlideW.value,
        OD:sliders.SlideOD.value,
        RD:sliders.SlideRD.value,
        From:sliders.SlideFrom.value,
        To: sliders.SlideTo.value,
        Time: sliders.SlideTime.value,
        Open: document.getElementById('Open').checked,
        Sealed: document.getElementById('Sealed').checked,
        Fixed: document.getElementById('Fixed').checked,
        Full: document.getElementById('Full').checked,
    };

    
    //Send inputs off to CalcIt where the names are instantly available
    //Get all the resonses as an object, result
    const result = CalcIt(inputs);
    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
//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({D,Width,OD, RD, From, To, Time, Open, Sealed, Fixed, Full }) {
     const theCanvas =document.getElementById('canvas');
    ctx = theCanvas.getContext("2d");
    cw=theCanvas.width;ch=theCanvas.height
    ctx.fillStyle="blue"
    ctx.rect(0,0,cw,ch)
    ctx.fill()
    // if (!DoTheCalc){
    //     return {
    //         plots: [prmap],
    //         canvas: ['canvas1'],
    //     };
    
    // }
        
    DoTheCalc=false
    let MinP=From
    let MaxP=To
    if (MaxP1000) {RSteps*=1/(1+(Time-1000)/8000)}
              RSteps=Math.floor(RSteps)
              if (RSteps/2-Math.floor(RSteps/2)>0.01) RSteps-=1
              Time*=3600 //Hrs to secs
              const FromVal = From;
              const ToVal = To;
              if (ToVal==FromVal) {ToVal=FromVal+10}
      
			//Set up the graphics

			const RadOut = RDiameter/ 2000
			const RadIn = RCore / 2000
			const TheLength = WebWidth
			const ZSteps = Math.floor(RSteps * TheLength / (RadOut - RadIn)/2)

			let R2 = Math.floor(RSteps/2) , Z2 = Math.floor(ZSteps/2)
			const Beta = DiffCoef/1e4 //cm2 to m2
			VFrom = FromVal
			VTo = ToVal
			const TMax = Time //Seconds
			const Volume = Math.PI * (RadOut*RadOut -RadIn*RadIn) * TheLength
			const DR = (RadOut - RadIn) / RSteps
			const CoreIsolated = Sealed
			const CoreFixed = Fixed

			if (CoreIsolated)  R2 = 1
			const DZ = TheLength / ZSteps
			const DR2 = DR*DR ,DZ2 = DZ*DZ
			const DT = 1 / Beta / (1 / DR2 + 1 / DZ2) / 4
			const TimPts = Math.max(1, TMax / DT)
			//console.log (RSteps,ZSteps,DT,TMax, Beta, DR2,DZ2)
			if (TimPts > 100000)
			{
				alert("This is likely to be too slow! Reduce your TMax or increase your roll diameter")
				return
			}
			const BetaDT = Beta * DT
			const Term3 = BetaDT / DZ2
			const Term4 = BetaDT / DZ2
			const Term5 = (1 - (1 / DR2 + 1 / DZ2) * 2 * BetaDT)
			//Set everything to initial value
			const C0 =[],C1=[],RFact=[]
			let tmp=[]
			let R,Z
			for (R = 0 ;R<= RSteps; R++)
			{
			tmp=RadIn+R/RSteps*(RadOut-RadIn)
			RFact[R]=tmp*tmp/(RadIn*RadIn)
				C0[R]=[];C1[R]=[]
				for (Z = 0 ; Z<=ZSteps; Z++)
				{
					C0[R][Z] = VFrom;C1[R][Z] = VFrom
				}
			}
			//Set boundaries
			for( R = 0; R<=RSteps; R++)
			{
				C0[R][0] = VTo
				C0[R][ZSteps] = VTo
			}
			for (Z = 0 ; Z<= ZSteps; Z++)
			{
				if (!CoreIsolated && (!CoreFixed )) C0[0][Z] = VTo
				C0[RSteps][Z] = VTo
			}
			let Diff = 0
			let DiffW=0
			for (R = 1;R< RSteps;R++)
			{
				for (Z = 1 ;Z< ZSteps; Z++)
				{
					Diff += Math.abs(VTo - C0[R][Z])
					DiffW+=Math.abs(VTo - C0[R][Z])*RFact[R]
				}
			}

			const DiffMax = Diff
			const DiffMaxW=DiffW
			const PPts =[], PPts2=[],PPtsW=[]
			PPts.push({x:0,y:VFrom});PPts2.push({x:0,y:VFrom});PPtsW.push({x:0,y:VFrom})
			//The big loop
			let NextTim=0, TimStep= Math.floor(TimPts / 100)
			let x, y
			const RadR=[]
			for (R=0;R<=RSteps;R++)
			{RadR[R]=RadIn+R*DR}
			for (Tim = 1 ;Tim<= TimPts;Tim++)
			{
				for (R = 1; R< RSteps;R++)
				{
					Rad = RadR[R]
					for (Z = 1;Z= NextTim)
				{
					x = Tim*DT/3600
					y =  (1 - Diff / DiffMax)
					y=VFrom+y*(VTo-VFrom)
					PPts.push({x:x,y:y})
					y =  (1 - DiffW / DiffMaxW)
					y=VFrom+y*(VTo-VFrom)
					PPtsW.push({x:x,y:y})
					y = (Math.abs((C0[R2][Z2] - VFrom) / (VTo - VFrom)))
					y=VFrom+y*(VTo-VFrom)
					PPts2.push({x:x,y:y})
					NextTim += TimStep
				}
			}
            const yheight=200
			const blocx=Math.floor(290/ZSteps)
			const blocy=Math.floor(yheight/RSteps)
			const AmFull= Full
			const FullDiv=2.05+RadIn/RadOut
            ctx.clearRect(0,0,cw,ch)
            let rbow
			for (R = 0;R<= RSteps;R++)
			{
				for (Z = 0 ;Z<= ZSteps; Z++)
				{
					rbow = Rainbow(Math.abs((VTo - C0[R][Z]) / (VTo - VFrom)))
                    ctx.fillStyle="rgb(" + rbow.r + "," + rbow.g + "," + rbow.b + ")"
                    if (AmFull)
					{
                        ctx.fillRect(Z*blocx,(RSteps-R)*blocy/FullDiv,blocx,blocy/2)
                        ctx.fillRect(Z*blocx,yheight-(RSteps-R)*blocy/FullDiv,blocx,blocy/2)
					}
					else
					{
                        ctx.fillRect(Z*blocx,(RSteps-R)*blocy,blocx,blocy)
				}
				}
			}
			let CoreText="Core Open"
			if (CoreIsolated) {CoreText="Core Sealed"}
			if (CoreFixed) {CoreText="Core Fixed"}
			if (AmFull) CoreText=""
            ctx.font = "12pt Verdana, sans-serif";
            ctx.fillStyle="white"
            ctx.fillText(CoreText,blocx*ZSteps/3,180);
            const plotData = [PPts,PPtsW,PPts2]
            const lineLabels = ["LinAv","VolAv","Furthest"]
            const prmap = {
                plotData: plotData, //An array of 1 or more datasets
                lineLabels: lineLabels, //An array of labels for each dataset
                hideLegend: false, //Set to true if you don't want to see any labels/legnds.
                xLabel: 't&hrs', //Label for the x axis, with an & to separate the units
                yLabel: 'Value& ', //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: [0,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
                yMinMax: [Math.min(From,To),Math.max(From,To)], //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: ['canvas1'],


    };
}

let RB = [[0, 48, 245], [0, 52, 242], [0, 55, 238], [0, 59, 235], [3, 62, 231], [9, 66, 228], [14, 69, 225], [18, 72, 221], [20, 74, 218], [22, 77, 214], [23, 80, 211], [24, 82, 207], [25, 85, 204], [25, 87, 200], [25, 90, 197], [25, 92, 193], [25, 94, 190], [25, 96, 187], [24, 99, 183], [24, 101, 180], [24, 103, 177], [23, 105, 173], [23, 106, 170], [24, 108, 167], [24, 110, 164], [25, 112, 160], [27, 113, 157], [28, 115, 154], [30, 117, 151], [32, 118, 148], [34, 120, 145], [36, 121, 142], [39, 122, 139], [41, 124, 136], [43, 125, 133], [45, 126, 130], [47, 128, 127], [49, 129, 124], [51, 130, 121], [53, 132, 118], [54, 133, 115], [56, 134, 112], [57, 136, 109], [58, 137, 106], [59, 138, 103], [60, 139, 99], [61, 141, 96], [62, 142, 93], [62, 143, 90], [63, 145, 87], [63, 146, 83], [64, 147, 80], [64, 149, 77], [64, 150, 74], [65, 151, 70], [65, 153, 67], [65, 154, 63], [65, 155, 60], [66, 156, 56], [66, 158, 53], [67, 159, 50], [68, 160, 46], [69, 161, 43], [70, 162, 40], [71, 163, 37], [73, 164, 34], [75, 165, 31], [77, 166, 28], [79, 167, 26], [82, 168, 24], [84, 169, 22], [87, 170, 20], [90, 171, 19], [93, 172, 18], [96, 173, 17], [99, 173, 17], [102, 174, 16], [105, 175, 16], [108, 176, 16], [111, 176, 16], [114, 177, 17], [117, 178, 17], [121, 179, 17], [124, 179, 18], [127, 180, 18], [130, 181, 19], [132, 182, 19], [135, 182, 20], [138, 183, 20], [141, 184, 20], [144, 184, 21], [147, 185, 21], [150, 186, 22], [153, 186, 22], [155, 187, 23], [158, 188, 23], [161, 188, 24], [164, 189, 24], [166, 190, 25], [169, 190, 25], [172, 191, 25], [175, 192, 26], [177, 192, 26], [180, 193, 27], [183, 194, 27], [186, 194, 28], [188, 195, 28], [191, 195, 29], [194, 196, 29], [196, 197, 30], [199, 197, 30], [202, 198, 30], [204, 199, 31], [207, 199, 31], [210, 200, 32], [212, 200, 32], [215, 201, 33], [217, 201, 33], [220, 202, 34], [223, 202, 34], [225, 202, 34], [227, 203, 35], [230, 203, 35], [232, 203, 35], [234, 203, 36], [236, 203, 36], [238, 203, 36], [240, 203, 36], [241, 202, 36], [243, 202, 36], [244, 201, 36], [245, 200, 36], [246, 200, 36], [247, 199, 36], [248, 197, 36], [248, 196, 36], [249, 195, 36], [249, 194, 35], [249, 192, 35], [250, 191, 35], [250, 190, 35], [250, 188, 34], [250, 187, 34], [250, 185, 34], [250, 184, 33], [250, 182, 33], [250, 180, 33], [250, 179, 32], [249, 177, 32], [249, 176, 32], [249, 174, 31], [249, 173, 31], [249, 171, 31], [249, 169, 30], [249, 168, 30], [249, 166, 30], [248, 165, 29], [248, 163, 29], [248, 161, 29], [248, 160, 29], [248, 158, 28], [248, 157, 28], [248, 155, 28], [247, 153, 27], [247, 152, 27], [247, 150, 27], [247, 148, 26], [247, 147, 26], [246, 145, 26], [246, 143, 26], [246, 142, 25], [246, 140, 25], [246, 138, 25], [245, 137, 24], [245, 135, 24], [245, 133, 24], [245, 132, 24], [244, 130, 23], [244, 128, 23], [244, 127, 23], [244, 125, 23], [244, 123, 22], [243, 121, 22], [243, 119, 22], [243, 118, 22], [243, 116, 21], [242, 114, 21], [242, 112, 21], [242, 110, 21], [241, 109, 21], [241, 107, 21], [241, 105, 21], [241, 103, 21], [240, 101, 21], [240, 100, 22], [240, 98, 22], [240, 96, 23], [240, 95, 24], [240, 93, 26], [240, 92, 27], [240, 90, 29], [240, 89, 31], [240, 88, 33], [240, 87, 36], [240, 87, 38], [241, 86, 41], [241, 86, 44], [242, 86, 47], [242, 86, 51], [243, 86, 54], [243, 87, 58], [244, 88, 62], [245, 88, 65], [245, 89, 69], [246, 90, 73], [247, 91, 77], [247, 92, 82], [248, 94, 86], [249, 95, 90], [249, 96, 94], [250, 97, 98], [251, 99, 102], [251, 100, 106], [252, 101, 111], [252, 103, 115], [253, 104, 119], [253, 105, 123], [254, 107, 128], [254, 108, 132], [255, 109, 136], [255, 111, 140], [255, 112, 145], [255, 114, 149], [255, 115, 153], [255, 116, 157], [255, 118, 162], [255, 119, 166], [255, 120, 170], [255, 122, 175], [255, 123, 179], [255, 125, 183], [255, 126, 188], [255, 127, 192], [255, 129, 196], [255, 130, 201], [255, 132, 205], [255, 133, 210], [255, 134, 214], [255, 136, 219], [255, 137, 223], [255, 139, 227], [255, 140, 232], [255, 141, 236], [254, 143, 241], [254, 144, 245], [253, 146, 250]]
function Rainbow(v) {
    var i = Math.floor((Math.min(v, 1), Math.max(v, 0)) * 255)
    r = RB[i][0]
    g = RB[i][1]
    b = RB[i][2]
    return { r: r, g: g, b: b }
}                        

Diffusion Calculations

Once your roll is wound, you might be interested to know how its temperature might change if placed into a hot or cold store, or how humidity might vary throughout the roll or how solvent vapours will diffuse out of the roll.

The calculations for all three cases are the same. They all require the obvious basics: Roll diameter, Core diameter and Roll width. They need a From value (e.g 30 for 30deg or 30%RH) and a To value (e.g. 5 for 5deg or 5%RH). And they need an estimate, Time, in hours of the timescale for diffusion. If you select too short a Time then you will get a curve that hardly changes across the width. If it’s too large then your first data point will reach equilibrium. Long timespans take a long time to calculate, so always use smaller Times when you are first experimenting.

Finally, the calculations need a Diffusivity. Note that the model assumes linear, isotropic properties – so the diffusion coefficient is the same in the axial and radial directions, and diffusion obeys Fick’s Laws.

Not many of us know values of diffusivities. A typical value for a polyester web and question of thermal equilibration is 0.001cm²/s. A typical value for a paper roll and hygroscopic equilibration is 0.0001. But in real life you will have to do some measurements on a small roll (which equilibrates quickly, making it easy to do the experiments) by which you calibrate the diffusivity value that you can use on larger rolls (where experimentation is much more difficult).

To experimentally determine the effective thermal diffusivity you can bury a thermocouple in a wound roll. Change the Diffusivity value in the RDC until the temperature predicted matches the temperature measured at that location. To be most practical, the thermocouple should be as far from all edges as practical and the roll chosen to be of a size that could cool noticeably in a few hours or a few days.

To experimentally determine the effective hygroscopic diffusivity you merely need to weigh, over time, a small (drying/wetting) roll and change Diffusivity value in the RDC until calculations match measurements. To be most practical, you should trial something like a delta of 5% moisture (such as 10% > 5%) in about the range you will be most interested in (because hygroscopic diffusivity is not truly a constant like it is with thermal problems). Also, roll size should be small enough to noticeably change weight in a few days. This would be something about the size of a loaf of bread which could be obtained as a butt roll from your slitter rewinders.

On pressing Calculate you see two things.

  1. The graph has two lower lines representing the volumetric and linear average deviation of the roll from equilibrium which show a typical exponential shape. The volumetric line decreases faster because the bulk of the roll is on the outside (larger radius). If you change values of From and To you may be surprised to see that the curves do not change! And it has no idea if it is calculating temperatures, humidities or solvents. The shape is characteristic only of the size of the roll and the diffusivity. If you move the mouse over the graph you get a readout of the values at different times. The graph also shows a line (top) showing the value at the part of the roll which is furthest from the outside and therefore the slowest to respond. The curves give you complementary information – some users are more interested in one value than the other.
  2. A rainbow-coloured map showing deviations from equilibrium across the roll which you see in semi cross-section (i.e. the other half of the roll is not shown because it is identical to the top half). The core is at the bottom of the image, along with text reminding you of the setup. Red is the furthest from equilibrium, blue is equilibrium. As time goes on, the map gets closer to the uniform blue colour. If Full mode is chosen then the roll is shown in full with a core (not to scale!) in the middle. This mode reduces resolution but gives a clearer picture of what is happening.

There is one more choice to make. The calculations assume that the outer and inner layers of the roll are in complete equilibrium (thermal, hygroscopic etc.) with the surrounding air. This may or may not be realistic, but it’s a reasonable assumption for many cases. However, if you are using an impermeable core (e.g. plastic) then no humidity (or solvent) can diffuse in or out of the core. In this case, select the Core Sealed option. Now diffusion will be much slower as one diffusion pathway is blocked. And if for some reason the core is held at the fixed “From” temperature, then selecting the Core Fixed option means that each part of the roll will reach an equilibrium value balanced between the outside and the inside conditions.