Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Gaussian plume — emission-rate parameter estimation

Parameter estimation — inferring Q from a downwind transect

In the forward notebook we generated a synthetic plume from a known emission rate Q. Here we run the inverse problem: given noisy downwind concentration measurements, recover Q via NumPyro’s NUTS sampler. The model is the same steady-state Gaussian plume, wrapped with a log-normal prior on the emission rate and a half-normal prior on an additive background. We’ll also repeat the fit with the atmospheric stability class treated as a discrete latent — a realistic scenario when local meteorology is uncertain.

1. Synthetic downwind transect

We pick a ground-level line of 24 receptors from x = 200 m to x = 2 km directly downwind of the source. The “true” emission rate is Q = 0.15 kg/s and the atmospheric stability is class D (neutral). Gaussian observation noise with σ = 2 × 10⁻⁸ kg/m³ is added on top of the forward-model output.

<Figure size 900x420 with 1 Axes>

2. NUTS with the stability class known

infer_emission_rate wraps the NumPyro gaussian_plume_model with a NUTS kernel, converts (wind_speed, wind_direction) to components, and returns the posterior as a dict of NumPy arrays. A short run (1 chain, 500 warmup, 800 samples) is plenty for this problem — the likelihood is very informative.

posterior mean Q = 0.1495 kg/s
posterior std  Q = 0.0012 kg/s
true Q           = 0.1500 kg/s

The marginal posterior over Q is tight around the true value. The overlaid dashed line marks the ground truth; we also show the 90 % credible interval.

<Figure size 900x400 with 1 Axes>

3. Posterior-predictive check

With the posterior in hand we push each draw back through the forward model and plot the predicted concentrations against the data. If our model is well-specified and the posterior well-calibrated, the data should sit comfortably inside the predictive envelope.

<Figure size 900x420 with 1 Axes>

4. Jointly inferring the stability class

In most operational scenarios the exact stability class is not known — at best we have a bracket like “neutral-ish” from nearby surface stations. NumPyro can sample a categorical latent over the six classes alongside the emission rate; internally the model indexes into the stacked Briggs parameter tensor. The resulting posterior over stability classes tells us which classes are consistent with the data.

Stability class posterior:
  A: 0.000
  B: 1.000
  C: 0.000
  D: 0.000
  E: 0.000
  F: 0.000

posterior mean Q (joint) = 0.6726 kg/s
posterior std  Q (joint) = 0.0058 kg/s

Two marginal posteriors side-by-side: the emission-rate histogram inflates slightly relative to the fixed-class fit (that’s the expected bias-variance trade — marginalising over stability adds uncertainty), and the categorical posterior concentrates mass on the neutral classes C–D.

<Figure size 1200x400 with 2 Axes>

Summary

NUTS recovers the emission rate Q to within a few percent of the truth from 24 noisy downwind observations, and jointly the posterior over the atmospheric stability class concentrates on the correct region (neutral classes). The next notebook extends this to a time-varying Q treated as a latent state sequence with a random-walk prior.