Skip to content

analysis⚓︎

Analysis-step analyses.

An analysis computes the state update for one assimilation iteration. The flavours differ only in how the ensemble-approximated sensitivity is inverted; they share a calling convention and their linear-algebra helpers.

The analysis is a parameter of a scheme, not part of its identity::

ESMDA(keys_da, keys_en, sim, analysis="subspace")
Layout

base :class:AnalysisBase -- the shared contract and helpers. approx, full, subspace, subspace2 The four registered flavours. hybrid, margis Flavours consumed as mixins rather than through the registry: hybrid belongs to the multilevel scheme and margis is backed by a private package when installed. registry Name-to-class lookup, plus :func:register_analysis for out-of-tree flavours.

These previously lived in update_schemes.update_methods_ns while this package held only the base class, because the flavours were consumed as mixins and re-exporting them here would have formed an import cycle. Now that schemes hold an analysis rather than inheriting one, they live together.

AnalysisBase ⚓︎

Bases: ABC

Base class for analysis-step analyses.

Provides the linear-algebra helpers every flavour needs. Both accept either a full 2-D matrix or a 1-D array holding just the diagonal, which is how PIPT represents a diagonal data covariance without materialising nd x nd zeros.

Two usages

Bound (what every algorithm class does, for every flavour in its COMPATIBLE_ANALYSES) -- constructed against a scheme it holds a reference to::

strategy = approx_update(scheme)
step = strategy.update(enX, enY, enE)

which is what lets analysis be a constructor argument of one scheme class rather than picking which of several classes you get. Inside update(), context is read explicitly off self.scheme -- there is no delegation step to run first; self.scheme is just the object passed to the constructor, and it exposes ensemble state as properties of its own.

Mixed in -- nothing shipped here still needs this (margis binds like the rest now); it remains supported for an analysis whose calling convention genuinely does not fit the bound shape above::

class some_scheme(SomeAlgorithm, some_analysis): ...

self is the scheme here, so self.scheme (the :attr:scheme property below) simply returns self -- self.scheme.lam and self.lam are then the same read, resolved by ordinary inheritance.

An unbound, un-mixed-in analysis has self.scheme fall back to self too, so a context read raises a plain AttributeError rather than finding a half-initialised scheme.

scheme ⚓︎

The scheme to read context from and write results onto.

The bound value if there is one; otherwise self -- which is exactly right when mixed in (self already is the scheme, so self.scheme.x and self.x are the same read) and merely produces a plain AttributeError from an unbound, un-mixed-in analysis rather than a special-cased error path.

__init__(scheme=None) ⚓︎

Parameters:

Name Type Description Default
scheme object

Scheme this analysis computes updates for. None leaves the analysis unbound. Never invoked in the mixin case: no __init__ in that MRO chains to super().

None

solve(A, B) ⚓︎

Apply A⁻¹ B, supporting both matrix (2-D) and diagonal (1-D) A.

np.ndim is used rather than A.ndim so that plain lists and scalars -- which a covariance can still be when it comes straight from a config file -- are handled instead of raising AttributeError.

sqrtm(A) ⚓︎

Matrix square root, supporting both matrix and diagonal inputs.

update(enX, enY, enE, **kwargs) ⚓︎

Compute the analysis update step.

Parameters:

Name Type Description Default
enX ndarray

State ensemble matrix, shape (nx, ne).

required
enY ndarray

Predicted data ensemble matrix, shape (nd, ne).

required
enE ndarray

Perturbed observation ensemble, shape (nd, ne).

required
**kwargs

Analysis-specific extras, e.g. prior or enAdj.

{}

Returns:

Type Description
AnalysisResult

The update: a state-space step of shape (nx, ne), or a weight-space w_step/W_step. Returning a plain array is taken as a state-space step.

AnalysisResult ⚓︎

What an analysis hands back to the scheme. Exactly one field is set.

step Additive step in state space, (nx, ne); the trial state is enX + scale * step. The multilevel analysis returns one array per fidelity level. w_step Additive step to the weight matrix W of the ensemble subspace formulation (Evensen et al. 2019), starting from W = 0; the trial state is prior_enX @ (I + W / sqrt(ne - 1)). W_step Additive step to the ensemble transform W of the matrix formulation (Raanes et al. 2019), starting from W = I; the trial state is mean(prior_enX) + prior_anomalies * sqrt(ne - 1) @ W.

scale is the scheme's step length (GN-EnRML's gamma; 1 elsewhere) and belongs to the scheme, which is why the analysis returns a step and not a state.

coerce(value) ⚓︎

An AnalysisResult as given; a plain array or list as a state-space step.

approx_update ⚓︎

Bases: AnalysisBase

Approximate LM Update scheme as defined in "Chen, Y., & Oliver, D. S. (2013). Levenberg–Marquardt forms of the iterative ensemble smoother for efficient history matching and uncertainty quantification. Computational Geosciences, 17(4), 689–703. https://doi.org/10.1007/s10596-013-9351-5". Note that for a EnKF or ES update, or for update within GN scheme, lambda = 0.

update(enX, enY, enE, **kwargs) ⚓︎

Perform the approximate LM update.

Parameters:
enX : np.ndarray
    State ensemble matrix (nx, ne)

enY : np.ndarray
    Predicted data ensemble matrix (nd, ne)

enE : np.ndarray
    Ensemble of perturbed observations (nd, ne)

full_update ⚓︎

Bases: AnalysisBase

Full LM update as in Chen & Oliver (2013).

Unlike the approximate update, the state-error covariance is represented in model space via the Am matrix, which adds an explicit regularisation term pulling the ensemble toward the prior.

Reference

Chen, Y., & Oliver, D. S. (2013). Levenberg-Marquardt forms of the iterative ensemble smoother for efficient history matching and uncertainty quantification. Computational Geosciences, 17(4), 689-703. https://doi.org/10.1007/s10596-013-9351-5

Note

No localization is implemented for this update scheme.

ext_Am() ⚓︎

Compute and cache the Am matrix from the scaled prior anomalies.

The anomalies are divided by state_scaling, the same scaled space update puts X_anom and the prior misfit in, so that Am @ Am.T approximates the inverse of the scaled prior covariance. Multiplying by the scaling instead, as this once did, made the regularisation term off by the squared standard deviation for any variable whose prior standard deviation was not 1.

update(enX, enY, enE, **kwargs) ⚓︎

Perform the full LM update.

Parameters:

Name Type Description Default
enX (ndarray, shape(nx, ne))

State ensemble matrix.

required
enY (ndarray, shape(nd, ne))

Predicted data ensemble matrix.

required
enE (ndarray, shape(nd, ne))

Perturbed observations ensemble.

required

Returns:

Type Description
(ndarray, shape(nx, ne))

Update step to be added to the state ensemble.

hybrid_update ⚓︎

Bases: AnalysisBase

Class for hybrid update schemes as described in: Fossum, K., Mannseth, T., & Stordal, A. S. (2020). Assessment of multilevel ensemble-based data assimilation for reservoir history matching. Computational Geosciences, 24(1), 217–239. https://doi.org/10.1007/s10596-019-09911-x

Note that the scheme is slightly modified to be inline with the standard (I)ES approximate update scheme. This is what lets it be bound as an analysis like approx_update and friends, despite working on lists of per-level matrices rather than single ones -- see esmda_hybrid.COMPATIBLE_ANALYSES.

update(enX, enY, enE, **kwargs) ⚓︎

Perform the hybrid update.

Parameters:
enX : list of np.ndarray
    List of state ensemble matrices for each level (nx, ne)

enY : list of np.ndarray
    List of predicted data ensemble matrices for each level (nd, ne)

enE : list of np.ndarray
    List of ensemble of perturbed observations for each level (nd, ne)

subspace2_update ⚓︎

Bases: AnalysisBase

Ensemble-transform subspace update (matrix-formulation IES).

Solves directly for the ensemble transform W (shape ne x ne), starting from W = I, minimising

J(W) = 0.5 (ne-1) ||W - I||_F^2 + 0.5 ||D - g(xbar + Xp W)||^2_{Cd^-1}

by Gauss-Newton. Unlike :class:subspace_update it uses the analytic data covariance throughout -- via scale_data -- rather than the ensemble representation E E.T, so there is no SVD and energy/trunc_energy is not consulted. The trial state is reconstructed by propose_state as mean(prior_enX) + prior_anomalies * sqrt(ne - 1) @ W.

This is exactly :class:margIS_update with Ratio fixed at 1: the data error scale is taken as known instead of being marginalised over an inverse-chi2 prior. tests/assimilation/test_subspace2.py pins that identity.

References

Raanes, P. N., Stordal, A. S., & Evensen, G. (2019). Revising the stochastic iterative ensemble smoother. Nonlinear Processes in Geophysics, 26(3), 325-338. https://doi.org/10.5194/npg-26-325-2019

update(enX, enY, enE, **kwargs) ⚓︎

Perform one Gauss-Newton step on the ensemble transform.

Parameters:

Name Type Description Default
enX (ndarray, shape(nx, ne))

State ensemble matrix (unused; the reconstruction works from the prior).

required
enY (ndarray, shape(nd, ne))

Predicted data ensemble matrix.

required
enE (ndarray, shape(nd, ne))

Perturbed observations.

required

Returns:

Type Description
AnalysisResult

The transform step W_step of shape (ne, ne).

subspace_update ⚓︎

Bases: AnalysisBase

Ensemble subspace update (weight-space IES).

The update is formulated in the ensemble weight space W (shape ne × ne) rather than model space, making it efficient when ne ≪ nx. The caller checks self.w_step (not self.step) to apply the update.

References

Raanes, P. N., Stordal, A. S., & Evensen, G. (2019). Revising the stochastic iterative ensemble smoother. Nonlinear Processes in Geophysics, 26(3), 325-338. https://doi.org/10.5194/npg-26-325-2019

Evensen, G., Raanes, P. N., Stordal, A. S., & Hove, J. (2019). Efficient implementation of an iterative ensemble smoother for data assimilation and reservoir history matching. Frontiers in Applied Mathematics and Statistics, 5, 47. https://doi.org/10.3389/fams.2019.00047

update(enX, enY, enE, **kwargs) ⚓︎

Perform the subspace (weight-space) LM update.

Sets self.scheme.w_step (shape ne × ne) and returns None -- the caller applies the weight update, not a state-space step.

Parameters:

Name Type Description Default
enX (ndarray, shape(nx, ne))

State ensemble matrix (unused directly; included for interface parity).

required
enY (ndarray, shape(nd, ne))

Predicted data ensemble matrix.

required
enE (ndarray, shape(nd, ne))

Perturbed observations ensemble.

required

Returns:

Type Description
AnalysisResult

The weight-space step w_step (ne, ne).

available_analyses() ⚓︎

Return the registered flavour names, sorted.

get_analysis(analysis) ⚓︎

Look up the analysis class for a flavour.

Raises:

Type Description
KeyError

If the flavour is not registered. The message lists the valid ones.

register_analysis(analysis, cls, *, overwrite=False) ⚓︎

Add an analysis under a flavour name, for later lookup by that name.

This alone does not make cls selectable on any existing scheme -- see the module docstring for how to actually wire a new flavour in.

Parameters:

Name Type Description Default
analysis str

Flavour name to register it under.

required
cls type

Analysis class implementing it.

required
overwrite bool

Allow replacing an existing entry. Defaults to False, so two packages claiming one name is an error rather than a load-order lottery -- matching registry.register_scheme.

False