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 :doc:`data_assimilation`, using the same notation: the extended state :math:`\mathbf{x}_k = [\mathbf{u}_k;\, \theta_k]`, observations :math:`\mathbf{y}_k`, sampling matrix :math:`\mathcal{H}(\mathbf{x}_k)`, anomalies :math:`\mathbf{X}`, :math:`\mathbf{S}`, observation covariance :math:`\mathbf{R}_k` and Kalman gain :math:`\mathbf{K}`. Algorithm and estimation switches ---------------------------------- .. list-table:: :header-rows: 1 :widths: 20 80 * - Key - Meaning * - ``algorithm`` - The DA method. Currently only ``"EnKF"`` is implemented (:class:`~conesToolBox.conesClasses.conesEnKF.conesEnKF`). * - ``stateEstSwitch`` - Whether the flow state :math:`\mathbf{u}_k` is part of the extended state :math:`\mathbf{x}_k` (``true``) or left untouched by the analysis (``false``). * - ``paramEstSwitch`` - Whether the model parameters :math:`\theta_k` are part of :math:`\mathbf{x}_k` (``true``) or held fixed (``false``). With both switches ``true``, CONES performs the joint state-parameter EnKF described in :doc:`data_assimilation` ("Ensemble Kalman Filter with extended state"): :math:`\mathbf{x}_k` stacks the flow state and the parameters, and a single Kalman gain corrects both at once. With ``stateEstSwitch false``, :math:`\mathbf{x}_k` reduces to :math:`\theta_k` alone — a pure parameter (e.g. boundary condition) calibration against observations, which is the setting used by the :doc:`cavity tutorial `. State and observations ----------------------- .. list-table:: :header-rows: 1 :widths: 20 80 * - Key - Meaning * - ``stateVar`` - The flow variables making up :math:`\mathbf{u}_k`, as a string of one-letter codes, e.g. ``"uvwp"`` for velocity + pressure. Only read when ``stateEstSwitch true``. * - ``obsVar`` - The observed variables making up :math:`\mathbf{y}_k`, e.g. ``"uvw"``. Determines both the sampling operator :math:`\mathcal{H}(\mathbf{x}_k)` (built by projecting the model state at the observation cells for these variables) and the observation degrees of freedom per observation point. * - ``obsFile`` - Path (relative to the case) to the observation netCDF database (mean and standard deviation of each ``obsVar`` at each observation location and time) — see :doc:`observations` for its exact format and how to build one. * - ``observationWindow`` - Number of model time steps the forecast runs between two analysis phases. Together with ``endTime`` and ``deltaT`` from ``system/controlDict``, it sets the total number of DA cycles: .. math:: n_{\text{cycles}} = \left\lfloor \frac{\text{endTime}}{ \text{observationWindow} \times \text{deltaT}} \right\rfloor Parameter estimation ---------------------- Only read when ``paramEstSwitch true``; together they define what :math:`\theta_k` actually contains. .. list-table:: :header-rows: 1 :widths: 20 80 * - Key - Meaning * - ``parameterType`` - Where the parameter lives in the model, e.g. ``"BC"`` for a boundary condition value. * - ``parameterVar`` - Which field component is estimated, e.g. ``"u"`` for a velocity component. * - ``parameterPatchName`` - The boundary patch the parameter is read from/written to, e.g. ``"movingWall"``. * - ``numberParameters`` - Size of :math:`\theta_k`, i.e. how many scalar parameters are appended to the state. Multiple Data Assimilation (MDA) ----------------------------------- .. list-table:: :header-rows: 1 :widths: 20 80 * - Key - Meaning * - ``mdaIterations`` - Number of ES-MDA (Ensemble Smoother with Multiple Data Assimilation) sub-iterations performed at *each* analysis phase. At every sub-iteration, :func:`~conesToolBox.conesClasses.conesEnKF.conesEnKF.set_R` inflates the observation covariance by the iteration count :math:`N_{\text{mda}}` (``mdaIterations``): .. math:: \mathbf{R}_i = N_{\text{mda}} \, \mathrm{diag}(\sigma^2) and :func:`~conesToolBox.conesClasses.conesEnKF.conesEnKF.setObs` perturbs the observations with matching noise scaled by :math:`\sqrt{N_{\text{mda}}}`, so that summing the :math:`1/N_{\text{mda}}` inflation factors over all sub-iterations recovers the original :math:`\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 :math:`\mathbf{x}` as the new background (:func:`~conesToolBox.conesClasses.conesEnKF.conesEnKF.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. .. list-table:: :header-rows: 1 :widths: 20 80 * - Key - Meaning * - ``inflationType`` - ``"deterministic"`` or ``"stochastic"`` (see below). * - ``stateInflation`` - Inflation coefficient :math:`\lambda` applied to the updated state. ``0`` disables state inflation. * - ``parametersInflation`` - Inflation coefficient :math:`\lambda` applied to the updated parameters. ``0`` disables parameter inflation. Given an updated ensemble matrix (state or parameters) with per-member columns :math:`\mathbf{a}_i` and ensemble mean :math:`\bar{\mathbf{a}}`, :func:`~conesToolBox.conesClasses.conesEnKF.conesEnKF.calculateInflation` implements: - **Deterministic**: every member is inflated by the same factor around the ensemble mean, .. math:: \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 :math:`\lambda`, truncated to :math:`[-2\lambda, 2\lambda]`, .. math:: \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 :doc:`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, :meth:`~conesToolBox.conesClasses.conesSettings.conesSettingsAbstract.conesSettingsAbstract.get_clippings` reads these clips, :func:`~conesToolBox.conesFunctions.mergeClips` merges any that share cells, and one :class:`~conesToolBox.conesClasses.conesRegion.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 (:class:`~conesToolBox.conesClasses.conesEnKF.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 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. list-table:: :header-rows: 1 :widths: 20 80 * - Key - Meaning * - ``stateCovarianceLocalisation`` - Whether to taper the Kalman gain with distance (``true``/``false``). * - ``localisationLength`` - Characteristic length :math:`\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. :func:`~conesToolBox.conesClasses.conesEnKF.conesEnKF.setL` builds a Gaussian weight from the euclidean distance :math:`d` between each state cell and each observation: .. math:: c = \sqrt{-2 \ln(0.01)}, \qquad L(d) = \exp\!\left(-\frac{d^2}{2 (\ell / c)^2}\right) so that the correlation has decayed to 1% of its peak value at distance :math:`d = \ell` — ``localisationLength`` is the practical "radius of influence" of each observation. :math:`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: .. math:: \mathbf{x}^a = \mathbf{x}^f + \mathbf{L} \odot \mathbf{K} \, (\mathbf{y} - \mathcal{H}(\mathbf{x}^f)) 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.