Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f9a9d4f
Add FrankenLOBSTER living-community integration boundary
Sep 22, 2026
90552f7
Add FrankenLOBSTER P/Z biology and NPD nutrient bridge
Sep 22, 2026
6ac2d36
Separate FrankenLOBSTER nitrate and ammonium affinities
Sep 22, 2026
4afb59c
Add size-structured DOM heterotrophy to FrankenLOBSTER
Sep 22, 2026
1cc2b17
Add bacterioplankton to FrankenLOBSTER living food web
Sep 22, 2026
6115385
Add public coupled FrankenLOBSTER constructor
Sep 22, 2026
9b06004
Rename FrankenLOBSTER bacterioplankton tracers to H
Sep 22, 2026
57b5017
Simplify FrankenLOBSTER and defer ammonium uptake
Sep 23, 2026
dd5c8ad
Fixes arbitrary named PFTs.
Sep 23, 2026
b3fa634
fix grid issue
Sep 23, 2026
290c81f
delete stale file
Sep 23, 2026
c13324a
Consolidate FrankenLOBSTER integration tests
Sep 23, 2026
ee52b69
Simplify FrankenLOBSTER grazing defaults and expose sinking
Sep 23, 2026
af6a924
Add FrankenLOBSTER inorganic nutrient and temperature limitation
Sep 23, 2026
6ce5376
fix alpha naming clash
Sep 23, 2026
c77cfb9
Add LOBSTER phytoplankton exudation and zooplankton excretion
Sep 23, 2026
671ac51
Add P-specific calcite routing to FrankenLOBSTER
Sep 23, 2026
564c923
fix calcite import
Sep 23, 2026
52fcb7c
Harden FrankenLOBSTER recipe replay
Sep 23, 2026
ba5b3df
Fix FrankenLOBSTER docs compat and Julia 1.12 precompilation
Sep 23, 2026
ee6147f
Make FrankenLOBSTER a composable LOBSTER plankton component
Sep 24, 2026
74cc722
Add continuous split power-law allometry
Sep 24, 2026
c52385a
Align FrankenLOBSTER physiology with LOBSTER and consolidate integration
Sep 24, 2026
f303d85
Reduce FrankenLOBSTER implementation and test surface
Sep 24, 2026
398e4ca
Fix test-fixture bug
Sep 24, 2026
43072ca
delete stale code
Sep 24, 2026
bb28796
Tidy temperature response
Sep 24, 2026
9d14c3c
tidy comments for example script
Sep 25, 2026
f7bc8df
Consolidate model-family plankton construction
Sep 25, 2026
3d02be4
Add generic OceanBioME NPD plankton integration
Sep 25, 2026
2fe347f
Generalize NPD plankton construction and replay
Sep 25, 2026
368b493
Add first-class model family settings
Sep 25, 2026
0177ffd
Fix versions
Sep 26, 2026
779a12c
Defer FrankenLOBSTER PIC coupling to coupled side fluxes
Sep 26, 2026
c653ba0
Validate stoichiometric Growth product routing
Sep 26, 2026
26e5109
Delete stale file
Sep 26, 2026
e9841db
Complete NPD wrapper public construction and introspection
Sep 26, 2026
281cdd2
Consolidate consumer-resource trait derivation
Sep 26, 2026
a311f6b
Add linear grazing and Monod light support for LOBSTER3
Sep 26, 2026
fe28201
Fix LinearGrazing process docstring placement
Sep 26, 2026
ef02d4c
Support literal matrix parameter defaults
Sep 26, 2026
68c8677
Fix linear grazing tuple comparison test
Sep 26, 2026
95f68c7
Treat NPD temperature as a physical driver
Sep 26, 2026
5d65356
Drop legacy recipe decoding and use v0.2 schema
Sep 27, 2026
ae7827b
Generalize NPD nutrients and clarify model vocabulary
Sep 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ CUDA = "5"
Enzyme = "0.13, 0.14"
ForwardDiff = "1"
JSON = "1"
OceanBioME = "0.16, 0.18"
OceanBioME = "0.19"
Oceananigans = "0.101.1, 0.102, 0.105, 0.106, 0.107, 0.108, 0.109, 0.110"
SciMLBase = "2"
julia = "1.10"
Expand Down
2 changes: 1 addition & 1 deletion docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
Documenter = "~1.17.0"
ForwardDiff = "1"
OrdinaryDiffEq = "6"
OceanBioME = "0.16, 0.18"
OceanBioME = "0.19"
Oceananigans = "0.101.1, 0.102, 0.105, 0.106, 0.107, 0.108, 0.109, 0.110"
julia = "1.10"

Expand Down
15 changes: 11 additions & 4 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,16 @@ identify a registered family and its version rather than serializing process or
Agate.Processes.AbstractFormulation
Agate.Processes.Smith
Agate.Processes.Geider
Agate.Processes.ExponentialSaturation
Agate.Processes.Monod
Agate.Processes.InhibitedMonod
Agate.Processes.NormalizedDroop
Agate.Processes.QuotaRegulatedMonod
Agate.Processes.Liebig
Agate.Processes.FrankTNorm
Agate.Processes.Q10
Agate.Processes.PreferentialGrazing
Agate.Processes.LinearGrazing
Agate.Processes.HeterotrophicConsumption
Agate.Processes.LinearMortality
Agate.Processes.QuadraticMortality
Expand Down Expand Up @@ -136,7 +139,8 @@ The keyed parameter block separates runtime process parameters from construction
Scientific slots and realized process applicability determine `Parameter` vector or matrix storage
automatically, so runtime parameters never restate axes. `ConstructionParameter` values exist only during
construction to feed `DerivedDefault` calculations; shaped construction parameters use the global
`axes=:plankton` construction domain. Scientific slot-to-parameter relationships are authored
`axes=:plankton` construction domain. Family-level scientific properties that affect the realized model but are not inputs to an
individual process equation use `ModelSetting` instead. Scientific slot-to-parameter relationships are authored
beside the process or factor through `bindings=`.

```@docs
Expand All @@ -148,7 +152,7 @@ Agate.Parameters.derive_default
Agate.Parameters.NoDefault
Agate.Parameters.DiameterIndexedVectorDefault
Agate.Parameters.AllometricPalatability
Agate.Parameters.ConsumerAssimilation
Agate.Parameters.ConsumerResourceFromConsumer
```

### Custom process and factor extension
Expand Down Expand Up @@ -182,11 +186,13 @@ Agate.Compilation.process_parameter_operands

## Named families, recipes, and replay

Named model families add stable code identity and durable recipe replay around the same definition-driven process compiler. `ModelRecipe` is the `agate.model_recipe.v1` family/version/realization document: it records the registered family, exact `definition_version`, canonical plankton/size realization, parameter overrides, sinking choices, and bottom state. Named scientific mappings are serialized as mappings, so key insertion order does not change recipe equality or the scientific content hash. The loaded family supplies the canonical component/process definition on replay. `ModelManifest` records the resolved execution state.
Named model families add stable code identity and durable recipe replay around the same definition-driven process compiler. `ModelRecipe` is the `agate.model_recipe.v0.2` family/version/realization document: it records the registered family, exact `definition_version`, canonical plankton/size realization, process parameter overrides, model-setting overrides, sinking choices, and bottom state. Named scientific mappings are serialized as mappings, so key insertion order does not change recipe equality or the scientific content hash. The loaded family supplies the canonical component/process definition and setting defaults on replay. `ModelManifest` records the resolved execution state, including fully resolved model settings.

External family packages subtype `AbstractModelFamily`, provide `default_components`, `default_processes`, `definition_version`, and `parameter_definitions`, and register durable recipe identity through `family_id` and `registered_family`. Their user-facing constructors translate family-specific keywords into the nested `plankton_pfts` mapping and parameter overrides, then call `Construction.construct(family; ...)`. `normalize_pft_size_structure` provides the shared named-family `(n=0,)` shorthand without weakening core size validation. Recipes are captured with `capture_model_recipe`, the durable schema identifier is available through `recipe_schema`, and replay uses `construct(recipe)` or `construct_plus_manifest(recipe)`.
External family packages subtype `AbstractModelFamily`, provide `default_components`, `default_processes`, `definition_version`, and `parameter_definitions`, and register durable recipe identity through `family_id` and `registered_family`. Family-level scientific properties that are not inputs to an individual process equation are declared separately with `setting_definitions` and `ModelSetting`; examples include elemental or diagnostic ratios used by an integration layer. User-facing constructors translate family-specific keywords into `plankton_pfts`, process `parameter_overrides`, and optional `setting_overrides`, then call `Construction.construct(family; ...)`. `normalize_pft_size_structure` provides the shared named-family `(n=0,)` shorthand without weakening core size validation. Recipes are captured with `capture_model_recipe`, the durable schema identifier is available through `recipe_schema`, and replay uses `construct(recipe)` or `construct_plus_manifest(recipe)`.

```@docs
Agate.ModelFamilies.ModelSetting
Agate.ModelFamilies.setting_definitions
Agate.Construction.ModelRecipe
Agate.Construction.ModelManifest
Agate.Construction.construct_plus_manifest
Expand All @@ -195,6 +201,7 @@ Agate.Construction.recipe_schema
Agate.Construction.normalize_pft_size_structure
Agate.Construction.replay_family
Agate.Construction.resolve_construction_scalar_type
Agate.Introspection.model_settings
Agate.Construction.family_id
Agate.Construction.registered_family
Agate.Construction.encode_recipe
Expand Down
4 changes: 2 additions & 2 deletions docs/src/architecture_overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ A process-defined model moves through five stages:
Agate validates scientific references and structural compatibility, canonicalizes process identity and factor order, canonicalizes intrinsic or family plankton realization once before layout construction, realizes each PFT into one or more SizeClasses separately from concrete prognostic tracers, discovers process drivers, and resolves process-local participant axes.

3. **Flux compilation** (`Compilation/`).
A setup-time compile context carries the canonical definition, realized layout, and parameter plan through lowering. Parameter operands resolve realized entity identity directly against storage labels during setup, so only final static indices enter runtime terms. Participant states and one-tracer-per-entity components are realized through one shared tracer + entity-position traversal, which is reused by one-axis and two-axis process lowering. Growth authoring declares `reference_resource` and any element-keyed `additional_resources` explicitly, so canonicalization no longer infers material transfer from nutrient-factor topology. Growth may have zero or more multiplicative factors; with none, its rate is simply the maximum-rate scale times biomass. Additional stoichiometric draws are valid only for Elements that the Growth plankton does not already carry as explicit prognostic States. `NutrientLimitation` may therefore mix external `NutrientResponse` and internal `QuotaResponse` subfactors while material transfer remains process-owned. Growth also owns its maximum-rate binding; Smith and Geider light factors consume that resolved process scale rather than declaring a second parameter slot. Each named process produces generic target + rate + weight flux specifications, which are grouped by target tracer and lowered into static compiled equations with no target symbols or process metadata in runtime terms.
A setup-time compile context carries the canonical definition, realized layout, and parameter plan through lowering. Parameter operands resolve realized entity identity directly against storage labels during setup, so only final static indices enter runtime terms. Participant states and one-tracer-per-entity components are realized through one shared tracer + entity-position traversal, which is reused by one-axis and two-axis process lowering. Growth authoring declares `reference_resource` and any element-keyed `additional_resources` explicitly, so canonicalization no longer infers material transfer from nutrient-factor topology. Growth may have zero or more multiplicative factors; with none, its rate is simply the maximum-rate scale times biomass. Additional stoichiometric draws are valid only for Elements that the Growth plankton does not already carry as explicit prognostic States. `NutrientLimitation` may therefore mix external `NutrientResponse` and internal `QuotaResponse` subfactors while material transfer remains process-owned. Growth also owns its maximum-rate binding; Smith and Geider light factors consume that resolved process scale rather than declaring a second parameter slot, while light formulations that do not depend on the growth scale use only their own inputs and parameters. Each named process produces generic target + rate + weight flux specifications, which are grouped by target tracer and lowered into static compiled equations with no target symbols or process metadata in runtime terms.

4. **Construction and replay** (`Construction/`).
Direct `ModelDefinition` construction resolves defaults and overrides from the model
Expand Down Expand Up @@ -84,7 +84,7 @@ Components describe structure rather than ecological role. A `Plankton` declares

A PFT is defined by its functional parameter identity, not by size. Every PFT realizes at least one SizeClass. Without an explicit size structure it realizes one implicit singleton SizeClass named by the PFT, with no diameter metadata; an explicit size structure realizes one or more `<pft>_<index>` SizeClasses with physical diameters. `n=0` is only a high-level named-model constructor shorthand for that implicit singleton and is normalized to `nothing` before core realization; `n=1` denotes one explicit SizeClass with a real diameter.

A mixotroph is an ordinary plankton participating in both growth and living-prey consumption. `Consumption` is the single consumer-resource process for living prey, bacterivory, mixotrophy, and material-pool consumption. `PreferentialGrazing` interprets `maximum_rate` as one consumer-level ingestion capacity shared across all declared living prey, while `HeterotrophicConsumption` shares one consumer-level uptake capacity across substitutable substrates according to their half-saturation and `substrate_preference`. Process products are expressed directly through `products=` or `unassimilated_products=`. `Products` provides conservative named allocation when one process flux has multiple destinations. Collection-valued participant roles use plural keywords such as `plankton=`, `consumers=`, `resources=`, and `sources=`; each accepts either one `Symbol` or a tuple and is canonicalized to a tuple during authoring. Remineralization maps many `sources=` to one `destination=`. Bacterioplankton may consume POM and be consumed as living prey through the same consumer-resource machinery. Pools are scalar material inventories; size/PFT realization is owned by `Plankton`.
A mixotroph is an ordinary plankton participating in both growth and living-prey consumption. `Consumption` is the single consumer-resource process for living prey, bacterivory, mixotrophy, and material-pool consumption. `PreferentialGrazing` interprets `maximum_rate` as one consumer-level ingestion capacity shared across all declared living prey, while `HeterotrophicConsumption` shares one consumer-level uptake capacity across substitutable substrates, with consumer-resource `half_saturation` and `substrate_preference` controlling affinity and accessibility. Process products are expressed directly through `products=` or `unassimilated_products=`. `Products` provides conservative named allocation when one process flux has multiple destinations. Collection-valued participant roles use plural keywords such as `plankton=`, `consumers=`, `resources=`, and `sources=`; each accepts either one `Symbol` or a tuple and is canonicalized to a tuple during authoring. Remineralization maps many `sources=` to one `destination=`. Bacterioplankton may consume organic-matter pools and be consumed as living prey through the same consumer-resource machinery. Pools are scalar material inventories; size/PFT realization is owned by `Plankton`.

Named factors are multiplicative within a process, while independent named processes add through their fluxes to a tracer equation. Growth bookkeeping is validated per Element: an Element represented by an explicit prognostic plankton State is updated by an explicit state-changing process such as `NutrientUptake` and cannot also be supplied implicitly through Growth stoichiometry, while implicit Elements may be coupled through `FixedStoichiometry`. `NutrientLimitation.responses` is keyed by Element identity, independently of whether each response reads an external Pool or an internal quota State. Built-in Growth and `NutrientUptake` do not synthesize arbitrary non-elemental prognostic states; model families that require such synthesis can currently provide it through the custom-process extension boundary. Products and stoichiometry map process rates into affected material and element pools. Multi-destination `Products` authors exactly N-1 fractions; the omitted destination receives `1 - sum(fractions)`, so routing closes conservatively without a redundant parameter. A product may target one pool directly, derive several elemental products from a one-element source through `FixedStoichiometry`, or route the actual elemental inventories of a multi-state plankton through an element-to-pool mapping. Multi-state mortality and living-prey consumption use the reference state to define the shared specific loss intensity, apply that intensity to every prognostic state, and route only states with an Element into elemental products. Non-elemental states such as chlorophyll are removed proportionally without creating a second elemental inventory. When one mortality or living-prey consumption process routes multi-element products from multiple source plankton, those sources currently must expose the same prognostic Element set; heterogeneous source Element sets are a deferred extension rather than part of the current contract.

Expand Down
6 changes: 3 additions & 3 deletions examples/detritus_bacteria.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# graze the living bacterial plankton. A Q10 factor modifies POM consumption.

using Agate.Components: Plankton, Pool
using Agate.Parameters: AllometricPalatability, ConsumerAssimilation, ConstructionParameter, DerivedDefault, Parameter
using Agate.Parameters: AllometricPalatability, ConsumerResourceFromConsumer, ConstructionParameter, DerivedDefault, Parameter
using Agate.Construction: construct
using Agate.Introspection: auxiliary_field_names, tracer_names
using Agate.Processes:
Expand Down Expand Up @@ -40,7 +40,7 @@ processes = (
),
factors=(
temperature=Temperature(
Q10();
Q10(:consumer);
bindings=(
q10=:temperature_q10,
reference_temperature=:reference_temperature,
Expand Down Expand Up @@ -90,7 +90,7 @@ parameters = (
)
),
living_assimilation=Parameter(
DerivedDefault(ConsumerAssimilation(); deps=(:assimilation_efficiency,))
DerivedDefault(ConsumerResourceFromConsumer(); deps=(:assimilation_efficiency,))
),
)

Expand Down
4 changes: 2 additions & 2 deletions examples/mixotrophy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# `Plankton` that participates in both growth and grazing.

using Agate.Components: Plankton, Pool
using Agate.Parameters: AllometricPalatability, ConsumerAssimilation, ConstructionParameter, DerivedDefault, Parameter
using Agate.Parameters: AllometricPalatability, ConsumerResourceFromConsumer, ConstructionParameter, DerivedDefault, Parameter
using Agate.Construction: construct
using Agate.Introspection: auxiliary_field_names, tracer_names
using Agate.Processes:
Expand Down Expand Up @@ -69,7 +69,7 @@ parameters = (
)
),
assimilation_matrix=Parameter(
DerivedDefault(ConsumerAssimilation(); deps=(:assimilation_efficiency,))
DerivedDefault(ConsumerResourceFromConsumer(); deps=(:assimilation_efficiency,))
),
)

Expand Down
83 changes: 83 additions & 0 deletions scripts/lobster3_parameter_only_setup.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Parameter-only FrankenLOBSTER approximation of the supplied LOBSTER3 model.
#
# FrankenLOBSTER defaults already match the LOBSTER3 3-D allometries for:
# P maximum growth, P nitrate affinity, H maximum uptake, H DOM affinity,
# P/Z/H mortality, Z/H assimilation, C:N, and chlorophyll:N.
# Only deliberate departures from those defaults are specified below.
#
# Temperature experiments:
# :temperature_off => Q10(P1, P2) = (1.0, 1.0)
# :p1_temperature => Q10(P1, P2) = (1.88, 1.0)
#
# Remaining functional-form approximations:
# - LOBSTER3 mass-action grazing is approached with K_G >> prey and
# g_max(d) = K_G * 15.9 * V(d)^(-0.16) / day.
# - The inactive LOBSTER3 NH4 growth branch is made negligible with a very
# large NH4 half-saturation.
# - LOBSTER3 Monod light (K=55 W m^-2) is matched at its 50% point by the
# FrankenLOBSTER exponential-saturation light response.

using Agate
using OceanBioME
using Oceananigans
using Oceananigans.Units: day
using Agate.Library.Allometry: AllometricParam, ConstantParam, PowerLaw
using OceanBioME.Models.NutrientsPlanktonDetritusModels: DissolvedParticulate, LOBSTER

const FrankenLOBSTER = Agate.Models.FrankenLOBSTER

const GRAZING_K = 100.0 # >99% of mass-action rate for total prey < 1 mmol N m^-3
const NH4_OFF_K = 1.0e12
const LIGHT_K = 55 / log(2) # exponential response = 0.5 at PAR = 55 W m^-2

function temperature_q10(experiment::Symbol)
experiment === :temperature_off && return (P_1=1.0, P_2=1.0)
experiment === :p1_temperature && return (P_1=1.88, P_2=1.0)
throw(ArgumentError("temperature_experiment must be :temperature_off or :p1_temperature"))
end

const BASE_PARAMETERS = (
# LOBSTER3 currently uses nitrate-supported P growth only.
ammonia_half_saturation=ConstantParam(NH4_OFF_K),
nitrate_ammonia_inhibition=0.0,

# LOBSTER3 uses Monod(PAR; K=55); FrankenLOBSTER uses exponential saturation.
light_half_saturation=(P_1=LIGHT_K, P_2=LIGHT_K),

# Disabled in the supplied LOBSTER3 setup.
phytoplankton_exudation_fraction=(P_1=0.0, P_2=0.0),
zooplankton_excretion_rate=(Z_1=0.0, Z_2=0.0),

# Z1 -> P1 + H1; Z2 -> P2. Scale g_max with K_G so the Holling response
# approaches LOBSTER3's mass-action g(d) * prey * predator formulation.
maximum_predation_rate=AllometricParam(
PowerLaw(); prefactor=GRAZING_K * 15.9 / day, exponent=-0.16
),
grazing_half_saturation=(Z_1=GRAZING_K, Z_2=GRAZING_K),
palatability_matrix=[1.0 0.0 1.0; 0.0 1.0 0.0],
)

lobster3_parameters(; temperature_experiment=:temperature_off) = merge(
BASE_PARAMETERS,
(temperature_q10=temperature_q10(temperature_experiment),),
)

function lobster3_like_bgc(grid; temperature_experiment=:temperature_off)
plankton = FrankenLOBSTER.construct(;
grid,
parameters=lobster3_parameters(; temperature_experiment),
)

# The only LOBSTER detritus default changed by the supplied LOBSTER3 setup.
detritus = DissolvedParticulate(
grid;
dissolved_remineralisation_rate=0.0,
)

return LOBSTER(grid; plankton, detritus)
end

# Directly runnable one-cell setups for the two intended experiments.
grid = RectilinearGrid(CPU(); size=(1, 1, 1), extent=(1, 1, 1))
bgc_temperature_off = lobster3_like_bgc(grid; temperature_experiment=:temperature_off)
bgc_p1_temperature = lobster3_like_bgc(grid; temperature_experiment=:p1_temperature)
2 changes: 2 additions & 0 deletions src/Agate.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ include("Runtime/Runtime.jl")
include("Compilation/Compilation.jl")
include("Diagnostics/Diagnostics.jl")
include("Construction/Construction.jl")
include("Integrations/Integrations.jl")
include("Models/Models.jl")
include("Introspection.jl")

Expand All @@ -25,6 +26,7 @@ export Processes
export Runtime
export Diagnostics
export Construction
export Integrations
export Introspection
export ModelDefinition

Expand Down
Loading
Loading