Exercise 05: Size range

This exercise changes the size ranges represented by the Agate.jl NiPiZD model. We compare the default two-phytoplankton, two-zooplankton community with a wider but physiologically realistic range for both trophic groups.

Loading dependencies

using Agate
using Agate.Introspection: tracer_names
using CairoMakie

workshop_script = let dir = @__DIR__
    while !isfile(joinpath(dir, "src", "AgateWorkshop.jl"))
        parent = dirname(dir)
        parent == dir && error("Could not find src/AgateWorkshop.jl")
        dir = parent
    end
    joinpath(dir, "src", "AgateWorkshop.jl")
end
include(workshop_script)

mkpath("outputs")
mkpath("figures")

Size-range cases

The wide-range case keeps two classes per trophic group and logarithmic spacing, but expands the endpoints to include small picophytoplankton through large microphytoplankton, and microzooplankton through small mesozooplankton.

bgc_default = default_quickstart_bgc()

bgc_wide_range = Agate.Models.NiPiZD.construct(;
    phyto_size_structure = (n = 2, min_esd = 0.6, max_esd = 60.0, splitting = :log_splitting),
    zoo_size_structure = (n = 2, min_esd = 6.0, max_esd = 600.0, splitting = :log_splitting),
)

bgcs = [bgc_default, bgc_wide_range]
case_labels = ["default size range", "wide realistic size range"]

for (label, bgc) in zip(case_labels, bgcs)
    println(label)
    println(tracer_names(bgc))
end
default size range
[:N, :D, :Z1, :Z2, :P1, :P2]
wide realistic size range
[:N, :D, :Z1, :Z2, :P1, :P2]

Run zero-dimensional ecosystem simulations

Total initial plankton biomass is the same in both cases and is split evenly across the plankton tracers.

runs = [
    run_box_model(
        bgc;
        filename = joinpath("outputs", "05_size_range_$(i).jld2"),
        initial_conditions = default_initial_conditions(bgc; nutrient = 8.0, total_plankton_biomass = 0.15),
    )
    for (i, bgc) in enumerate(bgcs)
]

timeseries = [read_box_tracer_timeseries(run.filename, run.tracer_syms) for run in runs]
[ Info: Initializing simulation...
[ Info:     ... simulation initialization complete (3.165 ms)
[ Info: Executing initial time step...
[ Info:     ... initial time step complete (160.282 μs).
[ Info: Simulation is stopping after running for 6.121 seconds.
[ Info: Simulation time 1095 days equals or exceeds stop time 1095 days.
[ Info: Initializing simulation...
[ Info:     ... simulation initialization complete (1.804 ms)
[ Info: Executing initial time step...
[ Info:     ... initial time step complete (202.786 μs).
[ Info: Simulation is stopping after running for 4.671 seconds.
[ Info: Simulation time 1095 days equals or exceeds stop time 1095 days.

Tracer concentration comparison

comparison_figure_path = joinpath("figures", "05_size_range_timeseries_comparison.png")
fig_comparison = plot_box_timeseries(timeseries; labels = case_labels)
save(comparison_figure_path, fig_comparison; px_per_unit = 1)
display(fig_comparison)
fig_comparison

Relative nitrogen contributions: default size range

nitrogen_default_figure_path = joinpath("figures", "05_size_range_relative_nitrogen_default.png")
fig_nitrogen_default = plot_contributions(timeseries[1].times, timeseries[1].data)
save(nitrogen_default_figure_path, fig_nitrogen_default; px_per_unit = 1)
display(fig_nitrogen_default)
fig_nitrogen_default

Relative nitrogen contributions: wide realistic size range

nitrogen_wide_figure_path = joinpath("figures", "05_size_range_relative_nitrogen_wide.png")
fig_nitrogen_wide = plot_contributions(timeseries[2].times, timeseries[2].data)
save(nitrogen_wide_figure_path, fig_nitrogen_wide; px_per_unit = 1)
display(fig_nitrogen_wide)
fig_nitrogen_wide

Community-weighted mean size comparison

size_comparison_figure_path = joinpath("figures", "05_size_range_cwm_size_comparison.png")
fig_size_comparison = plot_cwm_size(timeseries, bgcs; labels = case_labels)
save(size_comparison_figure_path, fig_size_comparison; px_per_unit = 1)
display(fig_size_comparison)
fig_size_comparison