Tips#

Differentiable smooth functions#

Smooth and differentiable functions are more friendly for automatic differentiation (AD) and differential equation solvers.

Smooth Heaviside step function#

A Heaviside step function (0 when x < a, 1 when x > a) could be approximated with a steep logistic function.

using Plots

The function output switches from zero to one around x=0.

function smoothheaviside(x, k=100)
    1 / (1 + exp(-k * x))
end

plot(smoothheaviside, -1, 1)
_images/67cfd022f0c905c7fd5d7a6e3d092abe2a05870b13aa693fc1f334fd61545788.png

Smooth single pulse#

A single pulse could be approximated with a product of two logistic functions.

function singlepulse(x, t0=0, t1=0.1, k=1000)
    smoothheaviside(x - t0, k) * smoothheaviside(t1 - x, k)
end

plot(singlepulse, -1, 1)
_images/409d0efb1d39019a1a48f90bf895651f0ed57125a9cdfb70dfa2ce152c710f85.png

Smooth absolute value#

Inspired by: https://discourse.julialang.org/t/smooth-approximation-to-max-0-x/109383/13

# Approximate abs(x)
function smooth_abs(x; c=(1//2)^10)
    sqrt(x^2 + c^2) - c
end

plot(smooth_abs, -10, 10, label="Smooth abs")
_images/629413aa422acbbbfc5f3480e9250e558648993a996d28a41513caebe981fa8a.png

Smooth max function#

Approximate max(0, x)

function smooth_max(x; c=(1//2)^10)
    0.5 * (x + smooth_abs(x;c))
end

plot(smooth_max, -10, 10, label="Smooth max")
_images/9b5ef74179e8a7fc5743fb84c7e2312babdf0ad90da16c844af1fe5cdba18855.png

Smooth minimal function#

Approximate min(0, x)

function smooth_min(x; c=(1//2)^10)
    0.5 * (smooth_abs(x;c) - x)
end

plot(smooth_min, -10, 10, label="Smooth min")
_images/6a29c747c64ed3dd1bd1b79d8abb8f3371c3e4e2f4bb842ff2485315f148b3dc.png

(Another) smooth step function#

function smooth_step(x; c=(1//2)^10)
    0.5 * (x / (sqrt(x^2 + c)) + 1)
end

plot(smooth_step, -10, 10, label="Smooth step")
_images/8a066877dec1aadeb72ffbe148f7eeec3e022de5e790a3f2aeb4a574b1515ab0.png

Periodic pulses#

From: https://www.noamross.net/2015/11/12/a-smooth-differentiable-pulse-function/

function smoothpulses(t, tstart, tend, period=1, amplitude=period / (tend - tstart), steepness=1000)
    @assert tstart < tend < period
    xi = 3 / 4 - (tend - tstart) / (2 * period)
    p = inv(1 + exp(steepness * (sinpi(2 * ((t - tstart) / period + xi)) - sinpi(2 * xi))))
    return amplitude * p
end

plot(t->smoothpulses(t, 0.2, 0.3, 0.5), 0.0, 2.0, lab="pulses")
_images/a0b5e2b7171a53cfeea9f9b197881b9a82fc03090653c77273899adb28d27477.png

Avoid DomainErrors#

Some functions such as sqrt(x), log(x), and pow(x), throw DomainError exceptions with negative x, interrupting differential equation solvers. One can use the respective functions in JuliaMath/NaNMath.jl, returning NaN instead of throwing a DomainError. Then, the differential equation solvers will reject the solution and retry with a smaller time step.

import NaNMath as nm
nm.sqrt(-1.0) ## returns NaN

# ODE function from an MTK ODE system
NaN

f = ODEFunction(sys) could be useful in visualizing vector fields.

using ModelingToolkit
using DifferentialEquations
using Plots

@variables t x(t) RHS(t)
@parameters τ
D = Differential(t)
Differential(t)

Equations in MTK use the tilde character (~) as equality. Every MTK system requires a name. The @named macro simply ensures that the symbolic name matches the name in the REPL.

eqs = [
    RHS  ~ (1 - x)/τ,
    D(x) ~ RHS
]

@named fol_separate = ODESystem(eqs, t)
sys = structural_simplify(fol_separate)
f = ODEFunction(sys)
┌ Warning: Independent variable t should be defined with @independent_variables t.
└ @ ModelingToolkit ~/.julia/packages/ModelingToolkit/zfOUk/src/utils.jl:119
(::SciMLBase.ODEFunction{true, SciMLBase.AutoSpecialize, ModelingToolkit.var"#f#937"{RuntimeGeneratedFunctions.RuntimeGeneratedFunction{(:ˍ₋arg1, :ˍ₋arg2, :t), ModelingToolkit.var"#_RGF_ModTag", ModelingToolkit.var"#_RGF_ModTag", (0x69be7227, 0x91e2d20a, 0x42eb269b, 0x709b97c3, 0x585d3d5e), Nothing}, RuntimeGeneratedFunctions.RuntimeGeneratedFunction{(:ˍ₋out, :ˍ₋arg1, :ˍ₋arg2, :t), ModelingToolkit.var"#_RGF_ModTag", ModelingToolkit.var"#_RGF_ModTag", (0x232ed58b, 0x110c8637, 0xe7b22a1a, 0x33d59865, 0xbced26da), Nothing}}, LinearAlgebra.UniformScaling{Bool}, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, Nothing, ModelingToolkit.ObservedFunctionCache{ModelingToolkit.ODESystem}, Nothing, ModelingToolkit.ODESystem, Nothing, Nothing}) (generic function with 1 method)

f(u, p, t) returns the value of derivatives

f([0.0], [1.0], 0.0)
1-element Vector{Float64}:
 1.0

This notebook was generated using Literate.jl.