← All examples
44

Canonical Grid model

Adsorption — Fit and Inspect

Fit NIST Misra1a observations and inspect coefficients, residuals, stopping reason, and local identifiability.

Scale
Small
Source
chemistry-adsorption-fit-report.grid
Length
91 lines
Collection
Advanced language
Level
Intermediate
Runtime
Portable
Version
1.0.0
chemistry-adsorption-fit-report.grid
Get Grid
MODEL "Adsorption — Fit and Inspect"
DESCRIPTION "Fit NIST Misra1a observations and inspect coefficients, residuals, stopping reason, and local identifiability."
VERSION "1.0.0"
AUTHOR "Grid Team"
TAGS "chemistry", "adsorption", "fitting", "diagnostics"

# Observed NIST dental monomolecular adsorption data, Misra (1978).
# Source: https://www.itl.nist.gov/div898/strd/nls/data/LINKS/DATA/Misra1a.dat
# NIST names pressure and volume without specifying their measurement units.
PressureData = {77.6,114.9,141.1,190.8,239.9,289,332.8,378.4,434.8,477.3,536.8,593.1,689.1,760}
VolumeData = {10.07,14.73,17.94,23.93,29.61,35.18,40.02,44.82,50.76,55.05,61.01,66.40,75.47,81.78}

# These bounds guide initial guesses; they are not constraints on the fit.
input InitialCapacity = 250
  VALIDATE BETWEEN 1 AND 1000 MESSAGE "Choose an initial capacity from 1 to 1000 for this example."
input InitialScaledRate = 0.5
  VALIDATE BETWEEN 0.001 AND 5 MESSAGE "Choose an initial scaled rate from 0.001 to 5."
input IterationBudget = 1000
  VALIDATE BETWEEN 1 AND 1000 MESSAGE "Choose an integer iteration budget from 1 to 1000."

# Scaling the rate makes the parameter units explicit and improves scaling.
# The NIST coefficient b2 equals scaled_rate / 1000.
output Fit = CURVE_FIT.REPORT(
  (pressure, capacity, scaled_rate) => capacity*(1-EXP(-scaled_rate*pressure/1000)),
  PressureData, VolumeData, {InitialCapacity, InitialScaledRate}, 1e-12, IterationBudget
)
FitSucceeded = NOT ISERROR(Fit)
output Capacity = FitSucceeded THEN INDEX(Fit.parameters,1,1) ELSE #N/A
output RatePerPressure = FitSucceeded THEN INDEX(Fit.parameters,1,2)/1000 ELSE #N/A
output SumSquaredResiduals = FitSucceeded THEN Fit.ssr ELSE #N/A
output Interpretation = FitSucceeded THEN "Inspect Fit for the stopping reason, rank, conditioning, and residuals. A returned fit is not proof of model adequacy." ELSE "Fit failed. Inspect Fit for the native error and recovery advice."
output Assumptions = "Unweighted, unconstrained least squares. Residuals are fitted minus observed. No parameter confidence intervals are estimated. Scaling affects the reported Jacobian condition number."
output ScenarioGuide = "Save the initial state. Compare InitialCapacity 500 and InitialScaledRate 0.1 with the starting values 250 and 0.5. Set IterationBudget to 1 to inspect an explicit failure; restore 1000 to recover."

FitData = FitSucceeded THEN MAP(SEQUENCE(1,14), (observation) => {
  pressure: INDEX(PressureData,1,observation),
  measured: INDEX(VolumeData,1,observation),
  fitted: INDEX(Fit.fitted_values,1,observation),
  residual: INDEX(Fit.residuals,1,observation)
}) ELSE []

A1 = "Fitted capacity (NIST volume scale)"
B1 = Capacity
A2 = "Fitted b2 (per NIST pressure scale)"
B2 = RatePerPressure
A3 = "Sum of squared residuals"
B3 = SumSquaredResiduals
A5 = Interpretation
A6 = Assumptions
A7 = ScenarioGuide

Charts_1!type = "charts"
Charts_1!config = <toml>
  version = 1
  kind = "charts"
  title = "NIST adsorption fit"
  [layout]
  x_axis = "Pressure (NIST scale)"
  y_axis = "Volume (NIST scale)"
  [[charts]]
  type = "scatter"
  bind = "FitData"
  x = "pressure"
  y = "measured"
  label = "Observed"
  [[charts]]
  type = "line"
  smooth = false
  bind = "FitData"
  x = "pressure"
  y = "fitted"
  label = "Fitted at observation pressures"
</toml>

Charts_2!type = "charts"
Charts_2!config = <toml>
  version = 1
  kind = "charts"
  title = "Adsorption residuals"
  [layout]
  x_axis = "Pressure (NIST scale)"
  y_axis = "Fitted minus observed volume"
  [[charts]]
  type = "scatter"
  bind = "FitData"
  x = "pressure"
  y = "residual"
  label = "Residual"
</toml>

END MODEL