Tsit5() and DP5() stability

Hi guys,

I am wrapping up a script to solve the TOV equation, similar to the TOVsolver_Julia, and I used ‘CommonSolve’ and ‘DifferentialEquations’ packages. In the solver, unlike the case in TOVsolver_Julia, I used callbacks to terminate the integration when P<0

    condition(u, t, integrator) = u[1] < 0
    affect!(integrator) = terminate!(integrator)
    cb = DiscreteCallback(condition, affect!)

and applied Tsit5 and DP5 (the same as TOVsolver_Julia). However, I found some unsmooth results that did not occur in my hand-crafted RK4 method in the mass-radius curves for different initial conditions for the density at r=0,

Using solve in CommonSolve

In addition, I found out that in some cases, the integration becomes unstable, and a warning pops up for both Tsit5 and DP5 algorithms.

> Warning: Verbosity toggle: dt_epsilon 
> │  At t= 9656.595735553243, dt was forced below floating point epsilon 1.8189894035458565e-12. Aborting. There is either an error in your model specification or the true solution is unstable (or the true solution can not be represented in the precision of Float64.
> └ @ SciMLBase ~/.julia/packages/SciMLBase/TKP6E/src/integrator_interface.jl:736

Does anyone know if there is a better approach for this type of differential equation?

Can you try ContinuousCallback instead? It can hit within the integration step, so it should be more stable

Thanks, just tried ContinuousCallback and also switched the callback off for another test. The same warning occurs.

If RK4 is OK, why don’t you stick to it?

There could be some discontinuities in the ODE, so I am not sure RK4 is the best approach.

If there are discontinuities, lower order methods are generally expected to be better than higher order ones.

You might want to try out BS3 to see how that does.

To be clear, this isn’t saying that it’s unstable, it means that it’s unable to achieve the accuracy to the requested tolerance.

This to me all looks and sounds like you did an invalid mutation. Is your f actually defining an ODE, i.e. do you get the same result from evaluating f(du,u,p,t) each time, or are you doing something like mutating u or trying to store some cache of past values? Without any code it’s hard to debug, but it seems like the one difference is the moment it starts to have a single step rejection it’s not able to recover, and that heavily points towards this.

This video could be helpful:

Thanks. I am going to have a look for the video. Below I attached my script. One of the purposes of this test script is that there will be a loop to change the initial condition. It seems to me that for a single run, the results look ok, but the loop for changing the parameter is also terminated.

import OrdinaryDiffEq as DE
using OrdinaryDiffEqLowOrderRK: OrdinaryDiffEqLowOrderRK
using SciMLBase: ODEProblem, DiscreteCallback, terminate!
using Plots

    const planck_constant = 6.62607015e-34
    const ħ = planck_constant / (2*pi)
    const gravitational_constant =  6.67408e-11
    const speed_of_light_vacuum = 2.99792458e8
    const neutron_mass = 1.674927471e-27

EoS_Param = (
    K_crust = (3*π^2)^(2/3) * ħ^2 / (5*neutron_mass^(8/3)), # Polytropic constant for the crust
    γ_crust = 5.0/3.0, # Polytropic index for the crust
    γ_core = 3.0, # Polytropic index for the core
    ρ_b = 3e14*1e3, # Transition density between crust and core in kg/m^3
)

# Simulation Input Parameters
TOV_Sol_Input = (
    ρ0 = 1e16*1e3, # Initial central density in kg/m^3 (1e15 g/cm^3)
    dr = 0.01*1e3, # Radial step in meters
    Dr = 0.01*1e3, # Radial interval for recording values in meters
    r_end = 20e3, # Maximum radius to solve up to in meters
);

function EoS_two_component_polytrope(
    ParamIn::NamedTuple;
    report_transition_pressure::Bool = false,
)
    K_core = ParamIn.K_crust * ParamIn.ρ_b ^ (ParamIn.γ_crust - ParamIn.γ_core)

    function EoS_P_from_rho(ρ)
        P = if ρ < 0
            0.0
        elseif ρ <= ParamIn.ρ_b
            ParamIn.K_crust * ρ .^ ParamIn.γ_crust
        else
            K_core * ρ .^ ParamIn.γ_core
        end
        return P
    end

    # Pressure at the crust-core transition for continuity check
    P_b = EoS_P_from_rho(ParamIn.ρ_b)

    if report_transition_pressure
        println("Pressure at crust-core transition (Pb): ", P_b)
    end

    function EoS_rho_from_P(P)
        ρ = if P < 0
            0.0
        elseif P <= P_b
            (P / ParamIn.K_crust) .^ (1/ParamIn.γ_crust)
        else
            (P / K_core) .^ (1/ParamIn.γ_core)
        end
        return ρ
    end

    return EoS_P_from_rho, EoS_rho_from_P
end



function tov_eq!(EoS_rho_from_P::Function)

    function tov_inner!(du, u, paras, r)
        P = u[1]
        m = u[2]
        ρ = EoS_rho_from_P(P)
        du[1] = if r == 0.0
            0.0
        else
            -gravitational_constant / r^2 *
            (ρ + P/speed_of_light_vacuum^2) *
            (m + 4*pi*r^3*P/speed_of_light_vacuum^2) /
            (1 - 2*gravitational_constant*m/(r*speed_of_light_vacuum^2))
        end
        du[2] = 4*pi*r^2*ρ
    end
    return tov_inner!
end

EoS, EoS_inv= EoS_two_component_polytrope(EoS_Param);
u0 = [EoS(TOV_Sol_Input.ρ0); 0.0]; # Initial conditions: central pressure and enclosed mass
tov! = tov_eq!(EoS_inv);

condition(u, t, integrator) = u[1] < 0
affect!(integrator) = terminate!(integrator)
cb = DiscreteCallback(condition, affect!)

problem = ODEProblem(tov!, u0, (0.0, TOV_Sol_Input.r_end); callback = cb)
sol = DE.solve(problem, OrdinaryDiffEqLowOrderRK.DP5(); reltol = 1e-12)
#sol = DE.solve(problem, DE.Tsit5(); reltol = 1e-12)

ur = Array(sol)
r = sol.t
Pr = ur[1, :]
mr = ur[2, :]
ρr = EoS_inv.(Pr)
plot(r,ρr)

Sounds a good idea, and I just tried. The same warning popped up. I had a look for the results by in the trimmed version (without a loop to vary parameters), a single run even with a warning looks actually fine. It may also terminate my loop I guess, so I could not get the results I want.

sol = solve(prob, DP5(); reltol=1e-8, abstol=[1.0, 1e15]) seems to work fine: per component tolerances help a lot here since the values are wildly different.

ρ0=1.0e18  retcode=Terminated  R=13168.7m  M=4.35e30 kg

The default absolute tolerances is not great for the value as unscaled. The other thing you could (and probably should) do is rescale the model towards 1.

Or you can put the continuous callback just before the surface.

Goodness. Thank you so much. And yes, I will seriously think about how to deal with those very large and very small constants in the equation.

You’ll probably want to convert your equations to Natural Units
i.e. \hbar = 1,...

ModelingToolkit.jl is also quite handy for this kind of problem when it comes to rescaling. And since you have one system that you will solve many time for different parameters and/or initial conditions, you can clearly afford the mtk-compilation once and then use EnsembleProblem Parallel Ensemble Simulations Interface · SciMLBase.jl