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

Predictor Corrector method #53

Merged
merged 8 commits into from
Feb 14, 2018

Conversation

onoderat
Copy link
Contributor

@onoderat onoderat commented Feb 14, 2018

The predictor corrector method for SDEs have been implemented and tested.

Both the in place and not in place versions have been implemented.
The testing code used the problem from the commutative_tests.jl .

The speed of the code has not been benchmarked rigorously. For a single setting of dt=0.002, the following speeds were achieved.

    EM in place            : 1.665ms (21771 allocations: 2.01 MiB)
    RKMil in place         : 1.881ms (23776 allocations: 2.10 MiB)
    PCEuler not in place   : 2.626ms (40673 allocations: 3.44 MiB)
    PCEuler in place       : 1.942ms (24271 allocations: 2.16 MiB)

The convergence tests shows that both the PC has better accuracy than EM and surprisingly also RKMilCommute. Although the PC is proven to be order 0.5, it showed order 1 scaling for this particular problem (see figure below). At dt=1/2^10, the l2 errors are respectively
RKMil : 6.02e-3
PCEuler : 1.44e-3

error_scaling

Added PCEuler as a type. alg_order and alg_compatible has been set. Has not set any other property - this may need to change.
The code has been refactored from basic_method_caches to the new file cache_types.jl.
Code passed the default tests.
It is a singleton object since there are no static constants for this algorithm.
Plan is to first focus on getting the ConstantCache version working, before creating the more optimized Cache version.
Now it's fields contains the bbprime functions.
The optional arguments of theta and eta are set to 0.5 by default.
Code current runs for not in place problems. It has been checked to agree with EM. Careful testing of the deviation from the analytics is the next step - to check for higher order bugs.
Both is in place and not in place versions have been implemented.
Test code is in test/predcorr_test.jl. This current version of script
includes all the plotting and the timing. The next commit which will
remove these plotting and timing features for brievity and runspeed.

In terms of speed, here are some output from the test script.
EM in place    : 1.665ms (21771 allocations: 2.01 MiB)
RKMil in place : 1.881ms (23776 allocations: 2.10 MiB)
PC not in place: 2.626ms (40673 allocations: 3.44 MiB)
PC in place    : 1.942ms (24271 allocations: 2.16 MiB)

The convergence test shows that both the PC has order 1 scaling in practice.
In fact, the overall offset point is also lower for PC. At dt=1/2^10
RKMil : 6.02e-3
PC    : 1.44e-3
The testing code has now been incoorporated into test/runtest.jl .
Due to some conflicts in the use of f and g, the test script
for predictor correcter now uses f_nondiag, g_nondiag.
Pkg.test on StochasticDiffEq passed.

A pull request will be submitted with this commit.
struct PCEuler{T<:Real} <: StochasticDiffEqAlgorithm
theta::T
eta::T
bbprime::Function
Copy link
Member

Choose a reason for hiding this comment

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

Parameterize this for stability purposes.

Copy link
Contributor Author

@onoderat onoderat Feb 14, 2018

Choose a reason for hiding this comment

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

Could you elaborate on this a little more? Would you like the PCEuler to be parametrized by the type of the function bbprime?

Copy link
Member

Choose a reason for hiding this comment

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

In Julia, every function is a different type and Function is just an abstract type. This allows specialization on functions and then allows things like inlining. If you ::Function in a type, then it just knows it's a <:Function and cannot optimize directly on it. So yup, just make that ::F and a free-roaming F parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see, will make the change.

Reference: Stochastics and Dynamics, Vol. 8, No. 3 (2008) 561–581
Note that the original paper has a typo in the definition of bbprime...
"""
# TODO: Check if this can be implemented with @pure.
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't need @pure. It'll infer without it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks - I will remove the comment.

end

f_n = f(uprev,p,t)
ubar = @muladd uprev + dt*f_n + gdW_n
Copy link
Member

Choose a reason for hiding this comment

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

@muladd on the function makes everything inside @muladd, so these aren't needed (there are probably some other functions in this library that need cleaning for that)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Awesome - I did note that adding this did not change the speed. Will remove them.

gdW_np1 = g(ubar,p,tnp1).*dW
end

u .= @muladd uprev + (theta*dt)*fbar_np1 + ((1-theta)*dt)*fbar_n + eta*gdW_np1 + (1-eta)*gdW_n
Copy link
Member

Choose a reason for hiding this comment

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

You don't want to .= for numbers and static arrays. It'll work but be slower.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I will copy what you did for EM then.

@@ -0,0 +1,89 @@
using StochasticDiffEq, DiffEqProblemLibrary, DiffEqDevTools, Base.Test
Copy link
Member

Choose a reason for hiding this comment

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

Just rename this file as nondiagonal_tests.jl: I assume other algorithms will get added to it later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do.

@ChrisRackauckas
Copy link
Member

Looks great! Just a few minor changes and I'll merge. I'm happily surprised it does that much better on this problem: that's a very big improvement! I wonder if it's order 1 on commutative noise problems or something like that: that could make sense.

Note that the original paper has a typo in the definition of bbprime...
"""
# TODO: Check if this can be implemented with @pure.
PCEuler(bbprime::Function; theta::T=1/2, eta::T=1/2) where {T<:Real} =
Copy link
Member

Choose a reason for hiding this comment

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

Don't ::Function in a type signature. It has a non-intuitive anti-specialization behavior, but also not all functions <:Function.

Copy link
Contributor Author

@onoderat onoderat Feb 14, 2018

Choose a reason for hiding this comment

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

Wow, I have been doing that for a while now (in code I write), thanks for the tip! Will change.

Copy link
Member

Choose a reason for hiding this comment

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

In personal codes it's fine. In package codes:

struct MyF end
(::MyF)(t) = 2t
f = MyF()

f(t) works but f is not <:Function. You run into these weird cases the more usage things get. But if you don't do silly things yourself, you're safe 👍 (except for the non-specialization thing).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah that reminds me that I saw you did something like that to define the analytic solution to f. Ok will remove the ::Function in the type signature.

@codecov
Copy link

codecov bot commented Feb 14, 2018

Codecov Report

❗ No coverage uploaded for pull request base (master@38fe1ca). Click here to learn what that means.
The diff coverage is 87.93%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master      #53   +/-   ##
=========================================
  Coverage          ?   76.98%           
=========================================
  Files             ?       32           
  Lines             ?     3389           
  Branches          ?        0           
=========================================
  Hits              ?     2609           
  Misses            ?      780           
  Partials          ?        0
Impacted Files Coverage Δ
src/caches/basic_method_caches.jl 73.21% <ø> (ø)
src/algorithms.jl 96.96% <100%> (ø)
src/alg_utils.jl 94.38% <100%> (ø)
src/caches/cache_types.jl 100% <100%> (ø)
src/perform_step/predcorr.jl 85.36% <85.36%> (ø)
src/caches/predcorr_caches.jl 90% <90%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 38fe1ca...6ba3629. Read the comment docs.

@ChrisRackauckas
Copy link
Member

Just noticed bbprime should change to ggprime since the rest of the docs and functions refer to the diffusion term by g.

@ChrisRackauckas ChrisRackauckas merged commit ccc5ca4 into SciML:master Feb 14, 2018
@ChrisRackauckas
Copy link
Member

Good stuff, thanks. After seeing that from RKMil, I wonder how much of that is from the RK handling of the derivative handling. Would you mind doing a PR to do ggprime implementations of Milstein?

@ChrisRackauckas
Copy link
Member

This needs to get added to the docs page http://docs.juliadiffeq.org/latest/solvers/sde_solve.html . It should probably be recommended when ggprime is known, and the definition of ggprime will need to go somewhere there for now. Eventually we'll want to handle ggprime like in:

http://docs.juliadiffeq.org/latest/features/performance_overloads.html

but that should wait on the change to DiffEqFunction where we can then have specific setups that are different for the different problem types.

@onoderat
Copy link
Contributor Author

Hi Chris - I would like to do the ggprime implementation of Milstein. The issue is that the one in the textbook does not have implicit noise and neither do any papers explicitly construct this.

I am a physicist so I cant really do the proofs for these methods. Perhaps, you want to have a stab at the proof? Once I have the formula, I can do a PR for the method.

@ChrisRackauckas
Copy link
Member

What do you mean by implicit noise? This shouldn't have any implicit terms.

@onoderat
Copy link
Contributor Author

Ah sorry - I meant implicitness in the diffusion. Basically the one in the textbook does not have a eta parameter - it only has implicitness in the drift. See last sentence of screenshot below -

screen shot 2018-02-14 at 11 07 32 am

@ChrisRackauckas
Copy link
Member

I was thinking of doing the simpler one first:

milstein

@onoderat
Copy link
Contributor Author

Ah I see. Sure that should be easy to code up. But currently I am kind of rushing to prepare for the APS march meeting. Perhaps we could discuss this further when we meet in person next month?

I am thinking of working on the Wiktorsson approximations and creating a better interface for that stuff through NoiseProcess. Once that is done, perhaps we can multiple dispatch on the type of problem (commutative, diagonal, etc...).

This way we do not need to define RKMIlCommute, RKMil etc. What do you think?

@ChrisRackauckas
Copy link
Member

I am thinking of working on the Wiktorsson approximations and creating a better interface for that stuff through NoiseProcess. Once that is done, perhaps we can multiple dispatch on the type of problem (commutative, diagonal, etc...). This way we do not need to define RKMIlCommute, RKMil etc. What do you think?

Yeah, share your idea when you can. As always, there's no rush. Could you open an issue to discuss this? I'm very curious about your idea here. I want to do something for this, and we should have a more constrained version of jumps that allow for these kinds of schemes from Platen.

@ChrisRackauckas
Copy link
Member

BTW, schemes are usually drift-implicit because the inverse of the normal has infinite moments making it hard to prove convergence. There's things like truncated schemes though to get around this.

@onoderat
Copy link
Contributor Author

onoderat commented Feb 14, 2018

Sure I will open an issue on the NoiseProcess stuff.

Thanks for explaining why the diffusion-implicit is hard. Interesting for the predictor-corrector, I numerically found that eta=0.5 is critical for getting that order 1 scaling.

Perhaps its an interesting research direction to investigate whether the same higher order scaling can be done if we have an eta (implicit diffusion) for the milstein case? Like say we get order 1.5 for doing order 1 level work?

screen shot 2018-02-14 at 12 06 57 pm

@ChrisRackauckas
Copy link
Member

That's super convergence. A lot of it is just finding the right conditions to make convergence "better", like how on additive noise the Euler-Maruyama scheme is Order 1.0. It would be very interesting to find out super convergence criterias for high order SDE schemes. I would assume that many of the common categories (additive, multiplicative) have some super convergence stuff going on.

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.

None yet

2 participants