-
Notifications
You must be signed in to change notification settings - Fork 29
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
WIP: Overhaul DynamicalSystem #24
Conversation
I only have to test out of place discrete. The others are tested already.
Still todo: Auxilary functions, performance boosts, time evolution functions (evolve / trajectory)
Alright everybody! This is iiiiiiit. I am so liiiiiiiiiiiiit! So the main file is at the moment NOTICE: The design is pretty straight-forward.
@ChrisRackauckas I would truly, truly, truly appreciate it if you took a look at it as well and gave me some hints. I am really bad at performance optimization and maybe I am doing something terribly wrong at some point. The timings do not look very bad, but I also have no knowledge to judge if they are actually bad! (Note: everything is evolved/solved with @RainerEngelken if you want you can also join the discussion! Long story short, we are reworking the fundamental way a dynamical system is defined. |
Both "auxilary"
Hm I think I am doing something super-wrong for discrete systems.
|
Nothing jumps out to me. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a significant improvement from the last version. But it can be even better. My overall impression is that the code is still not very DRY. I'm not sure if this is the final version so I'll not point out all the details. But I suggest making it more concise in the final version, since otherwise it's hard to be sure that you are providing well-factorized API.
Also, I think there are too many exports, especially the auxiliary ones. I think you'd want to keep the APIs minimal (let alone the exported names), since it makes maintaining the library harder. Furthermore, it's better to keep exported names minimal in general since that's going to contaminate user's namespace. I think that's the reason why ForwardDiff does not export anything.
Another general comment: I'd suggest being more careful about the import and the semantics of the functions you extend.
src/dynamicalsystem.jl
Outdated
import OrdinaryDiffEq: isinplace, ODEIntegrator, step! | ||
import Base: eltype | ||
|
||
export dimension, state, DynamicalSystem, DS, integrator, tangent_integrator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would be hesitant to export aliases like DS, CDS and DDS. Those are easily definable on the user side but they may want to use it differently. Like DS
as type parameter of the DynamicalSystem:
strcut Something{DS <: DynamicalSystem} end
const ContinuousSomething = Something{ContinuousDynamicalSystem}
const DiscreteSomething = Something{DiscreteDynamicalSystem}
Of course, I'm not opposed to internal use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Removed these exports.
src/dynamicalsystem.jl
Outdated
""" | ||
set_parameter!(prob, index, value) = (prob.p[index] = value) | ||
set_parameter!(prob, values) = (prob.p .= values) | ||
set_parameter!(ds::DS, args...) = set_parameter!(ds.prob, args...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of set_parameter!
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChaosTools, obviously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, ChaosTools just can ds.prob.p[index] = value
etc., right? Why would you define a function here and make it an API? I mean, it makes maintaining the package harder...
src/dynamicalsystem.jl
Outdated
# Create tangent eom: | ||
reducedeom = (du, u) -> ds.prob.f(du, u, ds.prob.p, ds.prob.t) | ||
cfg = ForwardDiff.JacobianConfig(reducedeom, rand(3), rand(3)) | ||
tangenteom = TangentIIP(ds.prob.f, similar(ds.prob.u0, D, D), cfg) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't similar(ds.prob.u0, D, D)
be similar(ds.prob.u0, D, size(Q0, 2))
to allow solving for Lyapunov exponents smaller than the system's dimension?
src/dynamicalsystem.jl
Outdated
# or continuous from just the equations of motion!) | ||
function ContinuousDynamicalSystem(eom, state::AbstractVector, p, j = nothing) | ||
IIP = isinplace(eom, 4) | ||
u0 = IIP ? Vector(state) : SVector{length(state)}(state...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the use of standard/static array has to be enforced. End-users should be able to choose whatever they like. For example, maybe some users want to use GPU arrays. I'd suggest to just use the array type they provided.
If you are desperate for handy static array initialization, how about using SVector
when state
is a tuple?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want to support something other than SVector
you are welcome to do a PR both here and at ChaosTools that supports it. I won't do it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You missed my point. DynamicalSystemsBase.jl has to be generic on array type. I'm not talking about ChaosTools.jl. Everything you rely on (DifferentialEquations.jl and ForwardDiff.jl) accept generic type so it's not difficult at all to define DynamicalSystemsBase.jl in a type-generic way. In fact, you are writing more code to make it less generic...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why don't you do it then?
Don't you think you are over-using the wording "has to be" ? For somebody that has written 0 lines of code for this package, I don't think you can be in a position to say what "has" to be done.
src/dynamicalsystem.jl
Outdated
function DiscreteDynamicalSystem(eom, state::AbstractVector, p, j = nothing) | ||
IIP = isinplace(eom, 4) | ||
u0 = IIP ? Vector(state) : SVector{length(state)}(state...) | ||
prob = DiscreteProblem(eom, u0, DDS_TSPAN, p) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DiscreteDynamicalSystem code is mostly identical to ContinuousDynamicalSystem... I'd suggest to define them by
ContinuousDynamicalSystem(f, u0, p, j = nothing) =
DynamicalSystem(ODEProblem(f, u0, CDS_TSPAN, p), j)
DiscreteDynamicalSystem(f, u0, p, j = nothing) =
DynamicalSystem(DiscreteProblem(f, u0, DDS_TSPAN, p), j)
and do all the routine work in the DynamicalSystem constructor.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already overhauled.
src/dynamicalsystem.jl
Outdated
solver, newkw = extract_solver(diff_eq_kwargs) | ||
|
||
prob = ODEProblem(ds.prob.f, U0, CDS_TSPAN, ds.prob.p) | ||
integ = init(prob, solver; newkw..., save_everystep = false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put save_everystep = false
in DEFAULT_DIFFEQ_KWARGS
and let people override the setting. Same for DDS. Some end-users may want to draw attractors with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use trajectory
for that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That exclude users from using DynamicalSystemsBase.jl ecosystem. For example, DynamicalSystemsBase.jl has a rich plotting pipeline. Besides, it's not at all difficult to support it (just one line).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it doesnt. Use solve(ds.prob)
with whatever arguments you want. This is less than 1 line, it is just 2 extra characters and you get all the capabilities you ask for.
src/dynamicalsystem.jl
Outdated
|
||
|
||
""" | ||
integrator(DS, u0 = ds.prob.u0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest integrator(ds, tspan = infinite_tspan(ds), u0 = ds.prob.u0)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also suggest integrator(ds, tspan = infinite_tspan(ds), u0 = ds.prob.u0; kwargs...)
and just pass kwargs
to init
for simplicity but that's not a strong opinion.
src/dynamicalsystem.jl
Outdated
systemtype(::ODEProblem) = "continuous" | ||
systemtype(::DiscreteProblem) = "discrete" | ||
|
||
function extract_solver(diff_eq_kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't it equivalent to this?
function extract_solver(diff_eq_kwargs)
newkw = deepcopy(diff_eq_kwargs)
solver = pop!(newkw, DEFAULT_SOLVER)
return solver, newkw
end
I mean, assuming this is a small dictionary so that deepcopy is super cheap.
src/dynamicalsystem.jl
Outdated
####################################################################################### | ||
|
||
dimension(prob::DEProblem) = length(prob.u0) | ||
eltype(prob::DEProblem) = eltype(prob.u0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No... No... DEProblem
is not the type defined in this library so you shouldn't change any of its API, especially for the Base-related APIs. (Monkey-patch is required in some cases but this is totally not the case.) Furthermore, eltype
mostly is for container or iterator. DEProblem
is neither.
The second reason applies to DS. Don't define eltype(::DS)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What? Why.
I am using dimension
, not DiffEq
. What you say doesn't make sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I now see why you are asking about dimension
in discourse. But please read carefully. I'm talking about Base.eltype
. Not dimension
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I have already removed that, it was only for internal test.
src/dynamicalsystem.jl
Outdated
@@ -0,0 +1,349 @@ | |||
using OrdinaryDiffEq, ForwardDiff, StaticArrays | |||
import OrdinaryDiffEq: isinplace, ODEIntegrator, step! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ODEIntegrator is not used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
taken care of.
As I mentioned in #20 (comment), I think the name/concept "tangent integrator" has to be reconsidered and so has to be in the TODOs. I think the most meaningful choice at the moment is "augmented integrator" but I still don't like it. |
Currently at the master branch of The results are in this gist: https://gist.github.com/Datseris/5123a27f5659d14344551a296b7e57d3 In general I always gets 1 order of magnitude speed, most of the time even more. I think we can say that because discrete problems are inherently fast, this speed can be sacrificed for the callback functionality. |
Yeah, pure discrete problems with cheap maps are tough because we need to make sure we skip as much as possible in the normal usage, but still allow the extra functionality. I'm not sure what the performance tradeoff is, but a straight loop would be faster, the question is how much faster. But what we should do is profile it and see if we can make this use case faster. My gut feeling is that we can make it significantly faster by making the internals of the integrator skip some parts via type checks, and by that it should be able to almost match a straight loop. I don't believe we are there yet though (but Julia v0.7 will be extremely helpful because of how it propagates constants). On a sidenote, I've wanted to implement SimpleDiffEq.jl that has a few straight loop (no callbacks, etc) implementations of the most useful algorithms to be able to get a sense of what the maximum performance is, and use that as a benchmark target. |
This is a great idea, not only for performance comparison but also for normal users that have use-cases that do not care about callbacks. I am willing to put my minimal Of course it has to be somehow refined/adapted to DiffEq styles, but I am guessing you would want to shape it yourself afterwards, since I have precisely 0 knowledge of DiffEq internals. |
I created the library. Let's do it. |
Todo left is to make tangent integrator and to use generic tangent function
and many other minor improvements
I commented on discourse but it looks like Edited: It's already approved. |
I also finished tangent_integrator for discrete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I've commented too much. I'll hit the approve button for now.
This is so god damn sad. When I compute lyapunovs of discrete systems I get 1000x slowdown from the current stable version. The problem is that the code has now become so complicated that I have no clue where the regressions are; probably in multiple places |
I am separating discrete and continuous with this commit. The `DynamicalSystem` is now an abstract type.
towel map, current version
|
Closes #20
This (currently WIP) is the overhauling of all
DynamicalSystem
types. I have managed to put everything under the typeDynamicalSystem
and use aliasing and type-parameters for dispatch.At the moment everything is looking extremely beatiful. I am literally drooling over how beatiful julia is and how extremely transparent code you can write even for such a complicated situation.
The accuracy of methods like e.g. lyapunov increased. Also, a lyapunov computation with NOT autodifferentiated jacobian for a CONTINUOUS system takes 0.07 seconds. I think this is god damn impressive!!! (I am using out-of-place method of course)
To-do:
DiscreteFast
or whatever, that uses the existing minimal implementation. This will be used by default if the user giveseom, state
instead ofDiscreteProblem
.set_parameters!
.trajectory
. We ditch bothevolve
andevolve!
.famous_systems.jl
parallel_evolver
.DiffEqCallbacks
dependency by making henon "not energy conserving" (and comment out the code for later use). This will only be implemented when Interface for Hamiltonian systems #4 is solved.