Ocean chemistry
These examples illustrate how to implement:
- a minimal model of the marine carbonate system
- air-sea exchange of CO2 between ocean and atmosphere Domains
See PALEOmodel
documentation for more information on analysing model output.
Example 1 Minimal Alk-pH model
This example implements a new Reaction_Alk_pH
to establish a minimal Alk-pH model for aqueous carbonate chemistry. Carbonate system equations are from (Zeebe and Wolf-Gladrow, 2001).
The ocean
domain contains a ReactionNoTransport
from the PALEOocean
Julia package. This defines standard ocean variables including cell volume
, and is configured here to provide one ocean cell.
There are two state variables for DIC
and TAlk
, implemented using a ReactionReservoir
from the PALEOboxes
Julia package. This Reaction also provides concentrations in mol m-3
.
The carbonate chemistry system is solved as an algebraic constraint, calculating TAlk_calcu
as a function of pH
and then using a PALEO solver to solve for the pH
value that makes TAlk_calcu
equal to the required value. In PALEO this is implemented by defining pH
as a VarState
(attribute :vfunction = PB.VF_State
) and the algebraic constraint TAlk_error
as a VarConstraint
(with attribute :vfunction = PB.VF_Constraint
). The combined system of TAlk
and DIC
reservoirs and pH
is then a Differential Algebraic Equation (DAE) with two state variables and one algebraic constraint, and is integrated forward in time by a DAE solver PALEOmodel.ODE.integrateDAE
.
Additional code files
The Reaction code (Reaction_Alk_pH
in file examples/ocean_chemistry/reactions_Alk_pH.jl
) now produces calculation of different carbon species HCO3_conc
, CO3_conc
, CO2_aq_conc
, boron species BOH4_conc
, water species H_conc
and OH_conc
given DIC_conc
, TAlk_conc
and pH
. Difference from required alkalinity TAlk_error
is then calculated and labelled as an algebraic constraint. Note that there is loop over ocean cells to support arbitrary ocean models:
module Min_Alk_pH
import PALEOboxes as PB
using PALEOboxes.DocStrings # for $(PARS) and $(METHODS_DO)
"""
Reaction_Alk_pH
Minimal example for aqueous carbonate system.
Solves for carbon, boron and water species given `pH`, calculates difference `TAlk_error` from
required alkalinity.
Use in conjunction with a DAE solver, where this Reaction provides an algebraic constraint `TAlk_error` on `pH`.
# Parameters
$(PARS)
# Methods and Variables
$(METHODS_DO)
"""
Base.@kwdef mutable struct Reaction_Alk_pH{P} <: PB.AbstractReaction
base::PB.ReactionBase
pars::P = PB.ParametersTuple(
PB.ParDouble("K_1", 1.4e-6, units="mol kg-1", description="equilibrium constant of CO2_aq and HCO3-"),
PB.ParDouble("K_2", 1.2e-9, units="mol kg-1", description="equilibrium constant of HCO3- and CO32-"),
PB.ParDouble("K_w", 6.0e-14, units="mol^2 kg-2", description="equilibrium constant of water at S=35, T=25°C"),
PB.ParDouble("K_B", 2.5e-9, units="mol kg-1", description="equilibrium constant of B(OH)4-"),
)
end
function PB.register_methods!(rj::Reaction_Alk_pH)
vars = [
PB.VarDep("DIC_conc", "mol m-3", "DIC concentration"),
PB.VarDep("TAlk_conc", "mol m-3", "TA concentration"),
PB.VarDep("B_conc", "mol m-3", "total Boron concentration"),
PB.VarConstraint("TAlk_error", "mol m-3", "in order to solve TA, we set it"),
PB.VarProp("HCO3_conc", "mol m-3", "HCO3- concentration"),
PB.VarProp("CO3_conc", "mol m-3", "CO32- concentration"),
PB.VarProp("CO2_aq_conc", "mol m-3", "CO2_aq concentration"),
PB.VarProp("BOH3_conc", "mol m-3", "BOH3 concentration"),
PB.VarProp("BOH4_conc", "mol m-3", "BOH4- concentration"),
PB.VarProp("H_conc", "mol m-3", "concentration of H+"),
PB.VarProp("OH_conc", "mol m-3", "concentration of OH-"),
PB.VarState("pH", "", "it is the calcalation for pH"),
PB.VarDep("density", "kg m-3", "ocean density"),
]
PB.add_method_do!(rj, do_Min_Alk_pH, (PB.VarList_namedtuple(vars), ) )
PB.add_method_setup!(rj, setup_carbchem, (PB.VarList_namedtuple(vars), ),)
return nothing
end
# called at model start
# provide an initial value for pH (exact value is not important, just needs to be in a reasonable range)
function setup_carbchem( m::PB.ReactionMethod, pars, (vars, ), cellrange::PB.AbstractCellRange, attribute_name )
attribute_name in (:initial_value, :norm_value) || return
for i in cellrange.indices
vars.pH[i] = 8.0
end
return nothing
end
# do method, called each main loop timestep
function do_Min_Alk_pH(m::PB.ReactionMethod, pars, (varsdata, ), cellrange::PB.AbstractCellRange, deltat)
# rj = m.reaction
for i in cellrange.indices
density = varsdata.density[i]
# mol kg-1 mol m-3 kg m-3
DIC_conc_kg = varsdata.DIC_conc[i] / density
B_total_kg = varsdata.B_conc[i] / density
# mol kg-1 = mol L-1 / kg m-3 * L m-3
H_conc_kg = 10^( -varsdata.pH[i] ) / density * 1000.0
# mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1
CO2_aq_conc_kg = DIC_conc_kg / ( 1 + pars.K_1[]/H_conc_kg + (pars.K_1[]*pars.K_2[])/((H_conc_kg)^2) )
# mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1
HCO3_conc_kg = DIC_conc_kg / ( 1 + H_conc_kg/pars.K_1[] + pars.K_2[]/H_conc_kg )
# mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1
CO3_conc_kg = DIC_conc_kg / ( 1 + H_conc_kg/pars.K_2[] + ((H_conc_kg)^2)/(pars.K_1[]*pars.K_2[]) )
# mol kg-1 mol kg-1 mol kg-1 mol kg-1
BOH4_conc_kg = B_total_kg / ( 1 + H_conc_kg/pars.K_B[] )
BOH3_conc_kg = B_total_kg - BOH4_conc_kg
# mol kg-1 mol2 kg-2 mol kg-1
OH_conc_kg = pars.K_w[] / H_conc_kg
# mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1 mol kg-1
TAlk_conc_kg_calcu = HCO3_conc_kg + 2 * CO3_conc_kg + BOH4_conc_kg + OH_conc_kg - H_conc_kg
# mol m-3 kg m-3 mol kg-1
varsdata.H_conc[i] = density * H_conc_kg
varsdata.OH_conc[i] = density * OH_conc_kg
varsdata.HCO3_conc[i] = density * HCO3_conc_kg
varsdata.CO3_conc[i] = density * CO3_conc_kg
varsdata.CO2_aq_conc[i] = density * CO2_aq_conc_kg
varsdata.BOH4_conc[i] = density * BOH4_conc_kg
varsdata.BOH3_conc[i] = density * BOH3_conc_kg
# mol m-3 mol m-3 mol kg-1 kg m-3
varsdata.TAlk_error[i] = varsdata.TAlk_conc[i] - TAlk_conc_kg_calcu * density
end
return nothing
end
end
# module
Documentation (generated by the Julia docstring) reads:
Main.Min_Alk_pH.Reaction_Alk_pH
— TypeReaction_Alk_pH
Minimal example for aqueous carbonate system.
Solves for carbon, boron and water species given pH
, calculates difference TAlk_error
from required alkalinity.
Use in conjunction with a DAE solver, where this Reaction provides an algebraic constraint TAlk_error
on pH
.
Parameters
K_1[Float64]
=1.4e-6 (mol kg-1),default_value
=1.4e-6,description
="equilibrium constant of CO2_aq and HCO3-"K_2[Float64]
=1.2e-9 (mol kg-1),default_value
=1.2e-9,description
="equilibrium constant of HCO3- and CO32-"K_w[Float64]
=6.0e-14 (mol^2 kg-2),default_value
=6.0e-14,description
="equilibrium constant of water at S=35, T=25°C"K_B[Float64]
=2.5e-9 (mol kg-1),default_value
=2.5e-9,description
="equilibrium constant of B(OH)4-"
Methods and Variables
do_Min_Alk_pH
DIC_conc
(mol m-3),VT_ReactDependency
,description
="DIC concentration"TAlk_conc
(mol m-3),VT_ReactDependency
,description
="TA concentration"B_conc
(mol m-3),VT_ReactDependency
,description
="total Boron concentration"TAlk_error
(mol m-3),VT_ReactContributor
,VF_Constraint
,description
="in order to solve TA, we set it"HCO3_conc
(mol m-3),VT_ReactProperty
,description
="HCO3- concentration"CO3_conc
(mol m-3),VT_ReactProperty
,description
="CO32- concentration"CO2_aq_conc
(mol m-3),VT_ReactProperty
,description
="CO2_aq concentration"BOH3_conc
(mol m-3),VT_ReactProperty
,description
="BOH3 concentration"BOH4_conc
(mol m-3),VT_ReactProperty
,description
="BOH4- concentration"H_conc
(mol m-3),VT_ReactProperty
,description
="concentration of H+"OH_conc
(mol m-3),VT_ReactProperty
,description
="concentration of OH-"pH
(),VT_ReactDependency
,VF_State
,description
="it is the calcalation for pH"density
(kg m-3),VT_ReactDependency
,description
="ocean density"
yaml configuration file
The model configuration (file examples/ocean_chemistry/config_ex1.yaml
) contains two Reservoirs DIC
, TAlk
. A ReactionFluxPerturb
from the PALEOboxes.jl reaction catalog is used to add a constant TAlk flux.
Minimal_Alk_pH:
domains:
global:
reactions:
add_Alk:
# from PALEOboxes reaction catalog: constant input flux of Alk
class: ReactionFluxPerturb
parameters:
# linear interpolation for input flux - set to constant
perturb_times: [-1e30, 1e30]
# ocean volume = 3.6e14 * 3688.0 = 1.33e18 m^3
# DIC in ocean = 2 * 1.33e18 = 2.66e18 mol
# set Alk flux so Alk = DIC after 100 yr-1 = 2.66e18/100 = 2.66e16 mol yr-1
perturb_totals: [2.66e16, 2.66e16]
variable_links:
F: ocean.TAlk_sms # add flux to TAlk reservoir NB: works for single-box ocean only !!
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ocean:
reactions:
oceantrasport:
class: ReactionOceanNoTransport
parameters:
area: [3.6e14]
depth: [3688.0]
reservoir_TA:
class: ReactionReservoir
variable_links:
R*: TAlk*
variable_attributes:
R:initial_value: 0.0
R:norm_value: 2.4e0
reservoir_DIC:
class: ReactionReservoir
variable_links:
R*: DIC*
variable_attributes:
R:initial_value: 2.0e0
R:norm_value: 2.0e0
constant_B:
class: ReactionReservoirConst
variable_links:
R*: B
variable_attributes:
R_conc:initial_value: 0.427 # mol m-3 contemporary value
solve_Alk_pH:
class: Reaction_Alk_pH
parameters:
K_1: 1.4e-6
K_2: 1.2e-9
K_w: 6.0e-14
K_B: 2.5e-9
variable_links:
density: rho_ref # OceanNoTransport provides density as rho_ref
oceanfloor:
# unused here, set up by ReactionOceanNoTransport
oceansurface:
# unused here, set up by ReactionOceanNoTransport
Run script
The script to run the model (file examples/ocean_chemistry/run_ex1.jl
) contains:
import PALEOboxes as PB
import PALEOmodel
import PALEOocean
using Plots
include(joinpath(@__DIR__, "reactions_Alk_pH.jl")) # @__DIR__ so still runs when building docs
#####################################################
# Create model
model = PB.create_model_from_config(joinpath(@__DIR__, "config_ex1.yaml"), "Minimal_Alk_pH")
#########################################################
# Initialize
##########################################################
initial_state, modeldata = PALEOmodel.initialize!(model)
#####################################################################
# Optional: call ODE function to check derivative
#######################################################################
initial_deriv = similar(initial_state)
PALEOmodel.ODE.ModelODE(modeldata)(initial_deriv, initial_state , nothing, 0.0)
println("initial_state: ", initial_state)
println("initial_deriv: ", initial_deriv)
#################################################################
# Integrate vs time
##################################################################
# create a Run object to hold model and output
paleorun = PALEOmodel.Run(model=model, output = PALEOmodel.OutputWriters.OutputMemory())
println("integrate, DAE")
# first run is slow as it includes JIT time
@time PALEOmodel.ODE.integrateDAE(
paleorun, initial_state, modeldata, (0.0, 200.0),
solvekwargs=(
reltol=1e-6,
# saveat=0.1, # save output every 0.1 yr see https://diffeq.sciml.ai/dev/basics/common_solver_opts/
)
);
########################################
# Plot output
########################################
display(plot(paleorun.output, ["ocean.TAlk_conc", "ocean.DIC_conc"], (cell=1,);
ylabel="TAlk, DIC conc (mol m-3)"))
# display(plot(paleorun.output, ["ocean.TAlk","ocean.TAlk_sms"], (cell=1,)))
display(plot(paleorun.output, "ocean.pH", (cell=1,)))
display(plot(paleorun.output, ["ocean.H_conc", "ocean.OH_conc"], (cell=1,);
ylabel="H2O species (mol m-3)", ylim=(0, Inf)))
display(plot(paleorun.output, ["ocean.B_conc", "ocean.BOH4_conc", "ocean.BOH3_conc"], (cell=1,);
ylabel="B species (mol m-3)", ylim=(0, Inf)))
display(plot(paleorun.output, ["ocean.DIC_conc", "ocean.HCO3_conc", "ocean.CO3_conc", "ocean.CO2_aq_conc"], (cell=1,);
ylabel="DIC species (mol m-3)", ylim=(0, Inf)))
display(
plot(
paleorun.output,
["ocean.HCO3_conc", "ocean.CO3_conc", "ocean.CO2_aq_conc", "ocean.BOH4_conc", "ocean.BOH3_conc", "ocean.H_conc", "ocean.OH_conc",],
(cell=1, ); #
coords=["tmodel"=>("ocean.pH",),], # plot against pH instead of tmodel
ylabel="H2O, B, DIC species (mol m-3)", ylim=(0.5e-3, 0.5e1), yscale=:log10,
legend_background_color=nothing,
legend=:bottom,
)
)
and produces output showing the change, if TAlk_conc
increase, how the carbonic acid and pH change in ocean:
┌ Info:
│ ================================================================================
│ create_model_from_config: configmodel Minimal_Alk_pH
│ config_file: /home/runner/work/PALEOtutorials.jl/PALEOtutorials.jl/examples/ocean_chemistry/config_ex1.yaml
└ ================================================================================
[ Info: Model.parameters:
┌ Info:
│ ================================================================================
│ creating Domains
└ ================================================================================
[ Info: generated Reaction catalog with 74 Reactions
┌ Info:
│ ================================================================================
│ creating domain 'global' ID=1
└ ================================================================================
┌ Info: create_reaction_from_config: global.add_Alk classname ReactionFluxPerturb
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [config.yaml] perturb_times =[-1.0e30, 1.0e30]
│ set parameters: [config.yaml] perturb_totals =[2.66e16, 2.66e16]
│ set parameters: [Default] perturb_deltas =[0.0, 0.0]
│ set parameters: [Default] extrapolate =throw
│ set parameters: [Default] extrapolate_before =NaN
│ set parameters: [Default] extrapolate_before_delta=NaN
│ set parameters: [Default] extrapolate_after =NaN
└ set parameters: [Default] extrapolate_after_delta=NaN
┌ Info:
│ ================================================================================
│ creating domain 'ocean' ID=2
└ ================================================================================
┌ Info: create_reaction_from_config: ocean.reservoir_DIC classname ReactionReservoir
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] total =false
│ set parameters: [Default] limit_delta_conc =0.0
└ set parameters: [Default] state_conc =false
┌ Info: create_reaction_from_config: ocean.constant_B classname ReactionReservoirConst
└ set parameters: [Default] field_data =PALEOboxes.ScalarData
┌ Info: create_reaction_from_config: ocean.reservoir_TA classname ReactionReservoir
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] total =false
│ set parameters: [Default] limit_delta_conc =0.0
└ set parameters: [Default] state_conc =false
┌ Info: create_reaction_from_config: ocean.oceantrasport classname ReactionOceanNoTransport
│ set parameters: [config.yaml] area =[3.6e14]
└ set parameters: [config.yaml] depth =[3688.0]
┌ Info: create_reaction_from_config: ocean.solve_Alk_pH classname Reaction_Alk_pH
│ set parameters: [config.yaml] K_1 =1.4e-6
│ set parameters: [config.yaml] K_2 =1.2e-9
│ set parameters: [config.yaml] K_w =6.0e-14
└ set parameters: [config.yaml] K_B =2.5e-9
┌ Info:
│ ================================================================================
│ creating domain 'oceansurface' ID=3
└ ================================================================================
┌ Info:
│ ================================================================================
│ creating domain 'oceanfloor' ID=4
└ ================================================================================
┌ Info:
│ ================================================================================
│ set_model_geometry
└ ================================================================================
[ Info: set ocean.oceansurface Subdomain size=1
[ Info: set ocean.oceanfloor Subdomain size=1
[ Info: Ocean.set_model_domains:
[ Info: ocean Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["oceansurface", "oceanfloor"])
[ Info: oceansurface Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
[ Info: oceanfloor Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
┌ Info:
│ ================================================================================
│ register_reaction_methods!
└ ================================================================================
[ Info: ReactionFluxPerturb.register_methods! global.add_Alk
┌ Info:
│ ================================================================================
│ link_variables: first pass
└ ================================================================================
┌ Info:
│ ================================================================================
│ link_variables: register_reaction_dynamic_methods and configure variables
└ ================================================================================
┌ Info: _configure_variables: ReactionFluxPerturb global.add_Alk variable_links:
└ set variable_links: F -> ocean.TAlk_sms
┌ Info: _configure_variables: ReactionReservoir ocean.reservoir_DIC variable_links:
│ set variable_links: R -> DIC
│ set variable_links: R_sms -> DIC_sms
│ set variable_links: R_conc -> DIC_conc
│ _configure_variables: ReactionReservoir ocean.reservoir_DIC variable_attributes:
│ set attribute: R :norm_value = 2.0
└ set attribute: R :initial_value = 2.0
┌ Info: _configure_variables: ReactionReservoirConst ocean.constant_B variable_links:
│ set variable_links: R_conc -> B_conc
│ _configure_variables: ReactionReservoirConst ocean.constant_B variable_attributes:
└ set attribute: R_conc :initial_value = 0.427
┌ Info: _configure_variables: ReactionReservoir ocean.reservoir_TA variable_links:
│ set variable_links: R -> TAlk
│ set variable_links: R_sms -> TAlk_sms
│ set variable_links: R_conc -> TAlk_conc
│ _configure_variables: ReactionReservoir ocean.reservoir_TA variable_attributes:
│ set attribute: R :norm_value = 2.4
└ set attribute: R :initial_value = 0.0
┌ Info: _configure_variables: Reaction_Alk_pH ocean.solve_Alk_pH variable_links:
└ set variable_links: density -> rho_ref
┌ Info:
│ ================================================================================
│ link_variables: second pass:
└ ================================================================================
┌ Info:
│ ================================================================================
│ link_variables! unlinked variables:
└ ================================================================================
┌ Info:
│ ================================================================================
│ create_model_from_config: done
└ ================================================================================
┌ Info:
│ ================================================================================
│ PALEOmodel.initialize! start
└ ================================================================================
┌ Info:
│ ================================================================================
│ allocate_variables! (modeldata arrays_idx=1)
└ ================================================================================
[ Info: Domain global data dimensions PALEOboxes.NamedDimension[] allocating 2 variables (hostdep=nothing)
[ Info: set :field_data=PALEOboxes.ScalarData for host-dependent Variable global.tforce
[ Info: Domain ocean data dimensions PALEOboxes.NamedDimension[] allocating 24 variables (hostdep=nothing)
[ Info: Domain oceansurface data dimensions PALEOboxes.NamedDimension[] allocating 1 variables (hostdep=nothing)
[ Info: Domain oceanfloor data dimensions PALEOboxes.NamedDimension[] allocating 3 variables (hostdep=nothing)
[ Info: set_default_solver_view:
┌ Info: SolverView:
│ host-dependent Variables:
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ Domain operatorID VF_StateExplicit VF_Total VF_Constraint VF_StateTotal VF_State VF_Undefined
│ global 0 0 0 0 0 0 1
│ ocean 0 2 0 1 0 1 0
│ oceansurface 0 0 0 0 0 0 0
│ oceanfloor 0 0 0 0 0 0 0
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ Total - 2 0 1 0 1 1
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ n_state_vars 3 (stateexplicit 2 + statetotal 0 + state 1)
│ n_equations 3 (stateexplicit 2 + total 0 + constraint 1)
│ including all host-dependent non-state Variables
└ host-dependent non-state Variables (:vfunction PB.VF_Undefined): ["global.tforce"]
┌ Info:
│ ================================================================================
│ initialize_reactiondata! (modeldata arrays_indices=1:1)
└ ================================================================================
┌ Info:
│ ================================================================================
│ dispatch_setup :setup
└ ================================================================================
[ Info: ocean.constant_B.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.B_conc = 0.427
┌ Info:
│ ================================================================================
│ dispatch_setup :norm_value
└ ================================================================================
[ Info: ocean.reservoir_DIC.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value ocean.DIC = 2.0 * volume
[ Info: ocean.reservoir_TA.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value ocean.TAlk = 2.4 * volume
┌ Info:
│ ================================================================================
│ dispatch_setup :initial_value
└ ================================================================================
[ Info: ocean.reservoir_DIC.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.DIC = 2.0 * volume
[ Info: ocean.reservoir_TA.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.TAlk = 0.0 * volume
┌ Info:
│ ================================================================================
│ PALEOmodel.initialize! done
└ ================================================================================
initial_state: [0.0, 2.65536e18, 8.0]
initial_deriv: [2.66e16, 0.0, -2.299331618163945]
integrate, DAE
┌ Info:
│ ================================================================================
│ PALEOmodel.ODE.integrateDAE:
│ tspan: (0.0, 200.0)
│ algorithm: Sundials.IDA{:Dense, Nothing, Nothing}(0, 0, 0, 5, 7, 0.33, 3, 10, 0.0033, 5, 4, 10, 100, true, false, nothing, nothing)
│ Jacobian: NoJacobian
│ using 1 BLAS threads
└ ================================================================================
[ Info: DAEfunction: using Jacobian NoJacobian
[ Info: jac_config_dae: jac_ad=NoJacobian
[ Info: calling get_inconsistent_initial_deriv
0.776391 seconds (946.73 k allocations: 64.904 MiB, 3.54% gc time, 99.91% compilation time)
┌ Info:
│ ================================================================================
│ print_sol_stats:
│ retcode=Success
│ alg=Sundials.IDA{:Dense, Nothing, Nothing}(0, 0, 0, 5, 7, 0.33, 3, 10, 0.0033, 5, 4, 10, 100, true, false, nothing, nothing)
│ stats=SciMLBase.DEStats
│ Number of function 1 evaluations: 405
│ Number of function 2 evaluations: 0
│ Number of W matrix evaluations: 98
│ Number of linear solves: 0
│ Number of Jacobians created: 98
│ Number of nonlinear solver iterations: 403
│ Number of nonlinear solver convergence failures: 0
│ Number of fixed-point solver iterations: 0
│ Number of fixed-point solver convergence failures: 0
│ Number of rootfind condition calls: 0
│ Number of accepted steps: 261
│ Number of rejected steps: 8
│ length(sol.t) 270
│ size(sol) (3, 270)
└ ================================================================================
[ Info: ODE.calc_output_sol!: 270 records
[ Info: ODE.calc_output_sol!: done
┌ Info:
│ ================================================================================
│ PALEOmodel.ODE.integrateDAE: done
└ ================================================================================
0.965961 seconds (1.14 M allocations: 78.370 MiB, 2.85% gc time, 99.10% compilation time)
Displaying model structure and variables
All metadata for model variables can be displayed with PB.show_variables
:
show(PB.show_variables(model), allcols=true, allrows=true) # display in REPL
# vscodedisplay(PB.show_variables(model)) # more convenient when using VS code
30×9 DataFrame
Row │ domain name type units vfunction space data_dims field_data description
│ String String DataType String Variable… DataType Tuple{} DataType String
─────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ global add_Alk/FApplied VariableDomPropDep mol yr-1 VF_Undefined ScalarSpace () ScalarData flux perturbation applied, for d…
2 │ global tforce VariableDomPropDep yr VF_Undefined ScalarSpace () ScalarData time at which to apply perturbat…
3 │ ocean Abox VariableDomPropDep m^2 VF_Undefined CellSpace () ScalarData horizontal area of box
4 │ ocean BOH3_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData BOH3 concentration
5 │ ocean BOH4_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData BOH4- concentration
6 │ ocean B_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration
7 │ ocean CO2_aq_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData CO2_aq concentration
8 │ ocean CO3_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData CO32- concentration
9 │ ocean DIC VariableDomPropDep mol VF_StateExplicit CellSpace () ScalarData vector reservoir
10 │ ocean DIC_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration
11 │ ocean DIC_sms VariableDomContribTarget mol yr-1 VF_Deriv CellSpace () ScalarData vector reservoir source-sinks
12 │ ocean HCO3_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData HCO3- concentration
13 │ ocean H_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration of H+
14 │ ocean OH_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration of OH-
15 │ ocean TAlk VariableDomPropDep mol VF_StateExplicit CellSpace () ScalarData vector reservoir
16 │ ocean TAlk_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration
17 │ ocean TAlk_error VariableDomContribTarget mol m-3 VF_Constraint CellSpace () ScalarData in order to solve TA, we set it
18 │ ocean TAlk_sms VariableDomContribTarget mol yr-1 VF_Deriv CellSpace () ScalarData vector reservoir source-sinks
19 │ ocean pH VariableDomPropDep VF_State CellSpace () ScalarData it is the calcalation for pH
20 │ ocean pressure VariableDomPropDep dbar VF_Undefined CellSpace () ScalarData Ocean pressure
21 │ ocean rho_ref VariableDomPropDep kg m^-3 VF_Undefined CellSpace () ScalarData Ocean transport model density co…
22 │ ocean volume VariableDomPropDep m^3 VF_Undefined CellSpace () ScalarData volume of ocean cells
23 │ ocean volume_total VariableDomPropDep m^3 VF_Undefined ScalarSpace () ScalarData total volume of ocean cells
24 │ ocean zlower VariableDomPropDep m VF_Undefined CellSpace () ScalarData depth of lower surface of box (m)
25 │ ocean zmid VariableDomPropDep m VF_Undefined CellSpace () ScalarData mean depth of box
26 │ ocean zupper VariableDomPropDep m VF_Undefined CellSpace () ScalarData depth of upper surface of box (m…
27 │ oceanfloor Afloor VariableDomPropDep m^2 VF_Undefined CellSpace () ScalarData horizontal area of seafloor at b…
28 │ oceanfloor Afloor_total VariableDomPropDep m^2 VF_Undefined ScalarSpace () ScalarData total area of seafloor
29 │ oceanfloor zfloor VariableDomPropDep m VF_Undefined CellSpace () ScalarData depth of ocean floor (m, -ve)
30 │ oceansurface Asurf VariableDomPropDep m^2 VF_Undefined CellSpace () ScalarData horizontal area of oceansurface
Example 2 Air-sea exchange
This example adds air-sea exchange of CO2 to Example 1 Minimal Alk-pH model.
This configuration adds an atmosphere Domain atm
with a state variable for atmospheric CO2. Following the standard PALEO convention for coupling spatial Domains
, air-sea exchange is implemented by a combination of the new reaction Reaction_Min_AirSeaExchange
in the oceansurface
Domain, a ReactionFluxTarget
in a fluxAtmtoOceansurface
Domain to store the calculated flux, and a pair of ReactionFluxTransfer
s to apply the calculated fluxes to the atmosphere CO2 and ocean DIC reservoirs. NB: the ocean.oceansurface
subdomain represents the subset of ocean cells adjacent to the surface, and contains the same number of cells as the oceansurface
and fluxAtmtoOceansurface
Domains, with a 1-1 correspondence.
Additional code files
In order to evaluate the CO2 flux change between air and sea, we add a file (file examples/ocean_chemistry/reactions_AirSeaExchange.jl
) that implements a Reaction_Min_AirSeaExchange
to calculate air-sea CO2 exchange following Henry's Law. NB: the reaction is implemented for a generic gas X
that is then linked to the CO2 and DIC variables using the variable_links:
section in the .yaml configuration file.
module Min_AirSeaExchange
import PALEOboxes as PB
using PALEOboxes.DocStrings # for $(PARS) and $(METHODS_DO)
"""
Reaction_Min_AirSeaExchange
Minimal example, just make a easy way to illustrate Air-Sea exchange.
This implements exchange between Air and Sea for a generic gas `X` with fixed Henry law coefficient `K_0`
and piston velocity `vpiston`.
The Reaction-local Variables `X_aq_conc`, `pXatm`, `X_airsea_exchange` should be linked to the appropriate variables using the
`variable_links:` section in the .yaml file.
# Parameters
$(PARS)
# Methods and Variables
$(METHODS_DO)
"""
Base.@kwdef mutable struct Reaction_Min_AirSeaExchange{P} <: PB.AbstractReaction
base::PB.ReactionBase
pars::P = PB.ParametersTuple(
PB.ParDouble("K_0", 3.4e-2*1e3, units="mol m-3 atm-1", description="Henry Law coefficient"),
PB.ParDouble("vpiston", 1138.8, units="m yr-1", description="piston value for a whole year, 365 days"),
)
end
function PB.register_methods!(rj::Reaction_Min_AirSeaExchange)
vars = [
PB.VarDep( "X_aq_conc", "mol m-3", "ocean concentration per cell"),
PB.VarDepScalar( "pXatm", "atm", "atmospheric partial pressure, unit is atm"),
PB.VarContrib( "X_airsea_exchange", "mol yr-1", "calculated airsea exchange flux for gas X"),
PB.VarDep( "area", "m2", "surface area"),
]
PB.add_method_do!(rj, do_Min_AirSeaExchange, (PB.VarList_namedtuple(vars), ) )
return nothing
end
# do method, called each main loop timestep
function do_Min_AirSeaExchange(m::PB.ReactionMethod, pars, (varsdata, ), cellrange::PB.AbstractCellRange, deltat)
# mol m-3 mol m-3 atm-1 atm
X_aq_conc_eqb = pars.K_0[] * varsdata.pXatm[]
for i in cellrange.indices
# mol m-3 mol m-3
X_aq_conc = varsdata.X_aq_conc[i]
# mol yr-1 mol m-3 mol m-3 m yr-1 m2
varsdata.X_airsea_exchange[i] -= (X_aq_conc - X_aq_conc_eqb) * pars.vpiston[] * varsdata.area[i]
end
return nothing
end
end # module
Documentation (generated by the Julia docstring) reads:
Main.Min_AirSeaExchange.Reaction_Min_AirSeaExchange
— TypeReaction_Min_AirSeaExchange
Minimal example, just make a easy way to illustrate Air-Sea exchange.
This implements exchange between Air and Sea for a generic gas X
with fixed Henry law coefficient K_0
and piston velocity vpiston
.
The Reaction-local Variables X_aq_conc
, pXatm
, X_airsea_exchange
should be linked to the appropriate variables using the variable_links:
section in the .yaml file.
Parameters
K_0[Float64]
=34.0 (mol m-3 atm-1),default_value
=34.0,description
="Henry Law coefficient"vpiston[Float64]
=1138.8 (m yr-1),default_value
=1138.8,description
="piston value for a whole year, 365 days"
Methods and Variables
do_Min_AirSeaExchange
X_aq_conc
(mol m-3),VT_ReactDependency
,description
="ocean concentration per cell"pXatm
(atm),VT_ReactDependency
,description
="atmospheric partial pressure, unit is atm"X_airsea_exchange
(mol yr-1),VT_ReactContributor
,description
="calculated airsea exchange flux for gas X"area
(m2),VT_ReactDependency
,description
="surface area"
yaml configuration file
The model configuration (file examples/ocean_chemistry/config_ex2.yaml
) contains three Reservoirs DIC
, TAlk
and CO2
. Following reservoirs
Example 4 Transfer between Domains, we use ReactionFluxTarget
and ReactionFluxTransfer
to transfer CO2_airsea_exchange
between DIC
reservoir and CO2
reservoir:
Minimal_Alk_pH_AirSea:
domains:
global:
reactions:
sum_C:
class: ReactionSum
parameters:
vars_to_add: ["ocean.DIC_total", "atm.CO2"]
variable_links:
sum: C_total
add_Alk:
# from PALEOboxes reaction catalog: constant input flux of Alk
class: ReactionFluxPerturb
parameters:
# linear interpolation for input flux - set to constant
perturb_times: [-1e30, 1e30]
# ocean volume = 3.6e14 * 3688.0 = 1.33e18 m^3
# DIC in ocean = 2 * 1.33e18 = 2.66e18 mol
# set Alk flux so Alk = DIC after 100 yr-1 = 2.66e18/100 = 2.66e16 mol yr-1
perturb_totals: [2.66e16, 2.66e16]
variable_links:
F: ocean.TAlk_sms # add flux to TAlk reservoir NB: works for single-box ocean only !!
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ocean:
reactions:
oceantrasport:
class: ReactionOceanNoTransport
parameters:
area: [3.6e14]
depth: [3688.0]
reservoir_TA:
class: ReactionReservoirTotal
variable_links:
R*: TAlk*
variable_attributes:
R:initial_value: 0.0
R:norm_value: 2.4e0
reservoir_DIC:
class: ReactionReservoirTotal
variable_links:
R*: DIC*
variable_attributes:
R:initial_value: 2.0e0
R:norm_value: 2.0e0
constant_B:
class: ReactionReservoirConst
variable_links:
R*: B
variable_attributes:
R_conc:initial_value: 0.427 # mol m-3 contemporary value
solve_Alk_pH:
class: Reaction_Alk_pH
parameters:
K_1: 1.4e-6
K_2: 1.2e-9
K_w: 6.0e-14
K_B: 2.5e-9
variable_links:
density: rho_ref # OceanNoTransport provides density as rho_ref
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
oceansurface:
# air-sea exchange reactions
reactions:
# calculate air-sea exchange flux
solve_AirSea_Exchange:
class: Reaction_Min_AirSeaExchange
parameters:
K_0: 3.4e1 # (mol m-3 atm-1) = 3.4e-2 (mol L-1 atm-1) * 1e3 (L m-3)
vpiston: 1138.8 # m yr-1
variable_links:
pXatm: atm.pCO2atm
X_aq_conc: ocean.oceansurface.CO2_aq_conc
area: Asurf # ocean surface area
X_airsea_exchange: fluxAtmtoOceansurface.flux_CO2
# apply air-sea flux to ocean surface
transfer_fluxAtmtoOceansurface:
class: ReactionFluxTransfer
parameters:
transfer_matrix: Identity
input_fluxes: fluxAtmtoOceansurface.flux_$fluxname$
output_fluxes: ocean.oceansurface.$fluxname$_sms
variable_links:
output_CO2: ocean.oceansurface.DIC_sms
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
oceanfloor:
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
atm:
reactions:
reservoir_CO2_atm:
class: ReactionReservoirAtm
parameters:
moles1atm: 1.77e20
variable_links:
R*: CO2*
pRatm: pCO2atm
pRnorm: pCO2PAL
variable_attributes:
R:norm_value: 4.956e16 # 280e-6 ppm
R:initial_value: 4.956e16
transfer_AtmtoOceansurface:
class: ReactionFluxTransfer
parameters:
transfer_matrix: Distribute
transfer_multiplier: -1.0
input_fluxes: fluxAtmtoOceansurface.flux_$fluxname$
output_fluxes: $fluxname$_sms
variable_links:
output_CO2: atm.CO2_sms
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
fluxAtmtoOceansurface:
reactions:
fluxtarget:
class: ReactionFluxTarget
parameters:
flux_totals: true
fluxlist: ["CO2"]
Run script
The script to run the model (file examples/ocean_chemistry/run_ex2.jl
) contains:
import PALEOboxes as PB
import PALEOmodel
# import PALEOcopse
import PALEOocean
using Plots
include(joinpath(@__DIR__,"reactions_Alk_pH.jl")) # @__DIR__ so still runs when building docs
include(joinpath(@__DIR__,"reactions_AirSeaExchange.jl")) # @__DIR__ so still runs when building docs
include(joinpath(@__DIR__,"reactions_ReservoirAtm.jl")) # @__DIR__ so still runs when building docs
#####################################################
# Create model
model = PB.create_model_from_config(joinpath(@__DIR__, "config_ex2.yaml"), "Minimal_Alk_pH_AirSea")
#########################################################
# Initialize
##########################################################
initial_state, modeldata = PALEOmodel.initialize!(model)
#####################################################################
# Optional: call ODE function to check derivative
#######################################################################
initial_deriv = similar(initial_state)
PALEOmodel.ODE.ModelODE(modeldata)(initial_deriv, initial_state , nothing, 0.0)
println("initial_state: ", initial_state)
println("initial_deriv: ", initial_deriv)
#################################################################
# Integrate vs time
##################################################################
# create a Run object to hold model and output
paleorun = PALEOmodel.Run(model=model, output = PALEOmodel.OutputWriters.OutputMemory())
println("integrate, DAE")
# first run is slow as it includes JIT time
@time PALEOmodel.ODE.integrateDAE(
paleorun, initial_state, modeldata, (0.0, 200.0),
solvekwargs=(
reltol=1e-6,
# saveat=0.1, # save output every 0.1 yr see https://diffeq.sciml.ai/dev/basics/common_solver_opts/
)
);
########################################
# Plot output
########################################
display(plot(paleorun.output, ["ocean.TAlk_conc", "ocean.DIC_conc"], (cell=1,);
ylabel="TAlk, DIC conc (mol m-3)"))
# display(plot(paleorun.output, ["ocean.TAlk","ocean.TAlk_sms"], (cell=1,)))
display(plot(paleorun.output, "ocean.pH", (cell=1,)))
display(plot(paleorun.output, ["ocean.DIC_conc", "ocean.HCO3_conc", "ocean.CO3_conc", "ocean.CO2_aq_conc"], (cell=1,);
ylabel="DIC species (mol m-3)", legend=:topleft))
display(plot(paleorun.output, "atm.CO2", ))
display(plot(paleorun.output, "atm.pCO2atm", ))
display(plot(paleorun.output, "atm.CO2_sms", ))
display(plot(paleorun.output, "fluxAtmtoOceansurface.flux_CO2", (cell=1,);
ylabel="air->sea flux (mol yr-1)"))
display(plot(paleorun.output, "global.C_total";
ylabel="total carbon (mol)"))
display(plot(paleorun.output, ["global.C_total", "atm.CO2", "ocean.DIC_total"];
ylabel="atm ocean carbon (mol)", legend=:topleft))
and produces output showing the change, if TAlk_conc
increase, how the carbonic acid and pH change in ocean and CO2 change in the air:
display(plot(paleorun.output, ["global.C_total", "atm.CO2", "ocean.DIC_total"] ; ylabel="atm-ocean carbon (mol)"))
┌ Info:
│ ================================================================================
│ create_model_from_config: configmodel Minimal_Alk_pH_AirSea
│ config_file: /home/runner/work/PALEOtutorials.jl/PALEOtutorials.jl/examples/ocean_chemistry/config_ex2.yaml
└ ================================================================================
[ Info: Model.parameters:
┌ Info:
│ ================================================================================
│ creating Domains
└ ================================================================================
[ Info: generated Reaction catalog with 74 Reactions
┌ Info:
│ ================================================================================
│ creating domain 'global' ID=1
└ ================================================================================
┌ Info: create_reaction_from_config: global.sum_C classname ReactionSum
│ set parameters: [config.yaml] vars_to_add =["ocean.DIC_total", "atm.CO2"]
│ set parameters: [Default] vars_prefix =
│ set parameters: [Default] component_to_add =0
└ set parameters: [Default] vectorsum =false
┌ Info: create_reaction_from_config: global.add_Alk classname ReactionFluxPerturb
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [config.yaml] perturb_times =[-1.0e30, 1.0e30]
│ set parameters: [config.yaml] perturb_totals =[2.66e16, 2.66e16]
│ set parameters: [Default] perturb_deltas =[0.0, 0.0]
│ set parameters: [Default] extrapolate =throw
│ set parameters: [Default] extrapolate_before =NaN
│ set parameters: [Default] extrapolate_before_delta=NaN
│ set parameters: [Default] extrapolate_after =NaN
└ set parameters: [Default] extrapolate_after_delta=NaN
┌ Info:
│ ================================================================================
│ creating domain 'ocean' ID=2
└ ================================================================================
┌ Info: create_reaction_from_config: ocean.reservoir_DIC classname ReactionReservoirTotal
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] total =true
│ set parameters: [Default] limit_delta_conc =0.0
└ set parameters: [Default] state_conc =false
┌ Info: create_reaction_from_config: ocean.constant_B classname ReactionReservoirConst
└ set parameters: [Default] field_data =PALEOboxes.ScalarData
┌ Info: create_reaction_from_config: ocean.reservoir_TA classname ReactionReservoirTotal
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] total =true
│ set parameters: [Default] limit_delta_conc =0.0
└ set parameters: [Default] state_conc =false
┌ Info: create_reaction_from_config: ocean.oceantrasport classname ReactionOceanNoTransport
│ set parameters: [config.yaml] area =[3.6e14]
└ set parameters: [config.yaml] depth =[3688.0]
┌ Info: create_reaction_from_config: ocean.solve_Alk_pH classname Reaction_Alk_pH
│ set parameters: [config.yaml] K_1 =1.4e-6
│ set parameters: [config.yaml] K_2 =1.2e-9
│ set parameters: [config.yaml] K_w =6.0e-14
└ set parameters: [config.yaml] K_B =2.5e-9
┌ Info:
│ ================================================================================
│ creating domain 'oceansurface' ID=3
└ ================================================================================
┌ Info: create_reaction_from_config: oceansurface.transfer_fluxAtmtoOceansurface classname ReactionFluxTransfer
│ set parameters: [config.yaml] input_fluxes =fluxAtmtoOceansurface.flux_$fluxname$
│ set parameters: [config.yaml] output_fluxes =ocean.oceansurface.$fluxname$_sms
│ set parameters: [Default] transfer_multiplier =1.0
└ set parameters: [config.yaml] transfer_matrix =Identity
┌ Info: create_reaction_from_config: oceansurface.solve_AirSea_Exchange classname Reaction_Min_AirSeaExchange
│ set parameters: [config.yaml] K_0 =34.0
└ set parameters: [config.yaml] vpiston =1138.8
┌ Info:
│ ================================================================================
│ creating domain 'oceanfloor' ID=4
└ ================================================================================
┌ Info:
│ ================================================================================
│ creating domain 'fluxAtmtoOceansurface' ID=5
└ ================================================================================
┌ Info: create_reaction_from_config: fluxAtmtoOceansurface.fluxtarget classname ReactionFluxTarget
│ set parameters: [config.yaml] fluxlist =["CO2"]
│ set parameters: [Default] target_prefix =flux_
│ set parameters: [config.yaml] flux_totals =true
└ set parameters: [Default] const_stub =false
┌ Info:
│ ================================================================================
│ creating domain 'atm' ID=6
└ ================================================================================
┌ Info: create_reaction_from_config: atm.transfer_AtmtoOceansurface classname ReactionFluxTransfer
│ set parameters: [config.yaml] input_fluxes =fluxAtmtoOceansurface.flux_$fluxname$
│ set parameters: [config.yaml] output_fluxes =$fluxname$_sms
│ set parameters: [config.yaml] transfer_multiplier =-1.0
└ set parameters: [config.yaml] transfer_matrix =Distribute
┌ Info: create_reaction_from_config: atm.reservoir_CO2_atm classname ReactionReservoirAtm
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] const =false
└ set parameters: [config.yaml] moles1atm =1.77e20
┌ Info:
│ ================================================================================
│ set_model_geometry
└ ================================================================================
[ Info: set ocean.oceansurface Subdomain size=1
[ Info: set ocean.oceanfloor Subdomain size=1
[ Info: Ocean.set_model_domains:
[ Info: ocean Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["oceansurface", "oceanfloor"])
[ Info: fluxAtmtoOceansurface Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
[ Info: oceansurface Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
[ Info: oceanfloor Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
┌ Info:
│ ================================================================================
│ register_reaction_methods!
└ ================================================================================
┌ Info: register_methods: global.sum_C PALEOboxes.VariableStats.ReactionSum
│ add 1 * ocean.DIC_total
└ add 1 * atm.CO2
[ Info: ReactionFluxPerturb.register_methods! global.add_Alk
[ Info: register_methods! ReactionReservoirAtm atm.reservoir_CO2_atm field_data=PALEOboxes.ScalarData
┌ Info:
│ ================================================================================
│ link_variables: first pass
└ ================================================================================
┌ Info:
│ ================================================================================
│ link_variables: register_reaction_dynamic_methods and configure variables
└ ================================================================================
[ Info: Reaction global.sum_C Variable global.C_total adding data=PALEOboxes.ScalarData
┌ Info: _configure_variables: ReactionSum global.sum_C variable_links:
└ set variable_links: sum -> C_total
┌ Info: _configure_variables: ReactionFluxPerturb global.add_Alk variable_links:
└ set variable_links: F -> ocean.TAlk_sms
┌ Info: _configure_variables: ReactionReservoir ocean.reservoir_DIC variable_links:
│ set variable_links: R -> DIC
│ set variable_links: R_sms -> DIC_sms
│ set variable_links: R_total -> DIC_total
│ set variable_links: R_conc -> DIC_conc
│ _configure_variables: ReactionReservoir ocean.reservoir_DIC variable_attributes:
│ set attribute: R :norm_value = 2.0
└ set attribute: R :initial_value = 2.0
┌ Info: _configure_variables: ReactionReservoirConst ocean.constant_B variable_links:
│ set variable_links: R_conc -> B_conc
│ _configure_variables: ReactionReservoirConst ocean.constant_B variable_attributes:
└ set attribute: R_conc :initial_value = 0.427
┌ Info: _configure_variables: ReactionReservoir ocean.reservoir_TA variable_links:
│ set variable_links: R -> TAlk
│ set variable_links: R_sms -> TAlk_sms
│ set variable_links: R_total -> TAlk_total
│ set variable_links: R_conc -> TAlk_conc
│ _configure_variables: ReactionReservoir ocean.reservoir_TA variable_attributes:
│ set attribute: R :norm_value = 2.4
└ set attribute: R :initial_value = 0.0
┌ Info: _configure_variables: Reaction_Alk_pH ocean.solve_Alk_pH variable_links:
└ set variable_links: density -> rho_ref
┌ Info: _configure_variables: ReactionFluxTransfer oceansurface.transfer_fluxAtmtoOceansurface variable_links:
└ set variable_links: output_CO2 -> ocean.oceansurface.DIC_sms
┌ Info: _configure_variables: Reaction_Min_AirSeaExchange oceansurface.solve_AirSea_Exchange variable_links:
│ set variable_links: X_airsea_exchange -> fluxAtmtoOceansurface.flux_CO2
│ set variable_links: X_aq_conc -> ocean.oceansurface.CO2_aq_conc
│ set variable_links: area -> Asurf
└ set variable_links: pXatm -> atm.pCO2atm
┌ Info: _configure_variables: ReactionFluxTransfer atm.transfer_AtmtoOceansurface variable_links:
└ set variable_links: output_CO2 -> atm.CO2_sms
┌ Info: _configure_variables: ReactionReservoirAtm atm.reservoir_CO2_atm variable_links:
│ set variable_links: R -> CO2
│ set variable_links: R_sms -> CO2_sms
│ set variable_links: pRatm -> pCO2atm
│ set variable_links: pRnorm -> pCO2PAL
│ _configure_variables: ReactionReservoirAtm atm.reservoir_CO2_atm variable_attributes:
│ set attribute: R :norm_value = 4.956e16
└ set attribute: R :initial_value = 4.956e16
┌ Info:
│ ================================================================================
│ link_variables: second pass:
└ ================================================================================
┌ Info:
│ ================================================================================
│ link_variables! unlinked variables:
└ ================================================================================
┌ Info:
│ ================================================================================
│ create_model_from_config: done
└ ================================================================================
┌ Info:
│ ================================================================================
│ PALEOmodel.initialize! start
└ ================================================================================
┌ Info:
│ ================================================================================
│ allocate_variables! (modeldata arrays_idx=1)
└ ================================================================================
[ Info: Domain global data dimensions PALEOboxes.NamedDimension[] allocating 3 variables (hostdep=nothing)
[ Info: set :field_data=PALEOboxes.ScalarData for host-dependent Variable global.tforce
[ Info: Domain ocean data dimensions PALEOboxes.NamedDimension[] allocating 26 variables (hostdep=nothing)
[ Info: Domain oceansurface data dimensions PALEOboxes.NamedDimension[] allocating 1 variables (hostdep=nothing)
[ Info: Domain oceanfloor data dimensions PALEOboxes.NamedDimension[] allocating 3 variables (hostdep=nothing)
[ Info: Domain fluxAtmtoOceansurface data dimensions PALEOboxes.NamedDimension[] allocating 2 variables (hostdep=nothing)
[ Info: Domain atm data dimensions PALEOboxes.NamedDimension[] allocating 4 variables (hostdep=nothing)
[ Info: set_default_solver_view:
┌ Info: SolverView:
│ host-dependent Variables:
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ Domain operatorID VF_StateExplicit VF_Total VF_Constraint VF_StateTotal VF_State VF_Undefined
│ global 0 0 0 0 0 0 1
│ ocean 0 2 0 1 0 1 0
│ oceansurface 0 0 0 0 0 0 0
│ oceanfloor 0 0 0 0 0 0 0
│ fluxAtmtoOceansurface 0 0 0 0 0 0 0
│ atm 0 1 0 0 0 0 0
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ Total - 3 0 1 0 1 1
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ n_state_vars 4 (stateexplicit 3 + statetotal 0 + state 1)
│ n_equations 4 (stateexplicit 3 + total 0 + constraint 1)
│ including all host-dependent non-state Variables
└ host-dependent non-state Variables (:vfunction PB.VF_Undefined): ["global.tforce"]
┌ Info:
│ ================================================================================
│ initialize_reactiondata! (modeldata arrays_indices=1:1)
└ ================================================================================
┌ Info: prepare_do_transfer: ReactionMethod oceansurface.transfer_fluxAtmtoOceansurface.do_transfer
│ active fluxes:
│ CO2 fluxAtmtoOceansurface.flux_CO2 -> ocean.DIC_sms
└ using Identity transfer matrix * 1.0
┌ Info: prepare_do_transfer: ReactionMethod atm.transfer_AtmtoOceansurface.do_transfer
│ active fluxes:
│ CO2 fluxAtmtoOceansurface.flux_CO2 -> atm.CO2_sms
└ using Distribute transfer matrix size (input, output) (1, 1) * -1.0
┌ Info:
│ ================================================================================
│ dispatch_setup :setup
└ ================================================================================
[ Info: ocean.constant_B.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.B_conc = 0.427
┌ Info:
│ ================================================================================
│ dispatch_setup :norm_value
└ ================================================================================
[ Info: atm.reservoir_CO2_atm.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value atm.CO2 = 4.956e16
[ Info: ocean.reservoir_DIC.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value ocean.DIC = 2.0 * volume
[ Info: ocean.reservoir_TA.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value ocean.TAlk = 2.4 * volume
┌ Info:
│ ================================================================================
│ dispatch_setup :initial_value
└ ================================================================================
[ Info: atm.reservoir_CO2_atm.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value atm.CO2 = 4.956e16
[ Info: ocean.reservoir_DIC.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.DIC = 2.0 * volume
[ Info: ocean.reservoir_TA.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.TAlk = 0.0 * volume
┌ Info:
│ ================================================================================
│ PALEOmodel.initialize! done
└ ================================================================================
initial_state: [0.0, 2.65536e18, 4.956e16, 8.0]
initial_deriv: [2.66e16, -1.1428818049576428e15, 1.1428818049576428e15, -2.299331618163945]
integrate, DAE
┌ Info:
│ ================================================================================
│ PALEOmodel.ODE.integrateDAE:
│ tspan: (0.0, 200.0)
│ algorithm: Sundials.IDA{:Dense, Nothing, Nothing}(0, 0, 0, 5, 7, 0.33, 3, 10, 0.0033, 5, 4, 10, 100, true, false, nothing, nothing)
│ Jacobian: NoJacobian
│ using 1 BLAS threads
└ ================================================================================
[ Info: DAEfunction: using Jacobian NoJacobian
[ Info: jac_config_dae: jac_ad=NoJacobian
[ Info: calling get_inconsistent_initial_deriv
0.776550 seconds (949.30 k allocations: 65.039 MiB, 3.64% gc time, 99.86% compilation time)
┌ Info:
│ ================================================================================
│ print_sol_stats:
│ retcode=Success
│ alg=Sundials.IDA{:Dense, Nothing, Nothing}(0, 0, 0, 5, 7, 0.33, 3, 10, 0.0033, 5, 4, 10, 100, true, false, nothing, nothing)
│ stats=SciMLBase.DEStats
│ Number of function 1 evaluations: 595
│ Number of function 2 evaluations: 0
│ Number of W matrix evaluations: 85
│ Number of linear solves: 0
│ Number of Jacobians created: 85
│ Number of nonlinear solver iterations: 593
│ Number of nonlinear solver convergence failures: 0
│ Number of fixed-point solver iterations: 0
│ Number of fixed-point solver convergence failures: 0
│ Number of rootfind condition calls: 0
│ Number of accepted steps: 408
│ Number of rejected steps: 5
│ length(sol.t) 414
│ size(sol) (4, 414)
└ ================================================================================
[ Info: ODE.calc_output_sol!: 414 records
[ Info: ODE.calc_output_sol!: done
┌ Info:
│ ================================================================================
│ PALEOmodel.ODE.integrateDAE: done
└ ================================================================================
0.957169 seconds (1.18 M allocations: 80.522 MiB, 2.95% gc time, 98.54% compilation time)
GKS: Possible loss of precision in routine SET_WINDOW
Displaying model structure and variables
All metadata for model variables can be displayed with PB.show_variables
:
show(PB.show_variables(model), allcols=true, allrows=true) # display in REPL
# vscodedisplay(PB.show_variables(model)) # more convenient when using VS code
39×9 DataFrame
Row │ domain name type units vfunction space data_dims field_data description
│ String String DataType String Variable… DataType Tuple{} DataType String
─────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ atm CO2 VariableDomPropDep mol VF_StateExplicit ScalarSpace () ScalarData scalar reservoir
2 │ atm CO2_sms VariableDomContribTarget mol yr-1 VF_Deriv ScalarSpace () ScalarData scalar reservoir source-sinks
3 │ atm pCO2PAL VariableDomPropDep VF_Undefined ScalarSpace () ScalarData scalar atmosphere reservoir norm…
4 │ atm pCO2atm VariableDomPropDep atm VF_Undefined ScalarSpace () ScalarData scalar atmosphere reservoir part…
5 │ fluxAtmtoOceansurface flux_CO2 VariableDomContribTarget mol yr-1 VF_Undefined CellSpace () ScalarData flux input
6 │ fluxAtmtoOceansurface flux_total_CO2 VariableDomPropDep mol yr-1 VF_Undefined ScalarSpace () ScalarData total flux input
7 │ global C_total VariableDomPropDep unknown VF_Undefined ScalarSpace () ScalarData sum of specified variables
8 │ global add_Alk/FApplied VariableDomPropDep mol yr-1 VF_Undefined ScalarSpace () ScalarData flux perturbation applied, for d…
9 │ global tforce VariableDomPropDep yr VF_Undefined ScalarSpace () ScalarData time at which to apply perturbat…
10 │ ocean Abox VariableDomPropDep m^2 VF_Undefined CellSpace () ScalarData horizontal area of box
11 │ ocean BOH3_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData BOH3 concentration
12 │ ocean BOH4_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData BOH4- concentration
13 │ ocean B_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration
14 │ ocean CO2_aq_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData CO2_aq concentration
15 │ ocean CO3_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData CO32- concentration
16 │ ocean DIC VariableDomPropDep mol VF_StateExplicit CellSpace () ScalarData vector reservoir
17 │ ocean DIC_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration
18 │ ocean DIC_sms VariableDomContribTarget mol yr-1 VF_Deriv CellSpace () ScalarData vector reservoir source-sinks
19 │ ocean DIC_total VariableDomPropDep mol VF_Undefined ScalarSpace () ScalarData total vector reservoir
20 │ ocean HCO3_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData HCO3- concentration
21 │ ocean H_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration of H+
22 │ ocean OH_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration of OH-
23 │ ocean TAlk VariableDomPropDep mol VF_StateExplicit CellSpace () ScalarData vector reservoir
24 │ ocean TAlk_conc VariableDomPropDep mol m-3 VF_Undefined CellSpace () ScalarData concentration
25 │ ocean TAlk_error VariableDomContribTarget mol m-3 VF_Constraint CellSpace () ScalarData in order to solve TA, we set it
26 │ ocean TAlk_sms VariableDomContribTarget mol yr-1 VF_Deriv CellSpace () ScalarData vector reservoir source-sinks
27 │ ocean TAlk_total VariableDomPropDep mol VF_Undefined ScalarSpace () ScalarData total vector reservoir
28 │ ocean pH VariableDomPropDep VF_State CellSpace () ScalarData it is the calcalation for pH
29 │ ocean pressure VariableDomPropDep dbar VF_Undefined CellSpace () ScalarData Ocean pressure
30 │ ocean rho_ref VariableDomPropDep kg m^-3 VF_Undefined CellSpace () ScalarData Ocean transport model density co…
31 │ ocean volume VariableDomPropDep m^3 VF_Undefined CellSpace () ScalarData volume of ocean cells
32 │ ocean volume_total VariableDomPropDep m^3 VF_Undefined ScalarSpace () ScalarData total volume of ocean cells
33 │ ocean zlower VariableDomPropDep m VF_Undefined CellSpace () ScalarData depth of lower surface of box (m)
34 │ ocean zmid VariableDomPropDep m VF_Undefined CellSpace () ScalarData mean depth of box
35 │ ocean zupper VariableDomPropDep m VF_Undefined CellSpace () ScalarData depth of upper surface of box (m…
36 │ oceanfloor Afloor VariableDomPropDep m^2 VF_Undefined CellSpace () ScalarData horizontal area of seafloor at b…
37 │ oceanfloor Afloor_total VariableDomPropDep m^2 VF_Undefined ScalarSpace () ScalarData total area of seafloor
38 │ oceanfloor zfloor VariableDomPropDep m VF_Undefined CellSpace () ScalarData depth of ocean floor (m, -ve)
39 │ oceansurface Asurf VariableDomPropDep m^2 VF_Undefined CellSpace () ScalarData horizontal area of oceansurface
Example 3 Minimal modern earth ocean-air
This example shows how ocean(TAlk-DIC)-air(CO2) exchange in modern state Example 3 Minimal modern earth ocean-air.
This configuration .yaml file is the same as Example 2 Air-sea exchange
. What is different is that we set TAlk
, DIC
and K_0
to be modern state value (from (Sarmiento and Gruber, 2006)) using PB.set_parameter_value!
and PB.set_variable_attribute!
NB: this is not an accurate model for the modern system ! See PALEOocean.jl for more complete models, including representations of ocean spatial structure and circulation, and more detailed parameterisations of carbonate chemistry (ReactionCO2SYS) and air-sea exchange (ReactionAirSeaCO2)
Run script
The script to run the model (file examples/ocean_chemistry/run_ex3.jl
) contains:
import PALEOboxes as PB
import PALEOmodel
# import PALEOcopse
import PALEOocean
using Plots
include(joinpath(@__DIR__,"reactions_Alk_pH.jl")) # @__DIR__ so still runs when building docs
include(joinpath(@__DIR__,"reactions_AirSeaExchange.jl")) # @__DIR__ so still runs when building docs
include(joinpath(@__DIR__,"reactions_ReservoirAtm.jl")) # @__DIR__ so still runs when building docs
#####################################################
# Create model
model = PB.create_model_from_config(joinpath(@__DIR__, "config_ex2.yaml"), "Minimal_Alk_pH_AirSea")
# set to ~modern (1990s) values from Sarmiento 2006 for global mean ocean surface
# K0 (NB: Table A.3 gives global mean ocean surface temperature 17.88 C)
PB.set_parameter_value!(model, "oceansurface", "solve_AirSea_Exchange", "K_0", 38.3) # mol m-3 atm-1, Table 3.2.3 at S=35. T=15℃
# PB.set_parameter_value!(model, "oceansurface", "solve_AirSea_Exchange", "K_0", 33.11) # mol m-3 atm-1, Table 3.2.3 at S=35. T=20℃
# PB.set_parameter_value!(model, "oceansurface", "solve_AirSea_Exchange", "K_0", 29.6) # mol m-3 atm-1, from Sarmiento 2006, at S=35. T=25℃
PB.set_parameter_value!(model, "global", "add_Alk", "perturb_totals", [0.0, 0.0]) # don't add any Alk
PB.set_variable_attribute!(model, "ocean.TAlk", :initial_value, 2.308) # mol m-3 ~ modern value global mean ocean surface (Table A.3)
PB.set_variable_attribute!(model, "ocean.DIC", :initial_value, 2.026) # mol m-3 ~ modern value global mean ocean surface (Table A.3)
#########################################################
# Initialize
##########################################################
initial_state, modeldata = PALEOmodel.initialize!(model)
#####################################################################
# Optional: call ODE function to check derivative
#######################################################################
initial_deriv = similar(initial_state)
PALEOmodel.ODE.ModelODE(modeldata)(initial_deriv, initial_state , nothing, 0.0)
println("initial_state: ", initial_state)
println("initial_deriv: ", initial_deriv)
#################################################################
# Integrate vs time
##################################################################
# create a Run object to hold model and output
paleorun = PALEOmodel.Run(model=model, output = PALEOmodel.OutputWriters.OutputMemory())
println("integrate, DAE")
# first run is slow as it includes JIT time
@time PALEOmodel.ODE.integrateDAE(
paleorun, initial_state, modeldata, (0.0, 100.0),
solvekwargs=(
reltol=1e-6,
# saveat=0.1, # save output every 0.1 yr see https://diffeq.sciml.ai/dev/basics/common_solver_opts/
)
);
########################################
# Plot output
########################################
display(plot(paleorun.output, ["ocean.TAlk_conc", "ocean.DIC_conc"], (cell=1,);
ylabel="TAlk, DIC conc (mol m-3)"))
# display(plot(paleorun.output, ["ocean.TAlk","ocean.TAlk_sms"], (cell=1,)))
display(plot(paleorun.output, "ocean.pH", (cell=1,)))
display(plot(paleorun.output, ["ocean.DIC_conc", "ocean.HCO3_conc", "ocean.CO3_conc", "ocean.CO2_aq_conc"], (cell=1,);
ylabel="DIC species (mol m-3)", legend=:topleft))
display(plot(paleorun.output, "atm.CO2", ))
display(plot(paleorun.output, "atm.pCO2atm", ))
display(plot(paleorun.output, "atm.CO2_sms", ))
display(plot(paleorun.output, "fluxAtmtoOceansurface.flux_CO2", (cell=1,);
ylabel="air->sea flux (mol yr-1)"))
display(plot(paleorun.output, ["global.C_total", "atm.CO2", "ocean.DIC_total"];
ylabel="atm ocean carbon (mol)", legend=:topleft))
and produces output showing an approximate modern steady state:
display(plot(paleorun.output, ["global.C_total", "atm.CO2", "ocean.DIC_total"] ; ylabel="atm-ocean carbon (mol)"))
┌ Info:
│ ================================================================================
│ create_model_from_config: configmodel Minimal_Alk_pH_AirSea
│ config_file: /home/runner/work/PALEOtutorials.jl/PALEOtutorials.jl/examples/ocean_chemistry/config_ex2.yaml
└ ================================================================================
[ Info: Model.parameters:
┌ Info:
│ ================================================================================
│ creating Domains
└ ================================================================================
[ Info: generated Reaction catalog with 74 Reactions
┌ Info:
│ ================================================================================
│ creating domain 'global' ID=1
└ ================================================================================
┌ Info: create_reaction_from_config: global.sum_C classname ReactionSum
│ set parameters: [config.yaml] vars_to_add =["ocean.DIC_total", "atm.CO2"]
│ set parameters: [Default] vars_prefix =
│ set parameters: [Default] component_to_add =0
└ set parameters: [Default] vectorsum =false
┌ Info: create_reaction_from_config: global.add_Alk classname ReactionFluxPerturb
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [config.yaml] perturb_times =[-1.0e30, 1.0e30]
│ set parameters: [config.yaml] perturb_totals =[2.66e16, 2.66e16]
│ set parameters: [Default] perturb_deltas =[0.0, 0.0]
│ set parameters: [Default] extrapolate =throw
│ set parameters: [Default] extrapolate_before =NaN
│ set parameters: [Default] extrapolate_before_delta=NaN
│ set parameters: [Default] extrapolate_after =NaN
└ set parameters: [Default] extrapolate_after_delta=NaN
┌ Info:
│ ================================================================================
│ creating domain 'ocean' ID=2
└ ================================================================================
┌ Info: create_reaction_from_config: ocean.reservoir_DIC classname ReactionReservoirTotal
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] total =true
│ set parameters: [Default] limit_delta_conc =0.0
└ set parameters: [Default] state_conc =false
┌ Info: create_reaction_from_config: ocean.constant_B classname ReactionReservoirConst
└ set parameters: [Default] field_data =PALEOboxes.ScalarData
┌ Info: create_reaction_from_config: ocean.reservoir_TA classname ReactionReservoirTotal
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] total =true
│ set parameters: [Default] limit_delta_conc =0.0
└ set parameters: [Default] state_conc =false
┌ Info: create_reaction_from_config: ocean.oceantrasport classname ReactionOceanNoTransport
│ set parameters: [config.yaml] area =[3.6e14]
└ set parameters: [config.yaml] depth =[3688.0]
┌ Info: create_reaction_from_config: ocean.solve_Alk_pH classname Reaction_Alk_pH
│ set parameters: [config.yaml] K_1 =1.4e-6
│ set parameters: [config.yaml] K_2 =1.2e-9
│ set parameters: [config.yaml] K_w =6.0e-14
└ set parameters: [config.yaml] K_B =2.5e-9
┌ Info:
│ ================================================================================
│ creating domain 'oceansurface' ID=3
└ ================================================================================
┌ Info: create_reaction_from_config: oceansurface.transfer_fluxAtmtoOceansurface classname ReactionFluxTransfer
│ set parameters: [config.yaml] input_fluxes =fluxAtmtoOceansurface.flux_$fluxname$
│ set parameters: [config.yaml] output_fluxes =ocean.oceansurface.$fluxname$_sms
│ set parameters: [Default] transfer_multiplier =1.0
└ set parameters: [config.yaml] transfer_matrix =Identity
┌ Info: create_reaction_from_config: oceansurface.solve_AirSea_Exchange classname Reaction_Min_AirSeaExchange
│ set parameters: [config.yaml] K_0 =34.0
└ set parameters: [config.yaml] vpiston =1138.8
┌ Info:
│ ================================================================================
│ creating domain 'oceanfloor' ID=4
└ ================================================================================
┌ Info:
│ ================================================================================
│ creating domain 'fluxAtmtoOceansurface' ID=5
└ ================================================================================
┌ Info: create_reaction_from_config: fluxAtmtoOceansurface.fluxtarget classname ReactionFluxTarget
│ set parameters: [config.yaml] fluxlist =["CO2"]
│ set parameters: [Default] target_prefix =flux_
│ set parameters: [config.yaml] flux_totals =true
└ set parameters: [Default] const_stub =false
┌ Info:
│ ================================================================================
│ creating domain 'atm' ID=6
└ ================================================================================
┌ Info: create_reaction_from_config: atm.transfer_AtmtoOceansurface classname ReactionFluxTransfer
│ set parameters: [config.yaml] input_fluxes =fluxAtmtoOceansurface.flux_$fluxname$
│ set parameters: [config.yaml] output_fluxes =$fluxname$_sms
│ set parameters: [config.yaml] transfer_multiplier =-1.0
└ set parameters: [config.yaml] transfer_matrix =Distribute
┌ Info: create_reaction_from_config: atm.reservoir_CO2_atm classname ReactionReservoirAtm
│ set parameters: [Default] field_data =PALEOboxes.ScalarData
│ set parameters: [Default] const =false
└ set parameters: [config.yaml] moles1atm =1.77e20
┌ Info:
│ ================================================================================
│ set_model_geometry
└ ================================================================================
[ Info: set ocean.oceansurface Subdomain size=1
[ Info: set ocean.oceanfloor Subdomain size=1
[ Info: Ocean.set_model_domains:
[ Info: ocean Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["oceansurface", "oceanfloor"])
[ Info: fluxAtmtoOceansurface Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
[ Info: oceansurface Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
[ Info: oceanfloor Domain size=1 grid=UnstructuredVectorGrid(ncells=1, cellnames=Dict{Symbol, Int64}(), subdomains: ["ocean"])
┌ Info:
│ ================================================================================
│ register_reaction_methods!
└ ================================================================================
┌ Info: register_methods: global.sum_C PALEOboxes.VariableStats.ReactionSum
│ add 1 * ocean.DIC_total
└ add 1 * atm.CO2
[ Info: ReactionFluxPerturb.register_methods! global.add_Alk
[ Info: register_methods! ReactionReservoirAtm atm.reservoir_CO2_atm field_data=PALEOboxes.ScalarData
┌ Info:
│ ================================================================================
│ link_variables: first pass
└ ================================================================================
┌ Info:
│ ================================================================================
│ link_variables: register_reaction_dynamic_methods and configure variables
└ ================================================================================
[ Info: Reaction global.sum_C Variable global.C_total adding data=PALEOboxes.ScalarData
┌ Info: _configure_variables: ReactionSum global.sum_C variable_links:
└ set variable_links: sum -> C_total
┌ Info: _configure_variables: ReactionFluxPerturb global.add_Alk variable_links:
└ set variable_links: F -> ocean.TAlk_sms
┌ Info: _configure_variables: ReactionReservoir ocean.reservoir_DIC variable_links:
│ set variable_links: R -> DIC
│ set variable_links: R_sms -> DIC_sms
│ set variable_links: R_total -> DIC_total
│ set variable_links: R_conc -> DIC_conc
│ _configure_variables: ReactionReservoir ocean.reservoir_DIC variable_attributes:
│ set attribute: R :norm_value = 2.0
└ set attribute: R :initial_value = 2.0
┌ Info: _configure_variables: ReactionReservoirConst ocean.constant_B variable_links:
│ set variable_links: R_conc -> B_conc
│ _configure_variables: ReactionReservoirConst ocean.constant_B variable_attributes:
└ set attribute: R_conc :initial_value = 0.427
┌ Info: _configure_variables: ReactionReservoir ocean.reservoir_TA variable_links:
│ set variable_links: R -> TAlk
│ set variable_links: R_sms -> TAlk_sms
│ set variable_links: R_total -> TAlk_total
│ set variable_links: R_conc -> TAlk_conc
│ _configure_variables: ReactionReservoir ocean.reservoir_TA variable_attributes:
│ set attribute: R :norm_value = 2.4
└ set attribute: R :initial_value = 0.0
┌ Info: _configure_variables: Reaction_Alk_pH ocean.solve_Alk_pH variable_links:
└ set variable_links: density -> rho_ref
┌ Info: _configure_variables: ReactionFluxTransfer oceansurface.transfer_fluxAtmtoOceansurface variable_links:
└ set variable_links: output_CO2 -> ocean.oceansurface.DIC_sms
┌ Info: _configure_variables: Reaction_Min_AirSeaExchange oceansurface.solve_AirSea_Exchange variable_links:
│ set variable_links: X_airsea_exchange -> fluxAtmtoOceansurface.flux_CO2
│ set variable_links: X_aq_conc -> ocean.oceansurface.CO2_aq_conc
│ set variable_links: area -> Asurf
└ set variable_links: pXatm -> atm.pCO2atm
┌ Info: _configure_variables: ReactionFluxTransfer atm.transfer_AtmtoOceansurface variable_links:
└ set variable_links: output_CO2 -> atm.CO2_sms
┌ Info: _configure_variables: ReactionReservoirAtm atm.reservoir_CO2_atm variable_links:
│ set variable_links: R -> CO2
│ set variable_links: R_sms -> CO2_sms
│ set variable_links: pRatm -> pCO2atm
│ set variable_links: pRnorm -> pCO2PAL
│ _configure_variables: ReactionReservoirAtm atm.reservoir_CO2_atm variable_attributes:
│ set attribute: R :norm_value = 4.956e16
└ set attribute: R :initial_value = 4.956e16
┌ Info:
│ ================================================================================
│ link_variables: second pass:
└ ================================================================================
┌ Info:
│ ================================================================================
│ link_variables! unlinked variables:
└ ================================================================================
┌ Info:
│ ================================================================================
│ create_model_from_config: done
└ ================================================================================
┌ Info:
│ ================================================================================
│ PALEOmodel.initialize! start
└ ================================================================================
┌ Info:
│ ================================================================================
│ allocate_variables! (modeldata arrays_idx=1)
└ ================================================================================
[ Info: Domain global data dimensions PALEOboxes.NamedDimension[] allocating 3 variables (hostdep=nothing)
[ Info: set :field_data=PALEOboxes.ScalarData for host-dependent Variable global.tforce
[ Info: Domain ocean data dimensions PALEOboxes.NamedDimension[] allocating 26 variables (hostdep=nothing)
[ Info: Domain oceansurface data dimensions PALEOboxes.NamedDimension[] allocating 1 variables (hostdep=nothing)
[ Info: Domain oceanfloor data dimensions PALEOboxes.NamedDimension[] allocating 3 variables (hostdep=nothing)
[ Info: Domain fluxAtmtoOceansurface data dimensions PALEOboxes.NamedDimension[] allocating 2 variables (hostdep=nothing)
[ Info: Domain atm data dimensions PALEOboxes.NamedDimension[] allocating 4 variables (hostdep=nothing)
[ Info: set_default_solver_view:
┌ Info: SolverView:
│ host-dependent Variables:
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ Domain operatorID VF_StateExplicit VF_Total VF_Constraint VF_StateTotal VF_State VF_Undefined
│ global 0 0 0 0 0 0 1
│ ocean 0 2 0 1 0 1 0
│ oceansurface 0 0 0 0 0 0 0
│ oceanfloor 0 0 0 0 0 0 0
│ fluxAtmtoOceansurface 0 0 0 0 0 0 0
│ atm 0 1 0 0 0 0 0
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ Total - 3 0 1 0 1 1
│ ------------------------------------------------------------------------------------------------------------------------------------------------
│ n_state_vars 4 (stateexplicit 3 + statetotal 0 + state 1)
│ n_equations 4 (stateexplicit 3 + total 0 + constraint 1)
│ including all host-dependent non-state Variables
└ host-dependent non-state Variables (:vfunction PB.VF_Undefined): ["global.tforce"]
┌ Info:
│ ================================================================================
│ initialize_reactiondata! (modeldata arrays_indices=1:1)
└ ================================================================================
┌ Info: prepare_do_transfer: ReactionMethod oceansurface.transfer_fluxAtmtoOceansurface.do_transfer
│ active fluxes:
│ CO2 fluxAtmtoOceansurface.flux_CO2 -> ocean.DIC_sms
└ using Identity transfer matrix * 1.0
┌ Info: prepare_do_transfer: ReactionMethod atm.transfer_AtmtoOceansurface.do_transfer
│ active fluxes:
│ CO2 fluxAtmtoOceansurface.flux_CO2 -> atm.CO2_sms
└ using Distribute transfer matrix size (input, output) (1, 1) * -1.0
┌ Info:
│ ================================================================================
│ dispatch_setup :setup
└ ================================================================================
[ Info: ocean.constant_B.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.B_conc = 0.427
┌ Info:
│ ================================================================================
│ dispatch_setup :norm_value
└ ================================================================================
[ Info: atm.reservoir_CO2_atm.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value atm.CO2 = 4.956e16
[ Info: ocean.reservoir_DIC.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value ocean.DIC = 2.0 * volume
[ Info: ocean.reservoir_TA.setup_initialvalue_vars_default:
[ Info: init_values! :norm_value ocean.TAlk = 2.4 * volume
┌ Info:
│ ================================================================================
│ dispatch_setup :initial_value
└ ================================================================================
[ Info: atm.reservoir_CO2_atm.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value atm.CO2 = 4.956e16
[ Info: ocean.reservoir_DIC.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.DIC = 2.026 * volume
[ Info: ocean.reservoir_TA.setup_initialvalue_vars_default:
[ Info: init_values! :initial_value ocean.TAlk = 2.308 * volume
┌ Info:
│ ================================================================================
│ PALEOmodel.initialize! done
└ ================================================================================
initial_state: [3.06428544e18, 2.6898796799999995e18, 4.956e16, 8.0]
initial_deriv: [0.0, -7.148754361020918e14, 7.148754361020918e14, -0.020006738816293]
integrate, DAE
┌ Info:
│ ================================================================================
│ PALEOmodel.ODE.integrateDAE:
│ tspan: (0.0, 100.0)
│ algorithm: Sundials.IDA{:Dense, Nothing, Nothing}(0, 0, 0, 5, 7, 0.33, 3, 10, 0.0033, 5, 4, 10, 100, true, false, nothing, nothing)
│ Jacobian: NoJacobian
│ using 1 BLAS threads
└ ================================================================================
[ Info: DAEfunction: using Jacobian NoJacobian
[ Info: jac_config_dae: jac_ad=NoJacobian
[ Info: calling get_inconsistent_initial_deriv
0.000232 seconds (1.76 k allocations: 63.375 KiB)
┌ Info:
│ ================================================================================
│ print_sol_stats:
│ retcode=Success
│ alg=Sundials.IDA{:Dense, Nothing, Nothing}(0, 0, 0, 5, 7, 0.33, 3, 10, 0.0033, 5, 4, 10, 100, true, false, nothing, nothing)
│ stats=SciMLBase.DEStats
│ Number of function 1 evaluations: 100
│ Number of function 2 evaluations: 0
│ Number of W matrix evaluations: 18
│ Number of linear solves: 0
│ Number of Jacobians created: 18
│ Number of nonlinear solver iterations: 98
│ Number of nonlinear solver convergence failures: 0
│ Number of fixed-point solver iterations: 0
│ Number of fixed-point solver convergence failures: 0
│ Number of rootfind condition calls: 0
│ Number of accepted steps: 70
│ Number of rejected steps: 2
│ length(sol.t) 73
│ size(sol) (4, 73)
└ ================================================================================
[ Info: ODE.calc_output_sol!: 73 records
[ Info: ODE.calc_output_sol!: done
┌ Info:
│ ================================================================================
│ PALEOmodel.ODE.integrateDAE: done
└ ================================================================================
0.003255 seconds (20.44 k allocations: 2.004 MiB)
For more information and cooperation, please communicate with us!