- Prepare a stock solution and dilute it
- pH of a weak acid without the approximation
- Amount of gas in a pressurized cylinder
- Equivalence point of a weak acid titration
- Balancing a redox equation with linear algebra
- Isotope pattern of a tetrachloro compound in MS
- Lattice parameter, X-ray density and crystallite size from XRD
- Method bias on a certified reference material
- Two dyes in one drink by least squares (QR)
- Phosphate buffer with activity correction (Davies)
Prepare a stock solution and dilute it
A lab technician must prepare 250 mL of 0.150 mol/L NaCl, then 100 mL at 0.010 mol/L from it. The script gives the mass to weigh (n = C.V, m = n.M) and the stock volume to pipette (C1V1 = C2V2), with the units checked.
#1. Mass of NaCl to weigh for 250 mL at 0.150 mol/L; -> "[m]" (unit calc "0.150 mol/L * 250 mL * 58.44 g/mol" "g"); #2. Volume of this stock for 100 mL at 0.010 mol/L, C1V1 = C2V2; -> "[v1]" (unit calc "0.010 mol/L * 100 mL / (0.150 mol/L)" "mL"); #3. Report; concat "Weigh " (math round [m] 3) " g of NaCl into a 250 mL flask, then pipette " (math round [v1] 2) " mL of it and make up to 100 mL."
Weigh 2.192 g of NaCl into a 250 mL flask, then pipette 6.67 mL of it and make up to 100 mL.
- Propagate the balance and flask tolerances with
num uncertainty. - Use
unit calcwith mg/L or ppm-like ratios for trace standards.
pH of a weak acid without the approximation
What is the pH of 0.10 mol/L acetic acid (Ka = 1.8e-5)? The script solves the exact equilibrium x^2/(C - x) = Ka, compares it with the classic sqrt(Ka.C) shortcut and gives the degree of dissociation.
#1. Acetic acid 0.10 mol/L, Ka = 1.8e-5; -> "[c]" 0.10; -> "[ka]" 1.8e-5; #2. Exact [H+]: x^2 / (c - x) = Ka; -> "[h]" (num solve (concat "x^2 - " [ka] "*(" [c] " - x)") "x" 0 [c]); #3. pH exact, pH with the approximation sqrt Ka*c, degree of dissociation; -> "[ph]" (math calc "-log10([h])"); -> "[ph_approx]" (math calc "-log10(sqrt([ka]*[c]))"); concat "pH = " (math round [ph] 3) " (approx. " (math round [ph_approx] 3) "), alpha = " (math round (math calc "100*[h]/[c]") 2) " %"
pH = 2.875 (approx. 2.872), alpha = 1.33 %
- Add the water autoprotolysis for very dilute acids with
num solve_system. - Plot the titration curve with
num tableover the added base volume.
Amount of gas in a pressurized cylinder
How much nitrogen is in a 50 L cylinder at 200 bar and 20 degC? The script compares the ideal gas law n = PV/RT with the van der Waals equation (a = 0.137 Pa.m6/mol2, b = 3.87e-5 m3/mol) solved for n, and gives the mass in kg.
#1. Nitrogen cylinder: 50 L at 200 bar and 20 degC; -> "[n_ideal]" (unit calc "200 bar * 50 L / (_R * 293.15 K)" "mol"); #2. Van der Waals for N2: a = 0.137 Pa.m6/mol2, b = 3.87e-5 m3/mol, SI units; -> "[n_vdw]" (num solve "(200e5 + 0.137*n^2/0.05^2)*(0.05 - n*3.87e-5) - n*8.314462618*293.15" "n" 1 600); #3. Mass of gas in kg, M = 28.014 g/mol; concat "Ideal gas: " (math round [n_ideal] 1) " mol, van der Waals: " (math round [n_vdw] 1) " mol = " (math round (math calc "[n_vdw]*28.014/1000") 2) " kg of N2"
Ideal gas: 410.3 mol, van der Waals: 408.9 mol = 11.45 kg of N2
- Compute the compressibility factor Z = PV/nRT from both results.
- Get R, Vm or NA with their CODATA metadata using
const get.
Equivalence point of a weak acid titration
Acetic acid titrated by 0.100 mol/L NaOH: the equivalence volume is the steepest point of the pH curve. The script differentiates a monotone interpolation on a fine grid, takes the maximum of the slope, reads the pKa at half equivalence and gives the acid concentration.
#1. 25.0 mL of acetic acid titrated with NaOH 0.100 mol/L: volume in mL and pH; -> "[V]" "[0, 2, 4, 6, 8, 9, 9.5, 9.8, 10, 10.2, 10.5, 11, 12, 14]"; -> "[pH]" "[2.88, 4.15, 4.57, 4.93, 5.35, 5.70, 6.03, 6.45, 8.72, 10.98, 11.38, 11.68, 11.96, 12.22]"; #2. Slope dpH/dV of the monotone interpolation on a fine grid from 9 to 11 mL; -> "[g]" (matrix col (num table "v" "v" 9 11 0.01) 1); -> "[d]" (num interpolate_derivative [V] [pH] [g] "pchip"); #3. Equivalence volume at the steepest point; json load "rx_eq" (dsp peaks [d] "{\"distance\": 1000}"); -> "[Veq]" (math calc (concat "9+0.01*" (json select "rx_eq" /indices[0]))); #4. pKa = pH at half equivalence, and the acid concentration; -> "[pKa]" (num interpolate [V] [pH] (math calc "[Veq]/2") "pchip"); concat "Veq = " (math round [Veq] 2) " mL, max slope = " (math round (json select "rx_eq" /values[0]) 1) " pH/mL, pKa = " (math round [pKa] 2) ", c acid = " (math round (math calc "0.100*[Veq]/25.0") 4) " mol/L"
Veq = 9.93 mL, max slope = 14.3 pH/mL, pKa = 4.74, c acid = 0.0397 mol/L
- Draw the curve and its interpolation with
plot interpolate [V] [pH] "pchip". - The second derivative (order 2 of
num interpolate_derivative) crosses zero at the equivalence point.
Balancing a redox equation with linear algebra
Balance KMnO4 + HCl -> KCl + MnCl2 + Cl2 + H2O. Each element gives one equation; the reduced row echelon form of the composition matrix gives the null space, and the lcm of the denominators gives the smallest integer coefficients.
#1. Permanganate oxidation of hydrochloric acid: a KMnO4 + b HCl -> c KCl + d MnCl2 + e Cl2 + f H2O; #Rows K, Mn, O, H, Cl, one column per species, products negative; -> "[A]" "[[1, 0, -1, 0, 0, 0], [1, 0, 0, -1, 0, 0], [4, 0, 0, 0, 0, -1], [0, 1, 0, 0, 0, -2], [0, 1, -1, -2, -2, 0]]"; json load "rx_tu1_sp" (string split "KMnO4 HCl KCl MnCl2 Cl2" " " -1); #2. Reduced row echelon form: rank 5, one-dimensional null space, coefficients relative to f = 1; -> "[R]" (matrix rref [A]); -> "[co]" ""; -> "[l]" 1; for (-> "[i]" 1) (<= [i] 5) (++ "[i]") { -> "[q]" (arith to_fraction (math calc (concat "-1*" (matrix get [R] [i] 6)))); -> "[co]" (concat [co] [q] " "); #3. Least common multiple of the denominators; -> "[l]" (arith lcm [l] (string regexp_extract (concat [q] "/1") "/([0-9]+)" 1)); }; #4. Smallest integer coefficients; -> "[eq]" ""; for (-> "[i]" 1) (<= [i] 5) (++ "[i]") { -> "[eq]" (concat [eq] (arith frac_mul (arith to_fraction (math calc (concat "-1*" (matrix get [R] [i] 6)))) [l]) " " (json select "rx_tu1_sp" (concat "/[" (- [i] 1) "]"))); if (== [i] 2) {-> "[eq]" (concat [eq] " -> ");} {-> "[eq]" (concat [eq] " + ");}; }; concat "rank " (matrix rank [A]) ", ratios to H2O: " [co] "=> " [eq] [l] " H2O"
rank 5, ratios to H2O: 1/4 2 1/4 1/4 5/8 => 2 KMnO4 + 16 HCl -> 2 KCl + 2 MnCl2 + 5 Cl2 + 8 H2O
- Check the electron balance: Mn +7 -> +2 gains 5, Cl -1 -> 0 loses 1, so 2 Mn for 10 Cl
- Try an equation with two independent reactions: the rank drops by 2 and the null space has 2 vectors
Isotope pattern of a tetrachloro compound in MS
Predict the relative intensities of the M, M+1, M+2, M+4 and M+6 peaks of C6H2Cl4 from the binomial law of the chlorine isotopes and the 13C abundance, to recognise the Cl4 signature in a mass spectrum.
#1. Molecular ion of a tetrachlorobenzene C6H2Cl4: 4 chlorine atoms, abundance of 37Cl = 0.2424; -> "[n]" 4; -> "[p]" 0.2424; #2. Binomial coefficients from the Gamma function: C n,k = n! / k!.n-k!; -> "[c1]" (math calc (concat (math gamma 5) "/(" (math gamma 2) "*" (math gamma 4) ")")); -> "[c2]" (math calc (concat (math gamma 5) "/(" (math gamma 3) "*" (math gamma 3) ")")); #3. Peaks M, M+2, M+4 relative to M: C n,k.p^k.1-p^-k; -> "[m2]" (math calc "100*[c1]*([p]/(1-[p]))"); -> "[m4]" (math calc "100*[c2]*([p]/(1-[p]))^2"); -> "[m6]" (math calc "100*4*([p]/(1-[p]))^3"); #4. 13C contribution to M+1 for 6 carbons, by log-Gamma to avoid big factorials; -> "[m1]" (math calc (concat "100*exp(" (math lngamma 7) "-" (math lngamma 6) "-" (math lngamma 2) ")*0.0107/0.9893")); concat "M = 100, M+1 = " (math round [m1] 1) ", M+2 = " (math round [m2] 1) ", M+4 = " (math round [m4] 1) ", M+6 = " (math round [m6] 1) " : the 100 / 128 / 61 pattern is the signature of Cl4"
M = 100, M+1 = 6.5, M+2 = 128, M+4 = 61.4, M+6 = 13.1 : the 100 / 128 / 61 pattern is the signature of Cl4
- Combine Cl and Br patterns by convolution with
num poly_mul. - For a big molecule like a peptide, use
math lngammafor all the binomial terms.
Lattice parameter, X-ray density and crystallite size from XRD
From the 200 reflection of NaCl, get the d-spacing (Bragg), the cubic cell edge, the X-ray density, the cell edge implied by a pycnometer density, and the crystallite size by the Scherrer equation.
#1. Powder XRD of NaCl with Cu K-alpha 1.5406 angstrom: 200 reflection at 2theta = 31.69 deg, FWHM 0.18 deg; -> "[lam]" 1.5406; -> "[th]" (math calc "31.69/2*pi/180"); -> "[beta]" (math calc "0.18*pi/180"); #2. Bragg law d = lambda/2.sin theta, cubic cell a = d.sqrt of h2+k2+l2; -> "[d]" (math calc (concat "[lam]/(2*" (math sin [th]) ")")); -> "[a]" (math calc (concat "[d]*" (math hypot (math hypot 2 0) 0))); #3. X-ray density: Z = 4 NaCl per cell, M = 58.44 g/mol; -> "[rho]" (unit calc (concat "4 * 58.44 g/mol / (_NA * (" [a] " angstrom)^3)") "g/cm^3"); #4. Cell edge that a pycnometer density of 2.160 g/cm3 would imply, by the cube root; -> "[a2]" (math cbrt (unit calc "4 * 58.44 g/mol / (_NA * 2.160 g/cm^3)" "angstrom^3")); #5. Scherrer crystallite size K.lambda.sec theta / beta, K = 0.9; -> "[size]" (math calc (concat "0.9*[lam]*" (math sec [th]) "/[beta]/10")); concat "d200 = " (math round [d] 4) " A, a = " (math round [a] 4) " A, X-ray density = " (math round [rho] 3) " g/cm3, a from the pycnometer = " (math round [a2] 4) " A, crystallite size = " (math round [size] 1) " nm"
d200 = 2.8212 A, a = 5.6425 A, X-ray density = 2.161 g/cm3, a from the pycnometer = 5.6432 A, crystallite size = 45.9 nm
- Refine a on several reflections with a Nelson-Riley extrapolation and
stat fit. - Subtract the instrumental broadening before Scherrer.
Method bias on a certified reference material
Check an ICP-MS method on a certified soil: Dixon Q test on a suspect replicate, then z-test of the mean against the certified value with the known method sigma, and recovery.
#1. Lead in a certified soil, ICP-MS, 7 replicates in mg/kg, certified 25.3 and method sigma 0.6; -> "[x]" "[25.9, 26.1, 25.6, 27.9, 26.0, 25.8, 26.3]"; #2. Dixon Q test on the suspect high value, on the sorted series; -> "[s]" (math order_double [x]); -> "[q]" (math calc (concat "(" (matrix get (concat "[" [s] "]") 1 7) "-" (matrix get (concat "[" [s] "]") 1 6) ")/(" (stat range [x]) ")")); #3. Q critical at 95 % for n = 7 is 0.568: drop the outlier, then z-test of the bias with the known sigma; -> "[x6]" "[25.9, 26.1, 25.6, 26.0, 25.8, 26.3]"; json load "rx_p4c_crm" (stat ztest [x6] 25.3 0.6); #4. Recovery; concat "Q = " (math round [q] 3) " > 0.568: 27.9 rejected; mean = " (math round (stat mean [x6]) 2) " mg/kg, recovery = " (math round (math calc (concat "100*" (stat mean [x6]) "/25.3")) 1) " %, z = " (math round (json select "rx_p4c_crm" /z) 2) ", p = " (math round (json select "rx_p4c_crm" /p_value) 4) " : significant bias"
Q = 0.696 > 0.568: 27.9 rejected; mean = 25.95 mg/kg, recovery = 102.6 %, z = 2.65, p = 0.008 : significant bias
- Use the En number with the uncertainty of the certificate instead of a z-test.
- Use
stat shapiroto check the normality before the test.
Two dyes in one drink by least squares (QR)
Determine two food dyes from absorbances at four wavelengths: solve the overdetermined Beer-Lambert system by the QR decomposition and read the residual norm of the fit directly in Q^T.A.
#1. Absorbance of a drink with two dyes at 430, 480, 580, 630 nm, 1 cm cell; -> "[e]" "[[27, 1], [15, 2.5], [0.3, 40], [0.05, 120]]"; -> "[b]" "[[0.545], [0.314], [0.205], [0.600]]"; #2. Overdetermined Beer-Lambert system E.c = A by QR: Q.R = E, molar absorptivities in L/mmol/cm; json load "rx_p4c_qr" (matrix qr [e]); -> "[y]" (matrix mul (matrix transpose (json select "rx_p4c_qr" /Q)) [b]); #3. Back substitution with the upper 2x2 block of R; -> "[r]" (concat "[[" (json select "rx_p4c_qr" /R/[0]/[0]) ", " (json select "rx_p4c_qr" /R/[0]/[1]) "], [0, " (json select "rx_p4c_qr" /R/[1]/[1]) "]]"); -> "[c]" (matrix solve [r] (concat "[" (matrix get [y] 1 1) ", " (matrix get [y] 2 1) "]")); #4. The last 2 components of Q^T.A give the residual norm of the fit; -> "[res]" (vector norm (concat "[" (matrix get [y] 3 1) ", " (matrix get [y] 4 1) "]")); concat "tartrazine = " (math round (math calc (concat "1000*" (matrix get (concat "[" [c] "]") 1 1))) 2) " umol/L, brilliant blue = " (math round (math calc (concat "1000*" (matrix get (concat "[" [c] "]") 1 2))) 2) " umol/L, residual norm = " (math round [res] 4) " AU"
tartrazine = 20.02 umol/L, brilliant blue = 4.99 umol/L, residual norm = 0.0015 AU
- Use the whole spectrum (100 wavelengths) for a classical least squares calibration.
- Compare with
matrix pinvand with the normal equations.
Phosphate buffer with activity correction (Davies)
Prepare 1 L of 0.100 M phosphate buffer at pH 7.40: solve the HPO4/H2PO4 ratio with the Davies activity coefficients (the ionic strength depends on the ratio) and give the masses to weigh.
#1. Phosphate buffer 0.100 M total at pH 7.40 and 25 degC, pKa2 = 7.198 at zero ionic strength; -> "[ct]" 0.100; -> "[pka]" 7.198; #2. Davies equation log gamma = -0.509.z^2.sqrt I / 1+sqrt I - 0.3 I, NaH2PO4 + Na2HPO4: I = ct + 2.base; -> "[lg]" "(-0.509*(sqrt(I)/(1+sqrt(I))-0.3*I))"; -> "[ph]" (concat "7.198 + log10(r) + 4*LG - LG - 7.40"); #3. Solve the base/acid ratio r with I = 0.1 + 2.0.1.r/1+r; -> "[eq]" (string replace (string replace [ph] "LG" [lg]) "I" "(0.1+0.2*r/(1+r))"); -> "[r]" (num solve [eq] "r" 0.1 10); #4. Masses for 1 L: NaH2PO4.H2O 137.99 g/mol, Na2HPO4.7H2O 268.07 g/mol; -> "[base]" (math calc "[ct]*[r]/(1+[r])"); concat "ratio HPO4/H2PO4 = " (math round [r] 3) " instead of " (math round (math power 10 (math calc "7.40-[pka]")) 3) " without activity; I = " (math round (math calc "0.1+2*[base]") 3) " M; weigh " (math round (math calc "([ct]-[base])*137.99") 2) " g NaH2PO4.H2O and " (math round (math calc "[base]*268.07") 2) " g Na2HPO4.7H2O"
ratio HPO4/H2PO4 = 3.968 instead of 1.592 without activity; I = 0.26 M; weigh 2.78 g NaH2PO4.H2O and 21.41 g Na2HPO4.7H2O
- Add the temperature dependence of pKa2 (dpKa/dT = -0.0028 /K).
- Check the buffer capacity beta = 2.303.C.Ka.H / Ka+H ^2.
