The conesDict reference
system/conesDict is where the Data Assimilation problem itself is
configured, on top of the plain OpenFOAM case. This page goes through every
option and ties it back to the EnKF formulation introduced in
Data Assimilation, using the same notation: the extended state
\(\mathbf{x}_k = [\mathbf{u}_k;\, \theta_k]\), observations
\(\mathbf{y}_k\), sampling matrix \(\mathcal{H}(\mathbf{x}_k)\),
anomalies \(\mathbf{X}\), \(\mathbf{S}\), observation covariance
\(\mathbf{R}_k\) and Kalman gain \(\mathbf{K}\).
Algorithm and estimation switches
Key |
Meaning |
|---|---|
|
The DA method. Currently only |
|
Whether the flow state \(\mathbf{u}_k\) is part of the extended
state \(\mathbf{x}_k\) ( |
|
Whether the model parameters \(\theta_k\) are part of
\(\mathbf{x}_k\) ( |
With both switches true, CONES performs the joint state-parameter EnKF
described in Data Assimilation (“Ensemble Kalman Filter with extended
state”): \(\mathbf{x}_k\) stacks the flow state and the parameters, and
a single Kalman gain corrects both at once. With stateEstSwitch false,
\(\mathbf{x}_k\) reduces to \(\theta_k\) alone — a pure parameter
(e.g. boundary condition) calibration against observations, which is the
setting used by the cavity tutorial.
State and observations
Instantaneous vs. time-averaged observations
The analysis corrects the state from the innovation
\(\mathbf{d}_k = \mathbf{y}_k - \mathcal{H}(\mathbf{x}_k)\). With
obsType "instantaneous" both terms are evaluated at the single analysis
instant. For a statistically stationary but unsteady flow (e.g. vortex
shedding) whose constant boundary-condition parameters are being
estimated, this is problematic: the observation and each ensemble member
carry independent phases of the unsteadiness, so
\(\mathbf{d}_k\) contains a phase-difference term that does not depend
on the parameters and acts as noise on the update — even at the true
parameter values.
obsType "timeAveraged" removes it by replacing both sides with their
time average over the forecast window,
which is phase-invariant: at the true parameters \(\overline{\mathcal{H}(\mathbf{x}_k)} \to \overline{\mathbf{y}_k}\) regardless of the shedding phase, so the innovation reflects only the parameter mismatch. Both sides must be averaged — averaging \(\mathbf{y}_k\) alone while \(\mathcal{H}(\mathbf{x}_k)\) stays instantaneous would leave the member phase in \(\mathbf{d}_k\) and add a mean-vs-instant bias.
The two averages are formed differently, because of what is available when:
\(\overline{\mathcal{H}(\mathbf{x}_k)}\) is accumulated on the fly in the solver. During the forecast window each process sums the velocity at its local observation cells every time step (
EnKF::cones_accumulate_sampling(), called once per step fromconesFoam); at the analysis,EnKF::cones_send_sampling()divides the running sum by the number of accumulated steps, sends the mean, and resets the accumulator for the next window. Each member averages its own realization, so the phase cancels member by member.\(\overline{\mathbf{y}_k}\) is read from the observation database:
set_obs_stats()averages the stored series over the trailingobsAverageWindowsteps. The observation covariance \(\mathbf{R}_k\) is still built from the_stdfield of the database (see The observation database), exactly as in the instantaneous case — averaging changes the mean of \(\mathbf{y}_k\), not the source of its uncertainty.
Note
The averaging window must be longer than the flow response time to
the parameters (advective travel from the boundary to the observation
points, plus re-establishment of the unsteadiness). A window shorter
than this averages mostly the transient left over from the previous
parameter values and is not representative of the current ones. Choose
observationWindow (and hence obsAverageWindow) accordingly.
Parameter estimation
Only read when paramEstSwitch true; together they define what
\(\theta_k\) actually contains.
Key |
Meaning |
|---|---|
|
Where the parameter lives in the model, e.g. |
|
Which field component is estimated, e.g. |
|
The boundary patch the parameter is read from/written to, e.g.
|
|
Size of \(\theta_k\), i.e. how many scalar parameters are appended to the state. |
Multiple Data Assimilation (MDA)
Key |
Meaning |
|---|---|
|
Number of ES-MDA (Ensemble Smoother with Multiple Data Assimilation) sub-iterations performed at each analysis phase. |
At every sub-iteration, set_R()
inflates the observation covariance by the iteration count \(N_{\text{mda}}\)
(mdaIterations):
and setObs() perturbs the
observations with matching noise scaled by \(\sqrt{N_{\text{mda}}}\), so
that summing the \(1/N_{\text{mda}}\) inflation factors over all
sub-iterations recovers the original \(\mathbf{R}\), per the classic
even-split ES-MDA scheme (Emerick & Reynolds). The Kalman gain and update
are then recomputed for each sub-iteration, using the previous
sub-iteration’s updated \(\mathbf{x}\) as the new background
(update_background()).
mdaIterations 1 is the classic, single-pass EnKF.
Inflation
Ensemble methods systematically underestimate the error covariance (the ensemble collapses over cycles); inflation counteracts this by artificially widening the ensemble spread of the updated state and/or parameters after each analysis.
Key |
Meaning |
|---|---|
|
|
|
Inflation coefficient \(\lambda\) applied to the updated state.
|
|
Inflation coefficient \(\lambda\) applied to the updated
parameters. |
Given an updated ensemble matrix (state or parameters) with per-member
columns \(\mathbf{a}_i\) and ensemble mean \(\bar{\mathbf{a}}\),
calculateInflation()
implements:
Deterministic: every member is inflated by the same factor around the ensemble mean,
\[\mathbf{a}_i \leftarrow \bar{\mathbf{a}} + (1+\lambda)(\mathbf{a}_i - \bar{\mathbf{a}})\]Stochastic: each member gets its own inflation factor, drawn from a normal distribution of mean 0 and standard deviation \(\lambda\), truncated to \([-2\lambda, 2\lambda]\),
\[\mathbf{a}_i \leftarrow (1+\epsilon_i)\, \mathbf{a}_i, \qquad \epsilon_i \sim \mathcal{N}(0, \lambda^2) \big|_{[-2\lambda, 2\lambda]}\]
Localisation: physical regions and covariance tapering
With few ensemble members, sampling noise creates spurious long-range
correlations between the state and observations that have nothing to do
with the real physics. CONES suppresses them in two independent, composable
ways: a hard, physical partition of the domain (via topoSet, not a
conesDict key), and a smooth, distance-based taper of the Kalman gain
inside each partition (stateCovarianceLocalisation below). Both can be
used together, only one, or neither.
Physical localisation (domain decomposition into regions)
This is what the tutorial’s system/topoSetDict sets up,
before CONES itself even starts: each cellSet action defines a region
of the mesh (a “clip”). At start-up, get_clippings()
reads these clips, mergeClips() merges
any that share cells, and one conesRegion
is built per merged clip — keeping only the observations whose cell falls
inside it, and dropping regions left with none.
Each region then runs its own, independent EnKF analysis
(conesEnKF), built from only
that region’s state cells and observations. An observation in one clip has
zero influence on the update anywhere outside it, however close it might
physically be to that clip’s boundary — this is a hard cutoff, decided once
by the mesh topology, not by any conesDict option. If your case has a
single topoSetDict action covering the whole DA domain (or none, when
LHYPER_LOCALISATION_SWITCH is off on the Meso-NH side), there is only
ever one region, and physical localisation plays no role.
State covariance localisation
Key |
Meaning |
|---|---|
|
Whether to taper the Kalman gain with distance ( |
|
Characteristic length \(\ell\) of the localisation, in the same units as the mesh coordinates. |
Where physical localisation draws a hard line between regions, covariance
localisation works within a single region’s analysis: it damps the
Kalman gain continuously with distance, instead of cutting it off, so
state cells close to an observation are still corrected more than distant
ones without discarding either.
setL()
builds a Gaussian weight from the euclidean distance \(d\) between each
state cell and each observation:
so that the correlation has decayed to 1% of its peak value at distance
\(d = \ell\) — localisationLength is the practical “radius of
influence” of each observation. \(L\) is then column-normalized (each
observation’s weights sum to 1) and applied to the update as an
element-wise (Schur/Hadamard) product on the Kalman gain term, rather than
a matrix product:
Parameters are not localised (their weight is 1 for every observation), since they are typically global quantities (e.g. a single boundary condition) rather than spatially distributed ones.