SciML / ModelingToolkit.jl

An acausal modeling framework for automatically parallelized scientific machine learning (SciML) in Julia. A computer algebra system for integrated symbolics for physics-informed machine learning and automated transformations of differential equations
https://mtk.sciml.ai/dev/
Other
1.43k stars 209 forks source link

Time-varying parameters and delay differential equations part 2 #108

Closed ChrisRackauckas closed 5 years ago

ChrisRackauckas commented 5 years ago

For initial time-varying parameter support, @HarrisonGrodin got the following test problem working:

@testset "time-varying parameters" begin
    @parameters σ′(t)
    eqs = [D(x) ~ σ′*(y-x),
           D(y) ~ x*(ρ-z)-y,
           D(z) ~ x*y - β*z]
    de = DiffEqSystem(eqs,t,[x,y,z],[σ′,ρ,β])
    @test begin
        f = eval(generate_function(de))
        du = [0.0,0.0,0.0]
        f(du, [1.0,2.0,3.0], [x->x+7,2,3], 5.0)
        du ≈ [12, -3, -7]
    end
end

This generates an expression that expands to σ′(t). This works, and in many cases is what the user will want, but what we need in order to support delay differential equations, and also make some cases easier to parse into MT format, is something like:

    @parameters σ′
    eqs = [D(x) ~ σ′(t-1)*(y-x),
           D(y) ~ x*(ρ-z)-y,
           D(z) ~ x*y - β*z]
    de = DiffEqSystem(eqs,t,[x,y,z],[σ′,ρ,β])
    @test begin
        f = eval(generate_function(de))
        du = [0.0,0.0,0.0]
        f(du, [1.0,2.0,3.0], [x->x+7,2,3], 5.0)
        du ≈ [12, -3, -7]
    end

So there's two questions to ask here:

  1. Is knowing the time-dependence during variable construction necessary? If it isn't, it's worth dropping it, or at least not requiring it.
  2. If we add a call to the variable to allow it to have an operation that defines its dependence, does it need to be a new Variable?
HarrisonGrodin commented 5 years ago
  1. Knowing time-dependence during variable construction isn't strictly necessary, but changing that will require another modification to the AST, treating Variables as functions rather than "leaf nodes". This is a change I would definitely be for, and I should be able to get this done fairly quickly.
  2. No, this shouldn't be required. If we treat Variables as functions, we should be able to call them as usual in the expression tree with no unexpected consequences.

Note that it's possible to get the second example working based on the existing model with a bit of outside manipulation of the function. (e.g. for each call to σ′, make a new variable - σ′t1, σ′t2, etc. - and pass it in as a separate parameter) This isn't elegant, though, and doesn't work in the context of unknown variables.


If we move forward and treat variables as functions, I propose the following macro meaning:

@parameters t()
@variables x(t) y(t) z(t)
@parameters σ ρ(t) β()

t = Variable(:t; known=true)()
x = Variable(:x)(t)
y = Variable(:y)(t)
z = Variable(:z)(t)
σ = Variable(:σ; known=true)
ρ = Variable(:ρ; known=true)(t)
β = Variable(:β; known=true)()

Providing a symbol name in a non-call form leaves the symbol as a function object to be called. Providing the arguments within the macro call aliases the variable to the specified call, however, which includes 0-ary calls (for independent variables and constant values).

Assuming this looks reasonable, I can swap out the internals of a few helper functions and have this functionality ready.

ChrisRackauckas commented 5 years ago

I think that's a reasonable way forward. Though I don't know what you mean by:

Providing a symbol name in a non-call form leaves the symbol as a function object to be called.

Does this mean in this case it's kept as β and not β()?

HarrisonGrodin commented 5 years ago

No, I mean the opposite, actually - it's an important distinction to make.

There, I mean if you simply give a symbol name (in this example, σ), it is left as a function which can be called in the expression (e.g. σ(t), σ(t-1), etc.). However, if you provide the macro with an expression in call format (like ρ(t) or β()), we alias the name to this specific call (ρ refers to ρ(t) and β refers to the constant β()).

Notice in the macro expansion I provided that all Variables are called and turned into Expressions, other than σ, which is left as a Variable <: Function.

HarrisonGrodin commented 5 years ago

I suppose an alternative scheme could be to use _ to denote functions, treating symbols as implicit 0-ary functions:

@parameters t
@variables x(t) y(t) z(t)
@parameters σ(_) ρ(t) β

This might reduce confusion with macro usage, actually.

ChrisRackauckas commented 5 years ago

There, I mean if you simply give a symbol name (in this example, σ), it is left as a function which can be called in the expression (e.g. σ(t), σ(t-1), etc.). However, if you provide the macro with an expression in call format (like ρ(t) or β()), we alias the name to this specific call (ρ refers to ρ(t) and β refers to the constant β()).

That sounds promising.

I suppose an alternative scheme could be to use _ to denote functions, treating symbols as implicit 0-ary functions:

That looks more confusing to me. I like the σ()