How to solve a limit boundary value ODE in SciML (possibly using shooting methods)

Albert_Zevelev
Tue, 09 Aug 2022 00:19:58 +0000
General Usage

In many fields it is common to encounter systems of differential equations with boundary conditions that occur at limits (rather than at finite points).

For example, the Neoclassical growth model boils down to a system of two boundary value ODEs:
\dot{C}(t)= \frac{ 1 }{ \gamma } \left( \alpha z (K(t))^{\alpha -1} - \delta - \rho \right) \times C(t)
\dot{K}(t) =z (K(t))^{\alpha} - \delta K(t) - C(t)
K(0)=K_{0} given (Initial Condition)
\lim_{T\to \infty} e^{-\rho T} \lambda(T) K(T) = e^{-\rho T} C(T)^{-\gamma} K(T) \to 0 (Terminal Condition)

This system has two variables, one state variable K(t), one control variable C(t).
The initial condition depends only on the state variable.
The terminal condition depends on both variables.
This is standard in optimal control problems.

AFAIK currently SciML does not allow boundary conditions at limits.
This post will attempt to “hack” a solution.

First, let’s look at the closed form solution (for a subset of parameters where we know C0):

using DifferentialEquations, Plots;

function ngm!(dX, X, θ, t)
    C, K          = X  # variables: Consumption, Capital
    ρ, γ, z, α, δ = θ  # parameters
    KN = abs(K)
    #
    dC = ((α*z*(KN^(α-1.0)) -δ -ρ)/γ)*C # Euler Eqn
    dK = z*KN^(α) - δ*KN - C             # Law of Motion
    dX .= (dC, dK)    # Time derivatives
end

α=0.20; γ=6.0; δ=0.25; z=1.0; ρ = δ*(α*γ-1.0)
K0 = 1.0; C0 = (1-(1/γ))*z*(K0)^α;
KSS= ((α)/(ρ+δ))^(1/(1-α)); CSS = (KSS)^α - δ*KSS;

X0 = [C0, K0]                         # initial conditions
T = 100.0                              # time horizon
tspan = (0.0, T)                      # time span
θ = [ρ, γ, z, α, δ]                   # parameters
#
TC(t,c,k;γ=γ,ρ=ρ) = exp(-ρ*t) * (c^(-γ)) * k  # Terminal condition
#
prob = ODEProblem(ngm!, [C0, K0], tspan, θ); # problem
sol = solve(prob,abstol=1e-10,reltol=1e-10)
#
sol_C(t) = sol(t, idxs=1)
sol_K(t) = sol(t, idxs=2)
#
tt=0.0:0.01:T
plot()
plot!(tt, sol_C, label="C(t)", color=1)
plot!(tt, sol_K, label="K(t)", color=2)
plot!(tt, t -> TC(t, sol_C(t), sol_K(t)), label="TC(t)", color=3)
plot!([CSS KSS 0.0],  seriestype = :hline, lab=["CSS" "KSS" ""], color=[1 2 3], l=:dash)

Observe:
1: the control variable C(t) goes to its steady state $C_{SS}
2: the state variable K(t) goes to its steady state K_{SS}
3: the terminal condition holds TC(t)\to 0

In most cases we don’t have a closed form solution, so we don’t know C0.
Consider a shooting method, to find the appropriate C0 such that the terminal condition holds TC(t)\to 0.
First consider a grid of C0

grid_c0 = .75*K0 :0.01 : 1.25*K0;
grid_TC =[]
for c0 in grid_c0
    prob = ODEProblem(ngm!, [c0, K0], tspan, θ); # problem
    #sol = solve(prob, Tsit5(), abstol=1e-10,reltol=1e-10, maxiters = 10_000_000)
    sol = solve(prob, Tsit5())
    if sol.retcode == :Success
        _tc = TC(T, sol(T, idxs=1), sol(T, idxs=2))
    else
        _tc = -100
    end
    push!(grid_TC, _tc)
    println(c0, " ", _tc)
end
cl(x) = clamp(x,-100,100)
plot(grid_c0, cl.(grid_TC) )

It appears the appropriate C0 (which satisfies the TC) is between 0.8 and 0.9.
This is good bc we know the true C0=0.833333

Another approach is to use a nonlinear solver (or optimized) to find the unique value of C0 at which the terminal condition is satisfied.

using Optim;
function f(x; K0=K0,T=100.0,tspan=(0.0, T),θ=θ)
    prob = ODEProblem(ngm!, [x[1], K0], tspan, θ); # problem
    #sol = solve(prob,Tsit5(),reltol=1e-10,maxiters=100_000,abstol=1e-8)
    sol = solve(prob,Tsit5())
    if sol.retcode == :Success #C, K
        cc(x) = sol(x, idxs=1)
        KK(x) = sol(x, idxs=2)
        _tc1 = TC(T, abs(cc(T)), abs(KK(T)))
    else
        _tc1 = -10_000.0
    end
    return abs(_tc1)
end
f([0.75]) #
result = optimize(f, [0.75])
xx=result.minimizer
result.minimum

Optim.jl returns C0=0.8333338128872135 which is close to the true C0=.83333333333.

1. Does anyone know of a good way to do this in the Julia ecosystem or otherwise

2. I’ve tried in on larger problems (2 state/2 control) variables w/o success

3. I arbitrarily choose T=100.0, is there a better way for finding the limit of TC(t) maybe when some tolerance is reached?

7 posts - 3 participants