Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to determine the maximum stable time step for biogeochemistry dynamics? #237

Open
ali-ramadhan opened this issue Jan 7, 2025 · 1 comment
Labels
question Further information is requested

Comments

@ali-ramadhan
Copy link
Collaborator

ali-ramadhan commented Jan 7, 2025

I was just playing around a LOBSTER box model (basically the first example in the docs) and noticed that I was getting some super negative values. Turns out it's probably numerical instability due to the time step because reducing the time step eventually leads to convergence (although sometimes blowup at intermediate time step sizes).

I'm curious if there's a way to determine the maximum stable time step for biogeochemistry dynamics (e.g. LOBSTER). This way we could take it into account when computing time step sizes for adaptive time-stepping.

If not, maybe the solution is an equivalent of the NaNChecker that stops a simulation if a tracer becomes too negative?

cc @suki-atdepthmrv @almacarolina


MWE:

using CairoMakie
using Oceananigans
using Oceananigans.Units
using Oceananigans.Fields: FunctionField

using OceanBioME

const year = years = 365day

grid = BoxModelGrid()
clock = Clock(time = zero(grid))

PAR⁰(t) = 100 * (1 - cos((t + 150days) * 2π / year)) * (1 / (1 + 0.2 * exp(-((mod(t, year) - 200days) / 50days)^2))) + 2

const z = -10
PAR_func(t) = PAR⁰(t) * exp(0.2z)

PAR = FunctionField{Center, Center, Center}(PAR_func, grid; clock)

biogeochemistry = LOBSTER(;
    grid,
    light_attenuation_model = PrescribedPhotosyntheticallyActiveRadiation(PAR),
    carbonates = true,
    oxygen = true,
    variable_redfield = true,
    scale_negatives = false
)

model = BoxModel(; biogeochemistry, clock)

set!(model,
    NO₃ = 50,
    DIC = 2300,
    Alk = 2400,
    O₂ = 250,
    NH₄ = 0.1,
    DOC = 100,
    DON = 10,
    sPOC = 10,
    bPOC = 1,
    sPON = 1,
    bPON = 1,
    P = 5,
    Z = 2
)

simulation = Simulation(model; Δt = 10seconds, stop_time = 30days)

simulation.output_writers[:fields] =
    JLD2OutputWriter(model, model.fields;
        filename = "box.jld2",
        schedule = TimeInterval(1hour),
        overwrite_existing = true
    )

prog(sim) = @info "$(prettytime(time(sim))) in $(prettytime(simulation.run_wall_time))"

simulation.callbacks[:progress] = Callback(prog, TimeInterval(1day))

@info "Running the model..."
run!(simulation)

times = FieldTimeSeries("box.jld2", "P").times

time_series = NamedTuple{keys(model.fields)}(FieldTimeSeries("box.jld2", "$field")[1, 1, 1, :] for field in keys(model.fields))

fig = Figure(size = (1200, 1200), fontsize = 24)

axs = []
for (name, tracer) in pairs(time_series)
    idx = (length(axs))
    push!(axs, Axis(fig[floor(Int, idx/2), Int(idx%2)], ylabel = "$name", xlabel = "Time (days)"))
    lines!(axs[end], times / day, tracer, linewidth = 3)
end

save("box_model.png", fig)

With a time step of 15 mins (super negative NO3, Alk, and DIC):

box_model_dt_15mins

With a time step of 5 mins (crash and NaNs after ~0.5 days):

box_model_dt_5mins

With a time step of 1 min (seems fine):

box_model_dt_1min

With a time step of 10 seconds (seems converged):

box_model_dt_10secs


Environment:

OceanBioME.jl main branch with

Julia Version 1.10.7
Commit 4976d05258e (2024-11-26 15:57 UTC)
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 48 × AMD Ryzen Threadripper 7960X 24-Cores
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 (ORCJIT, znver3)
Threads: 16 default, 0 interactive, 8 GC (on 48 virtual cores)
Environment:
  LD_PRELOAD = /usr/NX/lib/libnxegl.so
@ali-ramadhan ali-ramadhan added the question Further information is requested label Jan 7, 2025
@jagoosw
Copy link
Collaborator

jagoosw commented Jan 20, 2025

Hi Ali,

Sorry, I completely missed this before.

As far as I'm aware it's not generally possible to determine a stable timestep and it would be very involved to compute it.

This is why I came up with the ScaleNegativeTracers modifier, but I think in a box model it makes sense to error on negatives. Maybe it would be good to have a callback for that in the source code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants