You can measure vibration but not crack length. You can observe GPS, camera, and IMU signals but not the true state of the vehicle. You can see reflected radiation from a landscape but not the quantity you really care about on the ground. Inverse mathematics begins when the thing you want is not the thing you can measure.
The forward problem asks: if the state were known, what measurements would it produce? The inverse problem asks: given the measurements, what can you recover about the state or parameters?
The inverse direction is unstable. Small measurement errors can produce large errors in the recovered quantity. A good estimation method therefore does two jobs at once: it produces an estimate and it keeps track of how much trust the data deserves.
68.1 What this chapter helps you do
Symbols to keep handy
These are the bits of notation you'll see a lot. If a line of symbols feels like a fence, read it out loud once, then keep going.
H: H — the observation operator or measurement matrix
= H + : measurement equals observation map applied to the state plus noise
K: K — the update weight; a scalar between 0 and 1 in the scalar case, a matrix (the Kalman gain) in the vector case
^+: x hat plus — the state estimate after a measurement update
^-: x hat minus — the state estimate before a measurement update
Definitions to keep handy
These are the words we keep coming back to. If one feels slippery, come back here and steady it before you push on.
forward problem: Given the state, predict what the measurements should look like.
inverse problem: Given measurements, infer the state or parameters that produced them.
noise: The unavoidable randomness and error in measurements.
filtering: Updating an estimate as new data arrives, balancing what the model says with what the sensor says.
Kalman gain: The update weight that decides how much the estimate moves toward the new measurement.
Here is the main move this chapter is making, in plain terms. You do not need to be fast. You just need to keep the thread.
Coming in: You can observe outputs, but not always the state or parameter you actually care about.
Leaving with: Estimation turns uncertain observations into defensible inferences about hidden states, parameters, and trajectories.
68.2 Forward and inverse viewpoints
Let \mathbf{x} be the hidden state you care about and \mathbf{y} the measured data you actually have. A simple measurement model is
Measurement model, in words
The line
\mathbf{y} = H\mathbf{x} + \varepsilon
says: what you measure is a blurred/partial view of the true state, plus some messiness you do not control.
H is the measuring device as mathematics: it tells you what part of the state you can see.
\varepsilon is the noise and error you cannot avoid.
\mathbf{y} = H\mathbf{x} + \varepsilon
where H maps the state into observation space and \varepsilon represents measurement noise.
The forward problem is:
given \mathbf{x}, compute \mathbf{y}
The inverse problem is:
given \mathbf{y}, estimate \mathbf{x}
The inverse direction is harder because the map may be noisy, incomplete, or many-to-one. Some parts of the state may be visible in the data. Others may be weakly observable or not observable at all. Estimation is not algebraic rearrangement. It is controlled inference under limited information.
Use the controls to see how the estimate shifts as you trust the sensor more or less strongly.
Code
{const width =660;const height =360;const pad = { top:24,right:18,bottom:48,left:48 };const plotW = width - pad.left- pad.right;const plotH = height - pad.top- pad.bottom;const BLUE ="#2563eb";const AMBER ="#f59e0b";const GREEN ="#059669";const RED ="#ef4444";const GREY ="#6b7280";const GRID ="#d1d5db";const BG ="#f8fafc";const N =36;let noise =1.2;let gain =0.45;functionmakeSvg(tag) {returndocument.createElementNS("http://www.w3.org/2000/svg", tag); }functionrng(i) {const x =Math.sin(91.17* (i +1) +0.37) *43758.5453123;return x -Math.floor(x); }functionxScale(i) {return pad.left+ (i / (N -1)) * plotW; }functionbuildSeries(noiseLevel, K) {const truth = [];const meas = [];const prior = [];const post = [];let xhat =17.5;for (let i =0; i < N; i +=1) {const t = i;const trueVal =18+0.18* i+1.3*Math.sin(0.25* i)+0.55*Math.cos(0.11* i +0.5);const measurement = trueVal + (rng(i) -0.5) *2* noiseLevel;const prediction = i ===0? xhat : xhat +0.18;const updated = prediction + K * (measurement - prediction); truth.push({ i,y: trueVal }); meas.push({ i,y: measurement }); prior.push({ i,y: prediction }); post.push({ i,y: updated,innovation: measurement - prediction }); xhat = updated; t; }const ys = [...truth.map(p => p.y),...meas.map(p => p.y),...prior.map(p => p.y),...post.map(p => p.y) ];const ymin =Math.min(...ys) -1;const ymax =Math.max(...ys) +1;const yScale = y => pad.top+ plotH * (1- (y - ymin) / (ymax - ymin));return { truth, meas, prior, post, yScale, ymin, ymax }; }functionpathFor(points, yScale) {return points.map((p, idx) =>`${idx ===0?"M":"L"}${xScale(p.i).toFixed(2)}${yScale(p.y).toFixed(2)}`).join(" "); }const container =document.createElement("div"); container.className="wh-widget"; container.style.maxWidth="700px";const controls =document.createElement("div"); controls.className="wh-controls-grid";const noiseLabel =document.createElement("label"); noiseLabel.textContent="Measurement noise";const noiseSlider =document.createElement("input"); noiseSlider.type="range"; noiseSlider.min="0.2"; noiseSlider.max="3.0"; noiseSlider.step="0.1"; noiseSlider.value=String(noise);const noiseValue =document.createElement("span"); noiseValue.className="wh-num"; noiseValue.style.minWidth="4rem";const gainLabel =document.createElement("label"); gainLabel.textContent="Update weight K";const gainSlider =document.createElement("input"); gainSlider.type="range"; gainSlider.min="0.05"; gainSlider.max="0.95"; gainSlider.step="0.05"; gainSlider.value=String(gain);const gainValue =document.createElement("span"); gainValue.className="wh-num"; gainValue.style.minWidth="4rem"; controls.append(noiseLabel, noiseSlider, noiseValue, gainLabel, gainSlider, gainValue);const readout =document.createElement("div"); readout.className="wh-readout-grid-6";functionmakeCard(keyText) {const card =document.createElement("div"); card.className="wh-card";const key =document.createElement("div"); key.className="wh-card-k"; key.textContent= keyText;const value =document.createElement("div"); value.className="wh-card-v"; card.append(key, value); readout.append(card);return value; }const truthCard =makeCard("Current truth");const measCard =makeCard("Measurement");const priorCard =makeCard("Prior");const postCard =makeCard("Updated estimate");const innovationCard =makeCard("Innovation");const regimeCard =makeCard("Regime");const svg =makeSvg("svg"); svg.setAttribute("viewBox",`0 0 ${width}${height}`); svg.setAttribute("width","100%"); svg.classList.add("wh-board");const bg =makeSvg("rect"); bg.setAttribute("x","0"); bg.setAttribute("y","0"); bg.setAttribute("width", width); bg.setAttribute("height", height); bg.setAttribute("fill", BG); svg.appendChild(bg);const grid =makeSvg("g");const paths =makeSvg("g");const dots =makeSvg("g"); svg.append(grid, paths, dots);const truthPath =makeSvg("path"); truthPath.setAttribute("fill","none"); truthPath.setAttribute("stroke", BLUE); truthPath.setAttribute("stroke-width","3");const priorPath =makeSvg("path"); priorPath.setAttribute("fill","none"); priorPath.setAttribute("stroke", GREY); priorPath.setAttribute("stroke-width","2"); priorPath.setAttribute("stroke-dasharray","6 5");const postPath =makeSvg("path"); postPath.setAttribute("fill","none"); postPath.setAttribute("stroke", GREEN); postPath.setAttribute("stroke-width","3"); paths.append(truthPath, priorPath, postPath);const xAxis =makeSvg("line"); xAxis.setAttribute("x1", pad.left); xAxis.setAttribute("x2", pad.left+ plotW); xAxis.setAttribute("y1", pad.top+ plotH); xAxis.setAttribute("y2", pad.top+ plotH); xAxis.setAttribute("stroke", GREY); xAxis.setAttribute("stroke-width","1.5"); svg.appendChild(xAxis);const yAxis =makeSvg("line"); yAxis.setAttribute("x1", pad.left); yAxis.setAttribute("x2", pad.left); yAxis.setAttribute("y1", pad.top); yAxis.setAttribute("y2", pad.top+ plotH); yAxis.setAttribute("stroke", GREY); yAxis.setAttribute("stroke-width","1.5"); svg.appendChild(yAxis);const note =document.createElement("p"); note.className="wh-caption"; note.style.color="#374151";const legend =document.createElement("div"); legend.style.cssText="display:flex; gap:1rem; flex-wrap:wrap; margin-top:0.55rem; font-size:0.9rem; color:#374151;";functionlegendItem(color, dash, text, dot =false) {const item =document.createElement("div"); item.style.cssText="display:inline-flex; align-items:center; gap:0.45rem;";const swatch =document.createElement("span");if (dot) { swatch.style.cssText=`display:inline-block; width:0.7rem; height:0.7rem; border-radius:999px; background:${color};`; } else { swatch.style.cssText=`display:inline-block; width:1.6rem; height:0; border-top:3px ${dash ?"dashed":"solid"}${color};`; }const label =document.createElement("span"); label.textContent= text; item.append(swatch, label);return item; } legend.append(legendItem(BLUE,false,"true state"),legendItem(AMBER,false,"measurements",true),legendItem(GREY,true,"prior estimate"),legendItem(GREEN,false,"updated estimate") );functionrender() { noise =parseFloat(noiseSlider.value); gain =parseFloat(gainSlider.value); noiseValue.textContent= noise.toFixed(1); gainValue.textContent= gain.toFixed(2);const { truth, meas, prior, post, yScale, ymin, ymax } =buildSeries(noise, gain); grid.replaceChildren(); [-1,-0.5,0,0.5,1].forEach(() => {});for (let i =0; i <=6; i +=1) {const x = pad.left+ (i /6) * plotW;const line =makeSvg("line"); line.setAttribute("x1", x); line.setAttribute("x2", x); line.setAttribute("y1", pad.top); line.setAttribute("y2", pad.top+ plotH); line.setAttribute("stroke", GRID); line.setAttribute("stroke-width","1"); line.setAttribute("opacity","0.75"); grid.appendChild(line); }for (let i =0; i <=4; i +=1) {const yVal = ymin + (i /4) * (ymax - ymin);const y =yScale(yVal);const line =makeSvg("line"); line.setAttribute("x1", pad.left); line.setAttribute("x2", pad.left+ plotW); line.setAttribute("y1", y); line.setAttribute("y2", y); line.setAttribute("stroke", GRID); line.setAttribute("stroke-width","1"); line.setAttribute("opacity","0.75"); grid.appendChild(line);const label =makeSvg("text"); label.setAttribute("x", pad.left-10); label.setAttribute("y", y +4); label.setAttribute("fill", GREY); label.setAttribute("font-size","12"); label.setAttribute("text-anchor","end"); label.textContent= yVal.toFixed(1); grid.appendChild(label); } truthPath.setAttribute("d",pathFor(truth, yScale)); priorPath.setAttribute("d",pathFor(prior, yScale)); postPath.setAttribute("d",pathFor(post, yScale)); dots.replaceChildren(); meas.forEach((p, idx) => {const circle =makeSvg("circle"); circle.setAttribute("cx",xScale(p.i)); circle.setAttribute("cy",yScale(p.y)); circle.setAttribute("r", idx === N -1?"4.5":"3.2"); circle.setAttribute("fill", AMBER); circle.setAttribute("stroke","#92400e"); circle.setAttribute("stroke-width","1"); dots.appendChild(circle); });const last = N -1; truthCard.textContent= truth[last].y.toFixed(2); measCard.textContent= meas[last].y.toFixed(2); priorCard.textContent= prior[last].y.toFixed(2); postCard.textContent= post[last].y.toFixed(2); innovationCard.textContent= post[last].innovation>=0?`+${post[last].innovation.toFixed(2)}`: post[last].innovation.toFixed(2); innovationCard.style.color=Math.abs(post[last].innovation) > noise ? RED : GREY;// Simple qualitative guide: when K is high while noise is high, the estimate chases noise.// When K is low while noise is low, the estimate ignores good data and lags.const twitchy = gain >0.65&& noise >1.6;const sluggish = gain <0.25&& noise <1.0; regimeCard.textContent= twitchy ?"twitchy (chases noise)": sluggish ?"sluggish (lags)":"balanced"; regimeCard.style.color= twitchy ? RED : sluggish ? AMBER : GREEN; note.textContent= gain >0.7?"High K makes the estimate chase the measurements closely, which improves responsiveness but also passes more sensor noise into the estimate.": gain <0.3?"Low K makes the estimate trust the prior strongly, which smooths noise but can lag behind real changes in the hidden state.":"Intermediate K balances responsiveness against noise suppression: the estimate moves toward the data without fully inheriting measurement jitter."; } noiseSlider.addEventListener("input", render); gainSlider.addEventListener("input", render); container.append(controls, readout, svg, legend, note);render();return container;}
68.3 The simplest update logic
Suppose you have a prior estimate \hat{x}^- and then receive a new measurement y. In the scalar case where the measurement directly observes the state (H = 1), a simple update rule is
Update step, in words
\hat{x}^+ = \hat{x}^- + K(y - \hat{x}^-)
says: start from your prior, then move partway toward the measurement.
K is the knob: K=0 ignores the sensor, K=1 snaps to the sensor, and values in between compromise.
\hat{x}^+ = \hat{x}^- + K(y - \hat{x}^-)
where:
\hat{x}^- is the estimate before the measurement update
y - \hat{x}^- is the innovation or residual
K is the update weight — the same letter used for control gain in Vol 8 Chapter 1 and for the stiffness matrix in Vol 8 Chapter 4; here it plays a different role as a scalar between 0 and 1
In the general case with a non-trivial observation operator H, the innovation becomes y - H\hat{x}^-, but the structure of the weighted update is the same.
If K is close to 1, you trust the new measurement strongly. If K is close to 0, you trust the existing estimate strongly. Filtering methods differ in how they compute or adapt this weight, but the logic is already visible here. This means the update is a controlled compromise, not a hard switch from model to measurement.
The rule makes the compromise explicit. Estimation is never “use the measurement” or “use the model.” It is always a weighted combination of both.
68.4 Parameter estimation and state estimation
Not every inverse problem asks for the same thing.
State estimation
You want the current hidden state of a dynamic system: position, velocity, temperature field, internal concentration, or machine condition.
Parameter estimation
You want the unknown constant or slowly varying quantity in the model: diffusivity, drag coefficient, reaction rate, calibration factor.
Inverse field recovery
You want an entire spatially distributed quantity from indirect measurements: subsurface moisture, atmospheric state, or interior temperature.
The mathematics overlaps substantially. Keeping the common structure in view is what stops them looking like three unrelated problems.
68.5 The core method
A first pass through an estimation or inverse problem usually goes like this:
Decide whether the unknown is a state, a parameter, or a field.
Write the forward relation from hidden quantity to measurement.
Name the main source of uncertainty: measurement noise, model error, missing data, or poor observability.
Choose an update or fitting rule.
Interpret the residuals — the differences between what the model predicted and what was actually measured — not just the estimate itself.
Ask what information the data still cannot recover.
The last question is the one most often skipped. A polished estimate can still be misleading if the measurements never contained the needed information.
NoteWhy this works
The update works because it combines two sources that are wrong in different ways. The model prediction is coherent but incomplete. The measurement is immediate but noisy. The weights are chosen so each source corrects the other’s weakness.
That is why estimation sits at the intersection of control, signal processing, and statistics. It draws on all three.
68.6 Worked example 1: one-step scalar filter
Suppose the prior estimate of a temperature is \hat{x}^- = 18 degrees, a new sensor reading is y = 20 degrees, and the update weight is K = 0.3. Then
The updated estimate moves toward the measurement, but does not jump all the way there. That is exactly what you want when the sensor is informative but not perfect.
If the sensor were known to be extremely reliable, a larger K would make sense. If the measurement were very noisy, a smaller K would be more honest.
Code
{functionsvgEl(tag){returndocument.createElementNS("http://www.w3.org/2000/svg", tag);}const container =document.createElement("div"); container.style.cssText="max-width: 720px; margin: 1rem 0 1.25rem 0; font-family: inherit;";const controls =document.createElement("div"); controls.style.cssText="display:flex; gap:0.75rem; flex-wrap:wrap; align-items:end; margin-bottom:0.75rem;";functionsliderRow(labelText,min,max,step,value){const wrap=document.createElement("label"); wrap.style.cssText="display:grid; gap:0.25rem; font-size:0.9rem; color:#374151;"; wrap.appendChild(Object.assign(document.createElement("span"),{textContent:labelText}));const input=document.createElement("input"); input.type="range"; input.min=String(min); input.max=String(max); input.step=String(step); input.value=String(value);const out=document.createElement("span"); out.style.cssText="font-variant-numeric: tabular-nums; color:#6b7280;"; wrap.append(input,out); controls.appendChild(wrap);return {input,out};}const prior =sliderRow("Prior estimate x̂⁻",0,30,0.5,14);const meas =sliderRow("Measurement y",0,30,0.5,20);const gain =sliderRow("Update weight K",0,1,0.05,0.3);const readout =document.createElement("div"); readout.style.cssText="display:flex; gap:0.75rem; flex-wrap:wrap; margin-bottom:0.65rem;";functioncard(label){const box=document.createElement("div"); box.style.cssText="background:#f8fafc; border:1px solid #e5e7eb; border-radius:8px; padding:0.5rem 0.65rem;";const k=document.createElement("div"); k.style.cssText="font-size:0.76rem; color:#6b7280;"; k.textContent=label;const v=document.createElement("div"); v.style.cssText="font-size:0.96rem; font-variant-numeric: tabular-nums;"; box.append(k,v); readout.appendChild(box);return v;}const innovationBox =card("Innovation");const updateBox =card("Updated estimate x̂⁺");const trustBox =card("Interpretation");const svg =svgEl("svg"); svg.setAttribute("viewBox","0 0 680 160"); svg.setAttribute("width","100%"); svg.style.cssText="display:block; background:#fff; border:1px solid #e5e7eb; border-radius:10px;";const caption =document.createElement("p"); caption.style.cssText="font-size:0.9rem; color:#4b5563; margin:0.55rem 0 0 0;";functionredraw(){while(svg.firstChild) svg.removeChild(svg.firstChild);const xminus =parseFloat(prior.input.value), y =parseFloat(meas.input.value), K =parseFloat(gain.input.value); prior.out.textContent= xminus.toFixed(1); meas.out.textContent= y.toFixed(1); gain.out.textContent= K.toFixed(2);const innovation = y - xminus;const xplus = xminus + K * innovation;const map = v =>70+540* v /30;const line = (x1,y1,x2,y2,stroke,w=2,dash="")=>{const l=svgEl("line"); l.setAttribute("x1",x1); l.setAttribute("y1",y1); l.setAttribute("x2",x2); l.setAttribute("y2",y2); l.setAttribute("stroke",stroke); l.setAttribute("stroke-width",w);if(dash) l.setAttribute("stroke-dasharray",dash); svg.appendChild(l);};const text=(x,y0,t,fill="#374151",size=12,anchor="middle",weight="400")=>{const n=svgEl("text"); n.setAttribute("x",x); n.setAttribute("y",y0); n.setAttribute("fill",fill); n.setAttribute("font-size",size); n.setAttribute("text-anchor",anchor); n.setAttribute("font-weight",weight); n.textContent=t; svg.appendChild(n);};line(map(0),88,map(30),88,"#6b7280",1.8);for(let t=0;t<=30;t+=5){ line(map(t),82,map(t),94,"#9ca3af",1.2);text(map(t),108,`${t}`,"#6b7280",10); }const p =svgEl("circle"); p.setAttribute("cx",map(xminus)); p.setAttribute("cy","88"); p.setAttribute("r","5"); p.setAttribute("fill","#2563eb"); svg.appendChild(p);const m =svgEl("circle"); m.setAttribute("cx",map(y)); m.setAttribute("cy","88"); m.setAttribute("r","5"); m.setAttribute("fill","#f59e0b"); svg.appendChild(m);const u =svgEl("circle"); u.setAttribute("cx",map(xplus)); u.setAttribute("cy","88"); u.setAttribute("r","6"); u.setAttribute("fill","#059669"); svg.appendChild(u);line(map(xminus),60,map(y),60,"#d1d5db",4);line(map(xminus),60,map(xplus),60,"#059669",5);text(map(xminus),48,"prior x̂⁻","#2563eb",11);text(map(y),48,"measurement y","#b45309",11);text(map(xplus),132,"updated x̂⁺","#059669",11); innovationBox.textContent=`${innovation >=0?"+":""}${innovation.toFixed(1)}`; updateBox.textContent= xplus.toFixed(2); trustBox.textContent= K <0.3?"stays near prior": K >0.7?"jumps toward measurement":"balances both"; caption.textContent="The update does not jump blindly to the measurement. It moves partway from the prior estimate toward the measurement, and the fraction moved is exactly K."; } [prior.input, meas.input, gain.input].forEach(el => el.addEventListener("input", redraw)); container.append(controls, readout, svg, caption);redraw();return container;}
68.7 Worked example 2: linear least-squares parameter fit
Suppose a calibration model predicts measurements by
y_i \approx \theta x_i
for known inputs x_i and unknown scale parameter \theta. With observations
This is parameter estimation rather than state estimation, but the structure is the same: use noisy measurements to infer a hidden quantity in the model.
68.8 Worked example 3: remote sensing as an inverse problem
A satellite does not measure soil moisture directly. It measures reflected or emitted radiation. A forward model links surface state to what the instrument would see. The inverse problem works backward from the measured signal toward the surface property of interest.
The mathematics is the same as in engineering system identification:
there is a hidden state or parameter
there is an observation operator
there is noise and model mismatch
the estimate must balance data fit against plausibility
Remote sensing and process monitoring look unrelated on the surface. The mathematical structure is nearly identical.
68.9 The Kalman filter as the canonical example
The update rule in Worked Example 1 is not just a pedagogical toy. It is the core of the Kalman filter, which structures estimation around a predict-then-update cycle:
Predict: use the system model to propagate the state estimate forward one step, together with an estimate of how uncertain that prediction is.
Update: when a measurement arrives, compute how much to trust it relative to the prediction. The Kalman gain K is chosen to minimise the mean-squared estimation error — the average squared distance between the estimate and the true state. When the noise is Gaussian this gives the globally optimal estimator. For any noise distribution with finite variance, it is the best linear unbiased estimator — the Gauss-Markov result, which says no linear estimator of the same form can have smaller variance.
The two-step pattern generalises. Extended Kalman filters handle nonlinear models by linearising around the current estimate. Particle filters handle nonlinear dynamics and non-Gaussian noise by representing uncertainty as a weighted cloud of samples. Variational data assimilation in weather prediction is a related method at continental scale. Hidden Markov models use the same predict-update logic for discrete state spaces. Bayesian filtering is the general probabilistic framing that contains all of these as special cases.
The scalar example in this chapter already shows the logic. The rest is bookkeeping to handle vectors, covariances, and system models.
68.10 Where this goes
The next natural continuation is Chapter 6 of this volume, Reliability, stochastic systems, and quality. Once you are estimating hidden states and parameters from uncertain data, the next question is often what that uncertainty means for failure, quality, risk, or operational decision-making over time.
There is also a direct connection back to control. Modern control systems often operate on estimated states rather than directly measured ones. Estimation is not a sidebar to control theory. It is often the missing half.
TipWhere this mathematics appears
condition monitoring in machines and structures
navigation and sensor fusion
process monitoring and soft sensing
parameter estimation in simulation models
remote sensing and environmental inversion
streaming latent-state estimation in data systems
68.11 Exercises
These are project-style exercises. State what the estimate means and what the data still does not tell you.
68.11.1 Exercise 1
Use the scalar update rule
\hat{x}^+ = \hat{x}^- + K(y - \hat{x}^-)
with \hat{x}^- = 12, y = 15, and K = 0.4.
Compute the updated estimate and explain whether the update trusts the measurement strongly or cautiously.
68.11.2 Exercise 2
Given data
(x_i, y_i) = (1, 1.8), (2, 4.1), (4, 8.3),
estimate \theta in the model y_i \approx \theta x_i using
A monitoring system can measure motor current and casing temperature, but not bearing wear directly.
Write a short systems note answering:
what the hidden state is
what the measurements are
one plausible forward model relation
one reason the inverse problem may be ambiguous or poorly observable — for instance, two different hidden states that would produce very similar measurements (the formal term for this instability is “ill-conditioned”)
68.11.4 Exercise 4
Choose one inverse problem from your own field and prepare a one-page model brief naming: