FTIR Diffusion
Quick Start
By following the ATR-FTIR signal at a chosen absorption band you can follow diffusion from the opposite side and calculate the diffusion coefficient. It involves some fancy arithmetic with ATR convolution, but modern Javascript handles it no problem.
Comments
The blending of the standard Crank diffusion term with the ATR sensitivity is from Fieldson & Barbari1.
FTIR Diffusion
"use strict"
//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 = {
D: parseFloat(document.getElementById('D').value),
Crystal: document.getElementById("Crystal").value,
tmax: sliders.Slidetmax.value * 24*3600, //day to s
n1: sliders.Sliden1.value,
theta: sliders.Slidetheta.value * Math.PI / 180, //deg to rad
lambda: 1 / sliders.Slidelambda.value, // cm-1 to cm
L: sliders.SlideL.value/1e4, //μm to cm
};
//Send inputs off to CalcIt where the names are instantly available
//Get all the resonses as an object, result
console.time("check")
const result = CalcIt(inputs);
console.timeEnd("check")
//Set all the text box outputs
document.getElementById('Dp').value = result.Dp;
//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]);
}
}
//You might have some other stuff to do here, but for most apps that's it for Main!
}
//Here's the app calculation
function CalcIt({ D, tmax, Crystal, n1, theta, lambda, L }) {
let PlotA = [], i = 0
const cVal = Crystal.split(" : ")
const n2 = parseFloat(cVal[1]), pi = Math.PI
//gamma is the penetration depth but we use the inverse!
const gamma = 1/(lambda/(2 * n2 * pi * Math.sqrt(Math.sin(theta) ** 2 - (n1 / n2) ** 2) ))
const Dp=(1/gamma*1e4).toFixed(2)
let nterms = 100
let At = 0, AtMax=0, sigma = 0, f,g
L*=2 //We use 2L
const L2=L*L, pi2=pi*pi //and 4L² and pi²
const gL=-gamma*L, g42=4*gamma**2
//console.log(D,n1,n2,theta,lambda,L,lambda/L,g, g*2*L)
//Now we brute force it
PlotA.push({x:0,y:0})
for (let t = 100; t < tmax; t += 10) {
sigma=0
for (let n = 0; n < nterms; n++){
f=pi*(2*n+1)/L
g=-D*(2*n+1)**2*pi2*t/L2
sigma+=Math.exp(g)*(f*Math.exp(gL)+(-1)**n*(2*gamma))/((2*n+1)*(g42+f*f))
}
At=1-8*gamma/(pi*(1-Math.exp(-L*gamma)))*sigma
AtMax=Math.max(At,AtMax)
PlotA.push({x:t/3600/24,y:At})
//a large nterms is only needed at short times
if (t > 20000 && nterms == 100) nterms=10
}
if (AtMax <1e-3) { //These gave numerical arterfacts
PlotA=[]
PlotA.push({x:0,y:0})
PlotA.push({x:tmax/3600/24,y:0})
}
const plotData = [PlotA], lineLabels = ["At/A∞"], myColors = ["blue"], borderWidth = [3]
//Now set up all the graphing data detail by detail.
const prmap = {
plotData: plotData, //An array of 1 or more datasets
lineLabels: lineLabels, //An array of labels for each dataset
colors: myColors, //An array of colors for each dataset
hideLegend: true,
borderWidth: borderWidth,
xLabel: 't&d', //Label for the x axis, with an & to separate the units
yLabel: 'At/A∞& ', //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: [0,], //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: 'F1', //These are the sig figs for the Tooltip readout. A wide choice!
ySigFigs: 'F3', //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: ['canvas'],
Dp: Dp,
};
}
The ATR technique
One side of a thin polymer film, thickness L, is exposed to your permeant. The other side is in contact with your ATR crystal. How you do this depends on your system and ingenuity, especially if you want T-dependent values. Here we assume that you have everything set up nicely.
The permeant has a distinctive peak in an area where the film has no absorption. [If you don't have good separation of absorption peaks, you'll have to do more data massaging before using the app]. At time t the absorption At is given by the standard Crank diffusion equation convoluted with penetration-depth dependence of the ATR setup, which in turn depends on crystal (especially its RI n2), angle θ, wavelength Peak cm-1 and also the RI of the polymer n1. In reality, and as is obvious, when L > a few μm the ATR correction makes little difference; but it's good to have it there. At will eventually plateau to A∞. Using the complex equation from Fieldson & Barbari, we can calculate the At/A∞ curve depending on the diffusion coefficient, D.
Although we could in principle load your data and find D by fitting, it's just as easy to find the best visual match to your experimental data by adjusting D when the other known parameters have been entered.
G. T. Fieldson and T. A. Barbari, The use of FTIR-ATR spectroscopy to characterize penetrant diffusion in polymers, Polymer, 1993, 34, 1146-1153