Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial SDE Code Pull Request #76

Closed
wants to merge 7 commits into from
Closed

Conversation

cgrudz
Copy link
Contributor

@cgrudz cgrudz commented Jul 14, 2021

Hey Y'all,

Re #70, apologies for delays on this, I am finally getting back around to merging some changes that have been sitting for a while. In this request, I have added the general Runge-Kutta SDE scheme with a fairly simple change to the existing version in the integration schemes in mods. This includes adding an "s" parameter for the instantaneous standard deviation of the noise (the diffusion coefficent) assuming additive noise, and re-writes "order" into a "stages" parameter. This has to due with the discrepancy between the number of stages and the order of the method for the SDE models. This was tested versus the test cases and I found that there was only an issue with a special case in bocquet19, and otherwise the re-naming seems to have been propagated successfully.

I am still working on the full set up of the L96s model on which these changes depend to a certain extent, but I should have this model integrated in the coming weeks as the next stage. This will help me get more into DAPPER development now that I'm preparing my course materials consistently.

Cheers,
Colin

cgrudz and others added 5 commits June 15, 2021 16:28
This will introduce the L96s model as a unique model in the colllection
so that we can integrate some special features here first as a test
case.  However, integration.py can be minimally adjusted to rigorously
include the Euler-Maruyama and four-stage Runge-Kutta schemes for SDEs
with additive noise with minimal changes.
Initial simplifications have been introduced to homogenize the code with
the rest of the repo.  The simple case of a Fourier truncation with p=1
is emphasized, as this is all that matters for order 2.0 convergence.
The general scheme discussed in the manuscript is not of particular
interest for DA so we neglect this implementation, included in that
manucript's repo.
Using the stock Jacobian implementation in the extras file, changing
normal sampler to the standard univariate normal of appropriate
dimension for broadcasting.  Yet to be run on test cases, tomorrow will
most likely put additional time developing the model and testing it for
the overal integration with DAPPER for merge / push tutorial worksheets.
Copy link
Collaborator

@patnr patnr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good.

I have marked/suggested/discussed some changes.

One thing that's missing is a script demonstrating these features, ideally reproducing something. Is this coming with the fuller setup?

.gitignore Show resolved Hide resolved
Comment on lines +18 to +21
The rule has strong / weak convergence order 1.0 for generic SDEs and order 4.0
convergence for ODEs when stages=4. For stages=1, this becomes the Euler-Maruyama
schemefor SDEs (s > 0.0) with strong / weak convergence order 1.0 for SDEs with
additive noise as defined in the below. See `bib.grudzien2020numerical`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The rule has strong / weak convergence order 1.0 for generic SDEs and order 4.0
convergence for ODEs when stages=4. For stages=1, this becomes the Euler-Maruyama
schemefor SDEs (s > 0.0) with strong / weak convergence order 1.0 for SDEs with
additive noise as defined in the below. See `bib.grudzien2020numerical`.
This scheme has order-4 convergence for ODEs when `stages=4`.
For SDEs with additive noise as defined below, it has strong / weak convergence
of order 1.0 (for any value of `stages`). See `bib.grudzien2020numerical`.

Also, when is it strong, and when is it weak ?

Please also add a note saying that the stochastic integrator should not be used if the DA method has its own way of dealing with the noise, such as for gaussian mixtures, particle filters, and the classic/extended KF.

Copy link
Contributor Author

@cgrudz cgrudz Jul 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually regarding this -- how do we set options so that line 244 of DAPPER/dapper/mods/__init__.py

xx[k] = Dyn(xx[k-1], t-dt, dt) + np.sqrt(dt)*Dyn.noise.sample(1)

doesn't add additional noise? I think this may be the only compatibility issue, is the noise a function that we have to supply in the Dyn dict? Should I just set a lambda function to return zero in general?

Otherwise this model configuration can be used with multiplicative or additive inflation / Gaussian mixtures / etc... to handle issues like sampling error or to increase the variance of the estimator. The example that I am writing will be treated as a perfect-random model configuration in which the truth twin and each ensemble member will be propagated by the same random model under different outcomes. Note, the observations are still discrete-in-time so that this is not equivalent to the Kalman-Bucy filter, but rather represents the case in which the truth twin and ensemble members are actually evolved with respect to statistically equivalent, random diffeomorphisms. In this case, many of the usual issues of sampling error due to nonlinearity etc. still apply, though the SDE evolution tends to regularize some stochastic estimators like particle filters which are more degenerate in totally deterministic systems. This also tends to regularize some of the issues with the EnKF's need to use random rotations, etc. to numerically regularize the collapse of variances.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using HMM = modelling.HiddenMarkovModel(Dyn={..., 'noise': 0}, ...) should do it. But that means that your diffusion/noise must be specified elsewhere. Presumably, for perfect-yet-stochastic-model-case, it should just be inherent to the step function. I believe, for now at least, that is the easiest solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me -- I'll get a draft of this running shortly.

dapper/mods/L96s/__init__.py Show resolved Hide resolved
########################################################################################
# 2nd order strong taylor SDE step

def l96s_tay2_step(x, t, dt):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, maybe you do not need to create dapper/mods/L96s but rather just add a file to dapper/mods/Lorenz96 that is called stoch_integrator.py (for example) containing l96s_tay2_step and nothing more?

Or am I missing something? Please discuss hereunder.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main reason for using this scheme is for the high-accuracy of the truth twin simulation, where this scheme is only designed for the Lorenz-96 model with additive noise that has a scalar covariance at all times. The key is that strong convergence needs to be the criterion for whether a simulation of the truth twin will be consistent with the equations of motion in generating an observation process in a perfect-random model. Because this is a specific sub-class of stochastic Lorenz-96 models (though one that is probably most commonly used in the SDE configuration) I decided to name it the L96-s model where the s refers to the stochasticity, and particularly the diffusion coefficient which defines an "instantaneous" standard deviation of the noise process.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got you, I think. You still might be able to avoid duplicating Lorenz96.py/extras.py and large parts of __init__.py by importing them from Lorenz96 into L96s, I belive.

dapper/mods/integration.py Outdated Show resolved Hide resolved
dapper/mods/integration.py Outdated Show resolved Hide resolved
elif order ==4: return x + (k1 + 2*(k2 + k3) + k4)/6 # noqa
else: raise NotImplementedError # noqa

if s > 0.0:
Copy link
Collaborator

@patnr patnr Jul 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put the simplest case (s==0) up top. No need for .0 AFAIK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this case s should generally be a float though, not int. I don't know if it makes a difference in Python because of the type inference / coercion that occurs but this is just a habit from more static typing.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is good conformity in the code. But at least for python 3 it should not matter and I tend to lean to using just 0.

@cgrudz
Copy link
Contributor Author

cgrudz commented Jul 15, 2021 via email

@patnr
Copy link
Collaborator

patnr commented Jul 15, 2021

the trick is that this requires
having two distinct time-steppers for the ensemble and the the truth
twin respectively. Is there any example where this is performed
elsewhere in DAPPER?

I think this will do it (in the case of examples/basic_1.py):

@@ -15,6 +15,8 @@ import dapper.da_methods as da
 # #### Load experiment setup: the hidden Markov model (HMM)
 
 from dapper.mods.Lorenz63.sakov2012 import HMM
+HMM2 = HMM.copy()
+HMM2.step = my_custom_step_function
 
 # #### Generate the same random numbers each time this script is run
 
@@ -35,7 +37,7 @@ xp = da.EnKF('Sqrt', N=10, infl=1.02, rot=True)
 
 # #### Assimilate yy, knowing the HMM; xx is used to assess the performance
 
-xp.assimilate(HMM, xx, yy, liveplots=not nb)
+xp.assimilate(HMM2, xx, yy, liveplots=not nb)
 
 # #### Average the time series of various statistics

@patnr
Copy link
Collaborator

patnr commented Jul 15, 2021

BTW, I suggest reviewing the changes on GitHub rather than by email. You get pretty coloring, and my up-to-date comments (often edited for tone 🤣 )

@cgrudz
Copy link
Contributor Author

cgrudz commented Jul 15, 2021 via email

cgrudz and others added 2 commits July 15, 2021 10:58
Co-authored-by: Patrick N. Raanes <[email protected]>
Co-authored-by: Patrick N. Raanes <[email protected]>
@cgrudz
Copy link
Contributor Author

cgrudz commented Jul 15, 2021 via email

@cgrudz
Copy link
Contributor Author

cgrudz commented Jul 15, 2021

the trick is that this requires
having two distinct time-steppers for the ensemble and the the truth
twin respectively. Is there any example where this is performed
elsewhere in DAPPER?

I think this will do it (in the case of examples/basic_1.py):

@@ -15,6 +15,8 @@ import dapper.da_methods as da
 # #### Load experiment setup: the hidden Markov model (HMM)
 
 from dapper.mods.Lorenz63.sakov2012 import HMM
+HMM2 = HMM.copy()
+HMM2.step = my_custom_step_function
 
 # #### Generate the same random numbers each time this script is run
 
@@ -35,7 +37,7 @@ xp = da.EnKF('Sqrt', N=10, infl=1.02, rot=True)
 
 # #### Assimilate yy, knowing the HMM; xx is used to assess the performance
 
-xp.assimilate(HMM, xx, yy, liveplots=not nb)
+xp.assimilate(HMM2, xx, yy, liveplots=not nb)
 
 # #### Average the time series of various statistics

Ah ha, so it seems to be working so far, but I think I notice now a possible issue with the next step. Part of the configuration that I'm trying to build requires that the model twin is actually using a more coarse discretization in time, e.g.,

  • truth twin h = 0.005
  • ensemble twin h=0.01
  • \Delta_obs = 0.1

In this case, these align on the same absolute times when observations are given, but these have different partitions of the time-interval for the dynamic propagation.

I'll look into this to see if there's a good way to implement this with minimal invasion to the code, this can probably be made to work within the example scrip itself if I just end up needing to subset the finer discretization.

@patnr
Copy link
Collaborator

patnr commented Jul 16, 2021

Closed in favour of #77

@patnr patnr closed this Jul 16, 2021
@cgrudz cgrudz deleted the Quick-fix branch July 30, 2021 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants