I now have two solutions but neither is quite right.
First, combining ContinuousCallbacks and a DiscreteCallback works but I have to set dtmax
to a small value to get accuracy (is there an equivalent of rootfind=true
for DiscreteCallback?)
using DifferentialEquations, Plots
plotly()
@enum state neglimit moving poslimit
NEGLIMIT = -10.0
POSLIMIT = 10.0
MAGNITUDE = 25.0
fdrive(t) = MAGNITUDE*sin(2π*0.1*t)*exp(-t/15.0)
fspring(x) = -1.0*x
fdamping(ẋ) = -.7*ẋ
force(t,x,ẋ) = fdrive(t)+fspring(x)+fdamping(ẋ)
# if we are running into the negative travel limit
neglimit_cond(u,t,integrator) = NEGLIMIT-u[2]
function neglimit_affect!(integrator)
integrator.p[1] = neglimit
integrator.p[2] = neglimit
integrator.u[1] = 0.0
end
nlccb = ContinuousCallback(neglimit_cond, neglimit_affect!, nothing)
# if we are running into the positive travel limit
poslimit_cond(u,t,integrator) = u[2]-POSLIMIT
function poslimit_affect!(integrator)
integrator.p[1] = poslimit
integrator.p[2] = poslimit
integrator.u[1] = 0.0
end
plccb = ContinuousCallback(poslimit_cond, poslimit_affect!, nothing)
# if we are trying to get off of neglimit travel limit condition
function moving_cond(u,t,integrator)
ẋ,x = u
state, callback_prev_state = integrator.p
if callback_prev_state == neglimit
out = force(t,x,ẋ)>0
elseif callback_prev_state == poslimit
out = force(t,x,ẋ)<0
else
out = false
end
out
end
function moving_affect!(integrator)
integrator.p[1] = moving
end
mvccb = DiscreteCallback(moving_cond, moving_affect!)
cbs = CallbackSet(nlccb,plccb,mvccb)
function spring_mass_ODE!(du,u,p,t)
ẋ, x = u
state, calback_prev_force, mass = p
if state == moving
du[1] = 1/mass*force(t,x,ẋ)
du[2] = ẋ
else
du[1] = 0.0
du[2] = 0.0
end
end
function smsim()
tspan = [0.0, 50.0]
u0 = [0.0, 0.0]
p=[moving, moving, 1.0 ]
prob = ODEProblem(spring_mass_ODE!, u0, tspan,p)
sol = solve(prob, Tsit5(), callback=cbs, abstol=1e-7,reltol=1e-7,dtmax=0.1)
end
function plotsmsim(sol)
p1=plot(sol, vars=[1,],ylabel="velocity")
p2=plot(sol, vars=[2,],ylabel="position")
plot(p1,p2,layout=(2,1),link=:x,leg=false)
end
function simplot()
sol = smsim()
plotsmsim(sol)
end
The second uses two DiscreteCallbacks but has an issue in which the solutions “buzzes” when it’s against the limit stops.
using DifferentialEquations, Plots
plotly()
NEGLIMIT = -10.0
POSLIMIT = 10.0
MAGNITUDE = 25.0
fdrive(t) = MAGNITUDE*sin(2π*0.1*t)*exp(-t/15.0)
fspring(x) = -1.0*x
fdamping(ẋ) = -.7*ẋ
force(t,x,ẋ) = fdrive(t)+fspring(x)+fdamping(ẋ)
# if we are running into the negative travel limit
neglimit_cond(u,t,integrator) = NEGLIMIT-u[2]≥0
function neglimit_affect!(integrator)
ẋ, x = integrator.u
integrator.u[1] = 0.0
integrator.u[2] = NEGLIMIT
end
nlccb = DiscreteCallback(neglimit_cond, neglimit_affect!)
# if we are running into the positive travel limit
poslimit_cond(u,t,integrator) = u[2]-POSLIMIT≥0
function poslimit_affect!(integrator)
ẋ, x = integrator.u
integrator.u[1] = 0.0
integrator.u[2] = POSLIMIT
end
plccb = DiscreteCallback(poslimit_cond, poslimit_affect!)
cbs = CallbackSet(nlccb,plccb)
function spring_mass_ODE!(du,u,p,t)
ẋ, x = u
mass, = p
du[1] = 1/mass*force(t,x,ẋ)
du[2] = ẋ
end
function smsim()
tspan = [0.0, 50.0]
u0 = [0.0, 0.0]
p=[1.0]
prob = ODEProblem(spring_mass_ODE!, u0, tspan,p)
sol = solve(prob, Tsit5(), callback=cbs, abstol=1e-9,reltol=1e-9)
end
function plotsmsim(sol)
p1=plot(sol, vars=[1,],ylabel="velocity")
p2=plot(sol, vars=[2,],ylabel="position")
plot(p1,p2,layout=(2,1),link=:x,leg=false)
end
function simplot()
sol = smsim()
plotsmsim(sol)
end