enrml⚓︎
EnRML type schemes
GNEnRML
⚓︎
Bases: IterativeEnRML
Gauss-Newton Ensemble Randomized Maximum Likelihood (GN-EnRML).
Solves the same randomized maximum likelihood problem as :class:LMEnRML,
but takes undamped Gauss-Newton steps scaled by a step length
:math:\gamma \in (0, 1] rather than inflating the Hessian:
.. math::
m \leftarrow m + \gamma \, C_{md} (C_d + C_{dd})^{-1}
(d_{obs} - g(m))
Steps are accepted or rejected on the mean data misfit as in LM-EnRML. On
acceptance :math:\gamma is relaxed towards gamma_max; on rejection it
is divided by gamma_factor and the step re-solved, in the same
within-:meth:update_step loop LM-EnRML uses for :math:\lambda.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keys_da
|
dict
|
Parsed |
required |
keys_en
|
dict
|
Parsed |
required |
sim
|
object
|
Forward simulator instance, e.g. |
required |
analysis
|
(approx, full, subspace)
|
Analysis flavour, i.e. how the ensemble-approximated sensitivity is
inverted. Defaults to the |
'approx'
|
Attributes:
| Name | Type | Description |
|---|---|---|
ensemble |
AssimilationEnsemble
|
Collaborator holding the state realisations, observed data and
simulator. Its state is exposed as properties on the scheme, so
|
analysis |
AnalysisBase
|
The bound analysis object. Note the constructor takes |
analysis_name |
str
|
The flavour name that was resolved, e.g. |
iteration |
int
|
Accepted iterations completed so far. |
data_misfit, prior_data_misfit |
float
|
Current and initial mean data misfit. |
Notes
Configured through the iteration block of keys_da:
max_iter
Maximum accepted iterations.
gamma
Initial step length (default 0.2).
gamma_max
Value the step length relaxes towards on success (default 0.5).
gamma_factor
Divisor applied to the step length on rejection (default 2.5).
max_inner_iter
Step-length attempts one iteration may make before the run gives up
(default 10). There is no gamma_min, so this is what bounds it.
data_misfit_tol
Relative misfit change treated as converged (default 0.01).
The margis flavour is backed by margIS_update, ported from an
older layout. It returns a matrix-form ensemble transform step
(AnalysisResult(W_step=...), starting from W = I) rather than the
weight step most other flavours use; propose_state reconstructs the
state for either. Run against real data it produces a
large, sensible misfit reduction, but is still one run on one case with
no committed reference pinning it -- see its module docstring
(:mod:pipt.update_schemes.analysis.margis) for what was fixed in the
port and what remains a modelling choice rather than a bug.
Examples:
References
Chen and Oliver chen2013; see also Raanes, Stordal and Evensen,
Revising the stochastic iterative ensemble smoother raanes2019, and
Evensen et al. evensen2019.
See Also
IterativeEnRML : The loop, scoring and bookkeeping both schemes share. LMEnRML : Levenberg-Marquardt form, damped via the Hessian.
log_columns(prior_run=False)
⚓︎
GN-EnRML reports the step length the logged iteration took.
IterativeEnRML
⚓︎
Bases: AssimilationScheme
What LM-EnRML and GN-EnRML share: everything but the control parameter.
Both solve the randomized maximum likelihood problem by repeated
linearisation, accept or reject each step on the mean data misfit, retry
a rejected step from the same state inside :meth:update_step, and stop
on the relative misfit change, on max_inner_iter failed attempts in
one iteration, or on max_iter. They differ only in the control
parameter that reacts to an attempt: LM-EnRML's damping :math:\lambda
inflates the Hessian and grows on rejection; GN-EnRML's step length
:math:\gamma scales the step and shrinks on rejection. A subclass
supplies that behaviour through the hooks below and nothing else.
Hooks
_read_damping_options(options)
Read the control parameter(s) from the iteration block.
_step_scale()
Factor applied to the analysis step: 1 for LM-EnRML, :math:\gamma
for GN-EnRML.
_record_control()
Remember the control the attempt ran with, for the run table.
_control_exhausted() and _exhausted_message()
Whether the control itself says stop (LM-EnRML: :math:\lambda \ge
lambda_max), and the stop reason to report then.
_why_stop_control()
The control's entries in why_stop.
_on_improved()
Accepted with a smaller misfit spread: relax the control.
_on_rejected()
Rejected: tighten the control.
_give_up_message(attempt)
Stop reason when max_inner_iter attempts all failed.
log_columns()
The control's column in the run table.
__init__(keys_da, keys_en, sim, analysis=None, ensemble=None)
⚓︎
Build the ensemble from the config (or take the one given) and bind the analysis.
See the subclass docstrings for the parameters; ensemble is a
ready-made collaborator to run on instead of building one.
calc_analysis()
⚓︎
Compute the trial state: the analysis step, scaled and clipped.
check_convergence()
⚓︎
Report the verdict reached by the preceding :meth:score_and_commit.
score_and_commit()
⚓︎
Score the forecast, decide on the attempt, and adjust the control.
Returns:
| Name | Type | Description |
|---|---|---|
why_stop |
dict
|
The convergence criteria with their values, including the control's own entries. |
update_step()
⚓︎
Run one iteration, retrying until an attempt improves the misfit.
The retry loop is here rather than in the base loop: one call is one
iteration, and the attempts it took to get there are this scheme's
business. That mirrors popt, where EnOpt.update_step backtracks
over its own step length and returns only once it has an improving
step or has run out of attempts.
Each attempt re-solves the analysis with the current control
parameter, forecasts the proposal and scores it. A worse misfit
tightens the control (:meth:_on_rejected) and tries again from the
same state -- nothing was committed -- so the retries cost
forecasts, not correctness.
Returns:
| Type | Description |
|---|---|
StepReport
|
|
LMEnRML
⚓︎
Bases: IterativeEnRML
Levenberg-Marquardt Ensemble Randomized Maximum Likelihood (LM-EnRML).
An iterative ensemble smoother that solves the randomized maximum
likelihood problem by repeated linearisation, with a Levenberg-Marquardt
damping parameter :math:\lambda controlling the step size. The damped
update inflates the Hessian approximation:
.. math::
m \leftarrow m + C_{md} \big((1 + \lambda) C_d + C_{dd}\big)^{-1}
(d_{obs} - g(m))
Unlike ES-MDA, steps are accepted or rejected. A step that increases the
mean data misfit is discarded, :math:\lambda is multiplied by
lambda_factor and the step re-solved from the same state; one that
decreases it is kept and :math:\lambda reduced. That retry loop lives
inside :meth:update_step, so one iteration is one call however many
attempts it takes -- the shape popt's optimizers have. The run stops when
the relative misfit change falls below data_misfit_tol, when
:math:\lambda reaches lambda_max, when a single iteration exhausts
max_inner_iter attempts, or on max_iter.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
keys_da
|
dict
|
Parsed |
required |
keys_en
|
dict
|
Parsed |
required |
sim
|
object
|
Forward simulator instance, e.g. |
required |
analysis
|
(approx, full, subspace)
|
Analysis flavour, i.e. how the ensemble-approximated sensitivity is
inverted. Defaults to the |
'approx'
|
Attributes:
| Name | Type | Description |
|---|---|---|
ensemble |
AssimilationEnsemble
|
Collaborator holding the state realisations, observed data and
simulator. Its state is exposed as properties on the scheme, so
|
analysis |
AnalysisBase
|
The bound analysis object. Note the constructor takes |
analysis_name |
str
|
The flavour name that was resolved, e.g. |
iteration |
int
|
Accepted iterations completed so far. |
data_misfit, prior_data_misfit |
float
|
Current and initial mean data misfit. |
Notes
Configured through the iteration block of keys_da:
max_iter
Maximum accepted iterations.
lambda
Initial damping parameter (default 100). 'auto' derives it from the
prior data misfit.
lambda_factor
Factor by which damping grows on rejection and shrinks on acceptance
(default 5). Held as lam_factor -- not gamma, which is
GN-EnRML's step length, a different quantity entirely.
lambda_max, lambda_min
Bounds on the damping parameter.
max_inner_iter
Damping attempts one iteration may make before the run gives up
(default 10). lambda_max normally stops it first.
data_misfit_tol
Relative misfit change treated as converged (default 0.01).
Examples:
>>> result = LMEnRML.assimilate(keys_da, keys_en, flow(keys_sim))
>>> result.message
'Maximum number of iterations reached'
success distinguishes the two ways a run can end: True when a
convergence criterion fired, False when max_iter was reached first.
Both are ordinary outcomes -- check prior_data_misfit against
data_misfit to judge whether the run achieved anything.
References
Chen and Oliver, Levenberg-Marquardt forms of the iterative ensemble
smoother for efficient history matching and uncertainty quantification
chen2013.
See Also
IterativeEnRML : The loop, scoring and bookkeeping both schemes share. GNEnRML : Gauss-Newton form, damped by a step length instead. ESMDA : Fixed schedule rather than convergence-driven iteration.
log_columns(prior_run=False)
⚓︎
LM-EnRML reports the damping the logged iteration ran with.
score(pred_data=None)
⚓︎
Data misfit, sizing lambda='auto' the first time there is one.
:math:\lambda_0 = \Phi_{prior} / 2 N_d is defined against the prior
misfit, so it cannot be settled in __init__. The first score of a
run is the prior's, which makes this the earliest point it can be
resolved -- and everything downstream needs a number: the prior row
reports λ, and the prior QA/QC pass computes with it.
co_lm_enrml
⚓︎
Bases: LMEnRML
Approximate LM-EnRML of Chen and Oliver (2013), under its historical name.
This is LMEnRML(..., analysis="approx") and nothing more: the class
only ever differed from LM-EnRML by mixing in the approximate analysis,
which is a constructor argument now. It stays so that configs written as
scheme = "co_lm_enrml" and code importing the name keep working.
New code should say LMEnRML with analysis="approx".
gn_enrml
⚓︎
Bases: GNEnRML
Gauss-Newton stochastic IES of Raanes et al. (2019), under its historical name.
This is GNEnRML(..., analysis="subspace"): the weight-space update this
class used to carry inline is the subspace analysis, and the
step-length schedule it called lambda is GN-EnRML's gamma
schedule. It stays so that configs written as scheme = "gn_enrml" and
code importing the name keep working. New code should say GNEnRML
with analysis="subspace".