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
|
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 |
required |
enY
|
ndarray
|
Predicted data ensemble matrix, shape |
required |
enE
|
ndarray
|
Perturbed observation ensemble, shape |
required |
**kwargs
|
Analysis-specific extras, e.g. |
{}
|
Returns:
| Type | Description |
|---|---|
AnalysisResult
|
The update: a state-space |
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 |
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 |
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
|