Inverse thermal diffusivity estimation with a physics-informed neural network.
Can a neural network recover a material's thermal diffusivity from sparse, noisy temperature measurements — and how does it compare with conventional parameter fitting?
This repository implements an inverse PINN in PyTorch on a 1-D heat-conduction problem with a known closed-form solution, so that every claim can be checked against ground truth. It compares the PINN against a nonlinear least-squares fit on the analytical solution and against a matched network trained without physics constraints.
With 50 noisy observations at 5% noise, the PINN recovered the thermal diffusivity to 1.39 ± 0.54 % (3 seeds), against 1.16 ± 0.94 % for a nonlinear least-squares fit on the closed-form solution. The two methods are statistically indistinguishable here — and the PINN is about 190,000× more expensive.
The more interesting finding is why they agree: across all nine runs their signed errors match in sign every time, with a Pearson correlation of 0.975. Both methods are limited by the same thing — the particular noise realisation — rather than by any property of the method. The PINN is extracting essentially all the information the measurements contain.
A matched network trained on the same data with the physics terms switched off reaches 60 ± 22 % field error and produces no parameter estimate at all.
A 1-D slab held at the reference temperature at both ends, with no internal heat source:
| Quantity | Symbol | Value |
|---|---|---|
| Slab length | 0.01 m | |
| Observation duration | 0.5 s | |
| True diffusivity (synthetic) |
|
Training directly in SI units is badly conditioned:
The network learns test_conversion_factor_is_not_one
asserts it explicitly.
Both terms are eigenfunctions of
Mode
The analytical solution generates the synthetic observations and scores the results. It is never shown to the PINN during training.
A tanh MLP (3 hidden layers, 48 units, 4,898 parameters) maps
Positivity by construction.
The residual is computed by automatic differentiation, not finite differences:
The first torch.autograd.grad call passes create_graph=True, which makes the
first derivative a differentiable expression rather than a plain number, so it can
be differentiated again.
The loss has four mean-squared-error terms:
$$\mathcal{L} = w_d \mathcal{L}{\rm data} + w_f \mathcal{L}{\rm PDE}
- w_i \mathcal{L}{\rm IC} + w_b \mathcal{L}{\rm BC}$$
| Term | Points | Weight |
|---|---|---|
| Data — fit the noisy measurements | 20 / 50 / 100 sensors | 1 |
| PDE — satisfy the heat equation | 4,000 collocation points | 1 |
| IC — match the known initial profile | 200 at |
10 |
| BC — hold the walls at zero | 200 at |
10 |
The initial and boundary conditions are weighted above the data because they are known exactly while the measurements are noisy. The value 10 is a round number that worked, not a tuned optimum — see Limitations.
The PDE term is the only one containing
Optimisation is Adam (8,000 iterations, lr 2e-3 halving every 2,000) followed by L-BFGS with a strong-Wolfe line search. The second stage is not optional: inverse PINNs frequently stall under Adam alone, and a stalled optimiser is indistinguishable from a failed method.
-
Observations are drawn away from the boundaries (
$\xi \in [0.05, 0.95]$ ) and after the initial instant ($\tau \geq 0.02$ ). A sensor on a boundary would only restate something the model is already told exactly. -
Noise is additive and homoscedastic:
$\sigma = 0.05 \times 1.2990 = 0.0650$ , a fixed fraction of the initial-profile peak, not a percentage of each reading. Scaling per reading would make measurements near zero almost exact and quietly make the problem easier. Late in the window the effective noise is around 10%. - Evaluation is on a dense, clean 101×101 grid — not at the sensor locations — so the reported errors measure the field between the sensors.
- Three seeds per setting. The seed determines the data draw, the network initialisation and the collocation points.
-
Failed runs are kept.
results/metrics.csvcontains every run; nothing is filtered.
All numbers below are printed from results/metrics.csv by
scripts/results_tables.py. Nothing is transcribed by hand.
| Seed | Method | alpha_hat (m^2/s) | alpha error (%) | field L2 (%) | time (s) |
|---|---|---|---|---|---|
| 0 | PINN | 9.8218e-06 | 1.78 | 0.567 | 377.0 |
| 0 | Analytical least squares | 9.9088e-06 | 0.91 | 0.275 | 0.0 |
| 0 | Data-only MLP | — | — | 80.065 | 14.9 |
| 1 | PINN | 1.0162e-05 | 1.62 | 0.681 | 386.6 |
| 1 | Analytical least squares | 1.0220e-05 | 2.20 | 0.658 | 0.0 |
| 1 | Data-only MLP | — | — | 36.421 | 16.0 |
| 2 | PINN | 9.9216e-06 | 0.78 | 0.332 | 382.8 |
| 2 | Analytical least squares | 9.9627e-06 | 0.37 | 0.112 | 0.0 |
| 2 | Data-only MLP | — | — | 63.481 | 15.3 |
| Observations | Method | alpha error (%) | field L2 error (%) | time (s) |
|---|---|---|---|---|
| 20 | PINN | 5.62 ± 4.26 | 1.873 ± 1.462 | 380.4 ± 2.3 |
| 20 | Analytical least squares | 5.32 ± 5.23 | 1.638 ± 1.649 | 0.0 ± 0.0 |
| 20 | Data-only MLP | n/a | 40.383 ± 1.062 | 10.1 ± 0.4 |
| 50 | PINN | 1.39 ± 0.54 | 0.526 ± 0.178 | 382.1 ± 4.8 |
| 50 | Analytical least squares | 1.16 ± 0.94 | 0.348 ± 0.280 | 0.0 ± 0.0 |
| 50 | Data-only MLP | n/a | 59.989 ± 22.031 | 15.4 ± 0.6 |
| 100 | PINN | 2.92 ± 1.01 | 0.949 ± 0.156 | 378.9 ± 5.5 |
| 100 | Analytical least squares | 2.98 ± 0.29 | 0.896 ± 0.095 | 0.0 ± 0.0 |
| 100 | Data-only MLP | n/a | 61.306 ± 21.302 | 17.6 ± 1.0 |
| Metric | PINN | Analytical least squares | Data-only MLP |
|---|---|---|---|
| Relative alpha error (%) | 1.39 ± 0.54 | 1.16 ± 0.94 | n/a |
| Relative L2 field error (%) | 0.526 ± 0.178 | 0.348 ± 0.280 | 59.989 ± 22.031 |
| Field MAE | 0.00245 ± 0.00075 | 0.00153 ± 0.00123 | 0.23153 ± 0.08543 |
| Max absolute field error | 0.00698 ± 0.00326 | 0.00439 ± 0.00352 | 1.96637 ± 0.84160 |
| PDE residual RMSE | 0.00647 ± 0.00074 | 0.00000 ± 0.00000 | 5.61991 ± 3.07276 |
| Time (s) | 382.13 ± 4.81 | 0.00 ± 0.00 | 15.42 ± 0.56 |
The PINN matches the analytical baseline, and that is the point. At every sensor count the two agree within the seed-to-seed spread. Given that the least-squares fit is handed the exact modal form of the solution and has one scalar left to fit, matching it means the PINN — which is told only the differential equation — is recovering close to all the information available in the data.
Both methods make the same errors. Across all nine runs the signed errors of the PINN and the least-squares fit agree in sign every time, with a Pearson correlation of 0.975. When the PINN is 10.5% low, the baseline is 11.2% low on the same data. The error is therefore set by the noise realisation, not by the method.
The error is not monotonic in sensor count — 5.62% at 20 observations, 1.39% at 50, 2.92% at 100 — and the honest reading is that this study cannot resolve the trend. The near-optimal baseline shows the same non-monotonic pattern (5.32%, 1.16%, 2.98%), which rules out a defect in the PINN and points at the obvious cause: with only three seeds, the variation between noise realisations is larger than the effect of sensor count over this range. Resolving it would need tens of seeds. The jump from 20 to 50 observations is large enough to survive that caveat; the difference between 50 and 100 is not.
The physics constraints are doing the work. The data-only MLP reaches 40–61% relative field error against 0.5–1.9% for the PINN, and its PDE residual RMSE is about 6.0 against 0.0065 — roughly three orders of magnitude worse. With the same architecture, the same data and the same optimiser budget, the only difference is the physics terms. It also cannot estimate the diffusivity at all: with no PDE term, nothing in the objective depends on the parameter.
The data loss stops at the noise floor. In the reference run it converged to 3.86×10⁻³ against a noise variance of σ² = 4.22×10⁻³ — visible as the dotted line in the loss figure. A model memorising the individual noise draws would have driven it toward zero. This is the clearest evidence that the network learned the field rather than the measurements.
Where the error lives. The error field shows a near-zero band at ξ ≈ 0.5 and two lobes either side, which is the node of the second sine mode. Error grows toward late times, where the signal has decayed and the effective noise is largest.
| Baseline | What it is | What it tests |
|---|---|---|
| Analytical least squares |
scipy.optimize.least_squares fitting |
Parameter recovery |
| Data-only MLP | Identical architecture, optimiser and budget; PDE/IC/BC terms switched off | What the physics constraints buy |
| Exact solution | Closed form at the true |
Ground truth, not a trained baseline |
The least-squares baseline is deliberately stronger than the PINN, and this comparison is not apples-to-apples. It is handed the exact modal form of the solution — two sine modes, the right wavenumbers, the right exponential structure — and has a single scalar left to fit. The PINN is told only the differential equation and must discover the field shape as well as the coefficient.
Its role is therefore as a yardstick for the data, not a competitor: it indicates
how much information about
The data-only MLP cannot estimate
Tested on Python 3.12, CPU only. No GPU required.
git clone https://github.com/dd1o7/ThermalLens.git
cd ThermalLens
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt # required before the tests will import
# 38 tests, a few seconds
pytest -q
# the full study: ~70 minutes on two CPU cores
python run_experiments.py
# regenerate the README's results tables from the CSV
python scripts/results_tables.pyrun_experiments.py writes results/metrics.csv, results/summary.json,
results/reference_run.npz and all four figures. Every number in this README is
printed from those files by scripts/results_tables.py — none is transcribed by
hand.
thermallens/
physics.py analytical solution, unit conversions
data.py synthetic observations, collocation/IC/BC sampling
model.py the MLP and the trainable positive mu
train.py PDE residual, the four losses, Adam + L-BFGS
baselines.py analytical least-squares fit
experiments.py the sweep, metrics, CSV output
figures.py the four figures
tests/ 38 tests
scripts/ results tables generated from the CSV
results/ metrics.csv, summary.json, figures/
The most important test is test_autograd_residual_matches_closed_form_when_mu_is_wrong.
A wrong second derivative raises no error — it silently produces a plausible wrong
answer — so testing a trained network against its own output would catch nothing.
Instead a stub exposing the model's interface returns the closed-form solution, and
the residual is checked both where it must vanish and where it must equal a known
non-zero quantity. The second case pins down sign and scale, which a
vanishing-residual test alone cannot.
The inverse crime. The data was generated with the same equation the PINN was then given. This measures whether the method's machinery works; it does not show that a PINN recovers diffusivity from real measurements. A real experiment has contact resistance, unmodelled heat loss, a temperature-dependent diffusivity and imperfect boundaries — all model error, and model error is what breaks inverse methods in practice.
Scope. One dimension, one unknown coefficient, one geometry, one initial profile, one noise model (Gaussian, i.i.d.).
Loss weights are not tuned.
No uncertainty estimate. The method returns a point estimate with no confidence interval. On real data, with no ground truth to compare against, that is close to useless. Ensembling or a Bayesian PINN would be the route.
Three seeds is few — enough to show the spread exists, not to characterise it. Error bars in the figures are ±1 standard deviation over three runs, which is an indication of spread and not a confidence interval.
The noise sweep was not run. The code supports 0% / 5% / 10%; only 5% was executed, because the sensor-count study had priority.
Boundaries are enforced softly. Multiplying the network output by
- Hard-constrain the boundary conditions and measure the difference.
- Run the noise sweep the code already supports.
- Make the diffusivity temperature-dependent — this breaks the analytical baseline entirely, which is where a PINN starts to earn its cost.
- Add an uncertainty estimate, starting with an ensemble over seeds.
- Move to 2-D, where no closed form exists and a mesh-free method has a real advantage.
The code in this repository was written with AI assistance. The scientific design — which experiments to run, which baselines make the comparison fair, what the metrics should measure — and the interpretation of the results are my own. Every implementation decision here is one I can explain and defend.
No claim of novelty is made. This is a careful re-implementation of a known method on a problem small enough to verify completely.
- Raissi, Perdikaris & Karniadakis (2019), Physics-informed neural networks, Journal of Computational Physics 378, 686–707 — the formulation used here, including the inverse-problem variant.
- Cornell Virtual Workshop, Scientific Machine Learning — tutorial treatment of learning unknown coefficients jointly with a network.
MIT



