JuliaDynamics / DynamicalSystems.jl

Award winning software library for nonlinear dynamics and nonlinear timeseries analysis

Home Page:https://juliadynamics.github.io/DynamicalSystemsDocs.jl/dynamicalsystems/dev/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

DynamicalSystems.jl 3.0 redesign

Datseris opened this issue · comments

Intro

This issue outlines the future of DynamicalSystems.jl, so that it becomes more general, yet with simpler source code. The main problem it tries to address is that there are currently non-necessary limitations on what kind of things can be considered as dynamical systems. While working on the attractors via recurrences functionality (AttractorsViaRecurrences) we realized that many things in DynamicalSystems.jl could be written to be calculatable for a much more general class of "dynamical systems". This is what gave birth to the current GeneralizedDynamicalSystem, which includes the "standard" discrete and continuous systems, as well as derivative integrators such as projected integrator or poincare maps. However, things can be made even more general!

For example, after this interface is in place, one could actually calculate the "Lyapunov exponent" of an agent based model :D (not that I would advise someone to do it in a paper, but the algorithm that computes it only relies on whether, and how much, states diverge with time, so....)

General Dynamical Systems

In general, now for something to be considered as a "Dynamical System", it simply has to be an "integrator". Something that encapsulates its current state, as well as how to evolve this state forwards in time. The reason for this is that anyways how every single library function works: given a dynamical system it initializes an integrator and then calls the "actual" implementation that works with an integrator.

This will make some big changes of the internals:

  • DynamicalSystem is a generic umbrella term and the term GeneralizedDynamicalSystem is removed. Everything subtypes this DynamicalSystem.
  • ContinuousDynamicalSystem, DiscreteDynamicalSystem are both integrators. Before doing this, we have to re-think a bit how parallelization would work. But I guess one can always deepcopy the integrator instance and use pmap on the deepcopies which have modified initial conditions.
  • Then we have to think whether ContinuousDynamicalSystem has reason to exist or AbstractODEIntegrator is already good enough.
  • There is no reason to initialize the Jacobian business for dynamical systems. Instead, Jacobian is initialized when a tangent_integrator is initialized.
  • poincaremap, stroboscopicmap, projectedsystem all become subtypes of a DynamicalSystem.
  • The function integrator has no reason to exist. All functions of the library already use an integrator so we remove the middle man and make the dynamical systems themselves "integrators" (in the general sense of the word, e.g., the stroboscopic map is an "integrator").

API

Here I outline the list of functions that we need to generically handle dynamical systems. Based on this list, we can also think whether we need to define new types, or are the integrators from DiffEq already good enough. ds stands for "dynamical system" in the following.

  • reinit!(ds, u) re-initializes the integrator to state u.
  • isdiscretetime(ds) for whether the system operates in discrete (i.e., a map) or continuous time (i.e., some sort of differential equation).
  • isdeterministic(ds) for whether the system is deterministic.
  • initial_state(ds) gives the initial state the integrator has been defined with.
  • step!(ds) steps the system for one step, where "one" is defined according to the system integrator. This is the integer number 1 for all discrete time systems.
  • step!(ds, dt [, stop_at_dt]) which steps the system for dt and if specified exactly for dt. The third argument is only used in continuous time systems.
  • get_state(ds), set_state!(ds) to return or modify the state of the system
  • get_parameters(ds) to return the parameter container.
  • set_parameter!, which arguments as is right now, becomes the trivial set_parameter!(ds, v, i) = _set_parameter!(get_parameters(ds), v, i) which would work for any system type.
  • u_modified!(ds) for telling DiffEq that the state has been modified externally. The default implementation of this function will do nothing in non-DiffEq systems.

Downsides

  • DiffEq keywords must be provided in the ContinuousDynamicalSystem constructor, not in the individual functions of the library. On the other hand, this has the benefit of simplifying all functions in favor of complicating one function.
  • How does this impact performance and/or parallelization?
  • Now the "state" of the system is always altered. Nevertheless, I think, all dynamical systems would anyways have a field "u0" for "initial state", so I guess this doesn't really alter anything. We could add a function initial_state(ds) that returns that u0 as well.

@ChrisRackauckas If you by any chance have any comments to provide here, please do go ahead. I'll do this in some months from now, but given how large of a redesign this is, good to catch potential issues well in advance.

I'm not sure you have to try so hard. The tangent space is computed via the partial terms in dual numbers, so I think there could be a nice design to DynamicalSystems.jl which just builds standard integrators with dual numbers and uses helper functions to expose the tangent space values. It seems like that could lead to a lot simpler code, though I don't know about compile time growth.

Hi Chris, thanks for the reply. Tangent integrators aren't really any big concern for me at the moment, for two reasons: (1) Tangent space integration is a very small part of DynamicalSystems.jl functionality. (2) The design with the Jacobian works very well and is fast enough, so I can't allocate resources to try out the dual numbers setup at the moment. Perhaps in the future if I have students that want to consider this as a topic to work on...

For me, the main concern is that with my redesign, a dynamical system will essentially be an integrator. This is a mutable object that all functions like e.g., lyapunov will mutate. What I was afraid is whether this would have any strongly negative impact on performance or anything like that. However, in the meantime, we measured that it is possible to just "deep copy" the integrator so that one runs computations over e.g. threads, so this seems okay to me.