Skip to content

Type-Based DSL #261

@ChrisRackauckas

Description

@ChrisRackauckas

I think it's time that we put down a design for a type-based DSL which can specify ODEs, higher order ODEs, SDEs, regular jumps, DAEs, DDEs, PDEs, and all combinations. I think we actually get a DSL for linear and nonlinear solve problems for free. It sounds terrifying, but the specification doesn't seem all that bad after giving it a try.

I think I can just explain by examples. First there's the simple example for the fully type-based form:

# Define some variables
x = DependentVariable(:x)
y = DependentVariable(:y)
z = DependentVariable(:z)
t = IndependentVariable(:t)
D = Derivative(t) # Default of first derivative, Derivative(t,1)
σ = Parameter()
ρ = Parameter()
β = Parameter()

# Define some expressions
eqs = [D*x == σ*(y-x)
       D*y == x*-z)-y
       D*z == x*y - β*z]

# Define a differential equation
de = DiffEq(eqs)

# Now go to the DiffEq pipeline
prob = ODEProblem(de,u0,tspan,p)

Now we start adding sugar:

@DV x y z
@IV t
@Parameter σ,ρ,β
D = Derivative(t)
de = @diffeq begin
  D*x = σ*(y-x)
  D*y = x*-z)-y
  D*z = x*y - β*z
end
prob = ODEProblem(de,u0,tspan,p)

Now we start adding features.

# Array Variables
x = DependentVariable(:x,1:10) # Array of variables
@DV x[1:10]
# Can do the same on IndependentVariable, Parameters, ...

# Noise
dW = NoiseVariable(:dW,3)

# Regular Jump
dp = JumpVariable(5x) # rate(u,p,t) = 5u[1]

# Delayed Variable
y_τ  = y(t-5)
y_τ2 = y(t-f)

# Mass matrix DAE
de = @diffeq begin
  D*x + D*y = σ*(y-x)
  D*y = x*-z)-y
  D*z = x*y - β*z
end

# Fully Implicit SDAE
de = @diffeq begin
  exp(D*x + D*y) = σ*(y-x) + dW[1]
  D*y = x*-z)-y + dW[2]
  D*z = x*y - β*z + dW_3 # Same as dW[3]
end

# Define explicit orderings for variables, parameters, etc.
de = DiffEq(eqs,dep_vars,indep_vars,parameters,noises)

Now we start specifying some PDEs:

@DV u
@IV x t
D_t = Derivative(t)
D_x = Derivative(x)
D_xx = D_x*D_x
D_xx == Derivative(x,2)
D_t*u == D_xx*u

# Assume μ(u,p,t,x) <: Function and σ(u,p,t,x) <: Function are defined
L = Operator(mu*D_x + σ*D_xx)
D_t*u = L*u

And as an added bonus

# Nonlinear rootfinding problem
nl = @nl begin
  σ*(y-x)
  x*-z)-y
  x*y - β*z
end

nlprob = NonlinearProblem(nl,u0)
# Now send this over to NLsolve with pre-defined Jacobians!

# Similar for linear

# Possible extension down the line
nl = @nl begin
  σ*(y-x) + x*-z)-y + x*y - β*z
end
scalar_function = Scalar(nl) # Defines and computes Jacobians/Hessians, send to Optim!

Extra Features

The purpose of this is not to do a full physical modeling setup (though it should be possible to build one off of it), but it's to make it easy to specify/modify/optimize differential equations in a natural form that's both computer and human readable. Also, if Modia.jl is going to come out then I don't see a reason to directly build something like that, so I don't plan to spend much time in feature areas that they focus on (but of course those extensions can be readily added). But, there's definitely a few things from Modelica that we can put in here.

One thing is the possibility of initial conditions and parameter values in the model specification.

x = DependentVariable(:x,2.0)
@DV x 2.0 y 3.0 z 4.0

σ = Parameter(,3.0)

Then u0 and p wouldn't be needed in the ODEProblem construction.

Model composition can come later. It could be something like:

struct Lorenz <: DiffEqComponent
  x
  y
  z::Flow
end

eqs = [D*x == σ*(y-x)
       D*y == x*-z)-y
       D*z == x*y - β*z]
lorenz = Component(Lorenz,eqs)

or

@component struct Lorenz
  x
  y
  z::Flow
end begin
  D*x = σ*(y-x)
  D*y = x*-z)-y
  D*z = x*y - β*z
end

and then have some kind of connect. This is stuff that can be added later for people to build modeling frameworks, but it's not truly essential to the original goal.

Implementation details

Most of the implementation details are pretty obvious. It's a bunch of expressions with small types. But a few things to mention. Variables, parameters, etc. have direct conversions to SymEngine.Basics. This is then makes it easy to do math on them.

The "missing" implementation detail is that any arithmetic operation forms a DEStatement which holds the components by their types by their addition (differential statements, jump statements, noise statements, base statements). == then generates a DEquation with a statement on the lhs and a statement on the rhs.

Remaining Issues

  • How do you specify history functions (for DDEs)?

Deprecation Path

There won't truly be a "deprecation" of @ode_def since it is still nice. Instead, it would just output a DiffEq when this is ready, so it would be directly incorporated into the next DSL.

Remarks

Based off of SciML/DiffEqDSL.jl#1

Pinging @tshort @gabrielgellner @zahachtah @mauro3 due to how helpful you all were in round one!
@korsbo and @Gaussia probably have great ideas as well. @vjd and @simonbyrne

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions