From 22d009a9a098f496750d82b8f4ce847efe6d14ea Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 24 Feb 2024 17:05:04 -0500 Subject: [PATCH 01/17] change abstract types, their parameters, rm DynamicsConstants --- src/SpeedyWeather.jl | 143 ++----- src/abstract_types.jl | 48 --- src/dynamics/adiabatic_conversion.jl | 33 ++ src/dynamics/atmospheres.jl | 106 ++--- src/dynamics/constants.jl | 159 -------- src/dynamics/coriolis.jl | 52 +++ src/dynamics/drag.jl | 46 ++- src/dynamics/forcing.jl | 32 +- src/dynamics/geometry.jl | 163 ++++++++ src/dynamics/geopotential.jl | 167 ++------ src/dynamics/hole_filling.jl | 14 +- src/dynamics/horizontal_diffusion.jl | 22 +- src/dynamics/implicit.jl | 56 ++- src/dynamics/initial_conditions.jl | 16 +- src/dynamics/models.jl | 371 ------------------ src/dynamics/orography.jl | 14 +- src/dynamics/planets.jl | 24 +- src/dynamics/prognostic_variables.jl | 6 +- src/dynamics/spectral_grid.jl | 114 +----- src/dynamics/tendencies.jl | 70 ++-- src/dynamics/time_integration.jl | 12 +- src/dynamics/vertical_coordinates.jl | 10 +- src/dynamics/vertical_interpolation.jl | 54 --- src/dynamics/virtual_temperature.jl | 102 +++++ src/models/abstract_models.jl | 33 ++ src/models/barotropic.jl | 79 ++++ src/models/primitive_dry.jl | 101 +++++ src/models/primitive_wet.jl | 115 ++++++ src/models/shallow_water.jl | 66 ++++ src/models/simulation.jl | 52 +++ src/output/feedback.jl | 3 + src/output/output.jl | 53 +-- .../{pretty_printing.jl => abstract_types.jl} | 4 +- src/physics/albedo.jl | 1 + src/physics/boundary_layer.jl | 40 +- src/physics/column_variables.jl | 32 +- src/physics/convection.jl | 13 +- src/physics/define_column.jl | 5 +- src/physics/large_scale_condensation.jl | 24 +- src/physics/shortwave_radiation.jl | 5 +- src/physics/surface_fluxes.jl | 49 +-- src/physics/temperature_relaxation.jl | 47 +-- src/physics/tendencies.jl | 36 +- src/physics/thermodynamics.jl | 50 +-- src/physics/vertical_diffusion.jl | 7 + src/run_speedy.jl | 25 -- src/utility_functions.jl | 1 + 47 files changed, 1266 insertions(+), 1409 deletions(-) delete mode 100644 src/abstract_types.jl create mode 100644 src/dynamics/adiabatic_conversion.jl delete mode 100644 src/dynamics/constants.jl create mode 100644 src/dynamics/coriolis.jl create mode 100644 src/dynamics/geometry.jl delete mode 100644 src/dynamics/models.jl delete mode 100644 src/dynamics/vertical_interpolation.jl create mode 100644 src/dynamics/virtual_temperature.jl create mode 100644 src/models/abstract_models.jl create mode 100644 src/models/barotropic.jl create mode 100644 src/models/primitive_dry.jl create mode 100644 src/models/primitive_wet.jl create mode 100644 src/models/shallow_water.jl create mode 100644 src/models/simulation.jl rename src/physics/{pretty_printing.jl => abstract_types.jl} (55%) create mode 100644 src/physics/albedo.jl delete mode 100644 src/run_speedy.jl diff --git a/src/SpeedyWeather.jl b/src/SpeedyWeather.jl index 9a41ef48c..f85089611 100644 --- a/src/SpeedyWeather.jl +++ b/src/SpeedyWeather.jl @@ -30,39 +30,16 @@ import ProgressMeter # to avoid a `using Dates` to pass on DateTime arguments export DateTime, Second, Minute, Hour, Day -# EXPORT MONOLITHIC INTERFACE TO SPEEDY -export run_speedy, - run_speedy!, - initialize_speedy, - initialize!, - run! - -export NoVerticalCoordinates, - SigmaCoordinates, - SigmaPressureCoordinates - -# EXPORT MODELS -export Barotropic, # abstract - ShallowWater, - PrimitiveEquation, - PrimitiveDry, - PrimitiveWet, - ModelSetup - -export BarotropicModel, # concrete - ShallowWaterModel, - PrimitiveDryModel, - PrimitiveWetModel - -export Earth, - EarthAtmosphere - -# EXPORT GRIDS -export SpectralGrid, - Geometry - -export LowerTriangularMatrix, - FullClenshawGrid, +include("utility_functions.jl") + +# LowerTriangularMatrices for spherical harmonics +export LowerTriangularMatrices, LowerTriangularMatrix +include("LowerTriangularMatrices/LowerTriangularMatrices.jl") +using .LowerTriangularMatrices + +# RingGrids +export RingGrids +export FullClenshawGrid, FullGaussianGrid, FullHEALPixGrid, FullOctaHEALPixGrid, @@ -72,84 +49,12 @@ export LowerTriangularMatrix, OctaHEALPixGrid, plot -export Leapfrog - -# EXPORT OROGRAPHIES -export NoOrography, - EarthOrography, - ZonalRidge - -# NUMERICS -export HyperDiffusion, - ImplicitShallowWater, - ImplicitPrimitiveEq - -# EXPORT INITIAL CONDITIONS -export StartFromFile, - StartFromRest, - ZonalJet, - ZonalWind, - StartWithRandomVorticity - -# EXPORT TEMPERATURE RELAXATION SCHEMES -export NoTemperatureRelaxation, - HeldSuarez, - JablonowskiRelaxation - -# EXPORT BOUNDARY LAYER SCHEMES -export NoBoundaryLayerDrag, - LinearDrag, - QuadraticDrag - -# EXPORT FORCING -export forcing!, - JetStreamForcing, - AbstractForcing - -# EXPORT DRAG -export drag!, - AbstractDrag - -# EXPORT VERTICAL DIFFUSION -export NoVerticalDiffusion, - VerticalLaplacian - -# PRECIPITATOIN -export SpeedyCondensation, - SpeedyConvection - -# EXPORT STRUCTS -export DynamicsConstants, - SpectralTransform, - Boundaries, - PrognosticVariables, - PrognosticVariablesLayer, - DiagnosticVariables, - DiagnosticVariablesLayer, - ColumnVariables - -# EXPORT SPECTRAL FUNCTIONS -export SpectralTransform, - spectral, - gridded, - spectral_truncation - -export OutputWriter, Feedback - -include("utility_functions.jl") - -# LowerTriangularMatrices for spherical harmonics -export LowerTriangularMatrices -include("LowerTriangularMatrices/LowerTriangularMatrices.jl") -using .LowerTriangularMatrices - -# RingGrids -export RingGrids include("RingGrids/RingGrids.jl") using .RingGrids # SpeedyTransforms -export SpeedyTransforms +export SpeedyTransforms, SpectralTransform +export spectral, gridded, spectral_truncation include("SpeedyTransforms/SpeedyTransforms.jl") using .SpeedyTransforms @@ -157,13 +62,13 @@ using .SpeedyTransforms include("gpu.jl") # GEOMETRY CONSTANTS ETC -include("abstract_types.jl") +include("models/abstract_models.jl") include("dynamics/vertical_coordinates.jl") include("dynamics/spectral_grid.jl") -include("dynamics/vertical_interpolation.jl") +include("dynamics/geometry.jl") include("dynamics/planets.jl") include("dynamics/atmospheres.jl") -include("dynamics/constants.jl") +include("dynamics/adiabatic_conversion.jl") include("dynamics/orography.jl") include("physics/land_sea_mask.jl") @@ -186,6 +91,8 @@ include("dynamics/tendencies.jl") include("dynamics/hole_filling.jl") # PARAMETERIZATIONS +include("physics/abstract_types.jl") +include("physics/albedo.jl") include("physics/tendencies.jl") include("physics/column_variables.jl") include("physics/thermodynamics.jl") @@ -198,20 +105,20 @@ include("physics/convection.jl") include("physics/zenith.jl") include("physics/shortwave_radiation.jl") include("physics/longwave_radiation.jl") -include("physics/pretty_printing.jl") # OCEAN AND LAND include("physics/ocean.jl") include("physics/land.jl") -# MODELS -include("dynamics/models.jl") - # OUTPUT -include("output/output.jl") # defines Output -include("output/feedback.jl") # defines Feedback +include("output/output.jl") +include("output/feedback.jl") include("output/plot.jl") -# INTERFACE -include("run_speedy.jl") +# MODELS +include("models/simulation.jl") +include("models/barotropic.jl") +include("models/shallow_water.jl") +include("models/primitive_dry.jl") +include("models/primitive_wet.jl") end \ No newline at end of file diff --git a/src/abstract_types.jl b/src/abstract_types.jl deleted file mode 100644 index f9ef76f88..000000000 --- a/src/abstract_types.jl +++ /dev/null @@ -1,48 +0,0 @@ -# MODELS -abstract type AbstractSimulation{Model} end -abstract type ModelSetup end -abstract type Barotropic <: ModelSetup end -abstract type ShallowWater <: ModelSetup end -abstract type PrimitiveEquation <: ModelSetup end -abstract type PrimitiveDry <: PrimitiveEquation end -abstract type PrimitiveWet <: PrimitiveEquation end - -abstract type AbstractPlanet end -abstract type AbstractAtmosphere end - -# GEOMETRY, GRID -abstract type AbstractGeometry{NF} end -abstract type VerticalCoordinates end - -# CONSTANTS (face the dynamical core and not the user) -abstract type AbstractDynamicsConstants{NF} end - -# INITIAL CONDITIONS AND OROGRAPHY/BOUNDARIES -abstract type InitialConditions end -abstract type AbstractOrography{NF,Grid} end -abstract type AbstractAlbedo{NF,Grid} end - -# ATMOSPHERIC COLUMN FOR PARAMETERIZATIONS -abstract type AbstractColumnVariables{NF} end - -# FORCING and DRAG (Barotropic and ShallowWaterModel) -abstract type AbstractForcing{NF} end -abstract type AbstractDrag{NF} end - -# PARAMETERIZATIONS -abstract type AbstractParameterization{NF} end -abstract type TemperatureRelaxation{NF} <: AbstractParameterization{NF} end -abstract type VerticalDiffusion{NF} <: AbstractParameterization{NF} end -abstract type AbstractSurfaceWind{NF} <: AbstractParameterization{NF} end -abstract type AbstractSurfaceThermodynamics{NF} <: AbstractParameterization{NF} end -abstract type AbstractSurfaceHeat{NF} <: AbstractParameterization{NF} end -abstract type AbstractEvaporation{NF} <: AbstractParameterization{NF} end - -# INPUT/OUTPUT -abstract type AbstractFeedback end -abstract type AbstractOutputWriter end - -# NUMERICS -abstract type HorizontalDiffusion{NF} end -abstract type AbstractImplicit{NF} end -abstract type TimeStepper{NF} end \ No newline at end of file diff --git a/src/dynamics/adiabatic_conversion.jl b/src/dynamics/adiabatic_conversion.jl new file mode 100644 index 000000000..2b60aa10d --- /dev/null +++ b/src/dynamics/adiabatic_conversion.jl @@ -0,0 +1,33 @@ +abstract type AbstractAdiabaticConversion <: AbstractModelComponent end + +Base.@kwdef struct AdiabaticConversion{NF} <: AbstractAdiabaticConversion + nlev::Int + + "σ-related factor A needed for adiabatic conversion term" + σ_lnp_A::Vector{NF} = zeros(NF,nlev) + + "σ-related factor B needed for adiabatic conversion term" + σ_lnp_B::Vector{NF} = zeros(NF,nlev) +end + +AdiabaticConversion(SG::SpectralGrid;kwargs...) = AdiabaticConversion{SG.NF}(;nlev=SG.nlev;kwargs...) + +function initialize!( + adiabatic::AdiabaticConversion, + model::PrimitiveEquation, +) + (;σ_lnp_A, σ_lnp_B) = adiabatic + + # ADIABATIC TERM, Simmons and Burridge, 1981, eq. 3.12 + (;σ_levels_half, σ_levels_full, σ_levels_thick) = model.geometry + # precompute ln(σ_k+1/2) - ln(σ_k-1/2) but swap sign, include 1/Δσₖ + σ_lnp_A .= log.(σ_levels_half[1:end-1]./σ_levels_half[2:end]) ./ σ_levels_thick + σ_lnp_A[1] = 0 # the corresponding sum is 1:k-1 so 0 to replace log(0) from above + + # precompute the αₖ = 1 - p_k-1/2/Δpₖ*log(p_k+1/2/p_k-1/2) term in σ coordinates + σ_lnp_B .= 1 .- σ_levels_half[1:end-1]./σ_levels_thick .* + log.(σ_levels_half[2:end]./σ_levels_half[1:end-1]) + σ_lnp_B[1] = σ_levels_half[1] <= 0 ? log(2) : σ_lnp_B[1] # set α₁ = log(2), eq. 3.19 + σ_lnp_B .*= -1 # absorb sign from -1/Δσₖ only, eq. 3.12 + return nothing +end \ No newline at end of file diff --git a/src/dynamics/atmospheres.jl b/src/dynamics/atmospheres.jl index e43da9169..67b4e0945 100644 --- a/src/dynamics/atmospheres.jl +++ b/src/dynamics/atmospheres.jl @@ -1,82 +1,88 @@ +abstract type AbstractAtmosphere <: AbstractModelComponent end +export EarthAtmosphere + """ $(TYPEDSIGNATURES) -Create a struct `EarthAtmosphere<:AbstractPlanet`, with the following physical/chemical -characteristics. Note that `radius` is not part of it as this should be chosen -in `SpectralGrid`. Keyword arguments are +Create a struct `EarthAtmosphere <: AbstractAtmosphere`, with the following physical/chemical +characteristics. Keyword arguments are $(TYPEDFIELDS)""" -Base.@kwdef struct EarthAtmosphere <: AbstractAtmosphere - # ATMOSPHERE +Base.@kwdef struct EarthAtmosphere{NF<:AbstractFloat} <: AbstractAtmosphere "molar mass of dry air [g/mol]" - mol_mass_dry_air::Float64 = 28.9649 + mol_mass_dry_air::NF = 28.9649 "molar mass of water vapour [g/mol]" - mol_mass_vapour::Float64 = 18.0153 + mol_mass_vapour::NF = 18.0153 - "specific heat at constant pressure [J/K/kg]" - cₚ::Float64 = 1004 + "specific heat at constant pressure cₚ [J/K/kg]" + heat_capacity::NF = 1004 "universal gas constant [J/K/mol]" - R_gas::Float64 = 8.3145 + R_gas::NF = 8.3145 "specific gas constant for dry air [J/kg/K]" - R_dry::Float64 = 1000*R_gas/mol_mass_dry_air + R_dry::NF = 1000*R_gas/mol_mass_dry_air "specific gas constant for water vapour [J/kg/K]" - R_vapour::Float64 = 1000*R_gas/mol_mass_vapour + R_vapour::NF = 1000*R_gas/mol_mass_vapour + + "Ratio of gas constants: dry air / water vapour, often called ε [1]" + mol_ratio::NF = R_dry/R_vapour + + "Virtual temperature Tᵥ calculation, Tᵥ = T(1 + μ*q), humidity q, absolute tempereature T" + μ_virt_temp::NF = (1-mol_ratio)/mol_ratio + + "= R_dry/cₚ, gas const for air over heat capacity" + κ::NF = R_dry/cₚ "water density [kg/m³]" - water_density::Float64 = 1000 + water_density::NF = 1000 - "latent heat of condensation [J/kg] for consistency with specific humidity [kg/kg], also called alhc" - latent_heat_condensation::Float64 = 2501e3 + "latent heat of condensation [J/kg] for consistency with specific humidity [kg/kg]" + latent_heat_condensation::NF = 2501e3 - "latent heat of sublimation [J/kg], also called alhs" - latent_heat_sublimation::Float64 = 2801e3 + "latent heat of sublimation [J/kg]" + latent_heat_sublimation::NF = 2801e3 "stefan-Boltzmann constant [W/m²/K⁴]" - stefan_boltzmann::Float64 = 5.67e-8 + stefan_boltzmann::NF = 5.67e-8 - # STANDARD ATMOSPHERE (reference values) - "moist adiabatic temperature lapse rate ``-dT/dz`` [K/km]" - lapse_rate::Float64 = 5 + "surface reference pressure [Pa]" + pres_ref::NF = 1e5 - "absolute temperature at surface ``z=0`` [K]" - temp_ref::Float64 = 288 + "layer thickness for the shallow water model [m]" + layer_thickness::NF = 8500 +end - "absolute temperature in stratosphere [K]" - temp_top::Float64 = 216 +EarthAtmosphere(;kwargs...) = EarthAtmosphere{DEFAULT_NF}(;kwargs...) +EarthAtmosphere(SG::SpectralGrid;kwargs...) = EarthAtmosphere{SG.NF}(;kwargs...) - "for stratospheric lapse rate [K] after Jablonowski" - ΔT_stratosphere::Float64 = 4.8e5 +# "scale height for specific humidity [km]" +# scale_height_humid::NF = 2.5 - "start of the stratosphere in sigma coordinates" - σ_tropopause::Float64 = 0.2 +# "relative humidity of near-surface air [1]" +# relhumid_ref::NF = 0.7 - "top of the planetary boundary layer in sigma coordinates" - σ_boundary_layer::Float64 = 0.93 +# "saturation water vapour pressure [Pa]" +# water_pres_ref::NF = 17 - "scale height for pressure [km]" - scale_height::Float64 = 7.5 +# # STANDARD ATMOSPHERE (reference values) +# "moist adiabatic temperature lapse rate ``-dT/dz`` [K/km]" +# lapse_rate::NF = 5 - "surface pressure [hPa]" - pres_ref::Float64 = 1000 +# "absolute temperature at surface ``z=0`` [K]" +# temp_ref::NF = 288 - "scale height for specific humidity [km]" - scale_height_humid::Float64 = 2.5 +# "absolute temperature in stratosphere [K]" +# temp_top::NF = 216 - "relative humidity of near-surface air [1]" - relhumid_ref::Float64 = 0.7 +# "for stratospheric lapse rate [K] after Jablonowski" +# ΔT_stratosphere::NF = 4.8e5 - "saturation water vapour pressure [Pa]" - water_pres_ref::Float64 = 17 +# "start of the stratosphere in sigma coordinates" +# σ_tropopause::NF = 0.2 - # TODO maybe make this actually part of the spectral grid? - "layer thickness for the shallow water model [km]" - layer_thickness::Float64 = 8.5 -end +# "top of the planetary boundary layer in sigma coordinates" +# σ_boundary_layer::NF = 0.93 -function Base.show(io::IO,atm::AbstractAtmosphere) - println(io,"$(typeof(atm))") - keys = propertynames(atm) - print_fields(io,atm,keys) -end \ No newline at end of file +# "scale height for pressure [km]" +# scale_height::NF = 7.5 \ No newline at end of file diff --git a/src/dynamics/constants.jl b/src/dynamics/constants.jl deleted file mode 100644 index 88814b448..000000000 --- a/src/dynamics/constants.jl +++ /dev/null @@ -1,159 +0,0 @@ -""" -Struct holding constants needed at runtime for the dynamical core in number format NF. -$(TYPEDFIELDS)""" -Base.@kwdef struct DynamicsConstants{NF<:AbstractFloat} <: AbstractDynamicsConstants{NF} - # PHYSICAL CONSTANTS - "Radius of Planet [m]" - radius::NF - - "Angular frequency of Planet's rotation [s^-1]" - rotation::NF - - "Gravitational acceleration [m/s^2]" - gravity::NF - - "shallow water layer thickness [m]" - layer_thickness::NF - - # THERMODYNAMICS - "specific gas constant for dry air [J/kg/K]" - R_dry::NF - - "specific gas constant for water vapour [J/kg/K]" - R_vapour::NF - - "used in Tv = T(1+μq) for virt temp Tv(T,q) calculation" - μ_virt_temp::NF - - "specific heat at constant pressure [J/K/kg]" - cₚ::NF - - "= R_dry/cₚ, gas const for air over heat capacity" - κ::NF - - "water density [kg/m³]" - water_density::NF - - "reference pressure [Pa]" - pres_ref::NF - - "coriolis frequency [s^-1], scaled by radius as is vorticity = 2Ω*sin(lat)*radius" - f_coriolis::Vector{NF} - - # ADIABATIC TERM - "σ-related factor A needed for adiabatic conversion term" - σ_lnp_A::Vector{NF} - - "σ-related factor B needed for adiabatic conversion term" - σ_lnp_B::Vector{NF} - - # GEOPOTENTIAL INTEGRATION (on half/full levels) - "= R*(ln(p_k+1) - ln(p_k+1/2)), for half level geopotential" - Δp_geopot_half::Vector{NF} - - "= R*(ln(p_k+1/2) - ln(p_k)), for full level geopotential" - Δp_geopot_full::Vector{NF} - - # REFERENCE TEMPERATURE PROFILE for implicit - "reference temperature profile" - temp_ref_profile::Vector{NF} -end - -function Base.show(io::IO,C::DynamicsConstants) - println(io,"$(typeof(C)) <: AbstractDynamicsConstants") - keys = propertynames(C) - print_fields(io,C,keys) -end - -""" -$(TYPEDSIGNATURES) -Generator function for a DynamicsConstants struct. -""" -function DynamicsConstants( spectral_grid::SpectralGrid, - planet::AbstractPlanet, - atmosphere::AbstractAtmosphere, - geometry::Geometry) - - # PHYSICAL CONSTANTS - (;R_dry, R_vapour, lapse_rate, cₚ, water_density) = atmosphere - (;ΔT_stratosphere, σ_tropopause, temp_ref) = atmosphere - (;NF, radius) = spectral_grid - (;rotation, gravity) = planet - layer_thickness = atmosphere.layer_thickness*1000 # ShallowWater: convert from [km] to [m] - ξ = R_dry/R_vapour # Ratio of gas constants: dry air / water vapour [1] - μ_virt_temp = (1-ξ)/ξ # used in Tv = T(1+μq), for conversion from humidity q - # and temperature T to virtual temperature Tv - κ = R_dry/cₚ # = 2/7ish for diatomic gas - pres_ref = atmosphere.pres_ref*100 - - # CORIOLIS FREQUENCY (scaled by radius as is vorticity) - (;sinlat) = geometry - f_coriolis = 2rotation*sinlat*radius - - # ADIABATIC TERM, Simmons and Burridge, 1981, eq. 3.12 - (;σ_levels_half,σ_levels_full,σ_levels_thick) = geometry - # precompute ln(σ_k+1/2) - ln(σ_k-1/2) but swap sign, include 1/Δσₖ - σ_lnp_A = log.(σ_levels_half[1:end-1]./σ_levels_half[2:end]) ./ σ_levels_thick - σ_lnp_A[1] = 0 # the corresponding sum is 1:k-1 so 0 to replace log(0) from above - - # precompute the αₖ = 1 - p_k-1/2/Δpₖ*log(p_k+1/2/p_k-1/2) term in σ coordinates - σ_lnp_B = 1 .- σ_levels_half[1:end-1]./σ_levels_thick .* - log.(σ_levels_half[2:end]./σ_levels_half[1:end-1]) - σ_lnp_B[1] = σ_levels_half[1] <= 0 ? log(2) : σ_lnp_B[1] # set α₁ = log(2), eq. 3.19 - σ_lnp_B .*= -1 # absorb sign from -1/Δσₖ only, eq. 3.12 - - # GEOPOTENTIAL - Δp_geopot_half, Δp_geopot_full = initialize_geopotential(σ_levels_full,σ_levels_half,R_dry) - - # VERTICAL REFERENCE TEMPERATURE PROFILE (TODO: don't initialize here but in initalize! ?) - # integrate hydrostatic equation from pₛ to p, use ideal gas law p = ρRT and linear - # temperature decrease with height: T = Tₛ - ΔzΓ with lapse rate Γ - # for stratosphere (σ < σ_tropopause) increase temperature (Jablonowski & Williamson. 2006, eq. 5) - RΓg⁻¹ = R_dry*lapse_rate/(1000*gravity) # convert lapse rate from [K/km] to [K/m] - temp_ref_profile = [temp_ref*σ^RΓg⁻¹ for σ in σ_levels_full] - temp_ref_profile .+= [σ < σ_tropopause ? ΔT_stratosphere*(σ_tropopause-σ)^5 : 0 for σ in σ_levels_full] - - # This implies conversion to NF - return DynamicsConstants{NF}(; radius,rotation,gravity,layer_thickness, - R_dry,R_vapour,μ_virt_temp,cₚ,κ,water_density,pres_ref, - f_coriolis, - σ_lnp_A,σ_lnp_B, - Δp_geopot_half, Δp_geopot_full, - temp_ref_profile) -end - -# default angular frequency of Earth's rotation [1/s] -const DEFAULT_ROTATION = 7.29e-5 - -""" -$(TYPEDSIGNATURES) -Return the Coriolis parameter `f` on the grid `Grid` of resolution `nlat_half` -on a planet of `ratation` [1/s]. Default rotation of Earth.""" -function coriolis( - ::Type{Grid}, - nlat_half::Integer; - rotation=DEFAULT_ROTATION -) where {Grid<:AbstractGrid} - - f = zeros(Grid,nlat_half) - lat = get_lat(Grid,nlat_half) - - for (j,ring) in enumerate(eachring(f)) - fⱼ = 2rotation*sin(lat[j]) - for ij in ring - f[ij] = fⱼ - end - end - return f -end - -""" -$(TYPEDSIGNATURES) -Return the Coriolis parameter `f` on a grid like `grid` -on a planet of `ratation` [1/s]. Default rotation of Earth.""" -function coriolis( - grid::Grid; - rotation=DEFAULT_ROTATION -) where {Grid<:AbstractGrid} - return coriolis(Grid,grid.nlat_half;rotation) -end \ No newline at end of file diff --git a/src/dynamics/coriolis.jl b/src/dynamics/coriolis.jl new file mode 100644 index 000000000..d5e1377c5 --- /dev/null +++ b/src/dynamics/coriolis.jl @@ -0,0 +1,52 @@ +abstract type AbstractCoriolis <: AbstractModelComponent end + +Base.@kwdef struct Coriolis{NF} <: AbstractCoriolis + "number of latitude rings" + nlat::Int + + "coriolis frequency [s^-1], scaled by radius as is vorticity = 2Ω*sin(lat)*radius" + f::Vector{NF} = zeros(NF,nlat) +end + +Coriolis(SG::SpectralGrid;kwargs...) = Coriolis{SG.NF}(nlat=SG.nlat;kwargs...) + +function initialize!(coriolis::Coriolis, model::ModelSetup) + (;rotation) = model.planet + (;sinlat, radius) = model.geometry + + # =2Ωsin(lat) but scaled with radius as are the equations + coriolis.f .= 2rotation * sinlat * radius +end + +""" +$(TYPEDSIGNATURES) +Return the Coriolis parameter `f` on the grid `Grid` of resolution `nlat_half` +on a planet of `ratation` [1/s]. Default rotation of Earth.""" +function coriolis( + ::Type{Grid}, + nlat_half::Integer; + rotation=DEFAULT_ROTATION +) where {Grid<:AbstractGrid} + + f = zeros(Grid,nlat_half) + lat = get_lat(Grid,nlat_half) + + for (j,ring) in enumerate(eachring(f)) + fⱼ = 2rotation*sin(lat[j]) + for ij in ring + f[ij] = fⱼ + end + end + return f +end + +""" +$(TYPEDSIGNATURES) +Return the Coriolis parameter `f` on a grid like `grid` +on a planet of `ratation` [1/s]. Default rotation of Earth.""" +function coriolis( + grid::Grid; + rotation=DEFAULT_ROTATION +) where {Grid<:AbstractGrid} + return coriolis(Grid,grid.nlat_half;rotation) +end \ No newline at end of file diff --git a/src/dynamics/drag.jl b/src/dynamics/drag.jl index 629f7812d..8dc51b04d 100644 --- a/src/dynamics/drag.jl +++ b/src/dynamics/drag.jl @@ -1,17 +1,10 @@ -function Base.show(io::IO,F::AbstractDrag) - println(io,"$(typeof(F)) <: AbstractDrag") - keys = propertynames(F) - print_fields(io,F,keys) -end +abstract type AbstractDrag <: AbstractModelComponent end ## NO DRAG -struct NoDrag{NF} <: AbstractDrag{NF} end -NoDrag(SG::SpectralGrid) = NoDrag{SG.NF}() - -function initialize!( drag::NoDrag, - model::ModelSetup) - return nothing -end +export NoDrag +struct NoDrag <: AbstractDrag end +NoDrag(SG::SpectralGrid) = NoDrag() +initialize!(::NoDrag,::ModelSetup) = nothing function drag!( diagn::DiagnosticVariablesLayer, progn::PrognosticVariablesLayer, @@ -22,16 +15,21 @@ function drag!( diagn::DiagnosticVariablesLayer, end # Quadratic drag -Base.@kwdef struct QuadraticDrag{NF} <: AbstractDrag{NF} - "drag coefficient [1]" +export QuadraticDrag +Base.@kwdef struct QuadraticDrag{NF} <: AbstractDrag + "[OPTION] drag coefficient [1]" c_D::NF = 1e-5 + + "drag coefficient divided by layer thickness H, scaled with radius R [1]" + c::Base.RefValue{NF} = Ref(zero(NF)) end QuadraticDrag(SG::SpectralGrid;kwargs...) = QuadraticDrag{SG.NF}(;kwargs...) function initialize!( drag::QuadraticDrag, model::ModelSetup) - return nothing + # c = c_D / H * R + drag.c[] = drag.c_D / model.atmosphere.layer_thickness * model.geometry.radius end # function barrier @@ -40,7 +38,7 @@ function drag!( diagn::DiagnosticVariablesLayer, drag::QuadraticDrag, time::DateTime, model::ModelSetup) - drag!(diagn,drag,model.constants) + drag!(diagn, drag) end """ @@ -49,13 +47,13 @@ Quadratic drag for the momentum equations. F = -c_D/H*|(u,v)|*(u,v) -with c_D the non-dimensional drag coefficient -as defined in `drag::QuadraticDrag`. Layer thickness `H` -is taken from the `Atmosphere` via `DynamicsConstants`, -as defined for the `ShallowWaterModel`.""" -function drag!( diagn::DiagnosticVariablesLayer, - drag::QuadraticDrag{NF}, - C::DynamicsConstants) where NF +with c_D the non-dimensional drag coefficient as defined in `drag::QuadraticDrag`. +c_D and layer thickness `H` are precomputed in intialize!(::QuadraticDrag,::ModelSetup) +and scaled by the radius as are the momentum equations.""" +function drag!( + diagn::DiagnosticVariablesLayer, + drag::QuadraticDrag{NF}, +) where NF u = diagn.grid_variables.u_grid v = diagn.grid_variables.v_grid @@ -64,7 +62,7 @@ function drag!( diagn::DiagnosticVariablesLayer, Fv = diagn.tendencies.v_tend_grid # total drag coefficient with radius scaling and /layer_thickness - c = convert(NF,drag.c_D/C.layer_thickness*C.radius) + c = drag.c[] @inbounds for ij in eachgridpoint(u,v,Fu,Fv) speed = sqrt(u[ij]^2 + v[ij]^2) diff --git a/src/dynamics/forcing.jl b/src/dynamics/forcing.jl index 9f4d194d8..666c7c779 100644 --- a/src/dynamics/forcing.jl +++ b/src/dynamics/forcing.jl @@ -1,18 +1,11 @@ -function Base.show(io::IO,F::AbstractForcing) - println(io,"$(typeof(F)) <: AbstractForcing") - keys = propertynames(F) - print_fields(io,F,keys) -end - -## NO FORCING -struct NoForcing{NF} <: AbstractForcing{NF} end -NoForcing(SG::SpectralGrid) = NoForcing{SG.NF}() +abstract type AbstractForcing <: AbstractModelComponent end -function initialize!( forcing::NoForcing, - model::ModelSetup) - return nothing -end +## NO FORCING = dummy forcing +export NoForcing +struct NoForcing <: AbstractForcing end +NoForcing(SG::SpectralGrid) = NoForcing() +initialize!(::NoForcing,::ModelSetup) = nothing function forcing!( diagn::DiagnosticVariablesLayer, progn::PrognosticVariablesLayer, forcing::NoForcing, @@ -22,6 +15,7 @@ function forcing!( diagn::DiagnosticVariablesLayer, end # JET STREAM FORCING FOR SHALLOW WATER +export JetStreamForcing """ Forcing term for the Barotropic or ShallowWaterModel with an @@ -30,18 +24,18 @@ Galewsky, 2004, but mirrored for both hemispheres. $(TYPEDFIELDS) """ -Base.@kwdef struct JetStreamForcing{NF} <: AbstractForcing{NF} +Base.@kwdef struct JetStreamForcing{NF} <: AbstractForcing "Number of latitude rings" nlat::Int = 0 "jet latitude [˚N]" - latitude::Float64 = 45 + latitude::NF = 45 "jet width [˚], default ≈ 19.29˚" - width::Float64 = (1/4-1/7)*180 + width::NF = (1/4-1/7)*180 "jet speed scale [m/s]" - speed::Float64 = 85 + speed::NF = 85 "time scale [days]" time_scale::Second = Day(30) @@ -51,7 +45,7 @@ Base.@kwdef struct JetStreamForcing{NF} <: AbstractForcing{NF} end JetStreamForcing(SG::SpectralGrid;kwargs...) = JetStreamForcing{SG.NF}( - ;nlat=RingGrids.get_nlat(SG.Grid,SG.nlat_half),kwargs...) + ;nlat=SG.nlat,kwargs...) function initialize!( forcing::JetStreamForcing, model::ModelSetup) @@ -88,7 +82,7 @@ function forcing!( diagn::DiagnosticVariablesLayer, forcing::JetStreamForcing, time::DateTime, model::ModelSetup) - forcing!(diagn,forcing) + forcing!(diagn, forcing) end """ diff --git a/src/dynamics/geometry.jl b/src/dynamics/geometry.jl new file mode 100644 index 000000000..1eb75a259 --- /dev/null +++ b/src/dynamics/geometry.jl @@ -0,0 +1,163 @@ +abstract type AbstractGeometry end +export Geometry + +""" +$(TYPEDSIGNATURES) +Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid +and the vertical levels. Pass on `SpectralGrid` to calculate the following fields +$(TYPEDFIELDS) +""" +Base.@kwdef struct Geometry{NF<:AbstractFloat} <: AbstractGeometry + + "SpectralGrid that defines spectral and grid resolution" + spectral_grid::SpectralGrid + + "grid of the dynamical core" + Grid::Type{<:AbstractGrid} = spectral_grid.Grid + + "resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)" + nlat_half::Int = spectral_grid.nlat_half + + + # GRID-POINT SPACE + "maximum number of longitudes (at/around Equator)" + nlon_max::Int = get_nlon_max(Grid,nlat_half) + + "=nlon_max, same (used for compatibility), TODO: still needed?" + nlon::Int = nlon_max + + "number of latitude rings" + nlat::Int = get_nlat(Grid,nlat_half) + + "number of vertical levels" + nlev::Int = spectral_grid.nlev + + "total number of grid points" + npoints::Int = spectral_grid.npoints + + "Planet's radius [m]" + radius::NF = spectral_grid.radius + + + # ARRAYS OF LANGITUDES/LONGITUDES + "array of colatitudes in radians (0...π)" + colat::Vector{Float64} = get_colat(Grid,nlat_half) + + "array of latitudes in radians (π...-π)" + lat::Vector{NF} = get_lat(Grid,nlat_half) + + "array of latitudes in degrees (90˚...-90˚)" + latd::Vector{Float64} = get_latd(Grid,nlat_half) + + "array of longitudes in degrees (0...360˚), empty for non-full grids" + lond::Vector{Float64} = get_lond(Grid,nlat_half) + + "longitude (0˚...360˚) for each grid point in ring order" + londs::Vector{NF} = get_latdlonds(Grid,nlat_half)[2] + + "latitude (-90˚...˚90) for each grid point in ring order" + latds::Vector{NF} = get_latdlonds(Grid,nlat_half)[1] + + "longitude (0...2π) for each grid point in ring order" + lons::Vector{NF} = RingGrids.get_latlons(Grid,nlat_half)[2] + + "latitude (-π/2...π/2) for each grid point in ring order" + lats::Vector{NF} = RingGrids.get_latlons(Grid,nlat_half)[1] + + "sin of latitudes" + sinlat::Vector{NF} = sind.(latd) + + "cos of latitudes" + coslat::Vector{NF} = cosd.(latd) + + "= 1/cos(lat)" + coslat⁻¹::Vector{NF} = 1 ./ coslat + + "= cos²(lat)" + coslat²::Vector{NF} = coslat.^2 + + "# = 1/cos²(lat)" + coslat⁻²::Vector{NF} = 1 ./ coslat² + + # VERTICAL SIGMA COORDINATE σ = p/p0 (fraction of surface pressure) + "σ at half levels, σ_k+1/2" + σ_levels_half::Vector{NF} = spectral_grid.vertical_coordinates.σ_half + + "σ at full levels, σₖ" + σ_levels_full::Vector{NF} = 0.5*(σ_levels_half[2:end] + σ_levels_half[1:end-1]) + + "σ level thicknesses, σₖ₊₁ - σₖ" + σ_levels_thick::Vector{NF} = σ_levels_half[2:end] - σ_levels_half[1:end-1] + + "log of σ at full levels, include surface (σ=1) as last element" + ln_σ_levels_full::Vector{NF} = log.(vcat(σ_levels_full,1)) + + "Full to half levels interpolation" + full_to_half_interpolation::Vector{NF} = σ_interpolation_weights(σ_levels_full,σ_levels_half) +end + +""" +$(TYPEDSIGNATURES) +Generator function for `Geometry` struct based on `spectral_grid`.""" +function Geometry(spectral_grid::SpectralGrid) + return Geometry{spectral_grid.NF}(;spectral_grid) +end + +function Base.show(io::IO,G::Geometry) + print(io,"$(typeof(G)) for $(G.spectral_grid)") +end + +""" +$(TYPEDSIGNATURES) +Interpolation weights for full to half level interpolation +on sigma coordinates. Following Fortran SPEEDY documentation eq. (1).""" +function σ_interpolation_weights( + σ_levels_full::AbstractVector, + σ_levels_half::AbstractVector) + + weights = zero(σ_levels_full) + nlev = length(weights) + nlev == 1 && return weights # escape early for 1 layer to avoid out-of-bounds access + + for k in 1:nlev-1 + weights[k] = (log(σ_levels_half[k+1]) - log(σ_levels_full[k])) / + (log(σ_levels_full[k+1]) - log(σ_levels_full[k])) + end + # was log(0.99) in Fortran SPEEDY code but doesn't make sense to me + weights[end] = (log(σ_levels_half[nlev+1]) - log(σ_levels_full[nlev])) / + (log(σ_levels_full[nlev]) - log(σ_levels_full[nlev-1])) + + return weights +end + + +""" +$(TYPEDSIGNATURES) +Given a vector in column defined at full levels, do a linear interpolation in +log(σ) to calculate its values at half-levels, skipping top (k=1/2), extrapolating to bottom (k=NLEV+1/2). +""" +function vertical_interpolate!( + A_half::Vector, # quantity A on half levels (excl top) + A_full::Vector, # quantity A on full levels + G::Geometry, +) + nlev = length(A_half) + weights = G.full_to_half_interpolation + + # full levels contain one more for surface + # TODO this is currently confusing because the surface fluxes use full[end] + # as surface value which is technically on half levels though! + @boundscheck nlev <= length(A_full) || throw(BoundsError) + @boundscheck nlev <= length(weights) || throw(BoundsError) + + # For A at each full level k, compute A at the half-level below, i.e. at the boundary + # between the full levels k and k+1. Fortran SPEEDY documentation eq. (1) + for k = 1:nlev-1 + A_half[k] = A_full[k] + weights[k]*(A_full[k+1] - A_full[k]) + end + + # Compute the values at the surface separately + A_half[nlev] = A_full[nlev] + weights[nlev]*(A_full[nlev] - A_full[nlev-1]) + + return nothing +end \ No newline at end of file diff --git a/src/dynamics/geopotential.jl b/src/dynamics/geopotential.jl index d4746f73b..a367133f4 100644 --- a/src/dynamics/geopotential.jl +++ b/src/dynamics/geopotential.jl @@ -1,3 +1,11 @@ +abstract type AbstractGeopotential <: AbstractModelComponent end + +Base.@kwdef struct Geopotential{NF} <: AbstractGeopotential + nlev::Int + Δp_geopot_half::Vector{NF} = zeros(NF,nlev) + Δp_geopot_full::Vector{NF} = zeros(NF,nlev) +end + """ $(TYPEDSIGNATURES) Precomputes constants for the vertical integration of the geopotential, defined as @@ -6,15 +14,13 @@ Precomputes constants for the vertical integration of the geopotential, defined `Φ_k = Φ_{k+1/2} + R*T_k*(ln(p_{k+1/2}) - ln(p_k))` (full levels) Same formula but `k → k-1/2`.""" -function initialize_geopotential( σ_levels_full::Vector, - σ_levels_half::Vector, - R_dry::Real) - - nlev = length(σ_levels_full) # number of vertical levels - @assert nlev+1 == length(σ_levels_half) "σ half levels must have length nlev+1" - - Δp_geopot_half = zeros(nlev) # allocate arrays - Δp_geopot_full = zeros(nlev) +function initialize!( + geopotential::Geopotential, + model::PrimitiveEquation +) + (;Δp_geopot_half, Δp_geopot_full, nlev) = geopotential + (;R_dry) = model.atmosphere + (;σ_levels_full, σ_levels_half) = model.geometry # 1. integration onto half levels for k in 1:nlev-1 # k is full level index, 1=top, nlev=bottom @@ -27,24 +33,21 @@ function initialize_geopotential( σ_levels_full::Vector, # used for: Φ_k = Φ_{k+1/2} + R*T_k*(ln(p_{k+1/2}) - ln(p_k)) Δp_geopot_full[k] = R_dry*log(σ_levels_half[k+1]/σ_levels_full[k]) end - - return Δp_geopot_half, Δp_geopot_full end """ $(TYPEDSIGNATURES) Compute spectral geopotential `geopot` from spectral temperature `temp` -and spectral surface geopotential `geopot_surf` (orography*gravity). -""" +and spectral surface geopotential `geopot_surf` (orography*gravity).""" function geopotential!( diagn::DiagnosticVariables, - O::AbstractOrography, # contains surface geopotential - C::DynamicsConstants, # contains precomputed layer-thickness arrays + geopotential::Geopotential, + orography::AbstractOrography, ) - (;geopot_surf) = O # = orography*gravity - (;Δp_geopot_half, Δp_geopot_full) = C # = R*Δlnp either on half or full levels - (;nlev) = diagn # number of vertical levels + (;geopot_surf) = orography # = orography*gravity + (;Δp_geopot_half, Δp_geopot_full) = geopotential # = R*Δlnp either on half or full levels + (;nlev) = diagn # number of vertical levels @boundscheck nlev == length(Δp_geopot_full) || throw(BoundsError) @@ -78,13 +81,15 @@ $(TYPEDSIGNATURES) Calculate the geopotential based on `temp` in a single column. This exclues the surface geopotential that would need to be added to the returned vector. Function not used in the dynamical core but for post-processing and analysis.""" -function geopotential!( geopot::AbstractVector, - temp::AbstractVector, - C::DynamicsConstants, - geopot_surf::Real = 0) +function geopotential!( + geopot::AbstractVector, + temp::AbstractVector, + G::Geopotential, + geopot_surf::Real = 0 +) nlev = length(geopot) - (;Δp_geopot_half, Δp_geopot_full) = C # = R*Δlnp either on half or full levels + (;Δp_geopot_half, Δp_geopot_full) = G # = R*Δlnp either on half or full levels @boundscheck length(temp) >= nlev || throw(BoundsError) @boundscheck length(Δp_geopot_full) >= nlev || throw(BoundsError) @@ -97,14 +102,12 @@ function geopotential!( geopot::AbstractVector, @inbounds for k in nlev-1:-1:1 geopot[k] = geopot[k+1] + temp[k+1]*Δp_geopot_half[k+1] + temp[k]*Δp_geopot_full[k] end - - return geopot end function geopotential( temp::Vector, - C::DynamicsConstants) + G::Geopotential) geopot = zero(temp) - geopotential!(geopot,temp,C) + geopotential!(geopot,temp,G) return geopot end @@ -114,111 +117,7 @@ calculates the geopotential in the ShallowWaterModel as g*η, i.e. gravity times the interface displacement (field `pres`)""" function geopotential!( diagn::DiagnosticVariablesLayer, pres::LowerTriangularMatrix, - C::DynamicsConstants) - (;gravity) = C + planet::AbstractPlanet) (;geopot) = diagn.dynamics_variables - geopot .= pres*gravity -end - -# function barrier -function virtual_temperature!( diagn::DiagnosticVariablesLayer, - temp::LowerTriangularMatrix, # only needed for dispatch compat with DryCore - model::PrimitiveWet) - virtual_temperature!(diagn,temp,model.constants) -end - -""" -$(TYPEDSIGNATURES) -Calculates the virtual temperature Tᵥ as - - Tᵥ = T(1+μq) - -With absolute temperature T, specific humidity q and - - μ = (1-ξ)/ξ, ξ = R_dry/R_vapour. - -in grid-point space.""" -function virtual_temperature!( - diagn::DiagnosticVariablesLayer, - temp::LowerTriangularMatrix, # only needed for dispatch compat with DryCore - constants::DynamicsConstants, - ) - - (;temp_grid, humid_grid, temp_virt_grid) = diagn.grid_variables - (;temp_virt) = diagn.dynamics_variables - μ = constants.μ_virt_temp - - @inbounds for ij in eachgridpoint(temp_virt_grid, temp_grid, humid_grid) - temp_virt_grid[ij] = temp_grid[ij]*(1 + μ*humid_grid[ij]) - end - # TODO check that doing a non-linear virtual temperature in grid-point space - # but a linear virtual temperature in spectral space to avoid another transform - # does not cause any problems. Alternative do the transform or have a linear - # virtual temperature in both grid and spectral space - # spectral!(temp_virt,temp_virt_grid,S) -end - -""" -$(TYPEDSIGNATURES) -Virtual temperature in grid-point space: For the PrimitiveDry temperature -and virtual temperature are the same (humidity=0). Just copy over the arrays.""" -function virtual_temperature!( diagn::DiagnosticVariablesLayer, - temp::LowerTriangularMatrix, - model::PrimitiveDry) - - (;temp_grid, temp_virt_grid) = diagn.grid_variables - (;temp_virt) = diagn.dynamics_variables - - copyto!(temp_virt_grid,temp_grid) -end - -""" -$(TYPEDSIGNATURES) -Linear virtual temperature for `model::PrimitiveDry`: Just copy over -arrays from `temp` to `temp_virt` at timestep `lf` in spectral space -as humidity is zero in this `model`.""" -function linear_virtual_temperature!( - diagn::DiagnosticVariablesLayer, - progn::PrognosticLayerTimesteps, - model::PrimitiveDry, - lf::Integer, -) - (;temp_virt) = diagn.dynamics_variables - (;temp) = progn.timesteps[lf] - copyto!(temp_virt,temp) -end - -# function barrier -function linear_virtual_temperature!( - diagn::DiagnosticVariablesLayer, - progn::PrognosticLayerTimesteps, - model::PrimitiveWet, - lf::Integer, -) - linear_virtual_temperature!(diagn,progn,model.constants,lf) -end - -""" -$(TYPEDSIGNATURES) -Calculates a linearised virtual temperature Tᵥ as - - Tᵥ = T + Tₖμq - -With absolute temperature T, layer-average temperarture Tₖ (computed in temperature_average!), -specific humidity q and - - μ = (1-ξ)/ξ, ξ = R_dry/R_vapour. - -in spectral space.""" -function linear_virtual_temperature!( diagn::DiagnosticVariablesLayer, - progn::PrognosticLayerTimesteps, - constants::DynamicsConstants, - lf::Int) - - (;temp_virt) = diagn.dynamics_variables - μ = constants.μ_virt_temp - Tₖ = diagn.temp_average[] - (;temp,humid) = progn.timesteps[lf] - - @. temp_virt = temp + (Tₖ*μ)*humid -end \ No newline at end of file + geopot .= pres * planet.gravity +end \ No newline at end of file diff --git a/src/dynamics/hole_filling.jl b/src/dynamics/hole_filling.jl index 8bc450071..53c43c5e4 100644 --- a/src/dynamics/hole_filling.jl +++ b/src/dynamics/hole_filling.jl @@ -1,21 +1,21 @@ -abstract type AbstractHoleFilling{NF} end +abstract type AbstractHoleFilling end -struct ClipNegatives{NF} <: AbstractHoleFilling{NF} end -ClipNegatives(SG::SpectralGrid) = ClipNegatives{SG.NF}() +struct ClipNegatives <: AbstractHoleFilling end +ClipNegatives(SG::SpectralGrid) = ClipNegatives() # nothing to initialize -initialize!(H::ClipNegatives,::PrimitiveWet) = nothing +initialize!(::ClipNegatives,::PrimitiveWet) = nothing # function barrier function hole_filling!( A::AbstractGrid, H::ClipNegatives, - model::PrimitiveWet) - + model::PrimitiveWet +) hole_filling!(A,H) end -function hole_filling!(A::AbstractGrid,H::ClipNegatives) +function hole_filling!(A::AbstractGrid,::ClipNegatives) @inbounds for ij in eachgridpoint(A) A[ij] = max(A[ij],0) end diff --git a/src/dynamics/horizontal_diffusion.jl b/src/dynamics/horizontal_diffusion.jl index 52fda8f03..3fba4b9cd 100644 --- a/src/dynamics/horizontal_diffusion.jl +++ b/src/dynamics/horizontal_diffusion.jl @@ -1,3 +1,7 @@ +abstract type HorizontalDiffusion <: AbstractModelComponent end + +export HyperDiffusion + """ Struct for horizontal hyper diffusion of vor, div, temp; implicitly in spectral space with a `power` of the Laplacian (default=4) and the strength controlled by @@ -7,7 +11,7 @@ layers. Furthermore the power can be decreased above the `tapering_σ` to `power_stratosphere` (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are $(TYPEDFIELDS)""" -Base.@kwdef struct HyperDiffusion{NF} <: HorizontalDiffusion{NF} +Base.@kwdef struct HyperDiffusion{NF} <: HorizontalDiffusion # DIMENSIONS "spectral resolution" trunc::Int @@ -61,12 +65,6 @@ function HyperDiffusion(spectral_grid::SpectralGrid;kwargs...) return HyperDiffusion{NF}(;trunc,nlev,kwargs...) end -function Base.show(io::IO,HD::HorizontalDiffusion) - println(io,"$(typeof(HD))") - keys = propertynames(HD) - print_fields(io,HD,keys,arrays=false) -end - """$(TYPEDSIGNATURES) Precomputes the hyper diffusion terms in `scheme` based on the model time step, and possibly with a changing strength/power in @@ -87,7 +85,7 @@ end Precomputes the 2D hyper diffusion terms in `scheme` based on the model time step.""" function initialize!( scheme::HyperDiffusion, - L::TimeStepper) + L::AbstractTimeStepper) (;trunc,∇²ⁿ_2D,∇²ⁿ_2D_implicit,power) = scheme (;Δt, radius) = L @@ -120,8 +118,8 @@ the current (absolute) vorticity maximum level `vor_max`""" function initialize!( scheme::HyperDiffusion, k::Int, - G::Geometry, - L::TimeStepper, + G::AbstractGeometry, + L::AbstractTimeStepper, vor_max::Real = 0, ) (;trunc, resolution_scaling, ∇²ⁿ, ∇²ⁿ_implicit) = scheme @@ -170,8 +168,8 @@ calculates the (absolute) vorticity maximum for the layer of `diagn`.""" function initialize!( scheme::HyperDiffusion, diagn::DiagnosticVariablesLayer, - G::Geometry, - L::TimeStepper, + G::AbstractGeometry, + L::AbstractTimeStepper, ) scheme.adaptive || return nothing vor_min, vor_max = extrema(diagn.grid_variables.vor_grid) diff --git a/src/dynamics/implicit.jl b/src/dynamics/implicit.jl index 48f53ce5d..57f0fc703 100644 --- a/src/dynamics/implicit.jl +++ b/src/dynamics/implicit.jl @@ -1,14 +1,19 @@ +abstract type AbstractImplicit <: AbstractModelComponent end + # BAROTROPIC MODEL (no implicit needed) -struct NoImplicit{NF} <: AbstractImplicit{NF} end -NoImplicit(SG::SpectralGrid) = NoImplicit{SG.NF}() -initialize!(I::NoImplicit,dt::Real,::DiagnosticVariables,::ModelSetup) = nothing +export NoImplicit +struct NoImplicit <: AbstractImplicit end +NoImplicit(SG::SpectralGrid) = NoImplicit() +initialize!(::AbstractImplicit,args...) = nothing + +export ImplicitShallowWater # SHALLOW WATER MODEL """ Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the shallow water model. $(TYPEDFIELDS)""" -Base.@kwdef struct ImplicitShallowWater{NF<:AbstractFloat} <: AbstractImplicit{NF} +Base.@kwdef struct ImplicitShallowWater{NF<:AbstractFloat} <: AbstractImplicit # DIMENSIONS trunc::Int @@ -32,27 +37,24 @@ function ImplicitShallowWater(spectral_grid::SpectralGrid;kwargs...) return ImplicitShallowWater{NF}(;trunc,kwargs...) end -function Base.show(io::IO,I::ImplicitShallowWater) - println(io,"$(typeof(I)) <: AbstractImplicit") - keys = propertynames(I) - print_fields(io,I,keys) -end - # function barrier to unpack the constants struct for shallow water function initialize!(I::ImplicitShallowWater,dt::Real,::DiagnosticVariables,model::ShallowWater) - initialize!(I,dt,model.constants) + initialize!(I, dt, model.planet, model.atmosphere) end """ $(TYPEDSIGNATURES) Update the implicit terms in `implicit` for the shallow water model as they depend on the time step `dt`.""" -function initialize!( implicit::ImplicitShallowWater, - dt::Real, # time step used [s] - constants::DynamicsConstants) +function initialize!( + implicit::ImplicitShallowWater, + dt::Real, # time step used [s] + planet::AbstractPlanet, + atmosphere::AbstractAtmosphere, +) - (;α,H,ξH,g∇²,ξg∇²,S⁻¹) = implicit # precomputed arrays to be updated - (;gravity,layer_thickness) = constants # shallow water layer thickness [m] - # gravitational acceleration [m/s²] + (;α, H, ξH, g∇², ξg∇², S⁻¹) = implicit # precomputed arrays to be updated + (;gravity) = planet # gravitational acceleration [m/s²] + (;layer_thickness) = atmosphere # shallow water layer thickness [m] # implicit time step between i-1 and i+1 # α = 0 means the gravity wave terms are evaluated at i-1 (forward) @@ -122,11 +124,13 @@ function implicit_correction!( diagn::DiagnosticVariablesLayer{NF}, end end +export ImplicitPrimitiveEquation + """ Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the primitive equation model. $(TYPEDFIELDS)""" -Base.@kwdef struct ImplicitPrimitiveEq{NF<:AbstractFloat} <: AbstractImplicit{NF} +Base.@kwdef struct ImplicitPrimitiveEquation{NF<:AbstractFloat} <: AbstractImplicit # DIMENSIONS "spectral resolution" @@ -182,25 +186,19 @@ end """$(TYPEDSIGNATURES) Generator using the resolution from SpectralGrid.""" -function ImplicitPrimitiveEq(spectral_grid::SpectralGrid,kwargs...) +function ImplicitPrimitiveEquation(spectral_grid::SpectralGrid,kwargs...) (;NF,trunc,nlev) = spectral_grid - return ImplicitPrimitiveEq{NF}(;trunc,nlev,kwargs...) -end - -function Base.show(io::IO,I::ImplicitPrimitiveEq) - println(io,"$(typeof(I)) <: AbstractImplicit") - keys = propertynames(I) - print_fields(io,I,keys) + return ImplicitPrimitiveEquation{NF}(;trunc,nlev,kwargs...) end # function barrier to unpack the constants struct for primitive eq models -function initialize!(I::ImplicitPrimitiveEq,dt::Real,diagn::DiagnosticVariables,model::PrimitiveEquation) +function initialize!(I::ImplicitPrimitiveEquation,dt::Real,diagn::DiagnosticVariables,model::PrimitiveEquation) initialize!(I,dt,diagn,model.geometry,model.constants) end """$(TYPEDSIGNATURES) Initialize the implicit terms for the PrimitiveEquation models.""" -function initialize!( implicit::ImplicitPrimitiveEq, +function initialize!( implicit::ImplicitPrimitiveEquation, dt::Real, # the scaled time step radius*dt diagn::DiagnosticVariables, geometry::Geometry, @@ -294,7 +292,7 @@ end Apply the implicit corrections to dampen gravity waves in the primitive equation models.""" function implicit_correction!( diagn::DiagnosticVariables, - implicit::ImplicitPrimitiveEq, + implicit::ImplicitPrimitiveEquation, progn::PrognosticVariables, ) diff --git a/src/dynamics/initial_conditions.jl b/src/dynamics/initial_conditions.jl index 67a76c9f9..bff46e30e 100644 --- a/src/dynamics/initial_conditions.jl +++ b/src/dynamics/initial_conditions.jl @@ -1,7 +1,11 @@ -# default initial conditions by model -initial_conditions_default(::Type{<:Barotropic}) = StartWithRandomVorticity() -initial_conditions_default(::Type{<:ShallowWater}) = ZonalJet() -initial_conditions_default(::Type{<:PrimitiveEquation}) = ZonalWind() +abstract type InitialConditions end + +# EXPORT INITIAL CONDITIONS +export StartFromFile, + StartFromRest, + ZonalJet, + ZonalWind, + StartWithRandomVorticity Base.@kwdef struct StartFromRest <: InitialConditions pressure_on_orography::Bool = false @@ -315,7 +319,7 @@ function initialize!( progn::PrognosticVariables{NF}, end # PRESSURE (constant everywhere) - lnp₀ = log(pres_ref*100) # logarithm of reference surface pressure, *100 for [hPa] to [Pa] + lnp₀ = log(pres_ref) # logarithm of reference surface pressure [log(Pa)] progn.surface.timesteps[1].pres[1] = norm_sphere*lnp₀ lnpₛ = ones(Grid{NF},nlat_half) @@ -415,7 +419,7 @@ function pressure_on_orography!(progn::PrognosticVariables, (; orography ) = model.orography # orography on the grid Γ = lapse_rate/1000 # Lapse rate [K/km] -> [K/m] - lnp₀ = log(pres_ref*100) # logarithm of reference surface pressure, *100 for [hPa] to [Pa] + lnp₀ = log(pres_ref) # logarithm of reference surface pressure [log(Pa)] lnp_grid = zero(orography) # allocate log surface pressure on grid RΓg⁻¹ = R_dry*Γ/gravity # for convenience diff --git a/src/dynamics/models.jl b/src/dynamics/models.jl deleted file mode 100644 index f8800e98a..000000000 --- a/src/dynamics/models.jl +++ /dev/null @@ -1,371 +0,0 @@ -""" -$(TYPEDSIGNATURES) -Simulation is a container struct to be used with `run!(::Simulation)`. -It contains -$(TYPEDFIELDS)""" -struct Simulation{Model<:ModelSetup} <: AbstractSimulation{Model} - "define the current state of the model" - prognostic_variables::PrognosticVariables - - "contain the tendencies and auxiliary arrays to compute them" - diagnostic_variables::DiagnosticVariables - - "all parameters, constant at runtime" - model::Model -end - -""" -$(SIGNATURES) -The BarotropicModel struct holds all other structs that contain precalculated constants, -whether scalars or arrays that do not change throughout model integration. -$(TYPEDFIELDS)""" -Base.@kwdef mutable struct BarotropicModel{NF<:AbstractFloat, D<:AbstractDevice} <: Barotropic - spectral_grid::SpectralGrid = SpectralGrid(nlev=1) - - # DYNAMICS - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - forcing::AbstractForcing{NF} = NoForcing(spectral_grid) - drag::AbstractDrag{NF} = NoDrag(spectral_grid) - initial_conditions::InitialConditions = StartWithRandomVorticity() - - # NUMERICS - time_stepping::TimeStepper{NF} = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion{NF} = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit{NF} = NoImplicit(spectral_grid) - - # INTERNALS - geometry::Geometry{NF} = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) - - # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,Barotropic) - feedback::AbstractFeedback = Feedback() -end - -has(::Type{<:Barotropic}, var_name::Symbol) = var_name in (:vor,) -default_concrete_model(::Type{Barotropic}) = BarotropicModel - -""" -$(TYPEDSIGNATURES) -Calls all `initialize!` functions for components of `model`, -except for `model.output` and `model.feedback` which are always called -at in `time_stepping!`.""" -function initialize!(model::Barotropic;time::DateTime = DEFAULT_DATE) - (;spectral_grid) = model - - spectral_grid.nlev > 1 && @warn "Only nlev=1 supported for BarotropicModel, \ - SpectralGrid with nlev=$(spectral_grid.nlev) provided." - - # slightly adjust model time step to be a convenient divisor of output timestep - initialize!(model.time_stepping,model) - - # initialize components - initialize!(model.forcing,model) - initialize!(model.drag,model) - initialize!(model.horizontal_diffusion,model) - - # initial conditions - prognostic_variables = PrognosticVariables(spectral_grid,model) - initialize!(prognostic_variables,model.initial_conditions,model) - prognostic_variables.clock.time = time # set the time - - diagnostic_variables = DiagnosticVariables(spectral_grid,model) - return Simulation(prognostic_variables,diagnostic_variables,model) -end - -""" -$(SIGNATURES) -The ShallowWaterModel struct holds all other structs that contain precalculated constants, -whether scalars or arrays that do not change throughout model integration. -$(TYPEDFIELDS)""" -Base.@kwdef mutable struct ShallowWaterModel{NF<:AbstractFloat, D<:AbstractDevice} <: ShallowWater - spectral_grid::SpectralGrid = SpectralGrid(nlev=1) - - # DYNAMICS - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - forcing::AbstractForcing{NF} = NoForcing(spectral_grid) - drag::AbstractDrag{NF} = NoDrag(spectral_grid) - initial_conditions::InitialConditions = ZonalJet() - orography::AbstractOrography{NF} = EarthOrography(spectral_grid) - - # NUMERICS - time_stepping::TimeStepper{NF} = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion{NF} = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit{NF} = ImplicitShallowWater(spectral_grid) - - # INTERNALS - geometry::Geometry{NF} = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) - - # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,ShallowWater) - feedback::AbstractFeedback = Feedback() -end - -has(::Type{<:ShallowWater}, var_name::Symbol) = var_name in (:vor, :div, :pres) -default_concrete_model(::Type{ShallowWater}) = ShallowWaterModel - -""" -$(TYPEDSIGNATURES) -Calls all `initialize!` functions for components of `model`, -except for `model.output` and `model.feedback` which are always called -at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" -function initialize!(model::ShallowWater;time::DateTime = DEFAULT_DATE) - (;spectral_grid) = model - - spectral_grid.nlev > 1 && @warn "Only nlev=1 supported for ShallowWaterModel, \ - SpectralGrid with nlev=$(spectral_grid.nlev) provided." - - # slightly adjust model time step to be a convenient divisor of output timestep - initialize!(model.time_stepping,model) - - # initialize components - initialize!(model.forcing,model) - initialize!(model.drag,model) - initialize!(model.horizontal_diffusion,model) - initialize!(model.orography,model) - - # initial conditions - prognostic_variables = PrognosticVariables(spectral_grid,model) - initialize!(prognostic_variables,model.initial_conditions,model) - prognostic_variables.clock.time = time # set the time - - diagnostic_variables = DiagnosticVariables(spectral_grid,model) - return Simulation(prognostic_variables,diagnostic_variables,model) -end - -""" -$(SIGNATURES) -The PrimitiveDryModel struct holds all other structs that contain precalculated constants, -whether scalars or arrays that do not change throughout model integration. -$(TYPEDFIELDS)""" -Base.@kwdef mutable struct PrimitiveDryModel{NF<:AbstractFloat, D<:AbstractDevice} <: PrimitiveDry - spectral_grid::SpectralGrid = SpectralGrid() - - # DYNAMICS - dynamics::Bool = true - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - initial_conditions::InitialConditions = ZonalWind() - orography::AbstractOrography{NF} = EarthOrography(spectral_grid) - - # BOUNDARY CONDITIONS - land_sea_mask::AbstractLandSeaMask{NF} = LandSeaMask(spectral_grid) - ocean::AbstractOcean{NF} = SeasonalOceanClimatology(spectral_grid) - land::AbstractLand{NF} = SeasonalLandTemperature(spectral_grid) - solar_zenith::AbstractZenith{NF} = WhichZenith(spectral_grid,planet) - - # PHYSICS/PARAMETERIZATIONS - physics::Bool = true - boundary_layer_drag::BoundaryLayerDrag{NF} = BulkRichardsonDrag(spectral_grid) - temperature_relaxation::TemperatureRelaxation{NF} = NoTemperatureRelaxation(spectral_grid) - static_energy_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) - surface_thermodynamics::AbstractSurfaceThermodynamics{NF} = SurfaceThermodynamicsConstant(spectral_grid) - surface_wind::AbstractSurfaceWind{NF} = SurfaceWind(spectral_grid) - surface_heat_flux::AbstractSurfaceHeat{NF} = SurfaceSensibleHeat(spectral_grid) - shortwave_radiation::AbstractShortwave{NF} = NoShortwave(spectral_grid) - longwave_radiation::AbstractLongwave{NF} = UniformCooling(spectral_grid) - - # NUMERICS - time_stepping::TimeStepper{NF} = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion{NF} = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit{NF} = ImplicitPrimitiveEq(spectral_grid) - vertical_advection::VerticalAdvection{NF} = CenteredVerticalAdvection(spectral_grid) - - # INTERNALS - geometry::Geometry{NF} = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) - - # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,PrimitiveDry) - feedback::AbstractFeedback = Feedback() -end - -has(::Type{<:PrimitiveDry}, var_name::Symbol) = var_name in (:vor, :div, :temp, :pres) -default_concrete_model(::Type{PrimitiveDry}) = PrimitiveDryModel - -""" -$(TYPEDSIGNATURES) -Calls all `initialize!` functions for components of `model`, -except for `model.output` and `model.feedback` which are always called -at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" -function initialize!(model::PrimitiveDry;time::DateTime = DEFAULT_DATE) - (;spectral_grid) = model - - # slightly adjust model time step to be a convenient divisor of output timestep - initialize!(model.time_stepping, model) - - # numerics (implicit is initialized later) - initialize!(model.horizontal_diffusion, model) - - # boundary conditionss - initialize!(model.orography, model) - initialize!(model.land_sea_mask, model) - initialize!(model.ocean) - initialize!(model.land) - initialize!(model.solar_zenith,time,model) - - # parameterizations - initialize!(model.boundary_layer_drag,model) - initialize!(model.temperature_relaxation,model) - initialize!(model.static_energy_diffusion,model) - initialize!(model.shortwave_radiation,model) - initialize!(model.longwave_radiation,model) - - # initial conditions - prognostic_variables = PrognosticVariables(spectral_grid,model) - initialize!(prognostic_variables,model.initial_conditions,model) - (;clock) = prognostic_variables - clock.time = time # set the time - - # initialize ocean and land and synchronize clocks - initialize!(prognostic_variables.ocean,clock.time,model) - initialize!(prognostic_variables.land,clock.time,model) - - diagnostic_variables = DiagnosticVariables(spectral_grid,model) - return Simulation(prognostic_variables,diagnostic_variables,model) -end - -""" -$(SIGNATURES) -The PrimitiveDryModel struct holds all other structs that contain precalculated constants, -whether scalars or arrays that do not change throughout model integration. -$(TYPEDFIELDS)""" -Base.@kwdef mutable struct PrimitiveWetModel{NF<:AbstractFloat, D<:AbstractDevice} <: PrimitiveWet - spectral_grid::SpectralGrid = SpectralGrid() - - # DYNAMICS - dynamics::Bool = true - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - initial_conditions::InitialConditions = ZonalWind() - orography::AbstractOrography{NF} = EarthOrography(spectral_grid) - - # BOUNDARY CONDITIONS - land_sea_mask::AbstractLandSeaMask{NF} = LandSeaMask(spectral_grid) - ocean::AbstractOcean{NF} = SeasonalOceanClimatology(spectral_grid) - land::AbstractLand{NF} = SeasonalLandTemperature(spectral_grid) - soil::AbstractSoil{NF} = SeasonalSoilMoisture(spectral_grid) - vegetation::AbstractVegetation{NF} = VegetationClimatology(spectral_grid) - solar_zenith::AbstractZenith{NF} = WhichZenith(spectral_grid,planet) - - # PHYSICS/PARAMETERIZATIONS - physics::Bool = true - clausius_clapeyron::AbstractClausiusClapeyron{NF} = ClausiusClapeyron(spectral_grid,atmosphere) - boundary_layer_drag::BoundaryLayerDrag{NF} = BulkRichardsonDrag(spectral_grid) - temperature_relaxation::TemperatureRelaxation{NF} = NoTemperatureRelaxation(spectral_grid) - static_energy_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) - humidity_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) - large_scale_condensation::AbstractCondensation{NF} = ImplicitCondensation(spectral_grid) - surface_thermodynamics::AbstractSurfaceThermodynamics{NF} = SurfaceThermodynamicsConstant(spectral_grid) - surface_wind::AbstractSurfaceWind{NF} = SurfaceWind(spectral_grid) - surface_heat_flux::AbstractSurfaceHeat{NF} = SurfaceSensibleHeat(spectral_grid) - evaporation::AbstractEvaporation{NF} = SurfaceEvaporation(spectral_grid) - convection::AbstractConvection{NF} = SimplifiedBettsMiller(spectral_grid) - shortwave_radiation::AbstractShortwave{NF} = NoShortwave(spectral_grid) - longwave_radiation::AbstractLongwave{NF} = UniformCooling(spectral_grid) - - # NUMERICS - time_stepping::TimeStepper{NF} = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion{NF} = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit{NF} = ImplicitPrimitiveEq(spectral_grid) - vertical_advection::VerticalAdvection{NF} = CenteredVerticalAdvection(spectral_grid) - hole_filling::AbstractHoleFilling{NF} = ClipNegatives(spectral_grid) - - # INTERNALS - geometry::Geometry{NF} = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) - - # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,PrimitiveWet) - feedback::AbstractFeedback = Feedback() -end - -has(::Type{<:PrimitiveWet}, var_name::Symbol) = var_name in (:vor, :div, :temp, :pres, :humid) -default_concrete_model(::Type{PrimitiveWet}) = PrimitiveWetModel - -""" -$(TYPEDSIGNATURES) -Calls all `initialize!` functions for components of `model`, -except for `model.output` and `model.feedback` which are always called -at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" -function initialize!(model::PrimitiveWet;time::DateTime = DEFAULT_DATE) - (;spectral_grid) = model - - # slightly adjust model time step to be a convenient divisor of output timestep - initialize!(model.time_stepping, model) - - # numerics (implicit is initialized later) - initialize!(model.horizontal_diffusion, model) - - # boundary conditionss - initialize!(model.orography, model) - initialize!(model.land_sea_mask, model) - initialize!(model.ocean) - initialize!(model.land) - initialize!(model.soil) - initialize!(model.vegetation) - initialize!(model.solar_zenith,time,model) - - # parameterizations - initialize!(model.boundary_layer_drag,model) - initialize!(model.temperature_relaxation,model) - initialize!(model.static_energy_diffusion,model) - initialize!(model.humidity_diffusion,model) - initialize!(model.large_scale_condensation,model) - initialize!(model.convection,model) - initialize!(model.shortwave_radiation,model) - initialize!(model.longwave_radiation,model) - - # initial conditions - prognostic_variables = PrognosticVariables(spectral_grid,model) - initialize!(prognostic_variables,model.initial_conditions,model) - (;clock) = prognostic_variables - clock.time = time # set the time - - # initialize ocean and land and synchronize clocks - initialize!(prognostic_variables.ocean,clock.time,model) - initialize!(prognostic_variables.land,clock.time,model) - - diagnostic_variables = DiagnosticVariables(spectral_grid,model) - return Simulation(prognostic_variables,diagnostic_variables,model) -end - -"""$(TYPEDSIGNATURES) -Returns true if the model `M` has a prognostic variable `var_name`, false otherwise. -The default fallback is that all variables are included. """ -has(M::Type{<:ModelSetup}, var_name::Symbol) = var_name in (:vor, :div, :temp, :humid, :pres) -has(M::ModelSetup, var_name) = has(typeof(M), var_name) - -# strip away the parameters of the model type -model_class(::Type{<:Barotropic}) = Barotropic -model_class(::Type{<:ShallowWater}) = ShallowWater -model_class(::Type{<:PrimitiveDry}) = PrimitiveDry -model_class(::Type{<:PrimitiveWet}) = PrimitiveWet -model_class(model::ModelSetup) = model_class(typeof(model)) - -function Base.show(io::IO,M::ModelSetup) - println(io,"$(typeof(M))") - for key in propertynames(M)[1:end-1] - val = getfield(M,key) - println(io,"├ $key: $(typeof(val))") - end - print(io,"└ feedback: $(typeof(M.feedback))") -end - -function Base.show(io::IO,S::Simulation) - println(io,"$(typeof(S))") - println(io,"├ $(typeof(S.model))") - println(io,"├ $(typeof(S.prognostic_variables))") - print(io, "└ $(typeof(S.diagnostic_variables))") -end \ No newline at end of file diff --git a/src/dynamics/orography.jl b/src/dynamics/orography.jl index fe7f91bb9..8c7c52ccb 100644 --- a/src/dynamics/orography.jl +++ b/src/dynamics/orography.jl @@ -1,3 +1,6 @@ +abstract type AbstractOrography{NF,Grid} <: AbstractModelComponent end +export NoOrography + """Orography with zero height in `orography` and zero surface geopotential `geopot_surf`. $(TYPEDFIELDS)""" struct NoOrography{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractOrography{NF,Grid} @@ -18,15 +21,11 @@ function NoOrography(spectral_grid::SpectralGrid) return NoOrography{NF,Grid{NF}}(orography,geopot_surf) end -function Base.show(io::IO,orog::AbstractOrography) - println(io,"$(typeof(orog)) <: AbstractOrography") - keys = propertynames(orog) - print_fields(io,orog,keys) -end - # no further initialization needed initialize!(::NoOrography,::ModelSetup) = nothing +export ZonalRidge + """Zonal ridge orography after Jablonowski and Williamson, 2006. $(TYPEDFIELDS)""" Base.@kwdef struct ZonalRidge{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractOrography{NF,Grid} @@ -95,6 +94,7 @@ function initialize!( orog::ZonalRidge, spectral_truncation!(geopot_surf) # set the lmax+1 harmonics to zero end +export EarthOrography """Earth's orography read from file, with smoothing. $(TYPEDFIELDS)""" @@ -146,7 +146,7 @@ end # function barrier function initialize!( orog::EarthOrography, model::ModelSetup) - initialize!(orog,model.planet,model.spectral_transform) + initialize!(orog, model.planet, model.spectral_transform) end """ diff --git a/src/dynamics/planets.jl b/src/dynamics/planets.jl index 25c3d05ca..b29c71ba6 100644 --- a/src/dynamics/planets.jl +++ b/src/dynamics/planets.jl @@ -1,3 +1,10 @@ +abstract type AbstractPlanet <: AbstractModelComponent end + +const DEFAULT_ROTATION = 7.29e-5 # default angular frequency of Earth's rotation [1/s] +const DEFAULT_GRAVITY = 9.81 # default gravitational acceleration on Earth [m/s²] + +export Earth + """ $(TYPEDSIGNATURES) Create a struct `Earth<:AbstractPlanet`, with the following physical/orbital @@ -5,13 +12,13 @@ characteristics. Note that `radius` is not part of it as this should be chosen in `SpectralGrid`. Keyword arguments are $(TYPEDFIELDS) """ -Base.@kwdef struct Earth <: AbstractPlanet +Base.@kwdef struct Earth{NF<:AbstractFloat} <: AbstractPlanet "angular frequency of Earth's rotation [rad/s]" - rotation::Float64 = 7.29e-5 + rotation::NF = DEFAULT_ROTATION "gravitational acceleration [m/s^2]" - gravity::Float64 = 9.81 + gravity::NF = DEFAULT_GRAVITY "switch on/off daily cycle" daily_cycle::Bool = true @@ -29,14 +36,11 @@ Base.@kwdef struct Earth <: AbstractPlanet equinox::DateTime = DateTime(2000,3,20) "angle [˚] rotation axis tilt wrt to orbit" - axial_tilt::Float64 = 23.4 + axial_tilt::NF = 23.4 "Total solar irradiance at the distance of 1 AU [W/m²]" - solar_constant::Float64 = 1365 + solar_constant::NF = 1365 end -function Base.show(io::IO,planet::AbstractPlanet) - println(io,"$(typeof(planet)) <: AbstractPlanet") - keys = propertynames(planet) - print_fields(io,planet,keys) -end \ No newline at end of file +Earth(;kwargs...) = Earth{DEFAULT_NF}(;kwargs...) +Earth(SG::SpectralGrid;kwargs...) = Earth{SG.NF}(;kwargs...) \ No newline at end of file diff --git a/src/dynamics/prognostic_variables.jl b/src/dynamics/prognostic_variables.jl index 02f69ad50..77208750b 100644 --- a/src/dynamics/prognostic_variables.jl +++ b/src/dynamics/prognostic_variables.jl @@ -13,7 +13,7 @@ Base.@kwdef mutable struct Clock "period to integrate for, set in set_period!(::Clock, ::Dates.Period)" period::Second = Second(0) - "number of time steps to integrate for, set in initialize!(::Clock,::TimeStepper)" + "number of time steps to integrate for, set in initialize!(::Clock,::AbstractTimeStepper)" n_timesteps::Int = 0 end @@ -27,7 +27,7 @@ end """ $(TYPEDSIGNATURES) Initialize the clock with the time step `Δt` in the `time_stepping`.""" -function initialize!(clock::Clock,time_stepping::TimeStepper) +function initialize!(clock::Clock, time_stepping::AbstractTimeStepper) clock.n_timesteps = ceil(Int,clock.period.value/time_stepping.Δt_sec) return clock end @@ -58,6 +58,7 @@ end # how many time steps have to be stored for the time integration? Leapfrog = 2 const N_STEPS = 2 const LTM = LowerTriangularMatrix # just because it's shorter here +export PrognosticVariablesLayer """A layer of the prognostic variables in spectral space. $(TYPEDFIELDS)""" @@ -176,6 +177,7 @@ function PrognosticSurfaceTimesteps(SG::SpectralGrid) return PrognosticSurfaceTimesteps([PrognosticVariablesSurface(SG) for _ in 1:N_STEPS]) end +export PrognosticVariables struct PrognosticVariables{NF<:AbstractFloat,Grid<:AbstractGrid{NF},M<:ModelSetup} <: AbstractVariables # dimensions diff --git a/src/dynamics/spectral_grid.jl b/src/dynamics/spectral_grid.jl index 79ed38c12..daabf6dd6 100644 --- a/src/dynamics/spectral_grid.jl +++ b/src/dynamics/spectral_grid.jl @@ -1,3 +1,6 @@ +abstract type AbstractSpectralGrid end +abstract type AbstractGeometry end + const DEFAULT_NF = Float32 const DEFAULT_MODEL = PrimitiveDry const DEFAULT_GRID = OctahedralGaussianGrid @@ -5,6 +8,8 @@ const DEFAULT_RADIUS = 6.371e6 const DEFAULT_TRUNC = 31 const DEFAULT_NLEV = 8 +export SpectralGrid + """ Defines the horizontal spectral resolution and corresponding grid and the vertical coordinate for SpeedyWeather.jl. Options are @@ -12,7 +17,7 @@ $(TYPEDFIELDS) `nlat_half` and `npoints` should not be chosen but are derived from `trunc`, `Grid` and `dealiasing`.""" -Base.@kwdef struct SpectralGrid +Base.@kwdef struct SpectralGrid <: AbstractSpectralGrid "number format used throughout the model" NF::Type{<:AbstractFloat} = DEFAULT_NF @@ -79,113 +84,6 @@ function Base.show(io::IO,SG::SpectralGrid) print(io,"└ Vertical: $nlev-level $(typeof(vertical_coordinates))") end -""" -$(TYPEDSIGNATURES) -Construct Geometry struct containing parameters and arrays describing an iso-latitude grid <:AbstractGrid -and the vertical levels. Pass on `SpectralGrid` to calculate the following fields -$(TYPEDFIELDS) -""" -Base.@kwdef struct Geometry{NF<:AbstractFloat} <: AbstractGeometry{NF} # NF: Number format - - "SpectralGrid that defines spectral and grid resolution" - spectral_grid::SpectralGrid - - "grid of the dynamical core" - Grid::Type{<:AbstractGrid} = spectral_grid.Grid - - "resolution parameter nlat_half of Grid, # of latitudes on one hemisphere (incl Equator)" - nlat_half::Int = spectral_grid.nlat_half - - - # GRID-POINT SPACE - "maximum number of longitudes (at/around Equator)" - nlon_max::Int = get_nlon_max(Grid,nlat_half) - - "=nlon_max, same (used for compatibility), TODO: still needed?" - nlon::Int = nlon_max - - "number of latitude rings" - nlat::Int = get_nlat(Grid,nlat_half) - - "number of vertical levels" - nlev::Int = spectral_grid.nlev - - "total number of grid points" - npoints::Int = spectral_grid.npoints - - "Planet's radius [m]" - radius::NF = spectral_grid.radius - - - # ARRAYS OF LANGITUDES/LONGITUDES - "array of colatitudes in radians (0...π)" - colat::Vector{Float64} = get_colat(Grid,nlat_half) - - "array of latitudes in radians (π...-π)" - lat::Vector{NF} = get_lat(Grid,nlat_half) - - "array of latitudes in degrees (90˚...-90˚)" - latd::Vector{Float64} = get_latd(Grid,nlat_half) - - "array of longitudes in degrees (0...360˚), empty for non-full grids" - lond::Vector{Float64} = get_lond(Grid,nlat_half) - - "longitude (0˚...360˚) for each grid point in ring order" - londs::Vector{NF} = get_latdlonds(Grid,nlat_half)[2] - - "latitude (-90˚...˚90) for each grid point in ring order" - latds::Vector{NF} = get_latdlonds(Grid,nlat_half)[1] - - "longitude (0...2π) for each grid point in ring order" - lons::Vector{NF} = RingGrids.get_latlons(Grid,nlat_half)[2] - - "latitude (-π/2...π/2) for each grid point in ring order" - lats::Vector{NF} = RingGrids.get_latlons(Grid,nlat_half)[1] - - "sin of latitudes" - sinlat::Vector{NF} = sind.(latd) - - "cos of latitudes" - coslat::Vector{NF} = cosd.(latd) - - "= 1/cos(lat)" - coslat⁻¹::Vector{NF} = 1 ./ coslat - - "= cos²(lat)" - coslat²::Vector{NF} = coslat.^2 - - "# = 1/cos²(lat)" - coslat⁻²::Vector{NF} = 1 ./ coslat² - - - # VERTICAL SIGMA COORDINATE σ = p/p0 (fraction of surface pressure) - "σ at half levels, σ_k+1/2" - σ_levels_half::Vector{NF} = spectral_grid.vertical_coordinates.σ_half - - "σ at full levels, σₖ" - σ_levels_full::Vector{NF} = 0.5*(σ_levels_half[2:end] + σ_levels_half[1:end-1]) - - "σ level thicknesses, σₖ₊₁ - σₖ" - σ_levels_thick::Vector{NF} = σ_levels_half[2:end] - σ_levels_half[1:end-1] - - "log of σ at full levels, include surface (σ=1) as last element" - ln_σ_levels_full::Vector{NF} = log.(vcat(σ_levels_full,1)) - - "Full to half levels interpolation" - full_to_half_interpolation::Vector{NF} = σ_interpolation_weights(σ_levels_full,σ_levels_half) -end - -""" -$(TYPEDSIGNATURES) -Generator function for `Geometry` struct based on `spectral_grid`.""" -function Geometry(spectral_grid::SpectralGrid) - return Geometry{spectral_grid.NF}(;spectral_grid) -end - -function Base.show(io::IO,G::Geometry) - print(io,"$(typeof(G)) for $(G.spectral_grid)") -end - """ $(TYPEDSIGNATURES) Generator function for a SpectralTransform struct pulling in parameters from a SpectralGrid struct.""" diff --git a/src/dynamics/tendencies.jl b/src/dynamics/tendencies.jl index d5755f654..41ec89cd8 100644 --- a/src/dynamics/tendencies.jl +++ b/src/dynamics/tendencies.jl @@ -5,9 +5,9 @@ function dynamics_tendencies!( diagn::DiagnosticVariablesLayer, progn::PrognosticVariablesLayer, time::DateTime, model::Barotropic) - forcing!(diagn,progn,model.forcing,time,model) # = (Fᵤ, Fᵥ) forcing for u,v - drag!(diagn,progn,model.drag,time,model) # drag term for u,v - vorticity_flux!(diagn,model) # = ∇×(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ) + forcing!(diagn, progn, model.forcing, time, model) # = (Fᵤ, Fᵥ) forcing for u,v + drag!(diagn,progn, model.drag, time, model) # drag term for u,v + vorticity_flux!(diagn,model) # = ∇×(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ) end """ @@ -168,7 +168,7 @@ end """Convert absolute and virtual temperature to anomalies wrt to the reference profile""" function temperature_anomaly!( diagn::DiagnosticVariablesLayer, - I::ImplicitPrimitiveEq, + I::ImplicitPrimitiveEquation, ) Tₖ = I.temp_profile[diagn.k] # reference temperature on this layer @@ -335,12 +335,14 @@ spectral space function vordiv_tendencies!( diagn::DiagnosticVariablesLayer, surf::SurfaceVariables, - C::DynamicsConstants, - G::Geometry, + coriolis::AbstractCoriolis, + atmosphere::AbstractAtmosphere, + geometry::AbstractGeometry, S::SpectralTransform, ) - (;R_dry, f_coriolis) = C - (;coslat⁻¹) = G + (;R_dry) = atmosphere # gas constant for dry air + (;f) = coriolis # coriolis parameter + (;coslat⁻¹) = geometry (;u_tend_grid, v_tend_grid) = diagn.tendencies # already contains vertical advection u = diagn.grid_variables.u_grid # velocity @@ -355,9 +357,9 @@ function vordiv_tendencies!( @inbounds for (j,ring) in enumerate(rings) coslat⁻¹j = coslat⁻¹[j] - f = f_coriolis[j] + f_j = f[j] for ij in ring - ω = vor[ij] + f # absolute vorticity + ω = vor[ij] + f_j # absolute vorticity RTᵥ = R_dry*Tᵥ[ij] # dry gas constant * virtual temperature anomaly u_tend_grid[ij] = (u_tend_grid[ij] + v[ij]*ω - RTᵥ*∇lnp_x[ij])*coslat⁻¹j v_tend_grid[ij] = (v_tend_grid[ij] - u[ij]*ω - RTᵥ*∇lnp_y[ij])*coslat⁻¹j @@ -431,8 +433,8 @@ function temperature_tendency!( diagn::DiagnosticVariablesLayer, model::PrimitiveEquation, ) - temperature_tendency!(diagn,model.constants,model.geometry, - model.spectral_transform,model.implicit) + temperature_tendency!(diagn, model.atmosphere, model.geometry, + model.spectral_transform, model.implicit) end """ @@ -446,16 +448,16 @@ Compute the temperature tendency temperature used in the adiabatic term κTᵥ*Dlnp/Dt.""" function temperature_tendency!( diagn::DiagnosticVariablesLayer, - C::DynamicsConstants, + atmosphere::AbstractAtmosphere, G::Geometry, S::SpectralTransform, - I::ImplicitPrimitiveEq, + I::ImplicitPrimitiveEquation, ) (;temp_tend, temp_tend_grid) = diagn.tendencies (;div_grid, temp_grid) = diagn.grid_variables (;uv∇lnp, uv∇lnp_sum_above, div_sum_above) = diagn.dynamics_variables - (;κ) = C # thermodynamic kappa + (;κ) = atmosphere # thermodynamic kappa = R_Dry/heat_capacity Tᵥ = diagn.grid_variables.temp_virt_grid # anomaly wrt to Tₖ Tₖ = I.temp_profile[diagn.k] # average layer temperature from reference profile @@ -585,13 +587,13 @@ with `Fᵤ,Fᵥ` from `u_tend_grid`/`v_tend_grid` that are assumed to be alread set in `forcing!`. Set `div=false` for the BarotropicModel which doesn't require the divergence tendency.""" function vorticity_flux_curldiv!( diagn::DiagnosticVariablesLayer, - C::DynamicsConstants, + C::AbstractCoriolis, G::Geometry, S::SpectralTransform; div::Bool=true, # also calculate div of vor flux? add::Bool=false) # accumulate in vor/div tendencies? - (;f_coriolis) = C + (;f) = C (;coslat⁻¹) = G (;u_tend_grid, v_tend_grid) = diagn.tendencies # already contains forcing @@ -604,9 +606,9 @@ function vorticity_flux_curldiv!( diagn::DiagnosticVariablesLayer, @inbounds for (j,ring) in enumerate(rings) coslat⁻¹j = coslat⁻¹[j] - f = f_coriolis[j] + f_j = f[j] for ij in ring - ω = vor[ij] + f # absolute vorticity + ω = vor[ij] + f_j # absolute vorticity u_tend_grid[ij] = (u_tend_grid[ij] + v[ij]*ω)*coslat⁻¹j v_tend_grid[ij] = (v_tend_grid[ij] - u[ij]*ω)*coslat⁻¹j end @@ -640,10 +642,10 @@ with with Fᵤ,Fᵥ the forcing from `forcing!` already in `u_tend_grid`/`v_tend_grid` and vorticity ζ, coriolis f.""" function vorticity_flux!(diagn::DiagnosticVariablesLayer,model::ShallowWater) - C = model.constants + C = model.coriolis G = model.geometry S = model.spectral_transform - vorticity_flux_curldiv!(diagn,C,G,S,div=true,add=true) + vorticity_flux_curldiv!(diagn, C, G, S, div=true, add=true) end """ @@ -660,10 +662,10 @@ with with Fᵤ,Fᵥ the forcing from `forcing!` already in `u_tend_grid`/`v_tend_grid` and vorticity ζ, coriolis f.""" function vorticity_flux!(diagn::DiagnosticVariablesLayer,model::Barotropic) - C = model.constants + C = model.coriolis G = model.geometry S = model.spectral_transform - vorticity_flux_curldiv!(diagn,C,G,S,div=false,add=true) + vorticity_flux_curldiv!(diagn, C, G, S, div=false, add=true) end """ @@ -707,10 +709,10 @@ function linear_pressure_gradient!( diagn::DiagnosticVariablesLayer, surface::PrognosticSurfaceTimesteps, lf::Int, # leapfrog index to evaluate tendencies on - C::DynamicsConstants, - I::ImplicitPrimitiveEq, + atmosphere::AbstractAtmosphere, + I::ImplicitPrimitiveEquation, ) - (;R_dry) = C # dry gas constant + (;R_dry) = atmosphere # dry gas constant Tₖ = I.temp_profile[diagn.k] # reference profile at layer k (;pres) = surface.timesteps[lf] # logarithm of surface pressure (;geopot) = diagn.dynamics_variables @@ -728,13 +730,13 @@ Computes the (negative) divergence of the volume fluxes `uh,vh` for the continui function volume_flux_divergence!( diagn::DiagnosticVariablesLayer, surface::SurfaceVariables, orog::AbstractOrography, - constants::DynamicsConstants, - G::Geometry, + atmosphere::AbstractAtmosphere, + G::AbstractGeometry, S::SpectralTransform) (; pres_grid, pres_tend ) = surface (; orography ) = orog - H = constants.layer_thickness + H = atmosphere.layer_thickness # compute dynamic layer thickness h on the grid # pres_grid is η, the interface displacement, update to @@ -744,7 +746,7 @@ function volume_flux_divergence!( diagn::DiagnosticVariablesLayer, pres_grid .+= H .- orography # now do -∇⋅(uh,vh) and store in pres_tend - flux_divergence!(pres_tend,pres_grid,diagn,G,S,add=true,flipsign=true) + flux_divergence!(pres_tend, pres_grid, diagn, G, S, add=true, flipsign=true) end """ @@ -787,16 +789,16 @@ function SpeedyTransforms.gridded!( diagn::DiagnosticVariablesLayer, V = diagn.dynamics_variables.b # U = u*coslat, V=v*coslat S = model.spectral_transform - gridded!(vor_grid,vor,S) # get vorticity on grid from spectral vor + gridded!(vor_grid, vor, S) # get vorticity on grid from spectral vor # get spectral U,V from spectral vorticity via stream function Ψ # U = u*coslat = -coslat*∂Ψ/∂lat # V = v*coslat = ∂Ψ/∂lon, radius omitted in both cases - UV_from_vor!(U,V,vor,S) + UV_from_vor!(U, V, vor, S) # transform from U,V in spectral to u,v on grid (U,V = u,v*coslat) - gridded!(u_grid,U,S,unscale_coslat=true) - gridded!(v_grid,V,S,unscale_coslat=true) + gridded!(u_grid, U, S, unscale_coslat=true) + gridded!(v_grid, V, S, unscale_coslat=true) return nothing end diff --git a/src/dynamics/time_integration.jl b/src/dynamics/time_integration.jl index c0c4cdb20..285408bd4 100644 --- a/src/dynamics/time_integration.jl +++ b/src/dynamics/time_integration.jl @@ -1,8 +1,12 @@ +abstract type AbstractTimeStepper <: AbstractModelComponent end + +export Leapfrog + """ Leapfrog time stepping defined by the following fields $(TYPEDFIELDS) """ -Base.@kwdef mutable struct Leapfrog{NF} <: TimeStepper{NF} +Base.@kwdef mutable struct Leapfrog{NF<:AbstractFloat} <: AbstractTimeStepper # DIMENSIONS "spectral resolution (max degree of spherical harmonics)" @@ -92,12 +96,6 @@ function Leapfrog(spectral_grid::SpectralGrid;kwargs...) return Leapfrog{NF}(;trunc,radius,kwargs...) end -function Base.show(io::IO,L::Leapfrog) - println(io,"$(typeof(L)) <: TimeStepper") - keys = propertynames(L) - print_fields(io,L,keys) -end - """ $(TYPEDSIGNATURES) Initialize leapfrogging `L` by recalculating the timestep given the output time step diff --git a/src/dynamics/vertical_coordinates.jl b/src/dynamics/vertical_coordinates.jl index 6a6de8dc5..db284f510 100644 --- a/src/dynamics/vertical_coordinates.jl +++ b/src/dynamics/vertical_coordinates.jl @@ -1,7 +1,10 @@ +abstract type VerticalCoordinates end + Base.@kwdef struct NoVerticalCoordinates <: VerticalCoordinates nlev::Int = 1 end +export SigmaCoordinates Base.@kwdef struct SigmaCoordinates <: VerticalCoordinates nlev::Int = 8 σ_half::Vector{Float64} = default_sigma_coordinates(nlev) @@ -54,13 +57,6 @@ function sigma_okay(nlev::Integer,σ_half::AbstractVector) return true end -#TODO -Base.@kwdef struct SigmaPressureCoordinates <: VerticalCoordinates - nlev::Int = 8 - A::Vector{Float64} = default_hybrid_coordinates(:A,nlev) - B::Vector{Float64} = default_hybrid_coordinates(:B,nlev) -end - # currently not used as the grid has to be defined first default_vertical_coordinates(::Type{<:Barotropic}) = NoVerticalCoordinates default_vertical_coordinates(::Type{<:ShallowWater}) = NoVerticalCoordinates diff --git a/src/dynamics/vertical_interpolation.jl b/src/dynamics/vertical_interpolation.jl deleted file mode 100644 index 5b4ceda4c..000000000 --- a/src/dynamics/vertical_interpolation.jl +++ /dev/null @@ -1,54 +0,0 @@ -""" -$(TYPEDSIGNATURES) -Interpolation weights for full to half level interpolation -on sigma coordinates. Following Fortran SPEEDY documentation eq. (1).""" -function σ_interpolation_weights( - σ_levels_full::AbstractVector, - σ_levels_half::AbstractVector) - - weights = zero(σ_levels_full) - nlev = length(weights) - nlev == 1 && return weights # escape early for 1 layer to avoid out-of-bounds access - - for k in 1:nlev-1 - weights[k] = (log(σ_levels_half[k+1]) - log(σ_levels_full[k])) / - (log(σ_levels_full[k+1]) - log(σ_levels_full[k])) - end - # was log(0.99) in Fortran SPEEDY code but doesn't make sense to me - weights[end] = (log(σ_levels_half[nlev+1]) - log(σ_levels_full[nlev])) / - (log(σ_levels_full[nlev]) - log(σ_levels_full[nlev-1])) - - return weights -end - - -""" -$(TYPEDSIGNATURES) -Given a vector in column defined at full levels, do a linear interpolation in -log(σ) to calculate its values at half-levels, skipping top (k=1/2), extrapolating to bottom (k=NLEV+1/2). -""" -function vertical_interpolate!( - A_half::Vector, # quantity A on half levels (excl top) - A_full::Vector, # quantity A on full levels - G::Geometry, -) - nlev = length(A_half) - weights = G.full_to_half_interpolation - - # full levels contain one more for surface - # TODO this is currently confusing because the surface fluxes use full[end] - # as surface value which is technically on half levels though! - @boundscheck nlev <= length(A_full) || throw(BoundsError) - @boundscheck nlev <= length(weights) || throw(BoundsError) - - # For A at each full level k, compute A at the half-level below, i.e. at the boundary - # between the full levels k and k+1. Fortran SPEEDY documentation eq. (1) - for k = 1:nlev-1 - A_half[k] = A_full[k] + weights[k]*(A_full[k+1] - A_full[k]) - end - - # Compute the values at the surface separately - A_half[nlev] = A_full[nlev] + weights[nlev]*(A_full[nlev] - A_full[nlev-1]) - - return nothing -end \ No newline at end of file diff --git a/src/dynamics/virtual_temperature.jl b/src/dynamics/virtual_temperature.jl new file mode 100644 index 000000000..63ffe7f19 --- /dev/null +++ b/src/dynamics/virtual_temperature.jl @@ -0,0 +1,102 @@ +# function barrier +function virtual_temperature!( diagn::DiagnosticVariablesLayer, + temp::LowerTriangularMatrix, # only needed for dispatch compat with DryCore + model::PrimitiveWet) + virtual_temperature!(diagn,temp,model.constants) +end + +""" +$(TYPEDSIGNATURES) +Calculates the virtual temperature Tᵥ as + + Tᵥ = T(1+μq) + +With absolute temperature T, specific humidity q and + + μ = (1-ξ)/ξ, ξ = R_dry/R_vapour. + +in grid-point space.""" +function virtual_temperature!( + diagn::DiagnosticVariablesLayer, + temp::LowerTriangularMatrix, # only needed for dispatch compat with DryCore + constants::DynamicsConstants, + ) + + (;temp_grid, humid_grid, temp_virt_grid) = diagn.grid_variables + (;temp_virt) = diagn.dynamics_variables + μ = constants.μ_virt_temp + + @inbounds for ij in eachgridpoint(temp_virt_grid, temp_grid, humid_grid) + temp_virt_grid[ij] = temp_grid[ij]*(1 + μ*humid_grid[ij]) + end + # TODO check that doing a non-linear virtual temperature in grid-point space + # but a linear virtual temperature in spectral space to avoid another transform + # does not cause any problems. Alternative do the transform or have a linear + # virtual temperature in both grid and spectral space + # spectral!(temp_virt,temp_virt_grid,S) +end + +""" +$(TYPEDSIGNATURES) +Virtual temperature in grid-point space: For the PrimitiveDry temperature +and virtual temperature are the same (humidity=0). Just copy over the arrays.""" +function virtual_temperature!( diagn::DiagnosticVariablesLayer, + temp::LowerTriangularMatrix, + model::PrimitiveDry) + + (;temp_grid, temp_virt_grid) = diagn.grid_variables + (;temp_virt) = diagn.dynamics_variables + + copyto!(temp_virt_grid,temp_grid) +end + +""" +$(TYPEDSIGNATURES) +Linear virtual temperature for `model::PrimitiveDry`: Just copy over +arrays from `temp` to `temp_virt` at timestep `lf` in spectral space +as humidity is zero in this `model`.""" +function linear_virtual_temperature!( + diagn::DiagnosticVariablesLayer, + progn::PrognosticLayerTimesteps, + model::PrimitiveDry, + lf::Integer, +) + (;temp_virt) = diagn.dynamics_variables + (;temp) = progn.timesteps[lf] + copyto!(temp_virt,temp) +end + +# function barrier +function linear_virtual_temperature!( + diagn::DiagnosticVariablesLayer, + progn::PrognosticLayerTimesteps, + model::PrimitiveWet, + lf::Integer, +) + linear_virtual_temperature!(diagn,progn,model.constants,lf) +end + +""" +$(TYPEDSIGNATURES) +Calculates a linearised virtual temperature Tᵥ as + + Tᵥ = T + Tₖμq + +With absolute temperature T, layer-average temperarture Tₖ (computed in temperature_average!), +specific humidity q and + + μ = (1-ξ)/ξ, ξ = R_dry/R_vapour. + +in spectral space.""" +function linear_virtual_temperature!( diagn::DiagnosticVariablesLayer, + progn::PrognosticLayerTimesteps, + constants::DynamicsConstants, + lf::Int) + + (;temp_virt) = diagn.dynamics_variables + μ = constants.μ_virt_temp + Tₖ = diagn.temp_average[] + (;temp,humid) = progn.timesteps[lf] + + @. temp_virt = temp + (Tₖ*μ)*humid +end \ No newline at end of file diff --git a/src/models/abstract_models.jl b/src/models/abstract_models.jl new file mode 100644 index 000000000..c822a30b4 --- /dev/null +++ b/src/models/abstract_models.jl @@ -0,0 +1,33 @@ +export Barotropic, ShallowWater, PrimitiveEquation, PrimitiveDry, PrimitiveWet, ModelSetup + +abstract type AbstractSimulation{Model} end +abstract type ModelSetup end +abstract type Barotropic <: ModelSetup end +abstract type ShallowWater <: ModelSetup end +abstract type PrimitiveEquation <: ModelSetup end +abstract type PrimitiveDry <: PrimitiveEquation end +abstract type PrimitiveWet <: PrimitiveEquation end + +abstract type AbstractModelComponent end + +"""$(TYPEDSIGNATURES) +Returns true if the model `M` has a prognostic variable `var_name`, false otherwise. +The default fallback is that all variables are included. """ +has(M::Type{<:ModelSetup}, var_name::Symbol) = var_name in (:vor, :div, :temp, :humid, :pres) +has(M::ModelSetup, var_name) = has(typeof(M), var_name) + +# strip away the parameters of the model type +model_class(::Type{<:Barotropic}) = Barotropic +model_class(::Type{<:ShallowWater}) = ShallowWater +model_class(::Type{<:PrimitiveDry}) = PrimitiveDry +model_class(::Type{<:PrimitiveWet}) = PrimitiveWet +model_class(model::ModelSetup) = model_class(typeof(model)) + +function Base.show(io::IO,M::ModelSetup) + println(io,"$(typeof(M))") + for key in propertynames(M)[1:end-1] + val = getfield(M,key) + println(io,"├ $key: $(typeof(val))") + end + print(io,"└ feedback: $(typeof(M.feedback))") +end \ No newline at end of file diff --git a/src/models/barotropic.jl b/src/models/barotropic.jl new file mode 100644 index 000000000..0aaaac123 --- /dev/null +++ b/src/models/barotropic.jl @@ -0,0 +1,79 @@ +export BarotropicModel, initialize! + +""" +$(SIGNATURES) +The BarotropicModel struct holds all other structs that contain precalculated constants, +whether scalars or arrays that do not change throughout model integration. +$(TYPEDFIELDS)""" +Base.@kwdef mutable struct BarotropicModel{ + NF<:AbstractFloat, + PL<:AbstractPlanet, + AT<:AbstractAtmosphere, + CO<:AbstractCoriolis, + FR<:AbstractForcing, + DR<:AbstractDrag, + IC<:InitialConditions, + TS<:AbstractTimeStepper, + ST<:SpectralTransform{NF}, + HD<:AbstractHorizontalDiffusion, + GE<:AbstractGeometry, + DS<:AbstractDevice, + OW<:AbstractOutputWriter, + FB<:AbstractFeedback +} <: Barotropic + + spectral_grid::SpectralGrid = SpectralGrid(nlev=1) + + # DYNAMICS + planet::PL = Earth(spectral_grid) + atmosphere::AT = EarthAtmosphere(spectral_grid) + coriolis::CO = Coriolis(spectral_grid) + forcing::FR = NoForcing() + drag::DR = NoDrag() + initial_conditions::IC = StartWithRandomVorticity() + + # NUMERICS + time_stepping::TS = Leapfrog(spectral_grid) + spectral_transform::ST = SpectralTransform(spectral_grid) + horizontal_diffusion::HD = HyperDiffusion(spectral_grid) + geometry::GE = Geometry(spectral_grid) + + # INTERNALS + device_setup::DS = DeviceSetup(CPUDevice()) + + # OUTPUT + output::OW = OutputWriter(spectral_grid,Barotropic) + feedback::FB = Feedback() +end + +has(::Type{<:Barotropic}, var_name::Symbol) = var_name in (:vor,) +default_concrete_model(::Type{Barotropic}) = BarotropicModel + +""" +$(TYPEDSIGNATURES) +Calls all `initialize!` functions for components of `model`, +except for `model.output` and `model.feedback` which are always called +at in `time_stepping!`.""" +function initialize!(model::Barotropic; time::DateTime = DEFAULT_DATE) + (;spectral_grid) = model + + spectral_grid.nlev > 1 && @warn "Only nlev=1 supported for BarotropicModel, \ + SpectralGrid with nlev=$(spectral_grid.nlev) provided." + + # slightly adjust model time step to be a convenient divisor of output timestep + initialize!(model.time_stepping, model) + + # initialize components + initialize!(model.coriolis, model) + initialize!(model.forcing, model) + initialize!(model.drag, model) + initialize!(model.horizontal_diffusion, model) + + # initial conditions + prognostic_variables = PrognosticVariables(spectral_grid, model) + initialize!(prognostic_variables, model.initial_conditions, model) + prognostic_variables.clock.time = time # set the time + + diagnostic_variables = DiagnosticVariables(spectral_grid, model) + return Simulation(prognostic_variables, diagnostic_variables, model) +end \ No newline at end of file diff --git a/src/models/primitive_dry.jl b/src/models/primitive_dry.jl new file mode 100644 index 000000000..bc7b3310e --- /dev/null +++ b/src/models/primitive_dry.jl @@ -0,0 +1,101 @@ +export PrimitiveDryModel + +""" +$(SIGNATURES) +The PrimitiveDryModel struct holds all other structs that contain precalculated constants, +whether scalars or arrays that do not change throughout model integration. +$(TYPEDFIELDS)""" +Base.@kwdef mutable struct PrimitiveDryModel{NF<:AbstractFloat, D<:AbstractDevice} <: PrimitiveDry + spectral_grid::SpectralGrid = SpectralGrid() + + # DYNAMICS + dynamics::Bool = true + planet::AbstractPlanet = Earth() + atmosphere::AbstractAtmosphere = EarthAtmosphere() + coriolis::AbstractCoriolis = Coriolis(spectral_grid) + initial_conditions::InitialConditions = ZonalWind() + orography::AbstractOrography{NF} = EarthOrography(spectral_grid) + adiabatic_conversion::AbstractAdiabaticConversion = AdiabaticConversion(spectral_grid) + + # BOUNDARY CONDITIONS + land_sea_mask::AbstractLandSeaMask{NF} = LandSeaMask(spectral_grid) + ocean::AbstractOcean{NF} = SeasonalOceanClimatology(spectral_grid) + land::AbstractLand{NF} = SeasonalLandTemperature(spectral_grid) + solar_zenith::AbstractZenith{NF} = WhichZenith(spectral_grid,planet) + + # PHYSICS/PARAMETERIZATIONS + physics::Bool = true + boundary_layer_drag::BoundaryLayerDrag{NF} = BulkRichardsonDrag(spectral_grid) + temperature_relaxation::TemperatureRelaxation{NF} = NoTemperatureRelaxation(spectral_grid) + static_energy_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) + surface_thermodynamics::AbstractSurfaceThermodynamics{NF} = SurfaceThermodynamicsConstant(spectral_grid) + surface_wind::AbstractSurfaceWind{NF} = SurfaceWind(spectral_grid) + surface_heat_flux::AbstractSurfaceHeat{NF} = SurfaceSensibleHeat(spectral_grid) + shortwave_radiation::AbstractShortwave{NF} = NoShortwave(spectral_grid) + longwave_radiation::AbstractLongwave{NF} = UniformCooling(spectral_grid) + + # NUMERICS + time_stepping::TimeStepper = Leapfrog(spectral_grid) + spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) + horizontal_diffusion::HorizontalDiffusion = HyperDiffusion(spectral_grid) + implicit::AbstractImplicit = ImplicitPrimitiveEquation(spectral_grid) + vertical_advection::VerticalAdvection{NF} = CenteredVerticalAdvection(spectral_grid) + + # INTERNALS + geometry::AbstractGeometry = Geometry(spectral_grid) + constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) + device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) + + # OUTPUT + output::AbstractOutputWriter = OutputWriter(spectral_grid,PrimitiveDry) + feedback::AbstractFeedback = Feedback() +end + +has(::Type{<:PrimitiveDry}, var_name::Symbol) = var_name in (:vor, :div, :temp, :pres) +default_concrete_model(::Type{PrimitiveDry}) = PrimitiveDryModel + +""" +$(TYPEDSIGNATURES) +Calls all `initialize!` functions for components of `model`, +except for `model.output` and `model.feedback` which are always called +at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" +function initialize!(model::PrimitiveDry;time::DateTime = DEFAULT_DATE) + (;spectral_grid) = model + + # NUMERICS (implicit is initialized later) + # slightly adjust model time step to be a convenient divisor of output timestep + initialize!(model.time_stepping, model) + initialize!(model.horizontal_diffusion, model) + + # DYNAMICS + initialize!(model.coriolis, model) + initialize!(model.gepotential, model) + initialize!(model.adiabatic_conversion, model) + + # boundary conditionss + initialize!(model.orography, model) + initialize!(model.land_sea_mask, model) + initialize!(model.ocean, model) + initialize!(model.land, model) + initialize!(model.solar_zenith, time, model) + + # parameterizations + initialize!(model.boundary_layer_drag,model) + initialize!(model.temperature_relaxation,model) + initialize!(model.static_energy_diffusion,model) + initialize!(model.shortwave_radiation,model) + initialize!(model.longwave_radiation,model) + + # initial conditions + prognostic_variables = PrognosticVariables(spectral_grid, model) + initialize!(prognostic_variables, model.initial_conditions, model) + (;clock) = prognostic_variables + clock.time = time # set the time + + # initialize ocean and land and synchronize clocks + initialize!(prognostic_variables.ocean, clock.time, model) + initialize!(prognostic_variables.land, clock.time, model) + + diagnostic_variables = DiagnosticVariables(spectral_grid,model) + return Simulation(prognostic_variables,diagnostic_variables,model) +end \ No newline at end of file diff --git a/src/models/primitive_wet.jl b/src/models/primitive_wet.jl new file mode 100644 index 000000000..d13f24975 --- /dev/null +++ b/src/models/primitive_wet.jl @@ -0,0 +1,115 @@ +export PrimitiveWetModel + +""" +$(SIGNATURES) +The PrimitiveDryModel struct holds all other structs that contain precalculated constants, +whether scalars or arrays that do not change throughout model integration. +$(TYPEDFIELDS)""" +Base.@kwdef mutable struct PrimitiveWetModel{NF<:AbstractFloat, D<:AbstractDevice} <: PrimitiveWet + spectral_grid::SpectralGrid = SpectralGrid() + + # DYNAMICS + dynamics::Bool = true + planet::AbstractPlanet = Earth() + atmosphere::AbstractAtmosphere = EarthAtmosphere() + coriolis::AbstractCoriolis = Coriolis(spectral_grid) + initial_conditions::InitialConditions = ZonalWind() + orography::AbstractOrography{NF} = EarthOrography(spectral_grid) + geopotential::AbstractGeopotential = Geopotential(spectral_grid) + adiabatic_conversion::AbstractAdiabaticConversion = AdiabaticConversion(spectral_grid) + + # BOUNDARY CONDITIONS + land_sea_mask::AbstractLandSeaMask{NF} = LandSeaMask(spectral_grid) + ocean::AbstractOcean{NF} = SeasonalOceanClimatology(spectral_grid) + land::AbstractLand{NF} = SeasonalLandTemperature(spectral_grid) + soil::AbstractSoil{NF} = SeasonalSoilMoisture(spectral_grid) + vegetation::AbstractVegetation{NF} = VegetationClimatology(spectral_grid) + solar_zenith::AbstractZenith{NF} = WhichZenith(spectral_grid,planet) + + # PHYSICS/PARAMETERIZATIONS + physics::Bool = true + clausius_clapeyron::AbstractClausiusClapeyron{NF} = ClausiusClapeyron(spectral_grid,atmosphere) + boundary_layer_drag::BoundaryLayerDrag{NF} = BulkRichardsonDrag(spectral_grid) + temperature_relaxation::TemperatureRelaxation{NF} = NoTemperatureRelaxation(spectral_grid) + static_energy_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) + humidity_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) + large_scale_condensation::AbstractCondensation{NF} = ImplicitCondensation(spectral_grid) + surface_thermodynamics::AbstractSurfaceThermodynamics{NF} = SurfaceThermodynamicsConstant(spectral_grid) + surface_wind::AbstractSurfaceWind{NF} = SurfaceWind(spectral_grid) + surface_heat_flux::AbstractSurfaceHeat{NF} = SurfaceSensibleHeat(spectral_grid) + evaporation::AbstractEvaporation{NF} = SurfaceEvaporation(spectral_grid) + convection::AbstractConvection{NF} = SimplifiedBettsMiller(spectral_grid) + shortwave_radiation::AbstractShortwave{NF} = NoShortwave(spectral_grid) + longwave_radiation::AbstractLongwave{NF} = UniformCooling(spectral_grid) + + # NUMERICS + time_stepping::TimeStepper = Leapfrog(spectral_grid) + spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) + horizontal_diffusion::HorizontalDiffusion = HyperDiffusion(spectral_grid) + implicit::AbstractImplicit = ImplicitPrimitiveEquation(spectral_grid) + vertical_advection::VerticalAdvection{NF} = CenteredVerticalAdvection(spectral_grid) + hole_filling::AbstractHoleFilling = ClipNegatives() + + # INTERNALS + geometry::AbstractGeometry = Geometry(spectral_grid) + constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) + device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) + + # OUTPUT + output::AbstractOutputWriter = OutputWriter(spectral_grid,PrimitiveWet) + feedback::AbstractFeedback = Feedback() +end + +has(::Type{<:PrimitiveWet}, var_name::Symbol) = var_name in (:vor, :div, :temp, :pres, :humid) +default_concrete_model(::Type{PrimitiveWet}) = PrimitiveWetModel + +""" +$(TYPEDSIGNATURES) +Calls all `initialize!` functions for components of `model`, +except for `model.output` and `model.feedback` which are always called +at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" +function initialize!(model::PrimitiveWet;time::DateTime = DEFAULT_DATE) + (;spectral_grid) = model + + # NUMERICS (implicit is initialized later) + # slightly adjust model time step to be a convenient divisor of output timestep + initialize!(model.time_stepping, model) + initialize!(model.horizontal_diffusion, model) + + # DYNAMICS + initialize!(model.coriolis, model) + initialize!(model.gepotential, model) + initialize!(model.adiabatic_conversion, model) + + # boundary conditionss + initialize!(model.orography, model) + initialize!(model.land_sea_mask, model) + initialize!(model.ocean, model) + initialize!(model.land, model) + initialize!(model.soil, model) + initialize!(model.vegetation, model) + initialize!(model.solar_zenith, time, model) + + # parameterizations + initialize!(model.boundary_layer_drag, model) + initialize!(model.temperature_relaxation, model) + initialize!(model.static_energy_diffusion, model) + initialize!(model.humidity_diffusion, model) + initialize!(model.large_scale_condensation, model) + initialize!(model.convection, model) + initialize!(model.shortwave_radiation, model) + initialize!(model.longwave_radiation, model) + + # initial conditions + prognostic_variables = PrognosticVariables(spectral_grid,model) + initialize!(prognostic_variables,model.initial_conditions,model) + (;clock) = prognostic_variables + clock.time = time # set the time + + # initialize ocean and land and synchronize clocks + initialize!(prognostic_variables.ocean, clock.time, model) + initialize!(prognostic_variables.land, clock.time, model) + + diagnostic_variables = DiagnosticVariables(spectral_grid,model) + return Simulation(prognostic_variables,diagnostic_variables,model) +end \ No newline at end of file diff --git a/src/models/shallow_water.jl b/src/models/shallow_water.jl new file mode 100644 index 000000000..5d8f473ce --- /dev/null +++ b/src/models/shallow_water.jl @@ -0,0 +1,66 @@ +export ShallowWaterModel + +""" +$(SIGNATURES) +The ShallowWaterModel struct holds all other structs that contain precalculated constants, +whether scalars or arrays that do not change throughout model integration. +$(TYPEDFIELDS)""" +Base.@kwdef mutable struct ShallowWaterModel{NF<:AbstractFloat, D<:AbstractDevice} <: ShallowWater + spectral_grid::SpectralGrid = SpectralGrid(nlev=1) + + # DYNAMICS + planet::AbstractPlanet = Earth() + atmosphere::AbstractAtmosphere = EarthAtmosphere() + coriolis::AbstractCoriolis = Coriolis(spectral_grid) + forcing::AbstractForcing = NoForcing() + drag::AbstractDrag = NoDrag() + initial_conditions::InitialConditions = ZonalJet() + orography::AbstractOrography{NF} = EarthOrography(spectral_grid) + + # NUMERICS + time_stepping::TimeStepper = Leapfrog(spectral_grid) + spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) + horizontal_diffusion::HorizontalDiffusion = HyperDiffusion(spectral_grid) + implicit::AbstractImplicit = ImplicitShallowWater(spectral_grid) + + # INTERNALS + geometry::AbstractGeometry = Geometry(spectral_grid) + constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) + device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) + + # OUTPUT + output::AbstractOutputWriter = OutputWriter(spectral_grid,ShallowWater) + feedback::AbstractFeedback = Feedback() +end + +has(::Type{<:ShallowWater}, var_name::Symbol) = var_name in (:vor, :div, :pres) +default_concrete_model(::Type{ShallowWater}) = ShallowWaterModel + +""" +$(TYPEDSIGNATURES) +Calls all `initialize!` functions for components of `model`, +except for `model.output` and `model.feedback` which are always called +at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" +function initialize!(model::ShallowWater;time::DateTime = DEFAULT_DATE) + (;spectral_grid) = model + + spectral_grid.nlev > 1 && @warn "Only nlev=1 supported for ShallowWaterModel, \ + SpectralGrid with nlev=$(spectral_grid.nlev) provided." + + # slightly adjust model time step to be a convenient divisor of output timestep + initialize!(model.time_stepping,model) + + # initialize components + initialize!(model.forcing,model) + initialize!(model.drag,model) + initialize!(model.horizontal_diffusion,model) + initialize!(model.orography,model) + + # initial conditions + prognostic_variables = PrognosticVariables(spectral_grid,model) + initialize!(prognostic_variables,model.initial_conditions,model) + prognostic_variables.clock.time = time # set the time + + diagnostic_variables = DiagnosticVariables(spectral_grid,model) + return Simulation(prognostic_variables,diagnostic_variables,model) +end \ No newline at end of file diff --git a/src/models/simulation.jl b/src/models/simulation.jl new file mode 100644 index 000000000..c19644dc1 --- /dev/null +++ b/src/models/simulation.jl @@ -0,0 +1,52 @@ +export Simulation + +""" +$(TYPEDSIGNATURES) +Simulation is a container struct to be used with `run!(::Simulation)`. +It contains +$(TYPEDFIELDS)""" +struct Simulation{Model<:ModelSetup} <: AbstractSimulation{Model} + "define the current state of the model" + prognostic_variables::PrognosticVariables + + "contain the tendencies and auxiliary arrays to compute them" + diagnostic_variables::DiagnosticVariables + + "all parameters, constant at runtime" + model::Model +end + +function Base.show(io::IO,S::AbstractSimulation) + println(io,"$(typeof(S))") + println(io,"├ $(typeof(S.model))") + println(io,"├ $(typeof(S.prognostic_variables))") + print(io, "└ $(typeof(S.diagnostic_variables))") +end + +export run! + +""" +$(TYPEDSIGNATURES) +Run a SpeedyWeather.jl `simulation`. The `simulation.model` is assumed to be initialized.""" +function run!( simulation::AbstractSimulation; + period = Day(10), + output::Bool = false, + n_days::Union{Nothing, Real}=nothing) + + if !isnothing(n_days) + @warn "run!: n_days keyword is deprecated, use period = Day(n_days) instead." + period = Day(n_days) + end + + (;prognostic_variables, diagnostic_variables, model) = simulation + (;clock) = prognostic_variables + + # set the clock's enddate + set_period!(clock, period) + initialize!(clock, model.time_stepping) + + model.output.output = output # enable/disable output + + # run it, yeah! + time_stepping!(prognostic_variables,diagnostic_variables,model) +end diff --git a/src/output/feedback.jl b/src/output/feedback.jl index ed1ad6344..0f7248ba8 100644 --- a/src/output/feedback.jl +++ b/src/output/feedback.jl @@ -1,3 +1,6 @@ +abstract type AbstractFeedback end +export Feedback + """ Feedback struct that contains options and object for command-line feedback like the progress meter. diff --git a/src/output/output.jl b/src/output/output.jl index 104515492..ac384ea73 100644 --- a/src/output/output.jl +++ b/src/output/output.jl @@ -1,30 +1,12 @@ -""" -Number of mantissa bits to keep for each prognostic variable when compressed for -netCDF and .jld2 data output. -$(TYPEDFIELDS)""" -Base.@kwdef struct Keepbits - u::Int = 7 - v::Int = 7 - vor::Int = 5 - div::Int = 5 - temp::Int = 10 - pres::Int = 12 - humid::Int = 7 - precip_cond::Int = 7 - precip_conv::Int = 7 - cloud::Int = 7 -end - -function Base.show(io::IO,K::Keepbits) - println(io,"$(typeof(K))") - keys = propertynames(K) - print_fields(io,K,keys) -end +abstract type AbstractOutputWriter end +abstract type AbstractKeepbits end # default number format for output const DEFAULT_OUTPUT_NF = Float32 const DEFAULT_OUTPUT_DT = Hour(6) +export OutputWriter + """ $(TYPEDSIGNATURES) NetCDF output writer. Contains all output options and auxiliary fields for output interpolation. @@ -120,7 +102,7 @@ Base.@kwdef mutable struct OutputWriter{NF<:Union{Float32,Float64},Model<:ModelS const cloud::Matrix{NF} = fill(missing_value,nlon,nlat) end -# generator function pulling grid resolution and time stepping from ::SpectralGrid and ::TimeStepper +# generator function pulling grid resolution and time stepping from ::SpectralGrid and ::AbstractTimeStepper function OutputWriter( spectral_grid::SpectralGrid, ::Type{Model}; @@ -154,7 +136,7 @@ and dimensions. `write_output!` then writes consecuitive time steps into this fi function initialize!( output::OutputWriter{output_NF,Model}, feedback::AbstractFeedback, - time_stepping::TimeStepper, + time_stepping::AbstractTimeStepper, clock::Clock, diagn::DiagnosticVariables, model::Model @@ -308,6 +290,29 @@ function initialize!( close(parameters_txt) end +""" +Number of mantissa bits to keep for each prognostic variable when compressed for +netCDF and .jld2 data output. +$(TYPEDFIELDS)""" +Base.@kwdef struct Keepbits + u::Int = 7 + v::Int = 7 + vor::Int = 5 + div::Int = 5 + temp::Int = 10 + pres::Int = 12 + humid::Int = 7 + precip_cond::Int = 7 + precip_conv::Int = 7 + cloud::Int = 7 +end + +function Base.show(io::IO,K::Keepbits) + println(io,"$(typeof(K))") + keys = propertynames(K) + print_fields(io,K,keys) +end + """ $(TYPEDSIGNATURES) Checks existing `run_????` folders in `path` to determine a 4-digit `id` number diff --git a/src/physics/pretty_printing.jl b/src/physics/abstract_types.jl similarity index 55% rename from src/physics/pretty_printing.jl rename to src/physics/abstract_types.jl index 3b26f32c2..dfd5fa403 100644 --- a/src/physics/pretty_printing.jl +++ b/src/physics/abstract_types.jl @@ -1,5 +1,7 @@ +abstract type AbstractParameterization <: AbstractModelComponent end + # print all fields with type <: Number -function Base.show(io::IO,P::AbstractParameterization) +function Base.show(io::IO,P::AbstractModelComponent) println(io,"$(typeof(P)) <: $(supertype(typeof(P)))") keys = propertynames(P) print_fields(io,P,keys) diff --git a/src/physics/albedo.jl b/src/physics/albedo.jl new file mode 100644 index 000000000..659bb3a51 --- /dev/null +++ b/src/physics/albedo.jl @@ -0,0 +1 @@ +abstract type AbstractAlbedo end \ No newline at end of file diff --git a/src/physics/boundary_layer.jl b/src/physics/boundary_layer.jl index 39cfdd057..1e22fc4bf 100644 --- a/src/physics/boundary_layer.jl +++ b/src/physics/boundary_layer.jl @@ -1,32 +1,23 @@ -abstract type BoundaryLayerDrag{NF} <: AbstractParameterization{NF} end - -"""Concrete type that disables the boundary layer drag scheme.""" -struct NoBoundaryLayerDrag{NF} <: BoundaryLayerDrag{NF} end - -NoBoundaryLayerDrag(SG::SpectralGrid) = NoBoundaryLayerDrag{SG.NF}() - -"""NoBoundaryLayer scheme does not need any initialisation.""" -function initialize!( scheme::NoBoundaryLayerDrag, - model::PrimitiveEquation) - return nothing -end +abstract type AbstractBoundaryLayerDrag <: AbstractParameterization end # function barrier for all boundary layer drags function boundary_layer_drag!( column::ColumnVariables, model::PrimitiveEquation) - boundary_layer_drag!(column,model.boundary_layer_drag,model) + boundary_layer_drag!(column, model.boundary_layer_drag, model) end -"""NoBoundaryLayer scheme just passes.""" -function boundary_layer_drag!( column::ColumnVariables, - scheme::NoBoundaryLayerDrag, - model::PrimitiveEquation) - return nothing -end +# dummy boundary layer +export NoBoundaryLayerDrag +struct NoBoundaryLayerDrag <: AbstractBoundaryLayerDrag end +NoBoundaryLayerDrag(::SpectralGrid) = NoBoundaryLayerDrag() +initialize!(::NoBoundaryLayerDrag,::PrimitiveEquation) = nothing +boundary_layer_drag!(::ColumnVariables,::NoBoundaryLayerDrag,::PrimitiveEquation) = nothing + +export LinearDrag -"""Linear boundary layer drag Following Held and Suarez, 1996 BAMS +"""Linear boundary layer drag following Held and Suarez, 1996 BAMS $(TYPEDFIELDS)""" -Base.@kwdef struct LinearDrag{NF<:AbstractFloat} <: BoundaryLayerDrag{NF} +Base.@kwdef struct LinearDrag{NF<:AbstractFloat} <: AbstractBoundaryLayer # DIMENSIONS nlev::Int @@ -85,20 +76,21 @@ function boundary_layer_drag!( column::ColumnVariables, end end -Base.@kwdef struct ConstantDrag{NF} <: BoundaryLayerDrag{NF} +export ConstantDrag +Base.@kwdef struct ConstantDrag{NF} <: AbstractBoundaryLayerDrag drag::NF = 1e-3 end ConstantDrag(SG::SpectralGrid;kwargs...) = ConstantDrag{SG.NF}(;kwargs...) - initialize!(::ConstantDrag,::PrimitiveEquation) = nothing - function boundary_layer_drag!( column::ColumnVariables, scheme::ConstantDrag, model::PrimitiveEquation) column.boundary_layer_drag = scheme.drag end +export BulkRichardsonDrag + """Boundary layer drag coefficient from the bulk Richardson number, following Frierson, 2006, Journal of the Atmospheric Sciences. $(TYPEDFIELDS)""" diff --git a/src/physics/column_variables.jl b/src/physics/column_variables.jl index 48f222a60..b0eb3aefe 100644 --- a/src/physics/column_variables.jl +++ b/src/physics/column_variables.jl @@ -7,7 +7,7 @@ function get_column!( jring::Integer, # ring index 1 around North Pole to J around South Pole model::PrimitiveEquation, ) - get_column!(C, D, P, ij, jring, model.geometry, model.constants, model.orography, model.land_sea_mask) + get_column!(C, D, P, ij, jring, model.geometry, model.planet, model.orography, model.land_sea_mask) end """ @@ -19,22 +19,22 @@ function get_column!( C::ColumnVariables, P::PrognosticVariables, ij::Integer, # grid point index jring::Integer, # ring index 1 around North Pole to J around South Pole - G::Geometry, - constants::DynamicsConstants, - O::AbstractOrography, - L::AbstractLandSeaMask) + geometry::Geometry, + planet::AbstractPlanet, + orography::AbstractOrography, + land_sea_mask::AbstractLandSeaMask) - (;σ_levels_full,ln_σ_levels_full) = G + (;σ_levels_full, ln_σ_levels_full) = geometry @boundscheck C.nlev == D.nlev || throw(BoundsError) - C.latd = G.latds[ij] # pull latitude, longitude [˚N,˚E] for gridpoint ij from Geometry - C.lond = G.londs[ij] - C.ij = ij # grid-point index - C.jring = jring # ring index j of column, used to index latitude vectors - C.land_fraction = L.land_sea_mask[ij] - C.orography = O.orography[ij] - C.surface_geopotential = C.orography*constants.gravity + C.latd = geometry.latds[ij] # pull latitude, longitude [˚N,˚E] for gridpoint ij from Geometry + C.lond = geometry.londs[ij] + C.ij = ij # grid-point index + C.jring = jring # ring index j of column, used to index latitude vectors + C.land_fraction = land_sea_mask.land_sea_mask[ij] + C.orography = orography.orography[ij] + C.surface_geopotential = C.orography * planet.gravity # pressure [Pa] or [log(Pa)] lnpₛ = D.surface.pres_grid[ij] # logarithm of surf pressure used in dynamics @@ -85,7 +85,7 @@ function get_column( S::AbstractSimulation, model) # execute all parameterizations for this column to return a consistent state - parameterization_tendencies!(column,S.model) + parameterization_tendencies!(column, S.model) verbose && @info "Receiving column at $(column.latd)˚N, $(column.lond)˚E." return column @@ -97,7 +97,7 @@ Write the parametrization tendencies from `C::ColumnVariables` into the horizont of tendencies stored in `D::DiagnosticVariables` at gridpoint index `ij`.""" function write_column_tendencies!( D::DiagnosticVariables, column::ColumnVariables, - C::DynamicsConstants, + planet::AbstractPlanet, ij::Int) # grid point index (; nlev) = column @@ -117,7 +117,7 @@ function write_column_tendencies!( D::DiagnosticVariables, # Output cloud top in height [m] from geopotential height divided by gravity, # but NaN for no clouds D.surface.cloud_top[ij] = column.cloud_top == nlev+1 ? 0 : column.geopot[column.cloud_top] - D.surface.cloud_top[ij] /= C.gravity + D.surface.cloud_top[ij] /= planet.gravity # just use layer index 1 (top) to nlev (surface) for analysis, but 0 for no clouds # D.surface.cloud_top[ij] = column.cloud_top == nlev+1 ? 0 : column.cloud_top diff --git a/src/physics/convection.jl b/src/physics/convection.jl index 5045c1f42..48199873b 100644 --- a/src/physics/convection.jl +++ b/src/physics/convection.jl @@ -37,7 +37,8 @@ function convection!( scheme::SimplifiedBettsMiller, model::PrimitiveEquation, ) - convection!(column, scheme, model.clausius_clapeyron, model.geometry, model.constants, model.time_stepping) + convection!(column, scheme, model.clausius_clapeyron, + model.geometry, model.planet, model.atmosphere, model.time_stepping) end function convection!( @@ -45,8 +46,9 @@ function convection!( SBM::SimplifiedBettsMiller, clausius_clapeyron::AbstractClausiusClapeyron, geometry::Geometry, - constants::DynamicsConstants, - time_stepping::TimeStepper, + planet::AbstractPlanet, + atmosphere::AbstractAtmosphere, + time_stepping::AbstractTimeStepper, ) where NF σ = geometry.σ_levels_full @@ -136,12 +138,11 @@ function convection!( column.precip_convection += δq * Δσ[k] end - (;gravity, water_density) = constants + (;gravity) = planet + (;water_density) = atmosphere (;Δt_sec) = time_stepping pₛΔt_gρ = (pₛ * Δt_sec / gravity / water_density) * deep_convection # enfore no precip for shallow conv column.precip_convection *= pₛΔt_gρ # convert to [m] of rain during Δt - - # TODO at the moment the cloud top include shallow convection, should that be? column.cloud_top = min(column.cloud_top,level_zero_buoyancy) # clouds reach to top of convection end diff --git a/src/physics/define_column.jl b/src/physics/define_column.jl index 7a6c9ad66..866ce8fb9 100644 --- a/src/physics/define_column.jl +++ b/src/physics/define_column.jl @@ -1,10 +1,13 @@ +abstract type AbstractColumnVariables end +export ColumnVariables + """ Mutable struct that contains all prognostic (copies thereof) and diagnostic variables in a single column needed to evaluate the physical parametrizations. For now the struct is mutable as we will reuse the struct to iterate over horizontal grid points. Every column vector has `nlev` entries, from [1] at the top to [end] at the lowermost model level at the planetary boundary layer. $(TYPEDFIELDS)""" -Base.@kwdef mutable struct ColumnVariables{NF<:AbstractFloat} <: AbstractColumnVariables{NF} +Base.@kwdef mutable struct ColumnVariables{NF<:AbstractFloat} <: AbstractColumnVariables # DIMENSIONS const nlev::Int = 0 # number of vertical levels diff --git a/src/physics/large_scale_condensation.jl b/src/physics/large_scale_condensation.jl index ebac9f132..5f68c1d77 100644 --- a/src/physics/large_scale_condensation.jl +++ b/src/physics/large_scale_condensation.jl @@ -1,5 +1,12 @@ -abstract type AbstractCondensation{NF} <: AbstractParameterization{NF} end +abstract type AbstractCondensation <: AbstractParameterization end +export NoCondensation +struct NoCondensation <: AbstractCondensation end +NoCondesantion(::SpectralGrid) = NoCondensation() +initialize!(::NoCondensation,::PrimitiveEquation) = nothing +large_scale_condensation!(::ColumnVariables, ::NoCondensation, ::PrimitiveEquation) = nothing + +export ImplicitCondensation """ Large scale condensation as with implicit precipitation. $(TYPEDFIELDS)""" @@ -29,8 +36,9 @@ function large_scale_condensation!( column::ColumnVariables, model::PrimitiveWet, ) + #TODO not needed for NoCondensation saturation_humidity!(column, model.clausius_clapeyron) - large_scale_condensation!(column,model.large_scale_condensation,model) + large_scale_condensation!(column, model.large_scale_condensation, model) end # function barrier for ImplicitCondensation to unpack model @@ -39,8 +47,8 @@ function large_scale_condensation!( scheme::ImplicitCondensation, model::PrimitiveWet, ) - large_scale_condensation!(column,scheme, - model.clausius_clapeyron,model.geometry,model.constants,model.time_stepping) + large_scale_condensation!(column, scheme, + model.clausius_clapeyron, model.geometry, model.planet, model.atmosphere, model.time_stepping) end """ @@ -54,8 +62,9 @@ function large_scale_condensation!( scheme::ImplicitCondensation, clausius_clapeyron::AbstractClausiusClapeyron, geometry::Geometry, - constants::DynamicsConstants, - time_stepping::TimeStepper, + planet::AbstractPlanet, + atmosphere::AbstractAtmosphere, + time_stepping::AbstractTimeStepper, ) where NF (;temp, humid, pres) = column # prognostic vars: specific humidity, pressure @@ -64,10 +73,9 @@ function large_scale_condensation!( # precompute scaling constant for precipitation output pₛ = pres[end] # surface pressure - (;gravity, water_density) = constants (;Δt_sec) = time_stepping Δσ = geometry.σ_levels_thick - pₛΔt_gρ = pₛ*Δt_sec/(gravity*water_density) + pₛΔt_gρ = pₛ*Δt_sec/(planet.gravity * atmosphere.water_density) (;Lᵥ, cₚ, Lᵥ_Rᵥ) = clausius_clapeyron Lᵥ_cₚ = Lᵥ/cₚ # latent heat of vaporization over heat capacity diff --git a/src/physics/shortwave_radiation.jl b/src/physics/shortwave_radiation.jl index fddd3dba7..c8adce624 100644 --- a/src/physics/shortwave_radiation.jl +++ b/src/physics/shortwave_radiation.jl @@ -21,9 +21,10 @@ TransparentShortwave(SG::SpectralGrid) = TransparentShortwave{SG.NF}() function initialize!(scheme::TransparentShortwave,model::PrimitiveEquation) (;solar_constant, gravity) = model.planet - (;cₚ, pres_ref) = model.atmosphere + (;pres_ref) = model.atmosphere + cₚ = model.atmosphere.heat_capacity Δσ = model.geometry.σ_levels_thick - scheme.S[] = (1 - scheme.albedo) * solar_constant * gravity / (Δσ[end] * (pres_ref*100) * cₚ) + scheme.S[] = (1 - scheme.albedo) * solar_constant * gravity / (Δσ[end] * pres_ref * cₚ) end function shortwave_radiation!( diff --git a/src/physics/surface_fluxes.jl b/src/physics/surface_fluxes.jl index 09690be76..a11b3ad72 100644 --- a/src/physics/surface_fluxes.jl +++ b/src/physics/surface_fluxes.jl @@ -1,25 +1,28 @@ +abstract type AbstractSurfaceWind <: AbstractParameterization end +abstract type AbstractSurfaceThermodynamics <: AbstractParameterization end +abstract type AbstractSurfaceHeat <: AbstractParameterization end +abstract type AbstractEvaporation <: AbstractParameterization end + function surface_fluxes!(column::ColumnVariables,model::PrimitiveEquation) # get temperature, humidity and density at surface - surface_thermodynamics!(column,model.surface_thermodynamics,model.constants,model) + surface_thermodynamics!(column, model.surface_thermodynamics, model.atmosphere, model) # also calculates surface wind speed necessary for other fluxes too - surface_wind_stress!(column,model.surface_wind) + surface_wind_stress!(column, model.surface_wind) # now call other heat and humidity fluxes - sensible_heat_flux!(column,model.surface_heat_flux,model.constants) + sensible_heat_flux!(column, model.surface_heat_flux, model.constants) evaporation!(column,model) end -struct SurfaceThermodynamicsConstant{NF<:AbstractFloat} <: AbstractSurfaceThermodynamics{NF} end -struct SurfaceThermodynamicsExtrapolate{NF<:AbstractFloat} <: AbstractSurfaceThermodynamics{NF} end - +export SurfaceThermodynamicsConstant +struct SurfaceThermodynamicsConstant{NF<:AbstractFloat} <: AbstractSurfaceThermodynamics end SurfaceThermodynamicsConstant(SG::SpectralGrid) = SurfaceThermodynamicsConstant{SG.NF}() -SurfaceThermodynamicsExtrapolate(SG::SpectralGrid) = SurfaceThermodynamicsExtrapolate{SG.NF}() function surface_thermodynamics!( column::ColumnVariables, ::SurfaceThermodynamicsConstant, - C::DynamicsConstants, + atmosphere::AbstractAtmosphere, model::PrimitiveWet) # surface value is same as lowest model level @@ -27,24 +30,23 @@ function surface_thermodynamics!( column::ColumnVariables, column.surface_humid = column.humid[end] # humidity at surface is the same as # surface air density via virtual temperature - (;R_dry,μ_virt_temp) = C - T = column.surface_temp # surface air temperature - q = column.surface_humid # surface humidity - Tᵥ = T*(1 + μ_virt_temp*q) # virtual temperature - column.surface_air_density = column.pres[end]/R_dry/Tᵥ + (;R_dry) = atmosphere + Tᵥ = column.temp_virt[column.nlev] + column.surface_air_density = column.pres[end]/(R_dry*Tᵥ) end function surface_thermodynamics!( column::ColumnVariables, ::SurfaceThermodynamicsConstant, - C::DynamicsConstants, + atmosphere::AbstractAtmosphere, model::PrimitiveDry) - + (;R_dry) = atmosphere # surface value is same as lowest model level column.surface_temp = column.temp[end] # todo use constant POTENTIAL temperature - column.surface_air_density = column.pres[end]/C.R_dry/column.surface_temp + column.surface_air_density = column.pres[end]/(R_dry*column.surface_temp) end -Base.@kwdef struct SurfaceWind{NF<:AbstractFloat} <: AbstractSurfaceWind{NF} +export SurfaceWind +Base.@kwdef struct SurfaceWind{NF<:AbstractFloat} <: AbstractSurfaceWind "Ratio of near-surface wind to lowest-level wind [1]" f_wind::NF = 0.95 @@ -104,7 +106,8 @@ function surface_wind_stress!( column::ColumnVariables{NF}, return nothing end -Base.@kwdef struct SurfaceSensibleHeat{NF<:AbstractFloat} <: AbstractSurfaceHeat{NF} +export SurfaceSensibleHeat +Base.@kwdef struct SurfaceSensibleHeat{NF<:AbstractFloat} <: AbstractSurfaceHeat "Use (possibly) flow-dependent column.boundary_layer_drag coefficient" use_boundary_layer_drag::Bool = true @@ -123,8 +126,8 @@ SurfaceSensibleHeat(SG::SpectralGrid;kwargs...) = SurfaceSensibleHeat{SG.NF}(;kw function sensible_heat_flux!( column::ColumnVariables{NF}, heat_flux::SurfaceSensibleHeat, - C::DynamicsConstants) where NF - (;cₚ) = C + atmosphere::AbstractAtmosphere) where NF + cₚ = atmosphere.heat_capacity (;heat_exchange_land, heat_exchange_sea, max_flux) = heat_flux ρ = column.surface_air_density @@ -168,10 +171,12 @@ function sensible_heat_flux!( column::ColumnVariables{NF}, return nothing end +export SurfaceEvaporation + """ Surface evaporation following a bulk formula with wind from model.surface_wind $(TYPEDFIELDS)""" -Base.@kwdef struct SurfaceEvaporation{NF<:AbstractFloat} <: AbstractEvaporation{NF} +Base.@kwdef struct SurfaceEvaporation{NF<:AbstractFloat} <: AbstractEvaporation "Use column.boundary_layer_drag coefficient" use_boundary_layer_drag::Bool = true @@ -194,7 +199,7 @@ end # function barrier function evaporation!( column::ColumnVariables, model::PrimitiveWet) - evaporation!(column,model.evaporation,model.clausius_clapeyron) + evaporation!(column, model.evaporation, model.clausius_clapeyron) end function evaporation!( column::ColumnVariables{NF}, diff --git a/src/physics/temperature_relaxation.jl b/src/physics/temperature_relaxation.jl index fa2bfbe6a..3f3a5ab8f 100644 --- a/src/physics/temperature_relaxation.jl +++ b/src/physics/temperature_relaxation.jl @@ -1,22 +1,21 @@ -struct NoTemperatureRelaxation{NF} <: TemperatureRelaxation{NF} end -NoTemperatureRelaxation(SG::SpectralGrid) = NoTemperatureRelaxation{SG.NF}() +abstract type TemperatureRelaxation <: AbstractParameterization end -"""$(TYPEDSIGNATURES) just passes.""" -function temperature_relaxation!( column::ColumnVariables, - scheme::NoTemperatureRelaxation) - return nothing +function temperature_relaxation!(::ColumnVariables,::PrimitiveEquation) + temperature_relaxation!(column, model.temperature_relaxation, model) end -"""$(TYPEDSIGNATURES) just passes, does not need any initialization.""" -function initialize!( scheme::NoTemperatureRelaxation, - model::PrimitiveEquation) - return nothing -end +export NoTemperatureRelaxation +struct NoTemperatureRelaxation <: TemperatureRelaxation end +NoTemperatureRelaxation(::SpectralGrid) = NoTemperatureRelaxation() +initialize!(::TemperatureRelaxation,::PrimitiveEquation) = nothing +temperature_relaxation!(::ColumnVariables,::TemperatureRelaxation,::PrimitiveEquation) = nothing + +export HeldSuarez """ Struct that defines the temperature relaxation from Held and Suarez, 1996 BAMS $(TYPEDFIELDS)""" -Base.@kwdef struct HeldSuarez{NF<:AbstractFloat} <: TemperatureRelaxation{NF} +Base.@kwdef struct HeldSuarez{NF<:AbstractFloat} <: TemperatureRelaxation # DIMENSIONS "number of latitude rings" nlat::Int @@ -26,25 +25,25 @@ Base.@kwdef struct HeldSuarez{NF<:AbstractFloat} <: TemperatureRelaxation{NF} # OPTIONS "sigma coordinate below which faster surface relaxation is applied" - σb::Float64 = 0.7 + σb::NF = 0.7 "time scale [hrs] for slow global relaxation" - relax_time_slow::Float64 = 40*24 + relax_time_slow::NF = 40*24 "time scale [hrs] for faster tropical surface relaxation" - relax_time_fast::Float64 = 4*24 + relax_time_fast::NF = 4*24 "minimum equilibrium temperature [K]" - Tmin::Float64 = 200 + Tmin::NF = 200 "maximum equilibrium temperature [K]" - Tmax::Float64 = 315 + Tmax::NF = 315 "meridional temperature gradient [K]" - ΔTy::Float64 = 60 + ΔTy::NF = 60 "vertical temperature gradient [K]" - Δθz::Float64 = 10 + Δθz::NF = 10 # precomputed constants, allocate here, fill in initialize! κ::Base.RefValue{NF} = Ref(zero(NF)) @@ -74,7 +73,7 @@ function initialize!( scheme::HeldSuarez, (;σb, ΔTy, Δθz, relax_time_slow, relax_time_fast, Tmax) = scheme (;temp_relax_freq, temp_equil_a, temp_equil_b) = scheme - p₀ = model.atmosphere.pres_ref*100 # [hPa] → [Pa] + p₀ = model.atmosphere.pres_ref # surface reference pressure [Pa] scheme.p₀[] = p₀ scheme.κ[] = model.constants.κ # thermodynamic kappa @@ -95,11 +94,6 @@ function initialize!( scheme::HeldSuarez, end end -# function barrier -function temperature_relaxation!(column::ColumnVariables,model::PrimitiveEquation) - temperature_relaxation!(column,model.temperature_relaxation) -end - """$(TYPEDSIGNATURES) Apply temperature relaxation following Held and Suarez 1996, BAMS.""" function temperature_relaxation!( column::ColumnVariables{NF}, @@ -124,11 +118,12 @@ function temperature_relaxation!( column::ColumnVariables{NF}, end end +export JablonowskiRelaxation """$(TYPEDSIGNATURES) HeldSuarez-like temperature relaxation, but towards the Jablonowski temperature profile with increasing temperatures in the stratosphere.""" -Base.@kwdef struct JablonowskiRelaxation{NF<:AbstractFloat} <: TemperatureRelaxation{NF} +Base.@kwdef struct JablonowskiRelaxation{NF<:AbstractFloat} <: TemperatureRelaxation # DIMENSIONS nlat::Int nlev::Int diff --git a/src/physics/tendencies.jl b/src/physics/tendencies.jl index e1eb51a27..0ffed5707 100644 --- a/src/physics/tendencies.jl +++ b/src/physics/tendencies.jl @@ -29,10 +29,10 @@ function parameterization_tendencies!( get_column!(column,diagn,progn,ij,jring,model) # execute all parameterizations - parameterization_tendencies!(column,model) + parameterization_tendencies!(column, model) # write tendencies from parametrizations back into horizontal fields - write_column_tendencies!(diagn,column,model.constants,ij) + write_column_tendencies!(diagn, column, model.planet, ij) end end @@ -42,27 +42,27 @@ function parameterization_tendencies!( ) # Pre-compute thermodynamic quantities - get_thermodynamics!(column,model) + get_thermodynamics!(column, model) - temperature_relaxation!(column,model) - boundary_layer_drag!(column,model) + temperature_relaxation!(column, model) + boundary_layer_drag!(column, model) # VERTICAL DIFFUSION # diffusion_coefficient!(column,model) # momentum_diffusion!(column,model) - static_energy_diffusion!(column,model) - humidity_diffusion!(column,model) + static_energy_diffusion!(column, model) + humidity_diffusion!(column, model) # Calculate parametrizations - convection!(column,model) - large_scale_condensation!(column,model) + convection!(column, model) + large_scale_condensation!(column, model) # clouds!(column, model) - shortwave_radiation!(column,model) - longwave_radiation!(column,model) - surface_fluxes!(column,model) + shortwave_radiation!(column, model) + longwave_radiation!(column, model) + surface_fluxes!(column, model) # sum fluxes on half levels up and down for every layer - fluxes_to_tendencies!(column,model.geometry,model.constants) + fluxes_to_tendencies!(column, model.geometry, model.planet, model.atmosphere) end """ @@ -71,7 +71,8 @@ Convert the fluxes on half levels to tendencies on full levels.""" function fluxes_to_tendencies!( column::ColumnVariables, geometry::Geometry, - constants::DynamicsConstants, + planet::AbstractPlanet, + atmosphere::AbstractAtmosphere, ) (;nlev, u_tend, flux_u_upward, flux_u_downward) = column @@ -81,10 +82,11 @@ function fluxes_to_tendencies!( Δσ = geometry.σ_levels_thick pₛ = column.pres[end] # surface pressure - (;radius) = constants # used for scaling + (;radius) = geometry # used for scaling # for g/Δp and g/(Δp*cₚ), see Fortran SPEEDY documentation eq. (3,5) - g_pₛ = constants.gravity/pₛ + g_pₛ = planet.gravity/pₛ + cₚ = atmosphere.heat_capacity # fluxes are defined on half levels including top k=1/2 and surface k=nlev+1/2 @inbounds for k in 1:nlev @@ -106,7 +108,7 @@ function fluxes_to_tendencies!( # convert absorbed flux to tendency, accumulate with # non-flux tendencies and scale with radius g_Δp = g_pₛ/Δσ[k] - g_Δp_cₚ = g_Δp/constants.cₚ + g_Δp_cₚ = g_Δp/cₚ u_tend[k] = radius*(u_tend[k] + g_Δp*ΔF_u) v_tend[k] = radius*(v_tend[k] + g_Δp*ΔF_v) humid_tend[k] = radius*(humid_tend[k] + g_Δp*ΔF_humid) diff --git a/src/physics/thermodynamics.jl b/src/physics/thermodynamics.jl index 33f6c6c95..e76a33cc1 100644 --- a/src/physics/thermodynamics.jl +++ b/src/physics/thermodynamics.jl @@ -1,4 +1,6 @@ -abstract type AbstractClausiusClapeyron{NF} <: AbstractParameterization{NF} end +abstract type AbstractClausiusClapeyron <: AbstractParameterization end + +export ClausiusClapeyron """ Parameters for computing saturation vapour pressure of water using the Clausis-Clapeyron equation, @@ -7,7 +9,7 @@ Parameters for computing saturation vapour pressure of water using the Clausis-C where T is in Kelvin, Lᵥ the the latent heat of condensation and Rᵥ the gas constant of water vapour $(TYPEDFIELDS)""" -Base.@kwdef struct ClausiusClapeyron{NF<:AbstractFloat} <: AbstractClausiusClapeyron{NF} +Base.@kwdef struct ClausiusClapeyron{NF<:AbstractFloat} <: AbstractClausiusClapeyron "Saturation water vapour pressure at 0°C [Pa]" e₀::NF = 610.78 @@ -37,7 +39,7 @@ Base.@kwdef struct ClausiusClapeyron{NF<:AbstractFloat} <: AbstractClausiusClape end # generator function -function ClausiusClapeyron(SG::SpectralGrid,atm::AbstractAtmosphere;kwargs...) +function ClausiusClapeyron(SG::SpectralGrid, atm::AbstractAtmosphere; kwargs...) (;R_dry, R_vapour, latent_heat_condensation, cₚ) = atm return ClausiusClapeyron{SG.NF}(;Lᵥ=latent_heat_condensation,R_dry,R_vapour,cₚ,kwargs...) end @@ -117,37 +119,19 @@ end $(TYPEDSIGNATURES) Calculate geopotentiala and dry static energy for the primitive equation model.""" function get_thermodynamics!(column::ColumnVariables,model::PrimitiveEquation) - geopotential!(column.geopot,column.temp,model.constants) + geopotential!(column.geopot, column.temp, model.constants) dry_static_energy!(column, model.constants) end -""" -$(TYPEDSIGNATURES) -Full to half-level interpolation for humidity, saturation humidity, -dry static energy and saturation moist static energy. -""" -function vertical_interpolate!( - column::ColumnVariables, - model::PrimitiveEquation, -) - - for (full, half) in ( - (column.humid, column.humid_half), - (column.sat_humid, column.sat_humid_half), - (column.dry_static_energy, column.dry_static_energy_half), - (column.sat_moist_static_energy, column.sat_moist_static_energy_half), - ) - vertical_interpolate!(half, full, model.geometry) - end -end - """ $(TYPEDSIGNATURES) Compute the dry static energy SE = cₚT + Φ (latent heat times temperature plus geopotential) for the column.""" -function dry_static_energy!(column::ColumnVariables,constants::DynamicsConstants) - - (;cₚ) = constants +function dry_static_energy!( + column::ColumnVariables, + atmosphere::AbstractAtmosphere +) + cₚ = atmosphere.heat_capacity (;dry_static_energy, geopot, temp) = column @inbounds for k in eachlayer(column) @@ -157,9 +141,11 @@ function dry_static_energy!(column::ColumnVariables,constants::DynamicsConstants return nothing end -function bulk_richardson!(column::ColumnVariables,constants::DynamicsConstants) - - (;cₚ) = constants +function bulk_richardson!( + column::ColumnVariables, + atmosphere::AbstractAtmosphere, +) + cₚ = atmosphere.heat_capacity (;u, v, geopot, temp_virt, nlev, bulk_richardson) = column V² = u[nlev]^2 + v[nlev]^2 @@ -212,6 +198,8 @@ function moist_static_energy!( end end +export TetensEquation + """ Parameters for computing saturation vapour pressure of water using the Tetens' equation, @@ -220,7 +208,7 @@ Parameters for computing saturation vapour pressure of water using the Tetens' e where T is in Kelvin and i = 1,2 for saturation above and below freezing, respectively. From Tetens (1930), and Murray (1967) for below freezing. $(TYPEDFIELDS)""" -Base.@kwdef struct TetensEquation{NF<:AbstractFloat} <: AbstractClausiusClapeyron{NF} +Base.@kwdef struct TetensEquation{NF<:AbstractFloat} <: AbstractClausiusClapeyron "Saturation water vapour pressure at 0°C [Pa]" e₀::NF = 610.78 diff --git a/src/physics/vertical_diffusion.jl b/src/physics/vertical_diffusion.jl index 4dd49eb0d..4ca37c384 100644 --- a/src/physics/vertical_diffusion.jl +++ b/src/physics/vertical_diffusion.jl @@ -1,3 +1,6 @@ +abstract type VerticalDiffusion{NF} <: AbstractParameterization{NF} end + +export NoVerticalDiffusion struct NoVerticalDiffusion{NF} <: VerticalDiffusion{NF} end NoVerticalDiffusion(SG::SpectralGrid) = NoVerticalDiffusion{SG.NF}() @@ -17,6 +20,8 @@ function static_energy_diffusion!( column::ColumnVariables, return nothing end +export StaticEnergyDiffusion + """ Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation. @@ -75,6 +80,8 @@ function static_energy_diffusion!( column::ColumnVariables, end end +export HumidityDiffusion + """ Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation. diff --git a/src/run_speedy.jl b/src/run_speedy.jl deleted file mode 100644 index b1ea9919f..000000000 --- a/src/run_speedy.jl +++ /dev/null @@ -1,25 +0,0 @@ -""" -$(TYPEDSIGNATURES) -Run a SpeedyWeather.jl `simulation`. The `simulation.model` is assumed to be initialized.""" -function run!( simulation::Simulation; - period = Day(10), - output::Bool = false, - n_days::Union{Nothing, Real}=nothing) - - if !isnothing(n_days) - @warn "run!: n_days keyword is deprecated, use period = Day(n_days) instead." - period = Day(n_days) - end - - (;prognostic_variables, diagnostic_variables, model) = simulation - (;clock) = prognostic_variables - - # set the clock's enddate - set_period!(clock,period) - initialize!(clock,model.time_stepping) - - model.output.output = output # enable/disable output - - # run it, yeah! - time_stepping!(prognostic_variables,diagnostic_variables,model) -end diff --git a/src/utility_functions.jl b/src/utility_functions.jl index 4383e0ea3..bc719a6b9 100644 --- a/src/utility_functions.jl +++ b/src/utility_functions.jl @@ -111,6 +111,7 @@ Dates.second(x::Dates.Nanosecond) = round(Int,x.value*1e-9) Dates.second(x::Dates.Microsecond) = round(Int,x.value*1e-6) Dates.second(x::Dates.Millisecond) = round(Int,x.value*1e-3) +# defined to convert from floats to Dates.Second (which require ints by default) via rounding function Base.convert(::Type{Second},x::AbstractFloat) xr = round(Int64,x) x == xr || @info "Rounding and converting $x to $xr for integer seconds." From 109a681ffb633fc716d188dd0b0c4ab6ee152d0f Mon Sep 17 00:00:00 2001 From: Milan Date: Wed, 28 Feb 2024 20:55:23 -0500 Subject: [PATCH 02/17] parametric models: barotropic working --- src/SpeedyWeather.jl | 18 +++++++--- src/dynamics/abstract_types.jl | 1 + src/dynamics/adiabatic_conversion.jl | 2 +- src/dynamics/atmospheres.jl | 2 +- src/dynamics/coriolis.jl | 1 + src/dynamics/diagnostic_variables.jl | 2 ++ src/dynamics/forcing.jl | 6 ++-- src/dynamics/horizontal_diffusion.jl | 4 +-- src/dynamics/implicit.jl | 24 ++++++++----- src/dynamics/prognostic_variables.jl | 2 +- src/dynamics/spectral_grid.jl | 36 ++++++++++---------- src/dynamics/tendencies.jl | 12 +++---- src/dynamics/time_integration.jl | 41 +++++++++++----------- src/models/abstract_models.jl | 21 +++++++++--- src/models/barotropic.jl | 8 ++--- src/models/simulation.jl | 8 ++--- src/output/abstract_types.jl | 2 ++ src/output/feedback.jl | 3 +- src/output/output.jl | 45 ++++++++++++------------- src/physics/boundary_layer.jl | 8 ++--- src/physics/convection.jl | 4 +-- src/physics/large_scale_condensation.jl | 2 +- src/physics/longwave_radiation.jl | 8 ++--- src/physics/shortwave_radiation.jl | 14 ++++---- src/physics/thermodynamics.jl | 6 ++-- src/physics/vertical_diffusion.jl | 10 +++--- src/physics/zenith.jl | 14 ++++---- 27 files changed, 165 insertions(+), 139 deletions(-) create mode 100644 src/dynamics/abstract_types.jl create mode 100644 src/output/abstract_types.jl diff --git a/src/SpeedyWeather.jl b/src/SpeedyWeather.jl index f85089611..b1919d5bc 100644 --- a/src/SpeedyWeather.jl +++ b/src/SpeedyWeather.jl @@ -30,6 +30,9 @@ import ProgressMeter # to avoid a `using Dates` to pass on DateTime arguments export DateTime, Second, Minute, Hour, Day +# export functions that have many cross-component methods +export initialize!, finish! + include("utility_functions.jl") # LowerTriangularMatrices for spherical harmonics @@ -61,11 +64,17 @@ using .SpeedyTransforms # Utility for GPU / KernelAbstractions include("gpu.jl") -# GEOMETRY CONSTANTS ETC +# abstract types include("models/abstract_models.jl") +include("dynamics/abstract_types.jl") +include("output/abstract_types.jl") +include("physics/abstract_types.jl") + +# GEOMETRY CONSTANTS ETC include("dynamics/vertical_coordinates.jl") include("dynamics/spectral_grid.jl") include("dynamics/geometry.jl") +include("dynamics/coriolis.jl") include("dynamics/planets.jl") include("dynamics/atmospheres.jl") include("dynamics/adiabatic_conversion.jl") @@ -91,7 +100,6 @@ include("dynamics/tendencies.jl") include("dynamics/hole_filling.jl") # PARAMETERIZATIONS -include("physics/abstract_types.jl") include("physics/albedo.jl") include("physics/tendencies.jl") include("physics/column_variables.jl") @@ -118,7 +126,7 @@ include("output/plot.jl") # MODELS include("models/simulation.jl") include("models/barotropic.jl") -include("models/shallow_water.jl") -include("models/primitive_dry.jl") -include("models/primitive_wet.jl") +# include("models/shallow_water.jl") +# include("models/primitive_dry.jl") +# include("models/primitive_wet.jl") end \ No newline at end of file diff --git a/src/dynamics/abstract_types.jl b/src/dynamics/abstract_types.jl new file mode 100644 index 000000000..6d78edb7b --- /dev/null +++ b/src/dynamics/abstract_types.jl @@ -0,0 +1 @@ +abstract type AbstractTimeStepper <: AbstractModelComponent end diff --git a/src/dynamics/adiabatic_conversion.jl b/src/dynamics/adiabatic_conversion.jl index 2b60aa10d..c2b883cfe 100644 --- a/src/dynamics/adiabatic_conversion.jl +++ b/src/dynamics/adiabatic_conversion.jl @@ -10,7 +10,7 @@ Base.@kwdef struct AdiabaticConversion{NF} <: AbstractAdiabaticConversion σ_lnp_B::Vector{NF} = zeros(NF,nlev) end -AdiabaticConversion(SG::SpectralGrid;kwargs...) = AdiabaticConversion{SG.NF}(;nlev=SG.nlev;kwargs...) +AdiabaticConversion(SG::SpectralGrid;kwargs...) = AdiabaticConversion{SG.NF}(;nlev=SG.nlev,kwargs...) function initialize!( adiabatic::AdiabaticConversion, diff --git a/src/dynamics/atmospheres.jl b/src/dynamics/atmospheres.jl index 67b4e0945..fa5e3b93d 100644 --- a/src/dynamics/atmospheres.jl +++ b/src/dynamics/atmospheres.jl @@ -32,7 +32,7 @@ Base.@kwdef struct EarthAtmosphere{NF<:AbstractFloat} <: AbstractAtmosphere μ_virt_temp::NF = (1-mol_ratio)/mol_ratio "= R_dry/cₚ, gas const for air over heat capacity" - κ::NF = R_dry/cₚ + κ::NF = R_dry/heat_capacity "water density [kg/m³]" water_density::NF = 1000 diff --git a/src/dynamics/coriolis.jl b/src/dynamics/coriolis.jl index d5e1377c5..ab6440e9e 100644 --- a/src/dynamics/coriolis.jl +++ b/src/dynamics/coriolis.jl @@ -1,5 +1,6 @@ abstract type AbstractCoriolis <: AbstractModelComponent end +export Coriolis Base.@kwdef struct Coriolis{NF} <: AbstractCoriolis "number of latitude rings" nlat::Int diff --git a/src/dynamics/diagnostic_variables.jl b/src/dynamics/diagnostic_variables.jl index 62a560c63..f04671f21 100644 --- a/src/dynamics/diagnostic_variables.jl +++ b/src/dynamics/diagnostic_variables.jl @@ -149,6 +149,8 @@ function Base.zeros(::Type{SurfaceVariables}, return SurfaceVariables{NF,Grid{NF}}(;nlat_half,trunc,npoints) end +export DiagnosticVariables + """ All diagnostic variables. $(TYPEDFIELDS)""" diff --git a/src/dynamics/forcing.jl b/src/dynamics/forcing.jl index 666c7c779..96addec93 100644 --- a/src/dynamics/forcing.jl +++ b/src/dynamics/forcing.jl @@ -4,8 +4,8 @@ abstract type AbstractForcing <: AbstractModelComponent end export NoForcing struct NoForcing <: AbstractForcing end NoForcing(SG::SpectralGrid) = NoForcing() - initialize!(::NoForcing,::ModelSetup) = nothing + function forcing!( diagn::DiagnosticVariablesLayer, progn::PrognosticVariablesLayer, forcing::NoForcing, @@ -14,7 +14,7 @@ function forcing!( diagn::DiagnosticVariablesLayer, return nothing end -# JET STREAM FORCING FOR SHALLOW WATER +# JET STREAM FORCING export JetStreamForcing """ @@ -45,7 +45,7 @@ Base.@kwdef struct JetStreamForcing{NF} <: AbstractForcing end JetStreamForcing(SG::SpectralGrid;kwargs...) = JetStreamForcing{SG.NF}( - ;nlat=SG.nlat,kwargs...) + ;nlat=SG.nlat, kwargs...) function initialize!( forcing::JetStreamForcing, model::ModelSetup) diff --git a/src/dynamics/horizontal_diffusion.jl b/src/dynamics/horizontal_diffusion.jl index 3fba4b9cd..480f4524c 100644 --- a/src/dynamics/horizontal_diffusion.jl +++ b/src/dynamics/horizontal_diffusion.jl @@ -1,4 +1,4 @@ -abstract type HorizontalDiffusion <: AbstractModelComponent end +abstract type AbstractHorizontalDiffusion <: AbstractModelComponent end export HyperDiffusion @@ -11,7 +11,7 @@ layers. Furthermore the power can be decreased above the `tapering_σ` to `power_stratosphere` (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are $(TYPEDFIELDS)""" -Base.@kwdef struct HyperDiffusion{NF} <: HorizontalDiffusion +Base.@kwdef struct HyperDiffusion{NF} <: AbstractHorizontalDiffusion # DIMENSIONS "spectral resolution" trunc::Int diff --git a/src/dynamics/implicit.jl b/src/dynamics/implicit.jl index 57f0fc703..7f1d84847 100644 --- a/src/dynamics/implicit.jl +++ b/src/dynamics/implicit.jl @@ -4,11 +4,12 @@ abstract type AbstractImplicit <: AbstractModelComponent end export NoImplicit struct NoImplicit <: AbstractImplicit end NoImplicit(SG::SpectralGrid) = NoImplicit() -initialize!(::AbstractImplicit,args...) = nothing +initialize!(::NoImplicit,args...) = nothing +implicit_correction!(::DiagnosticVariables,::PrognosticVariables,::NoImplicit) = nothing +# SHALLOW WATER MODEL export ImplicitShallowWater -# SHALLOW WATER MODEL """ Struct that holds various precomputed arrays for the semi-implicit correction to prevent gravity waves from amplifying in the shallow water model. @@ -193,20 +194,25 @@ end # function barrier to unpack the constants struct for primitive eq models function initialize!(I::ImplicitPrimitiveEquation,dt::Real,diagn::DiagnosticVariables,model::PrimitiveEquation) - initialize!(I,dt,diagn,model.geometry,model.constants) + initialize!(I, dt, diagn, model.geometry, model.geopotential, model.adiabatic_conversion) end """$(TYPEDSIGNATURES) Initialize the implicit terms for the PrimitiveEquation models.""" -function initialize!( implicit::ImplicitPrimitiveEquation, - dt::Real, # the scaled time step radius*dt - diagn::DiagnosticVariables, - geometry::Geometry, - constants::DynamicsConstants) +function initialize!( + implicit::ImplicitPrimitiveEquation, + dt::Real, # the scaled time step radius*dt + diagn::DiagnosticVariables, + geometry::AbstractGeometry, + geopotential::AbstractGeopotential, + adiabatic_conversion::AbstractAdiabaticConversion +) (;trunc, nlev, α,temp_profile,S,S⁻¹,L,R,U,W,L0,L1,L2,L3,L4) = implicit (;σ_levels_full, σ_levels_thick) = geometry - (;R_dry, κ, Δp_geopot_half, Δp_geopot_full, σ_lnp_A, σ_lnp_B) = constants + (;R_dry, κ) = atmosphere + (;Δp_geopot_half, Δp_geopot_full) = geopotential + (;σ_lnp_A, σ_lnp_B) = adiabatic_conversion for k in 1:nlev # use current vertical temperature profile diff --git a/src/dynamics/prognostic_variables.jl b/src/dynamics/prognostic_variables.jl index 77208750b..43262121d 100644 --- a/src/dynamics/prognostic_variables.jl +++ b/src/dynamics/prognostic_variables.jl @@ -50,7 +50,7 @@ end """ $(TYPEDSIGNATURES) Create and initialize a clock from `time_stepping`""" -function Clock(time_stepping::TimeStepper;kwargs...) +function Clock(time_stepping::AbstractTimeStepper;kwargs...) clock = Clock(;kwargs...) initialize!(clock,time_stepping) end diff --git a/src/dynamics/spectral_grid.jl b/src/dynamics/spectral_grid.jl index daabf6dd6..8fad2e18c 100644 --- a/src/dynamics/spectral_grid.jl +++ b/src/dynamics/spectral_grid.jl @@ -18,43 +18,46 @@ $(TYPEDFIELDS) `nlat_half` and `npoints` should not be chosen but are derived from `trunc`, `Grid` and `dealiasing`.""" Base.@kwdef struct SpectralGrid <: AbstractSpectralGrid - "number format used throughout the model" + "[OPTION] number format used throughout the model" NF::Type{<:AbstractFloat} = DEFAULT_NF # HORIZONTAL - "horizontal resolution as the maximum degree of spherical harmonics" + "[OPTION] horizontal resolution as the maximum degree of spherical harmonics" trunc::Int = DEFAULT_TRUNC - "horizontal grid used for calculations in grid-point space" + "[OPTION] horizontal grid used for calculations in grid-point space" Grid::Type{<:AbstractGrid} = DEFAULT_GRID - "how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid" + "[OPTION] how to match spectral with grid resolution: dealiasing factor, 1=linear, 2=quadratic, 3=cubic grid" dealiasing::Float64 = 2 - "radius of the sphere [m]" + "[OPTION] radius of the sphere [m]" radius::Float64 = DEFAULT_RADIUS # SIZE OF GRID from trunc, Grid, dealiasing: "number of latitude rings on one hemisphere (Equator incl)" nlat_half::Int = SpeedyTransforms.get_nlat_half(trunc,dealiasing) + "number of latitude rings on both hemispheres" + nlat::Int = RingGrids.get_nlat(Grid,nlat_half) + "total number of grid points in the horizontal" npoints::Int = RingGrids.get_npoints(Grid,nlat_half) # VERTICAL - "number of vertical levels" + "[OPTION] number of vertical levels" nlev::Int = DEFAULT_NLEV - "coordinates used to discretize the vertical" + "[OPTION] coordinates used to discretize the vertical" vertical_coordinates::VerticalCoordinates = SigmaCoordinates(;nlev) # make sure nlev and vertical_coordinates.nlev match - function SpectralGrid(NF,trunc,Grid,dealiasing,radius,nlat_half,npoints,nlev,vertical_coordinates) + function SpectralGrid(NF,trunc,Grid,dealiasing,radius,nlat_half,nlat,npoints,nlev,vertical_coordinates) if nlev == vertical_coordinates.nlev - return new(NF,trunc,Grid,dealiasing,radius,nlat_half,npoints, + return new(NF,trunc,Grid,dealiasing,radius,nlat_half,nlat,npoints, nlev,vertical_coordinates) else # use nlev from vert_coords: - return new(NF,trunc,Grid,dealiasing,radius,nlat_half,npoints, + return new(NF,trunc,Grid,dealiasing,radius,nlat_half,nlat,npoints, vertical_coordinates.nlev,vertical_coordinates) end end @@ -66,21 +69,16 @@ SpectralGrid(Grid::Type{<:AbstractGrid};kwargs...) = SpectralGrid(;Grid,kwargs.. SpectralGrid(NF::Type{<:AbstractFloat},Grid::Type{<:AbstractGrid};kwargs...) = SpectralGrid(;NF,Grid,kwargs...) function Base.show(io::IO,SG::SpectralGrid) - (;NF,trunc,Grid,radius,nlat_half,npoints,nlev,vertical_coordinates) = SG - # truncation = if dealiasing < 2 "linear" elseif dealiasing < 3 "quadratic" else "cubic" end - + (;NF, trunc, Grid, radius, nlat, npoints, nlev, vertical_coordinates) = SG + # resolution information res_ave = sqrt(4π*radius^2/npoints)/1000 # in [km] - res_eq_x = 2π*radius/RingGrids.get_nlon_max(Grid,nlat_half)/1000 - lat = get_lat(Grid,nlat_half) - res_eq_y = (lat[nlat_half] - lat[nlat_half+1])*radius/1000 - s(x) = x > 1000 ? @sprintf("%i",x) : @sprintf("%.3g",x) println(io,"$(typeof(SG)):") println(io,"├ Spectral: T$trunc LowerTriangularMatrix{Complex{$NF}}, radius = $radius m") - println(io,"├ Grid: $(get_nlat(Grid,nlat_half))-ring $Grid{$NF}, $npoints grid points") - println(io,"├ Resolution: $(s(res_ave))km (average), $(s(res_eq_x))km × $(s(res_eq_y))km (Equator)") + println(io,"├ Grid: $nlat-ring $Grid{$NF}, $npoints grid points") + println(io,"├ Resolution: $(s(res_ave))km (average)") print(io,"└ Vertical: $nlev-level $(typeof(vertical_coordinates))") end diff --git a/src/dynamics/tendencies.jl b/src/dynamics/tendencies.jl index 41ec89cd8..097cd2f76 100644 --- a/src/dynamics/tendencies.jl +++ b/src/dynamics/tendencies.jl @@ -7,7 +7,7 @@ function dynamics_tendencies!( diagn::DiagnosticVariablesLayer, model::Barotropic) forcing!(diagn, progn, model.forcing, time, model) # = (Fᵤ, Fᵥ) forcing for u,v drag!(diagn,progn, model.drag, time, model) # drag term for u,v - vorticity_flux!(diagn,model) # = ∇×(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ) + vorticity_flux!(diagn, model) # = ∇×(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ) end """ @@ -587,14 +587,14 @@ with `Fᵤ,Fᵥ` from `u_tend_grid`/`v_tend_grid` that are assumed to be alread set in `forcing!`. Set `div=false` for the BarotropicModel which doesn't require the divergence tendency.""" function vorticity_flux_curldiv!( diagn::DiagnosticVariablesLayer, - C::AbstractCoriolis, - G::Geometry, + coriolis::AbstractCoriolis, + geometry::Geometry, S::SpectralTransform; div::Bool=true, # also calculate div of vor flux? add::Bool=false) # accumulate in vor/div tendencies? - (;f) = C - (;coslat⁻¹) = G + (;f) = coriolis + (;coslat⁻¹) = geometry (;u_tend_grid, v_tend_grid) = diagn.tendencies # already contains forcing u = diagn.grid_variables.u_grid # velocity @@ -641,7 +641,7 @@ with with Fᵤ,Fᵥ the forcing from `forcing!` already in `u_tend_grid`/`v_tend_grid` and vorticity ζ, coriolis f.""" -function vorticity_flux!(diagn::DiagnosticVariablesLayer,model::ShallowWater) +function vorticity_flux!(diagn::DiagnosticVariablesLayer, model::ShallowWater) C = model.coriolis G = model.geometry S = model.spectral_transform diff --git a/src/dynamics/time_integration.jl b/src/dynamics/time_integration.jl index 285408bd4..0817dd255 100644 --- a/src/dynamics/time_integration.jl +++ b/src/dynamics/time_integration.jl @@ -1,5 +1,3 @@ -abstract type AbstractTimeStepper <: AbstractModelComponent end - export Leapfrog """ @@ -234,24 +232,25 @@ end """ $(TYPEDSIGNATURES) Calculate a single time step for the `model <: Barotropic`.""" -function timestep!( progn::PrognosticVariables, # all prognostic variables - diagn::DiagnosticVariables, # all pre-allocated diagnostic variables - dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2) - model::Barotropic, # everything that's constant at runtime - lf1::Int=2, # leapfrog index 1 (dis/enables Robert+Williams filter) - lf2::Int=2) # leapfrog index 2 (time step used for tendencies) - +function timestep!( + progn::PrognosticVariables, # all prognostic variables + diagn::DiagnosticVariables, # all pre-allocated diagnostic variables + dt::Real, # time step (mostly =2Δt, but for init steps =Δt,Δt/2) + model::Barotropic, # everything that's constant at runtime + lf1::Int=2, # leapfrog index 1 (dis/enables Robert+Williams filter) + lf2::Int=2, # leapfrog index 2 (time step used for tendencies) +) model.feedback.nars_detected && return nothing # exit immediately if NaRs already present (;time) = progn.clock # current time zero_tendencies!(diagn) # start with zero for accumulation # LOOP OVER LAYERS FOR TENDENCIES, DIFFUSION, LEAPFROGGING AND PROPAGATE STATE TO GRID - for (progn_layer,diagn_layer) in zip(progn.layers,diagn.layers) + for (progn_layer,diagn_layer) in zip(progn.layers, diagn.layers) progn_lf = progn_layer.timesteps[lf2] # pick the leapfrog time step lf2 for tendencies - dynamics_tendencies!(diagn_layer,progn_lf,time,model) - horizontal_diffusion!(diagn_layer,progn_layer,model) - leapfrog!(progn_layer,diagn_layer,dt,lf1,model) - gridded!(diagn_layer,progn_lf,model) + dynamics_tendencies!(diagn_layer, progn_lf, time, model) + horizontal_diffusion!(diagn_layer, progn_layer, model) + leapfrog!(progn_layer, diagn_layer, dt, lf1, model) + gridded!(diagn_layer, progn_lf, model) end end @@ -364,15 +363,15 @@ function time_stepping!( (;time_stepping) = model # SCALING: we use vorticity*radius,divergence*radius in the dynamical core - scale!(progn,model.spectral_grid.radius) + scale!(progn, model.spectral_grid.radius) # OUTPUT INITIALISATION AND STORING INITIAL CONDITIONS + FEEDBACK # propagate spectral state to grid variables for initial condition output (;output,feedback) = model lf = 1 # use first leapfrog index - gridded!(diagn,progn,lf,model) - initialize!(output,feedback,time_stepping,clock,diagn,model) - initialize!(feedback,clock,model) + gridded!(diagn, progn, lf, model) + initialize!(output, feedback, time_stepping, clock, diagn, model) + initialize!(feedback, clock, model) # FIRST TIMESTEPS: EULER FORWARD THEN 1x LEAPFROG first_timesteps!(progn,diagn,model,output) @@ -392,7 +391,7 @@ function time_stepping!( unscale!(progn) # undo radius-scaling for vor,div from the dynamical core close(output) # close netCDF file write_restart_file(progn,output) # as JLD2 - progress_finish!(feedback) # finishes the progress meter bar + finish!(feedback) # finishes the progress meter bar - return progn -end \ No newline at end of file + return progn # to trigger UnicodePlot via show(::IO,::PrognosticVariables) +end \ No newline at end of file diff --git a/src/models/abstract_models.jl b/src/models/abstract_models.jl index c822a30b4..e16a392ca 100644 --- a/src/models/abstract_models.jl +++ b/src/models/abstract_models.jl @@ -16,18 +16,29 @@ The default fallback is that all variables are included. """ has(M::Type{<:ModelSetup}, var_name::Symbol) = var_name in (:vor, :div, :temp, :humid, :pres) has(M::ModelSetup, var_name) = has(typeof(M), var_name) -# strip away the parameters of the model type +# model class is the abstract supertype model_class(::Type{<:Barotropic}) = Barotropic model_class(::Type{<:ShallowWater}) = ShallowWater model_class(::Type{<:PrimitiveDry}) = PrimitiveDry model_class(::Type{<:PrimitiveWet}) = PrimitiveWet model_class(model::ModelSetup) = model_class(typeof(model)) +# model type is the parameter-free type of a model +# TODO what happens if we have several concrete types under each abstract type? +model_type(::Type{<:Barotropic}) = BarotropicModel +model_type(::Type{<:ShallowWater}) = ShallowWaterModel +model_type(::Type{<:PrimitiveDry}) = PrimitiveDryModel +model_type(::Type{<:PrimitiveWet}) = PrimitiveWetModel +model_type(model::ModelSetup) = model_type(typeof(model)) + function Base.show(io::IO,M::ModelSetup) - println(io,"$(typeof(M))") - for key in propertynames(M)[1:end-1] + println(io,"$(model_type(M)) <: $(model_class(M))") + properties = propertynames(M) + n = length(properties) + for (i,key) in enumerate(properties) val = getfield(M,key) - println(io,"├ $key: $(typeof(val))") + s = i == n ? "└" : "├" # choose ending └ for last property + p = i == n ? print : println + p(io,"$s $key: $(typeof(val))") end - print(io,"└ feedback: $(typeof(M.feedback))") end \ No newline at end of file diff --git a/src/models/barotropic.jl b/src/models/barotropic.jl index 0aaaac123..bff45886b 100644 --- a/src/models/barotropic.jl +++ b/src/models/barotropic.jl @@ -7,6 +7,7 @@ whether scalars or arrays that do not change throughout model integration. $(TYPEDFIELDS)""" Base.@kwdef mutable struct BarotropicModel{ NF<:AbstractFloat, + DS<:DeviceSetup, PL<:AbstractPlanet, AT<:AbstractAtmosphere, CO<:AbstractCoriolis, @@ -15,14 +16,15 @@ Base.@kwdef mutable struct BarotropicModel{ IC<:InitialConditions, TS<:AbstractTimeStepper, ST<:SpectralTransform{NF}, + IM<:AbstractImplicit, HD<:AbstractHorizontalDiffusion, GE<:AbstractGeometry, - DS<:AbstractDevice, OW<:AbstractOutputWriter, FB<:AbstractFeedback } <: Barotropic spectral_grid::SpectralGrid = SpectralGrid(nlev=1) + device_setup::DS = DeviceSetup(CPUDevice()) # DYNAMICS planet::PL = Earth(spectral_grid) @@ -35,12 +37,10 @@ Base.@kwdef mutable struct BarotropicModel{ # NUMERICS time_stepping::TS = Leapfrog(spectral_grid) spectral_transform::ST = SpectralTransform(spectral_grid) + implicit::IM = NoImplicit(spectral_grid) horizontal_diffusion::HD = HyperDiffusion(spectral_grid) geometry::GE = Geometry(spectral_grid) - # INTERNALS - device_setup::DS = DeviceSetup(CPUDevice()) - # OUTPUT output::OW = OutputWriter(spectral_grid,Barotropic) feedback::FB = Feedback() diff --git a/src/models/simulation.jl b/src/models/simulation.jl index c19644dc1..512be45ae 100644 --- a/src/models/simulation.jl +++ b/src/models/simulation.jl @@ -17,10 +17,10 @@ struct Simulation{Model<:ModelSetup} <: AbstractSimulation{Model} end function Base.show(io::IO,S::AbstractSimulation) - println(io,"$(typeof(S))") - println(io,"├ $(typeof(S.model))") + println(io,"Simulation{$(model_type(S.model))}") println(io,"├ $(typeof(S.prognostic_variables))") - print(io, "└ $(typeof(S.diagnostic_variables))") + println(io,"├ $(typeof(S.diagnostic_variables))") + print(io,"└ model::$(model_type(S.model))") end export run! @@ -31,7 +31,7 @@ Run a SpeedyWeather.jl `simulation`. The `simulation.model` is assumed to be ini function run!( simulation::AbstractSimulation; period = Day(10), output::Bool = false, - n_days::Union{Nothing, Real}=nothing) + n_days::Union{Nothing, Real} = nothing) if !isnothing(n_days) @warn "run!: n_days keyword is deprecated, use period = Day(n_days) instead." diff --git a/src/output/abstract_types.jl b/src/output/abstract_types.jl new file mode 100644 index 000000000..9487db7d0 --- /dev/null +++ b/src/output/abstract_types.jl @@ -0,0 +1,2 @@ +abstract type AbstractOutputWriter end +abstract type AbstractFeedback end \ No newline at end of file diff --git a/src/output/feedback.jl b/src/output/feedback.jl index 0f7248ba8..eedd3169b 100644 --- a/src/output/feedback.jl +++ b/src/output/feedback.jl @@ -1,4 +1,3 @@ -abstract type AbstractFeedback end export Feedback """ @@ -140,7 +139,7 @@ end """ $(TYPEDSIGNATURES) Finalises the progress meter and the progress txt file.""" -function progress_finish!(F::Feedback) +function finish!(F::Feedback) ProgressMeter.finish!(F.progress_meter) if F.output # write final progress to txt file diff --git a/src/output/output.jl b/src/output/output.jl index ac384ea73..227ef69fa 100644 --- a/src/output/output.jl +++ b/src/output/output.jl @@ -1,6 +1,27 @@ -abstract type AbstractOutputWriter end abstract type AbstractKeepbits end +"""Number of mantissa bits to keep for each prognostic variable when compressed for +netCDF and .jld2 data output. +$(TYPEDFIELDS)""" +Base.@kwdef struct Keepbits + u::Int = 7 + v::Int = 7 + vor::Int = 5 + div::Int = 5 + temp::Int = 10 + pres::Int = 12 + humid::Int = 7 + precip_cond::Int = 7 + precip_conv::Int = 7 + cloud::Int = 7 +end + +function Base.show(io::IO,K::Keepbits) + println(io,"$(typeof(K))") + keys = propertynames(K) + print_fields(io,K,keys) +end + # default number format for output const DEFAULT_OUTPUT_NF = Float32 const DEFAULT_OUTPUT_DT = Hour(6) @@ -290,28 +311,6 @@ function initialize!( close(parameters_txt) end -""" -Number of mantissa bits to keep for each prognostic variable when compressed for -netCDF and .jld2 data output. -$(TYPEDFIELDS)""" -Base.@kwdef struct Keepbits - u::Int = 7 - v::Int = 7 - vor::Int = 5 - div::Int = 5 - temp::Int = 10 - pres::Int = 12 - humid::Int = 7 - precip_cond::Int = 7 - precip_conv::Int = 7 - cloud::Int = 7 -end - -function Base.show(io::IO,K::Keepbits) - println(io,"$(typeof(K))") - keys = propertynames(K) - print_fields(io,K,keys) -end """ $(TYPEDSIGNATURES) diff --git a/src/physics/boundary_layer.jl b/src/physics/boundary_layer.jl index 1e22fc4bf..03cd22793 100644 --- a/src/physics/boundary_layer.jl +++ b/src/physics/boundary_layer.jl @@ -1,4 +1,4 @@ -abstract type AbstractBoundaryLayerDrag <: AbstractParameterization end +abstract type AbstractBoundaryLayer <: AbstractParameterization end # function barrier for all boundary layer drags function boundary_layer_drag!( column::ColumnVariables, @@ -8,7 +8,7 @@ end # dummy boundary layer export NoBoundaryLayerDrag -struct NoBoundaryLayerDrag <: AbstractBoundaryLayerDrag end +struct NoBoundaryLayerDrag <: AbstractBoundaryLayer end NoBoundaryLayerDrag(::SpectralGrid) = NoBoundaryLayerDrag() initialize!(::NoBoundaryLayerDrag,::PrimitiveEquation) = nothing boundary_layer_drag!(::ColumnVariables,::NoBoundaryLayerDrag,::PrimitiveEquation) = nothing @@ -77,7 +77,7 @@ function boundary_layer_drag!( column::ColumnVariables, end export ConstantDrag -Base.@kwdef struct ConstantDrag{NF} <: AbstractBoundaryLayerDrag +Base.@kwdef struct ConstantDrag{NF} <: AbstractBoundaryLayer drag::NF = 1e-3 end @@ -94,7 +94,7 @@ export BulkRichardsonDrag """Boundary layer drag coefficient from the bulk Richardson number, following Frierson, 2006, Journal of the Atmospheric Sciences. $(TYPEDFIELDS)""" -Base.@kwdef struct BulkRichardsonDrag{NF} <: BoundaryLayerDrag{NF} +Base.@kwdef struct BulkRichardsonDrag{NF} <: AbstractBoundaryLayer "von Kármán constant [1]" κ::NF = 0.4 diff --git a/src/physics/convection.jl b/src/physics/convection.jl index 48199873b..a7c78da47 100644 --- a/src/physics/convection.jl +++ b/src/physics/convection.jl @@ -1,6 +1,6 @@ -abstract type AbstractConvection{NF} <: AbstractParameterization{NF} end +abstract type AbstractConvection <: AbstractParameterization end -Base.@kwdef struct SimplifiedBettsMiller{NF} <: AbstractConvection{NF} +Base.@kwdef struct SimplifiedBettsMiller{NF} <: AbstractConvection "number of vertical layers/levels" nlev::Int diff --git a/src/physics/large_scale_condensation.jl b/src/physics/large_scale_condensation.jl index 5f68c1d77..90027dced 100644 --- a/src/physics/large_scale_condensation.jl +++ b/src/physics/large_scale_condensation.jl @@ -10,7 +10,7 @@ export ImplicitCondensation """ Large scale condensation as with implicit precipitation. $(TYPEDFIELDS)""" -Base.@kwdef struct ImplicitCondensation{NF<:AbstractFloat} <: AbstractCondensation{NF} +Base.@kwdef struct ImplicitCondensation{NF<:AbstractFloat} <: AbstractCondensation "Flux limiter for latent heat release [K] per timestep" max_heating::NF = 0.2 diff --git a/src/physics/longwave_radiation.jl b/src/physics/longwave_radiation.jl index d3bb9b981..e285cdf21 100644 --- a/src/physics/longwave_radiation.jl +++ b/src/physics/longwave_radiation.jl @@ -1,7 +1,7 @@ -abstract type AbstractLongwave{NF} <: AbstractRadiation{NF} end +abstract type AbstractLongwave <: AbstractRadiation end -struct NoLongwave{NF} <: AbstractLongwave{NF} end -NoLongwave(SG::SpectralGrid) = NoLongwave{SG.NF}() +struct NoLongwave <: AbstractLongwave end +NoLongwave(SG::SpectralGrid) = NoLongwave() initialize!(::NoLongwave,::PrimitiveEquation) = nothing # function barrier for all AbstractLongwave @@ -11,7 +11,7 @@ end longwave_radiation!(::ColumnVariables,::NoLongwave,::PrimitiveEquation) = nothing -Base.@kwdef struct UniformCooling{NF} <: AbstractLongwave{NF} +Base.@kwdef struct UniformCooling{NF} <: AbstractLongwave time_scale::Second = Hour(16) temp_min::NF = 207.5 temp_stratosphere::NF = 200 diff --git a/src/physics/shortwave_radiation.jl b/src/physics/shortwave_radiation.jl index c8adce624..6232da4d3 100644 --- a/src/physics/shortwave_radiation.jl +++ b/src/physics/shortwave_radiation.jl @@ -1,18 +1,18 @@ -abstract type AbstractRadiation{NF} <: AbstractParameterization{NF} end -abstract type AbstractShortwave{NF} <: AbstractRadiation{NF} end +abstract type AbstractRadiation <: AbstractParameterization end +abstract type AbstractShortwave <: AbstractRadiation end -struct NoShortwave{NF} <: AbstractShortwave{NF} end -NoShortwave(SG::SpectralGrid) = NoShortwave{SG.NF}() +struct NoShortwave <: AbstractShortwave end +NoShortwave(SG::SpectralGrid) = NoShortwave() initialize!(::NoShortwave,::PrimitiveEquation) = nothing # function barrier for all AbstractShortwave -function shortwave_radiation!(column::ColumnVariables,model::PrimitiveEquation) - shortwave_radiation!(column,model.shortwave_radiation,model) +function shortwave_radiation!(column::ColumnVariables, model::PrimitiveEquation) + shortwave_radiation!(column, model.shortwave_radiation, model) end shortwave_radiation!(::ColumnVariables,::NoShortwave,::PrimitiveEquation) = nothing -Base.@kwdef struct TransparentShortwave{NF} <: AbstractShortwave{NF} +Base.@kwdef struct TransparentShortwave{NF} <: AbstractShortwave albedo::NF = 0.3 S::Base.RefValue{NF} = Ref(zero(NF)) end diff --git a/src/physics/thermodynamics.jl b/src/physics/thermodynamics.jl index e76a33cc1..21d8baaae 100644 --- a/src/physics/thermodynamics.jl +++ b/src/physics/thermodynamics.jl @@ -59,7 +59,7 @@ function (CC::ClausiusClapeyron{NF})(temp_kelvin::NF) where NF end # convert to number format of struct -function (CC::AbstractClausiusClapeyron{NF})(temp_kelvin) where NF +function (CC::ClausiusClapeyron{NF})(temp_kelvin) where NF CC(convert(NF,temp_kelvin)) end @@ -72,7 +72,7 @@ function grad(CC::ClausiusClapeyron{NF},temp_kelvin::NF) where NF end # convert to input argument to number format from struct -grad(CC::AbstractClausiusClapeyron{NF},temp_kelvin) where NF = grad(CC,convert(NF,temp_kelvin)) +grad(CC::ClausiusClapeyron{NF},temp_kelvin) where NF = grad(CC,convert(NF,temp_kelvin)) """ $(TYPEDSIGNATURES) @@ -101,7 +101,7 @@ Saturation humidity [kg/kg] from temperature [K], pressure [Pa] via function saturation_humidity( temp_kelvin::NF, pres::NF, - clausius_clapeyron::AbstractClausiusClapeyron{NF} + clausius_clapeyron::AbstractClausiusClapeyron, ) where NF sat_vap_pres = clausius_clapeyron(temp_kelvin) return saturation_humidity(sat_vap_pres,pres;mol_ratio=clausius_clapeyron.mol_ratio) diff --git a/src/physics/vertical_diffusion.jl b/src/physics/vertical_diffusion.jl index 4ca37c384..a1e7bd99e 100644 --- a/src/physics/vertical_diffusion.jl +++ b/src/physics/vertical_diffusion.jl @@ -1,8 +1,8 @@ -abstract type VerticalDiffusion{NF} <: AbstractParameterization{NF} end +abstract type AbstractVerticalDiffusion <: AbstractParameterization end export NoVerticalDiffusion -struct NoVerticalDiffusion{NF} <: VerticalDiffusion{NF} end -NoVerticalDiffusion(SG::SpectralGrid) = NoVerticalDiffusion{SG.NF}() +struct NoVerticalDiffusion <: AbstractVerticalDiffusion end +NoVerticalDiffusion(SG::SpectralGrid) = NoVerticalDiffusion() function initialize!( scheme::NoVerticalDiffusion, model::PrimitiveEquation) @@ -26,7 +26,7 @@ export StaticEnergyDiffusion Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation. $(TYPEDFIELDS)""" -Base.@kwdef struct StaticEnergyDiffusion{NF<:AbstractFloat} <: VerticalDiffusion{NF} +Base.@kwdef struct StaticEnergyDiffusion{NF<:AbstractFloat} <: AbstractVerticalDiffusion "time scale for strength" time_scale::Second = Hour(6) @@ -86,7 +86,7 @@ export HumidityDiffusion Diffusion of dry static energy: A relaxation towards a reference gradient of static energy wrt to geopotential, see Fortran SPEEDY documentation. $(TYPEDFIELDS)""" -Base.@kwdef struct HumidityDiffusion{NF<:AbstractFloat} <: VerticalDiffusion{NF} +Base.@kwdef struct HumidityDiffusion{NF<:AbstractFloat} <: AbstractVerticalDiffusion "time scale for strength" time_scale::Second = Hour(24) diff --git a/src/physics/zenith.jl b/src/physics/zenith.jl index 7ad650161..6fe5fb4e5 100644 --- a/src/physics/zenith.jl +++ b/src/physics/zenith.jl @@ -1,11 +1,11 @@ -abstract type AbstractSolarDeclination{NF} end -abstract type AbstractSolarTimeCorrection{NF} end +abstract type AbstractSolarDeclination end +abstract type AbstractSolarTimeCorrection end abstract type AbstractZenith{NF,Grid} end """Coefficients to calculate the solar declination angle δ [radians] based on a simple sine function, with Earth's axial tilt as amplitude, equinox as phase shift. $(TYPEDFIELDS)""" -Base.@kwdef struct SinSolarDeclination{NF} <: AbstractSolarDeclination{NF} +Base.@kwdef struct SinSolarDeclination{NF} <: AbstractSolarDeclination axial_tilt::NF = 23.44 equinox::DateTime = DateTime(2000,3,20) length_of_year::Second = Day(365.25) @@ -42,7 +42,7 @@ end with g the angular fraction of the year in radians. Following Spencer 1971, Fourier series representation of the position of the sun. Search 2(5):172. $(TYPEDFIELDS)""" -Base.@kwdef struct SolarDeclination{NF} <: AbstractSolarDeclination{NF} +Base.@kwdef struct SolarDeclination{NF<:AbstractFloat} <: AbstractSolarDeclination a::NF = 0.006918 # the offset + s1::NF = 0.070257 # s1*sin(g) + c1::NF = -0.399912 # c1*cos(g) + @@ -77,7 +77,7 @@ end """Coefficients for the solar time correction (also called Equation of time) which adjusts the solar hour to an oscillation of sunrise/set by about +-16min throughout the year.""" -Base.@kwdef struct SolarTimeCorrection{NF} <: AbstractSolarTimeCorrection{NF} +Base.@kwdef struct SolarTimeCorrection{NF<:AbstractFloat} <: AbstractSolarTimeCorrection a::NF = 0.004297 # the offset + s1::NF = -1.837877 # s1*sin(g) + c1::NF = 0.107029 # c1*cos(g) + @@ -201,7 +201,7 @@ depending on parameters in SolarZenith.""" function cos_zenith!( S::SolarZenith{NF}, time::DateTime, - geometry::Geometry + geometry::AbstractGeometry, ) where NF (;sinlat, coslat, lons) = geometry @@ -263,7 +263,7 @@ depending on parameters in SolarZenithSeason.""" function cos_zenith!( S::SolarZenithSeason{NF}, time::DateTime, - geometry::Geometry + geometry::AbstractGeometry, ) where NF (;sinlat, coslat, lat) = geometry From 896291d86676cbe9606e39eb88df85a3c10f181a Mon Sep 17 00:00:00 2001 From: Milan Date: Thu, 29 Feb 2024 02:28:10 -0500 Subject: [PATCH 03/17] make some model components mutable --- src/dynamics/atmospheres.jl | 2 +- src/dynamics/drag.jl | 2 +- src/dynamics/forcing.jl | 2 +- src/dynamics/horizontal_diffusion.jl | 2 +- src/dynamics/initial_conditions.jl | 4 ++-- src/dynamics/orography.jl | 12 ++++++------ src/dynamics/planets.jl | 2 +- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/dynamics/atmospheres.jl b/src/dynamics/atmospheres.jl index fa5e3b93d..d300160b0 100644 --- a/src/dynamics/atmospheres.jl +++ b/src/dynamics/atmospheres.jl @@ -6,7 +6,7 @@ $(TYPEDSIGNATURES) Create a struct `EarthAtmosphere <: AbstractAtmosphere`, with the following physical/chemical characteristics. Keyword arguments are $(TYPEDFIELDS)""" -Base.@kwdef struct EarthAtmosphere{NF<:AbstractFloat} <: AbstractAtmosphere +Base.@kwdef mutable struct EarthAtmosphere{NF<:AbstractFloat} <: AbstractAtmosphere "molar mass of dry air [g/mol]" mol_mass_dry_air::NF = 28.9649 diff --git a/src/dynamics/drag.jl b/src/dynamics/drag.jl index 8dc51b04d..45165ee2d 100644 --- a/src/dynamics/drag.jl +++ b/src/dynamics/drag.jl @@ -16,7 +16,7 @@ end # Quadratic drag export QuadraticDrag -Base.@kwdef struct QuadraticDrag{NF} <: AbstractDrag +Base.@kwdef mutable struct QuadraticDrag{NF} <: AbstractDrag "[OPTION] drag coefficient [1]" c_D::NF = 1e-5 diff --git a/src/dynamics/forcing.jl b/src/dynamics/forcing.jl index 96addec93..24c036d44 100644 --- a/src/dynamics/forcing.jl +++ b/src/dynamics/forcing.jl @@ -24,7 +24,7 @@ Galewsky, 2004, but mirrored for both hemispheres. $(TYPEDFIELDS) """ -Base.@kwdef struct JetStreamForcing{NF} <: AbstractForcing +Base.@kwdef mutable struct JetStreamForcing{NF} <: AbstractForcing "Number of latitude rings" nlat::Int = 0 diff --git a/src/dynamics/horizontal_diffusion.jl b/src/dynamics/horizontal_diffusion.jl index 480f4524c..1cbf7c92c 100644 --- a/src/dynamics/horizontal_diffusion.jl +++ b/src/dynamics/horizontal_diffusion.jl @@ -11,7 +11,7 @@ layers. Furthermore the power can be decreased above the `tapering_σ` to `power_stratosphere` (default 2). For Barotropic, ShallowWater, the default non-adaptive constant-time scale hyper diffusion is used. Options are $(TYPEDFIELDS)""" -Base.@kwdef struct HyperDiffusion{NF} <: AbstractHorizontalDiffusion +Base.@kwdef mutable struct HyperDiffusion{NF} <: AbstractHorizontalDiffusion # DIMENSIONS "spectral resolution" trunc::Int diff --git a/src/dynamics/initial_conditions.jl b/src/dynamics/initial_conditions.jl index bff46e30e..5e5a1c886 100644 --- a/src/dynamics/initial_conditions.jl +++ b/src/dynamics/initial_conditions.jl @@ -27,7 +27,7 @@ end """Start with random vorticity as initial conditions $(TYPEDFIELDS)""" -Base.@kwdef struct StartWithRandomVorticity <: InitialConditions +Base.@kwdef mutable struct StartWithRandomVorticity <: InitialConditions "Power of the spectral distribution k^power" power::Float64 = -3 @@ -62,7 +62,7 @@ $(TYPEDSIGNATURES) Create a struct that contains all parameters for the Galewsky et al, 2004 zonal jet intitial conditions for the shallow water model. Default values as in Galewsky. $(TYPEDFIELDS)""" -Base.@kwdef struct ZonalJet <: InitialConditions +Base.@kwdef mutable struct ZonalJet <: InitialConditions "jet latitude [˚N]" latitude::Float64 = 45 diff --git a/src/dynamics/orography.jl b/src/dynamics/orography.jl index 8c7c52ccb..80e9a8fb5 100644 --- a/src/dynamics/orography.jl +++ b/src/dynamics/orography.jl @@ -28,7 +28,7 @@ export ZonalRidge """Zonal ridge orography after Jablonowski and Williamson, 2006. $(TYPEDFIELDS)""" -Base.@kwdef struct ZonalRidge{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractOrography{NF,Grid} +Base.@kwdef mutable struct ZonalRidge{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractOrography{NF,Grid} "conversion from σ to Jablonowski's ηᵥ-coordinates" η₀::Float64 = 0.252 @@ -38,10 +38,10 @@ Base.@kwdef struct ZonalRidge{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: Abstr # FIELDS (to be initialized in initialize!) "height [m] on grid-point space." - orography::Grid + const orography::Grid "surface geopotential, height*gravity [m²/s²]" - geopot_surf::LowerTriangularMatrix{Complex{NF}} + const geopot_surf::LowerTriangularMatrix{Complex{NF}} end """ @@ -98,7 +98,7 @@ export EarthOrography """Earth's orography read from file, with smoothing. $(TYPEDFIELDS)""" -Base.@kwdef struct EarthOrography{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractOrography{NF,Grid} +Base.@kwdef mutable struct EarthOrography{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractOrography{NF,Grid} # OPTIONS "path to the folder containing the orography file, pkg path default" @@ -127,10 +127,10 @@ Base.@kwdef struct EarthOrography{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: A # FIELDS (to be initialized in initialize!) "height [m] on grid-point space." - orography::Grid + const orography::Grid "surface geopotential, height*gravity [m²/s²]" - geopot_surf::LowerTriangularMatrix{Complex{NF}} + const geopot_surf::LowerTriangularMatrix{Complex{NF}} end """ diff --git a/src/dynamics/planets.jl b/src/dynamics/planets.jl index b29c71ba6..ac242018a 100644 --- a/src/dynamics/planets.jl +++ b/src/dynamics/planets.jl @@ -12,7 +12,7 @@ characteristics. Note that `radius` is not part of it as this should be chosen in `SpectralGrid`. Keyword arguments are $(TYPEDFIELDS) """ -Base.@kwdef struct Earth{NF<:AbstractFloat} <: AbstractPlanet +Base.@kwdef mutable struct Earth{NF<:AbstractFloat} <: AbstractPlanet "angular frequency of Earth's rotation [rad/s]" rotation::NF = DEFAULT_ROTATION From a7901aae14b7b0c2c38961817f3e08ac6e425377 Mon Sep 17 00:00:00 2001 From: Milan Date: Thu, 29 Feb 2024 02:28:33 -0500 Subject: [PATCH 04/17] parametric ShallowWaterModel --- src/SpeedyWeather.jl | 5 +- src/dynamics/coriolis.jl | 1 + src/dynamics/geometry.jl | 2 +- src/dynamics/tendencies.jl | 37 ++++++++------ src/dynamics/time_integration.jl | 2 +- src/models/abstract_models.jl | 7 +++ src/models/barotropic.jl | 20 +++++--- src/models/shallow_water.jl | 87 ++++++++++++++++++++------------ src/output/output.jl | 2 +- src/physics/abstract_types.jl | 9 +--- 10 files changed, 100 insertions(+), 72 deletions(-) diff --git a/src/SpeedyWeather.jl b/src/SpeedyWeather.jl index 322b7549a..5fbaa0d24 100644 --- a/src/SpeedyWeather.jl +++ b/src/SpeedyWeather.jl @@ -124,13 +124,10 @@ include("output/feedback.jl") include("output/plot.jl") include("output/callbacks.jl") -# MODELS -include("dynamics/models.jl") - # MODELS include("models/simulation.jl") include("models/barotropic.jl") -# include("models/shallow_water.jl") +include("models/shallow_water.jl") # include("models/primitive_dry.jl") # include("models/primitive_wet.jl") end \ No newline at end of file diff --git a/src/dynamics/coriolis.jl b/src/dynamics/coriolis.jl index ab6440e9e..abf7f10d0 100644 --- a/src/dynamics/coriolis.jl +++ b/src/dynamics/coriolis.jl @@ -17,6 +17,7 @@ function initialize!(coriolis::Coriolis, model::ModelSetup) # =2Ωsin(lat) but scaled with radius as are the equations coriolis.f .= 2rotation * sinlat * radius + return nothing end """ diff --git a/src/dynamics/geometry.jl b/src/dynamics/geometry.jl index 1eb75a259..9e5bd04f3 100644 --- a/src/dynamics/geometry.jl +++ b/src/dynamics/geometry.jl @@ -27,7 +27,7 @@ Base.@kwdef struct Geometry{NF<:AbstractFloat} <: AbstractGeometry nlon::Int = nlon_max "number of latitude rings" - nlat::Int = get_nlat(Grid,nlat_half) + nlat::Int = spectral_grid.nlat "number of vertical levels" nlev::Int = spectral_grid.nlev diff --git a/src/dynamics/tendencies.jl b/src/dynamics/tendencies.jl index 097cd2f76..2f5df1c22 100644 --- a/src/dynamics/tendencies.jl +++ b/src/dynamics/tendencies.jl @@ -13,25 +13,30 @@ end """ $(TYPEDSIGNATURES) Calculate all tendencies for the ShallowWaterModel.""" -function dynamics_tendencies!( diagn::DiagnosticVariablesLayer, - progn::PrognosticVariablesLayer, - surface::SurfaceVariables, - pres::LowerTriangularMatrix, # spectral pressure/η for geopotential - time::DateTime, # time to evaluate the tendencies at - model::ShallowWater) # struct containing all constants - - S,C,G,O = model.spectral_transform, model.constants, model.geometry, model.orography - F,D = model.forcing, model.drag +function dynamics_tendencies!( + diagn::DiagnosticVariablesLayer, + progn::PrognosticVariablesLayer, + surface::SurfaceVariables, + pres::LowerTriangularMatrix, # spectral pressure/η for geopotential + time::DateTime, # time to evaluate the tendencies at + model::ShallowWater, +) + (;forcing, drag, planet, atmosphere, orography) = model + (;spectral_transform, geometry) = model # for compatibility with other ModelSetups pressure pres = interface displacement η here - forcing!(diagn,progn,F,time,model) # = (Fᵤ, Fᵥ, Fₙ) forcing for u,v,η - drag!(diagn,progn,D,time,model) # drag term for momentum u,v - vorticity_flux!(diagn,model) # = ∇×(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ), tendency for vorticity - # = ∇⋅(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ), tendency for divergence + forcing!(diagn, progn, forcing, time, model) # = (Fᵤ, Fᵥ, Fₙ) forcing for u,v,η + drag!(diagn, progn, drag, time, model) # drag term for momentum u,v + + # = ∇×(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ), tendency for vorticity + # = ∇⋅(v(ζ+f) + Fᵤ,-u(ζ+f) + Fᵥ), tendency for divergence + vorticity_flux!(diagn, model) + + geopotential!(diagn, pres, planet) # geopotential Φ = gη in shallow water + bernoulli_potential!(diagn, spectral_transform) # = -∇²(E+Φ), tendency for divergence - geopotential!(diagn,pres,C) # geopotential Φ = gη in the shallow water model - bernoulli_potential!(diagn,S) # = -∇²(E+Φ), tendency for divergence - volume_flux_divergence!(diagn,surface,O,C,G,S) # = -∇⋅(uh,vh), tendency pressure + # = -∇⋅(uh,vh), tendency for "pressure" η + volume_flux_divergence!(diagn, surface, orography, atmosphere, geometry, spectral_transform) end """ diff --git a/src/dynamics/time_integration.jl b/src/dynamics/time_integration.jl index 73b1a508d..76ca3acd9 100644 --- a/src/dynamics/time_integration.jl +++ b/src/dynamics/time_integration.jl @@ -365,7 +365,7 @@ and calls the output and feedback functions.""" function time_stepping!( progn::PrognosticVariables, # all prognostic variables diagn::DiagnosticVariables, # all pre-allocated diagnostic variables - model::ModelSetup, # all precalculated structs + model::ModelSetup, # all model components ) (;clock) = progn diff --git a/src/models/abstract_models.jl b/src/models/abstract_models.jl index e16a392ca..16eefccd2 100644 --- a/src/models/abstract_models.jl +++ b/src/models/abstract_models.jl @@ -10,6 +10,13 @@ abstract type PrimitiveWet <: PrimitiveEquation end abstract type AbstractModelComponent end +# print all fields with type <: Number +function Base.show(io::IO,P::AbstractModelComponent) + println(io,"$(typeof(P)) <: $(supertype(typeof(P)))") + keys = propertynames(P) + print_fields(io,P,keys) +end + """$(TYPEDSIGNATURES) Returns true if the model `M` has a prognostic variable `var_name`, false otherwise. The default fallback is that all variables are included. """ diff --git a/src/models/barotropic.jl b/src/models/barotropic.jl index bff45886b..3fd1cb960 100644 --- a/src/models/barotropic.jl +++ b/src/models/barotropic.jl @@ -1,9 +1,14 @@ export BarotropicModel, initialize! """ -$(SIGNATURES) -The BarotropicModel struct holds all other structs that contain precalculated constants, -whether scalars or arrays that do not change throughout model integration. +The BarotropicModel contains all model components needed for the simulation of the +barotropic vorticity equations. To be constructed like + + model = BarotropicModel(;spectral_grid, kwargs...) + +with `spectral_grid::SpectralGrid` used to initalize all non-default components +passed on as keyword arguments, e.g. `planet=Earth(spectral_grid)`. Fields, representing +model components, are $(TYPEDFIELDS)""" Base.@kwdef mutable struct BarotropicModel{ NF<:AbstractFloat, @@ -20,7 +25,7 @@ Base.@kwdef mutable struct BarotropicModel{ HD<:AbstractHorizontalDiffusion, GE<:AbstractGeometry, OW<:AbstractOutputWriter, - FB<:AbstractFeedback + FB<:AbstractFeedback, } <: Barotropic spectral_grid::SpectralGrid = SpectralGrid(nlev=1) @@ -43,6 +48,7 @@ Base.@kwdef mutable struct BarotropicModel{ # OUTPUT output::OW = OutputWriter(spectral_grid,Barotropic) + callbacks::Vector{AbstractCallback} = AbstractCallback[] feedback::FB = Feedback() end @@ -51,7 +57,7 @@ default_concrete_model(::Type{Barotropic}) = BarotropicModel """ $(TYPEDSIGNATURES) -Calls all `initialize!` functions for components of `model`, +Calls all `initialize!` functions for most fields, representing components, of `model`, except for `model.output` and `model.feedback` which are always called at in `time_stepping!`.""" function initialize!(model::Barotropic; time::DateTime = DEFAULT_DATE) @@ -60,10 +66,8 @@ function initialize!(model::Barotropic; time::DateTime = DEFAULT_DATE) spectral_grid.nlev > 1 && @warn "Only nlev=1 supported for BarotropicModel, \ SpectralGrid with nlev=$(spectral_grid.nlev) provided." - # slightly adjust model time step to be a convenient divisor of output timestep - initialize!(model.time_stepping, model) - # initialize components + initialize!(model.time_stepping, model) initialize!(model.coriolis, model) initialize!(model.forcing, model) initialize!(model.drag, model) diff --git a/src/models/shallow_water.jl b/src/models/shallow_water.jl index 5d8f473ce..f3433343c 100644 --- a/src/models/shallow_water.jl +++ b/src/models/shallow_water.jl @@ -1,36 +1,57 @@ export ShallowWaterModel """ -$(SIGNATURES) -The ShallowWaterModel struct holds all other structs that contain precalculated constants, -whether scalars or arrays that do not change throughout model integration. +The ShallowWaterModel contains all model components needed for the simulation of the +shallow water equations. To be constructed like + + model = ShallowWaterModel(;spectral_grid, kwargs...) + +with `spectral_grid::SpectralGrid` used to initalize all non-default components +passed on as keyword arguments, e.g. `planet=Earth(spectral_grid)`. Fields, representing +model components, are $(TYPEDFIELDS)""" -Base.@kwdef mutable struct ShallowWaterModel{NF<:AbstractFloat, D<:AbstractDevice} <: ShallowWater +Base.@kwdef mutable struct ShallowWaterModel{ + NF<:AbstractFloat, + DS<:DeviceSetup, + PL<:AbstractPlanet, + AT<:AbstractAtmosphere, + CO<:AbstractCoriolis, + OR<:AbstractOrography, + FR<:AbstractForcing, + DR<:AbstractDrag, + IC<:InitialConditions, + TS<:AbstractTimeStepper, + ST<:SpectralTransform{NF}, + IM<:AbstractImplicit, + HD<:AbstractHorizontalDiffusion, + GE<:AbstractGeometry, + OW<:AbstractOutputWriter, + FB<:AbstractFeedback, +} <: ShallowWater + spectral_grid::SpectralGrid = SpectralGrid(nlev=1) + device_setup::DS = DeviceSetup(CPUDevice()) # DYNAMICS - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - coriolis::AbstractCoriolis = Coriolis(spectral_grid) - forcing::AbstractForcing = NoForcing() - drag::AbstractDrag = NoDrag() - initial_conditions::InitialConditions = ZonalJet() - orography::AbstractOrography{NF} = EarthOrography(spectral_grid) + planet::PL = Earth(spectral_grid) + atmosphere::AT = EarthAtmosphere(spectral_grid) + coriolis::CO = Coriolis(spectral_grid) + orography::OR = EarthOrography(spectral_grid) + forcing::FR = NoForcing() + drag::DR = NoDrag() + initial_conditions::IC = ZonalJet() # NUMERICS - time_stepping::TimeStepper = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit = ImplicitShallowWater(spectral_grid) - - # INTERNALS - geometry::AbstractGeometry = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) + time_stepping::TS = Leapfrog(spectral_grid) + spectral_transform::ST = SpectralTransform(spectral_grid) + implicit::IM = ImplicitShallowWater(spectral_grid) + horizontal_diffusion::HD = HyperDiffusion(spectral_grid) + geometry::GE = Geometry(spectral_grid) # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,ShallowWater) - feedback::AbstractFeedback = Feedback() + output::OW = OutputWriter(spectral_grid,Barotropic) + callbacks::Vector{AbstractCallback} = AbstractCallback[] + feedback::FB = Feedback() end has(::Type{<:ShallowWater}, var_name::Symbol) = var_name in (:vor, :div, :pres) @@ -38,23 +59,23 @@ default_concrete_model(::Type{ShallowWater}) = ShallowWaterModel """ $(TYPEDSIGNATURES) -Calls all `initialize!` functions for components of `model`, -except for `model.output` and `model.feedback` which are always called -at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" -function initialize!(model::ShallowWater;time::DateTime = DEFAULT_DATE) +Calls all `initialize!` functions for most components (=fields) of `model`, +except for `model.output` and `model.feedback` which are always initialized +in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" +function initialize!(model::ShallowWater; time::DateTime = DEFAULT_DATE) (;spectral_grid) = model spectral_grid.nlev > 1 && @warn "Only nlev=1 supported for ShallowWaterModel, \ SpectralGrid with nlev=$(spectral_grid.nlev) provided." - # slightly adjust model time step to be a convenient divisor of output timestep - initialize!(model.time_stepping,model) - # initialize components - initialize!(model.forcing,model) - initialize!(model.drag,model) - initialize!(model.horizontal_diffusion,model) - initialize!(model.orography,model) + initialize!(model.time_stepping, model) + initialize!(model.coriolis, model) + initialize!(model.orography, model) + initialize!(model.forcing, model) + initialize!(model.drag, model) + initialize!(model.horizontal_diffusion, model) + # model.implicit is initialized in first_timesteps! # initial conditions prognostic_variables = PrognosticVariables(spectral_grid,model) diff --git a/src/output/output.jl b/src/output/output.jl index 227ef69fa..3b8340bc7 100644 --- a/src/output/output.jl +++ b/src/output/output.jl @@ -160,7 +160,7 @@ function initialize!( time_stepping::AbstractTimeStepper, clock::Clock, diagn::DiagnosticVariables, - model::Model + model::ModelSetup, ) where {output_NF,Model} output.output || return nothing # exit immediately for no output diff --git a/src/physics/abstract_types.jl b/src/physics/abstract_types.jl index dfd5fa403..51bb69cd7 100644 --- a/src/physics/abstract_types.jl +++ b/src/physics/abstract_types.jl @@ -1,8 +1 @@ -abstract type AbstractParameterization <: AbstractModelComponent end - -# print all fields with type <: Number -function Base.show(io::IO,P::AbstractModelComponent) - println(io,"$(typeof(P)) <: $(supertype(typeof(P)))") - keys = propertynames(P) - print_fields(io,P,keys) -end \ No newline at end of file +abstract type AbstractParameterization <: AbstractModelComponent end \ No newline at end of file From b2d6251e1e3976a75937289399c4e76b05019f80 Mon Sep 17 00:00:00 2001 From: Milan Date: Fri, 1 Mar 2024 12:25:14 -0500 Subject: [PATCH 05/17] primitive dry model parametric --- src/SpeedyWeather.jl | 3 +- src/dynamics/adiabatic_conversion.jl | 1 + src/dynamics/atmospheres.jl | 6 ++ src/dynamics/geopotential.jl | 3 + src/dynamics/implicit.jl | 13 ++- src/dynamics/initial_conditions.jl | 21 +++-- src/dynamics/tendencies.jl | 32 ++++--- src/dynamics/time_integration.jl | 2 +- src/dynamics/vertical_advection.jl | 4 +- src/dynamics/virtual_temperature.jl | 10 +-- src/models/barotropic.jl | 9 +- src/models/primitive_dry.jl | 119 ++++++++++++++---------- src/physics/boundary_layer.jl | 5 +- src/physics/define_column.jl | 2 - src/physics/land.jl | 8 +- src/physics/longwave_radiation.jl | 2 + src/physics/ocean.jl | 5 +- src/physics/shortwave_radiation.jl | 2 + src/physics/surface_fluxes.jl | 10 ++- src/physics/temperature_relaxation.jl | 125 +++++++++++++++----------- src/physics/thermodynamics.jl | 4 +- src/physics/zenith.jl | 4 + 22 files changed, 242 insertions(+), 148 deletions(-) diff --git a/src/SpeedyWeather.jl b/src/SpeedyWeather.jl index 5fbaa0d24..85a99d405 100644 --- a/src/SpeedyWeather.jl +++ b/src/SpeedyWeather.jl @@ -91,6 +91,7 @@ include("dynamics/time_integration.jl") include("dynamics/forcing.jl") include("dynamics/drag.jl") include("dynamics/geopotential.jl") +include("dynamics/virtual_temperature.jl") include("dynamics/initial_conditions.jl") include("dynamics/horizontal_diffusion.jl") include("dynamics/vertical_advection.jl") @@ -128,6 +129,6 @@ include("output/callbacks.jl") include("models/simulation.jl") include("models/barotropic.jl") include("models/shallow_water.jl") -# include("models/primitive_dry.jl") +include("models/primitive_dry.jl") # include("models/primitive_wet.jl") end \ No newline at end of file diff --git a/src/dynamics/adiabatic_conversion.jl b/src/dynamics/adiabatic_conversion.jl index c2b883cfe..10258e1b5 100644 --- a/src/dynamics/adiabatic_conversion.jl +++ b/src/dynamics/adiabatic_conversion.jl @@ -1,5 +1,6 @@ abstract type AbstractAdiabaticConversion <: AbstractModelComponent end +export AdiabaticConversion Base.@kwdef struct AdiabaticConversion{NF} <: AbstractAdiabaticConversion nlev::Int diff --git a/src/dynamics/atmospheres.jl b/src/dynamics/atmospheres.jl index d300160b0..0c91613a2 100644 --- a/src/dynamics/atmospheres.jl +++ b/src/dynamics/atmospheres.jl @@ -49,6 +49,12 @@ Base.@kwdef mutable struct EarthAtmosphere{NF<:AbstractFloat} <: AbstractAtmosph "surface reference pressure [Pa]" pres_ref::NF = 1e5 + "surface reference temperature [K]" + temp_ref::NF = 288 + + "reference moist-adiabatic temperature lapse rate [K/m]" + lapse_rate::NF = 5/1000 + "layer thickness for the shallow water model [m]" layer_thickness::NF = 8500 end diff --git a/src/dynamics/geopotential.jl b/src/dynamics/geopotential.jl index a367133f4..3199ac932 100644 --- a/src/dynamics/geopotential.jl +++ b/src/dynamics/geopotential.jl @@ -1,11 +1,14 @@ abstract type AbstractGeopotential <: AbstractModelComponent end +export Geopotential Base.@kwdef struct Geopotential{NF} <: AbstractGeopotential nlev::Int Δp_geopot_half::Vector{NF} = zeros(NF,nlev) Δp_geopot_full::Vector{NF} = zeros(NF,nlev) end +Geopotential(SG::SpectralGrid) = Geopotential{SG.NF}(;nlev=SG.nlev) + """ $(TYPEDSIGNATURES) Precomputes constants for the vertical integration of the geopotential, defined as diff --git a/src/dynamics/implicit.jl b/src/dynamics/implicit.jl index 7f1d84847..159caf505 100644 --- a/src/dynamics/implicit.jl +++ b/src/dynamics/implicit.jl @@ -193,8 +193,14 @@ function ImplicitPrimitiveEquation(spectral_grid::SpectralGrid,kwargs...) end # function barrier to unpack the constants struct for primitive eq models -function initialize!(I::ImplicitPrimitiveEquation,dt::Real,diagn::DiagnosticVariables,model::PrimitiveEquation) - initialize!(I, dt, diagn, model.geometry, model.geopotential, model.adiabatic_conversion) +function initialize!( + I::ImplicitPrimitiveEquation, + dt::Real, + diagn::DiagnosticVariables, + model::PrimitiveEquation, +) + (; geometry, geopotential, atmosphere, adiabatic_conversion) = model + initialize!(I, dt, diagn, geometry, geopotential, atmosphere, adiabatic_conversion) end """$(TYPEDSIGNATURES) @@ -205,7 +211,8 @@ function initialize!( diagn::DiagnosticVariables, geometry::AbstractGeometry, geopotential::AbstractGeopotential, - adiabatic_conversion::AbstractAdiabaticConversion + atmosphere::AbstractAtmosphere, + adiabatic_conversion::AbstractAdiabaticConversion, ) (;trunc, nlev, α,temp_profile,S,S⁻¹,L,R,U,W,L0,L1,L2,L3,L4) = implicit diff --git a/src/dynamics/initial_conditions.jl b/src/dynamics/initial_conditions.jl index 5e5a1c886..aa3a6d273 100644 --- a/src/dynamics/initial_conditions.jl +++ b/src/dynamics/initial_conditions.jl @@ -183,6 +183,9 @@ $(TYPEDFIELDS)""" Base.@kwdef struct ZonalWind <: InitialConditions "conversion from σ to Jablonowski's ηᵥ-coordinates" η₀::Float64 = 0.252 + + "Sigma coordinates of the tropopause [1]" + σ_tropopause::Float64 = 0.2 "max amplitude of zonal wind [m/s]" u₀::Float64 = 35 @@ -201,6 +204,9 @@ Base.@kwdef struct ZonalWind <: InitialConditions perturb_radius::Float64 = 1/10 # TERMPERATURE + "reference dry-adiabtic lapse rate [K/m]" + lapse_rate::Float64 = 5/1000 + "temperature difference used for stratospheric lapse rate [K], Jablonowski uses ΔT = 4.8e5 [K]" ΔT::Float64 = 0 @@ -221,7 +227,8 @@ function initialize!( progn::PrognosticVariables{NF}, (;u₀, η₀, ΔT, Tmin, pressure_on_orography) = initial_conditions (;perturb_lat, perturb_lon, perturb_uₚ, perturb_radius) = initial_conditions - (;temp_ref, R_dry, lapse_rate, pres_ref, σ_tropopause) = model.atmosphere + (;lapse_rate, σ_tropopause) = initial_conditions + (;temp_ref, R_dry, pres_ref) = model.atmosphere (;radius, Grid, nlat_half, nlev) = model.spectral_grid (;rotation, gravity) = model.planet (;σ_levels_full) = model.geometry @@ -279,15 +286,14 @@ function initialize!( progn::PrognosticVariables{NF}, # TEMPERATURE Tη = zero(σ_levels_full) - Γ = lapse_rate/1000 # from [K/km] to [K/m] # vertical profile for k in 1:nlev σ = σ_levels_full[k] - Tη[k] = temp_ref*σ^(R_dry*Γ/gravity) # Jablonowski and Williamson eq. 4 + Tη[k] = temp_ref*σ^(R_dry*lapse_rate/gravity) # Jablonowski and Williamson eq. 4 if σ < σ_tropopause - Tη[k] += ΔT*(σ_tropopause-σ)^5 # Jablonowski and Williamson eq. 5 + Tη[k] += ΔT*(σ_tropopause-σ)^5 # Jablonowski and Williamson eq. 5 end end @@ -410,7 +416,7 @@ hydrostatic equation with the reference temperature lapse rate.""" function pressure_on_orography!(progn::PrognosticVariables, model::PrimitiveEquation) # temp_ref: Reference absolute T [K] at surface z = 0, constant lapse rate - # lapse_rate: Reference temperature lapse rate -dT/dz [K/km] + # lapse_rate: Reference temperature lapse rate -dT/dz [K/m] # gravity: Gravitational acceleration [m/s^2] # R: Specific gas constant for dry air [J/kg/K] # pres_ref: Reference surface pressure [hPa] @@ -418,12 +424,11 @@ function pressure_on_orography!(progn::PrognosticVariables, (; gravity ) = model.planet (; orography ) = model.orography # orography on the grid - Γ = lapse_rate/1000 # Lapse rate [K/km] -> [K/m] lnp₀ = log(pres_ref) # logarithm of reference surface pressure [log(Pa)] lnp_grid = zero(orography) # allocate log surface pressure on grid - RΓg⁻¹ = R_dry*Γ/gravity # for convenience - ΓT⁻¹ = Γ/temp_ref + RΓg⁻¹ = R_dry*lapse_rate/gravity # for convenience + ΓT⁻¹ = lapse_rate/temp_ref for ij in eachgridpoint(lnp_grid,orography) lnp_grid[ij] = lnp₀ + log(1 - ΓT⁻¹*orography[ij])/RΓg⁻¹ diff --git a/src/dynamics/tendencies.jl b/src/dynamics/tendencies.jl index 2f5df1c22..36345c661 100644 --- a/src/dynamics/tendencies.jl +++ b/src/dynamics/tendencies.jl @@ -50,7 +50,8 @@ function dynamics_tendencies!( diagn::DiagnosticVariables, O = model.orography G = model.geometry S = model.spectral_transform - C = model.constants + GP = model.geopotential + A = model.atmosphere I = model.implicit (; surface ) = diagn @@ -69,14 +70,14 @@ function dynamics_tendencies!( diagn::DiagnosticVariables, temperature_anomaly!(diagn_layer,I) # temperature relative to profile end - geopotential!(diagn,O,C) # from ∂Φ/∂ln(pₛ) = -RTᵥ, used in bernoulli_potential! - vertical_integration!(diagn,progn,lf_implicit,G) # get ū,v̄,D̄ on grid; and and D̄ in spectral + geopotential!(diagn,GP,O) # from ∂Φ/∂ln(pₛ) = -RTᵥ, used in bernoulli_potential! + vertical_integration!(diagn,progn,lf_implicit,G)# get ū,v̄,D̄ on grid; and and D̄ in spectral surface_pressure_tendency!(surface,S) # ∂ln(pₛ)/∂t = -(ū,v̄)⋅∇ln(pₛ) - D̄ @floop for layer in diagn.layers vertical_velocity!(layer,surface,G) # calculate σ̇ for the vertical mass flux M = pₛσ̇ # add the RTₖlnpₛ term to geopotential - linear_pressure_gradient!(layer,progn.surface,lf_implicit,C,I) + linear_pressure_gradient!(layer,progn.surface,lf_implicit,A,I) end # wait all because vertical_velocity! needs to # finish before vertical_advection! @floop for layer in diagn.layers @@ -316,7 +317,8 @@ function vordiv_tendencies!( surf::SurfaceVariables, model::PrimitiveEquation, ) - vordiv_tendencies!(diagn,surf,model.constants,model.geometry,model.spectral_transform) + (;coriolis, atmosphere, geometry, spectral_transform) = model + vordiv_tendencies!(diagn, surf, coriolis, atmosphere, geometry, spectral_transform) end """$(TYPEDSIGNATURES) @@ -398,12 +400,14 @@ to transform the physics tendencies from grid-point to spectral space including necessary coslat⁻¹ scaling.""" function tendencies_physics_only!( diagn::DiagnosticVariablesLayer, - G::Geometry, + G::AbstractGeometry, S::SpectralTransform, wet_core::Bool = true ) (;coslat⁻¹) = G - (;u_tend_grid, v_tend_grid, temp_tend_grid, humid_tend_grid) = diagn.tendencies # already contains parameterizations + + # already contain parameterizations + (;u_tend_grid, v_tend_grid, temp_tend_grid, humid_tend_grid) = diagn.tendencies # precompute ring indices and boundscheck rings = eachring(u_tend_grid,v_tend_grid) @@ -431,13 +435,12 @@ function tendencies_physics_only!( return nothing end -""" -$(TYPEDSIGNATURES) -Function barrier to unpack `model`.""" +# function barrier function temperature_tendency!( diagn::DiagnosticVariablesLayer, model::PrimitiveEquation, ) + (;adiabatic_conversion, atmosphere, geometry, spectral_transform, implicit) = model temperature_tendency!(diagn, model.atmosphere, model.geometry, model.spectral_transform, model.implicit) end @@ -453,6 +456,7 @@ Compute the temperature tendency temperature used in the adiabatic term κTᵥ*Dlnp/Dt.""" function temperature_tendency!( diagn::DiagnosticVariablesLayer, + adiabatic_conversion::AbstractAdiabaticConversion, atmosphere::AbstractAtmosphere, G::Geometry, S::SpectralTransform, @@ -467,14 +471,14 @@ function temperature_tendency!( Tₖ = I.temp_profile[diagn.k] # average layer temperature from reference profile # coefficients from Simmons and Burridge 1981 - σ_lnp_A = C.σ_lnp_A[diagn.k] # eq. 3.12, -1/Δσₖ*ln(σ_k+1/2/σ_k-1/2) - σ_lnp_B = C.σ_lnp_B[diagn.k] # eq. 3.12 -αₖ + σ_lnp_A = adiabatic_conversion.σ_lnp_A[diagn.k] # eq. 3.12, -1/Δσₖ*ln(σ_k+1/2/σ_k-1/2) + σ_lnp_B = adiabatic_conversion.σ_lnp_B[diagn.k] # eq. 3.12 -αₖ # semi-implicit: terms here are explicit+implicit evaluated at time step i # implicit_correction! then calculated the implicit terms from Vi-1 minus Vi # to move the implicit terms to i-1 which is cheaper then the alternative below - # Diabatic term following Simmons and Burridge 1981 but for σ coordinates + # Adiabatic conversion term following Simmons and Burridge 1981 but for σ coordinates # += as tend already contains parameterizations + vertical advection @. temp_tend_grid += temp_grid*div_grid + # +T'D term of hori advection κ*(Tᵥ+Tₖ)*( # +κTᵥ*Dlnp/Dt, adiabatic term @@ -485,7 +489,7 @@ function temperature_tendency!( spectral!(temp_tend,temp_tend_grid,S) # now add the -∇⋅((u,v)*T') term - flux_divergence!(temp_tend,temp_grid,diagn,G,S,add=true,flipsign=true) + flux_divergence!(temp_tend, temp_grid, diagn, G, S, add=true,flipsign=true) return nothing end diff --git a/src/dynamics/time_integration.jl b/src/dynamics/time_integration.jl index 76ca3acd9..feba1067d 100644 --- a/src/dynamics/time_integration.jl +++ b/src/dynamics/time_integration.jl @@ -381,8 +381,8 @@ function time_stepping!( lf = 1 # use first leapfrog index gridded!(diagn,progn,lf,model) initialize!(output, feedback, time_stepping, clock, diagn, model) - initialize!(feedback, clock, model) initialize!(model.callbacks, progn, diagn, model) + initialize!(feedback, clock, model) # FIRST TIMESTEPS: EULER FORWARD THEN 1x LEAPFROG first_timesteps!(progn,diagn, model, output) diff --git a/src/dynamics/vertical_advection.jl b/src/dynamics/vertical_advection.jl index 44ccb8fb0..f34d18aee 100644 --- a/src/dynamics/vertical_advection.jl +++ b/src/dynamics/vertical_advection.jl @@ -1,9 +1,11 @@ -abstract type VerticalAdvection{NF,B} end +abstract type AbstractVerticalAdvection end +abstract type VerticalAdvection{NF,B} <: AbstractVerticalAdvection end # Dispersive and diffusive advection schemes `NF` is the type, `B` the half-stencil size abstract type DiffusiveVerticalAdvection{NF, B} <: VerticalAdvection{NF, B} end abstract type DispersiveVerticalAdvection{NF, B} <: VerticalAdvection{NF, B} end +export UpwindVerticalAdvection, WENOVerticalAdvection, CenteredVerticalAdvection struct UpwindVerticalAdvection{NF, B} <: DiffusiveVerticalAdvection{NF, B} end struct WENOVerticalAdvection{NF} <: DiffusiveVerticalAdvection{NF, 3} end struct CenteredVerticalAdvection{NF, B} <: DispersiveVerticalAdvection{NF, B} end diff --git a/src/dynamics/virtual_temperature.jl b/src/dynamics/virtual_temperature.jl index 63ffe7f19..6968e2a52 100644 --- a/src/dynamics/virtual_temperature.jl +++ b/src/dynamics/virtual_temperature.jl @@ -2,7 +2,7 @@ function virtual_temperature!( diagn::DiagnosticVariablesLayer, temp::LowerTriangularMatrix, # only needed for dispatch compat with DryCore model::PrimitiveWet) - virtual_temperature!(diagn,temp,model.constants) + virtual_temperature!(diagn, temp, model.atmosphere) end """ @@ -19,12 +19,12 @@ in grid-point space.""" function virtual_temperature!( diagn::DiagnosticVariablesLayer, temp::LowerTriangularMatrix, # only needed for dispatch compat with DryCore - constants::DynamicsConstants, + atmosphere::AbstractAtmosphere, ) (;temp_grid, humid_grid, temp_virt_grid) = diagn.grid_variables (;temp_virt) = diagn.dynamics_variables - μ = constants.μ_virt_temp + μ = atmosphere.μ_virt_temp @inbounds for ij in eachgridpoint(temp_virt_grid, temp_grid, humid_grid) temp_virt_grid[ij] = temp_grid[ij]*(1 + μ*humid_grid[ij]) @@ -90,11 +90,11 @@ specific humidity q and in spectral space.""" function linear_virtual_temperature!( diagn::DiagnosticVariablesLayer, progn::PrognosticLayerTimesteps, - constants::DynamicsConstants, + atmosphere::AbstractAtmosphere, lf::Int) (;temp_virt) = diagn.dynamics_variables - μ = constants.μ_virt_temp + μ = atmosphere.μ_virt_temp Tₖ = diagn.temp_average[] (;temp,humid) = progn.timesteps[lf] diff --git a/src/models/barotropic.jl b/src/models/barotropic.jl index 3fd1cb960..5f1015df5 100644 --- a/src/models/barotropic.jl +++ b/src/models/barotropic.jl @@ -28,9 +28,10 @@ Base.@kwdef mutable struct BarotropicModel{ FB<:AbstractFeedback, } <: Barotropic + # GRID spectral_grid::SpectralGrid = SpectralGrid(nlev=1) - device_setup::DS = DeviceSetup(CPUDevice()) - + geometry::GE = Geometry(spectral_grid) + # DYNAMICS planet::PL = Earth(spectral_grid) atmosphere::AT = EarthAtmosphere(spectral_grid) @@ -38,13 +39,13 @@ Base.@kwdef mutable struct BarotropicModel{ forcing::FR = NoForcing() drag::DR = NoDrag() initial_conditions::IC = StartWithRandomVorticity() - + # NUMERICS + device_setup::DS = DeviceSetup(CPUDevice()) time_stepping::TS = Leapfrog(spectral_grid) spectral_transform::ST = SpectralTransform(spectral_grid) implicit::IM = NoImplicit(spectral_grid) horizontal_diffusion::HD = HyperDiffusion(spectral_grid) - geometry::GE = Geometry(spectral_grid) # OUTPUT output::OW = OutputWriter(spectral_grid,Barotropic) diff --git a/src/models/primitive_dry.jl b/src/models/primitive_dry.jl index bc7b3310e..5ee21bd80 100644 --- a/src/models/primitive_dry.jl +++ b/src/models/primitive_dry.jl @@ -5,50 +5,80 @@ $(SIGNATURES) The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration. $(TYPEDFIELDS)""" -Base.@kwdef mutable struct PrimitiveDryModel{NF<:AbstractFloat, D<:AbstractDevice} <: PrimitiveDry - spectral_grid::SpectralGrid = SpectralGrid() +Base.@kwdef mutable struct PrimitiveDryModel{ + NF<:AbstractFloat, + DS<:DeviceSetup, + PL<:AbstractPlanet, + AT<:AbstractAtmosphere, + CO<:AbstractCoriolis, + GO<:AbstractGeopotential, + OR<:AbstractOrography, + AC<:AbstractAdiabaticConversion, + IC<:InitialConditions, + LS<:AbstractLandSeaMask, + OC<:AbstractOcean, + LA<:AbstractLand, + ZE<:AbstractZenith, + BL<:AbstractBoundaryLayer, + TR<:AbstractTemperatureRelaxation, + SE<:AbstractVerticalDiffusion, + SUT<:AbstractSurfaceThermodynamics, + SUW<:AbstractSurfaceWind, + SH<:AbstractSurfaceHeat, + SW<:AbstractShortwave, + LW<:AbstractLongwave, + TS<:AbstractTimeStepper, + ST<:SpectralTransform{NF}, + IM<:AbstractImplicit, + HD<:AbstractHorizontalDiffusion, + VA<:AbstractVerticalAdvection, + GE<:AbstractGeometry, + OW<:AbstractOutputWriter, + FB<:AbstractFeedback, +} <: PrimitiveDry + spectral_grid::SpectralGrid = SpectralGrid() + geometry::GE = Geometry(spectral_grid) + # DYNAMICS dynamics::Bool = true - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - coriolis::AbstractCoriolis = Coriolis(spectral_grid) - initial_conditions::InitialConditions = ZonalWind() - orography::AbstractOrography{NF} = EarthOrography(spectral_grid) - adiabatic_conversion::AbstractAdiabaticConversion = AdiabaticConversion(spectral_grid) - + planet::PL = Earth(spectral_grid) + atmosphere::AT = EarthAtmosphere(spectral_grid) + coriolis::CO = Coriolis(spectral_grid) + geopotential::GO = Geopotential(spectral_grid) + adiabatic_conversion::AC = AdiabaticConversion(spectral_grid) + initial_conditions::IC = ZonalWind() + # BOUNDARY CONDITIONS - land_sea_mask::AbstractLandSeaMask{NF} = LandSeaMask(spectral_grid) - ocean::AbstractOcean{NF} = SeasonalOceanClimatology(spectral_grid) - land::AbstractLand{NF} = SeasonalLandTemperature(spectral_grid) - solar_zenith::AbstractZenith{NF} = WhichZenith(spectral_grid,planet) - + orography::OR = EarthOrography(spectral_grid) + land_sea_mask::LS = LandSeaMask(spectral_grid) + ocean::OC = SeasonalOceanClimatology(spectral_grid) + land::LA = SeasonalLandTemperature(spectral_grid) + solar_zenith::ZE = WhichZenith(spectral_grid, planet) + # PHYSICS/PARAMETERIZATIONS physics::Bool = true - boundary_layer_drag::BoundaryLayerDrag{NF} = BulkRichardsonDrag(spectral_grid) - temperature_relaxation::TemperatureRelaxation{NF} = NoTemperatureRelaxation(spectral_grid) - static_energy_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) - surface_thermodynamics::AbstractSurfaceThermodynamics{NF} = SurfaceThermodynamicsConstant(spectral_grid) - surface_wind::AbstractSurfaceWind{NF} = SurfaceWind(spectral_grid) - surface_heat_flux::AbstractSurfaceHeat{NF} = SurfaceSensibleHeat(spectral_grid) - shortwave_radiation::AbstractShortwave{NF} = NoShortwave(spectral_grid) - longwave_radiation::AbstractLongwave{NF} = UniformCooling(spectral_grid) - + boundary_layer_drag::BL = BulkRichardsonDrag(spectral_grid) + temperature_relaxation::TR = NoTemperatureRelaxation(spectral_grid) + static_energy_diffusion::SE = NoVerticalDiffusion(spectral_grid) + surface_thermodynamics::SUT = SurfaceThermodynamicsConstant(spectral_grid) + surface_wind::SUW = SurfaceWind(spectral_grid) + surface_heat_flux::SH = SurfaceSensibleHeat(spectral_grid) + shortwave_radiation::SW = NoShortwave(spectral_grid) + longwave_radiation::LW = UniformCooling(spectral_grid) + # NUMERICS - time_stepping::TimeStepper = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit = ImplicitPrimitiveEquation(spectral_grid) - vertical_advection::VerticalAdvection{NF} = CenteredVerticalAdvection(spectral_grid) + device_setup::DS = DeviceSetup(CPUDevice()) + time_stepping::TS = Leapfrog(spectral_grid) + spectral_transform::ST = SpectralTransform(spectral_grid) + implicit::IM = ImplicitPrimitiveEquation(spectral_grid) + horizontal_diffusion::HD = HyperDiffusion(spectral_grid) + vertical_advection::VA = CenteredVerticalAdvection(spectral_grid) - # INTERNALS - geometry::AbstractGeometry = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) - # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,PrimitiveDry) - feedback::AbstractFeedback = Feedback() + output::OW = OutputWriter(spectral_grid, PrimitiveDry) + callbacks::Vector{AbstractCallback} = AbstractCallback[] + feedback::FB = Feedback() end has(::Type{<:PrimitiveDry}, var_name::Symbol) = var_name in (:vor, :div, :temp, :pres) @@ -59,17 +89,16 @@ $(TYPEDSIGNATURES) Calls all `initialize!` functions for components of `model`, except for `model.output` and `model.feedback` which are always called at in `time_stepping!` and `model.implicit` which is done in `first_timesteps!`.""" -function initialize!(model::PrimitiveDry;time::DateTime = DEFAULT_DATE) +function initialize!(model::PrimitiveDry; time::DateTime = DEFAULT_DATE) (;spectral_grid) = model # NUMERICS (implicit is initialized later) - # slightly adjust model time step to be a convenient divisor of output timestep initialize!(model.time_stepping, model) initialize!(model.horizontal_diffusion, model) # DYNAMICS initialize!(model.coriolis, model) - initialize!(model.gepotential, model) + initialize!(model.geopotential, model) initialize!(model.adiabatic_conversion, model) # boundary conditionss @@ -80,11 +109,11 @@ function initialize!(model::PrimitiveDry;time::DateTime = DEFAULT_DATE) initialize!(model.solar_zenith, time, model) # parameterizations - initialize!(model.boundary_layer_drag,model) - initialize!(model.temperature_relaxation,model) - initialize!(model.static_energy_diffusion,model) - initialize!(model.shortwave_radiation,model) - initialize!(model.longwave_radiation,model) + initialize!(model.boundary_layer_drag, model) + initialize!(model.temperature_relaxation, model) + initialize!(model.static_energy_diffusion, model) + initialize!(model.shortwave_radiation, model) + initialize!(model.longwave_radiation, model) # initial conditions prognostic_variables = PrognosticVariables(spectral_grid, model) @@ -96,6 +125,6 @@ function initialize!(model::PrimitiveDry;time::DateTime = DEFAULT_DATE) initialize!(prognostic_variables.ocean, clock.time, model) initialize!(prognostic_variables.land, clock.time, model) - diagnostic_variables = DiagnosticVariables(spectral_grid,model) - return Simulation(prognostic_variables,diagnostic_variables,model) + diagnostic_variables = DiagnosticVariables(spectral_grid, model) + return Simulation(prognostic_variables, diagnostic_variables, model) end \ No newline at end of file diff --git a/src/physics/boundary_layer.jl b/src/physics/boundary_layer.jl index 03cd22793..080dceee0 100644 --- a/src/physics/boundary_layer.jl +++ b/src/physics/boundary_layer.jl @@ -115,8 +115,9 @@ function initialize!(scheme::BulkRichardsonDrag, model::PrimitiveEquation) # Typical height Z of lowermost layer from geopotential of reference surface temperature # minus surface geopotential (orography * gravity) (;temp_ref) = model.atmosphere - (;Δp_geopot_full, gravity) = model.constants - Z = temp_ref*Δp_geopot_full[end] / gravity + (;gravity) = model.planet + (;Δp_geopot_full) = model.geopotential + Z = temp_ref * Δp_geopot_full[end] / gravity # maximum drag Cmax from that height, stable conditions would decrease Cmax towards 0 # Frierson 2006, eq (12) diff --git a/src/physics/define_column.jl b/src/physics/define_column.jl index 866ce8fb9..84e1a9d7e 100644 --- a/src/physics/define_column.jl +++ b/src/physics/define_column.jl @@ -11,8 +11,6 @@ Base.@kwdef mutable struct ColumnVariables{NF<:AbstractFloat} <: AbstractColumnV # DIMENSIONS const nlev::Int = 0 # number of vertical levels - const nband::Int = 0 # number of radiation bands, needed for radiation - const n_stratosphere_levels::Int = 0 # number of stratospheric levels, needed for radiation # COORDINATES ij::Int = 0 # grid-point index diff --git a/src/physics/land.jl b/src/physics/land.jl index e5053c719..55cf34db1 100644 --- a/src/physics/land.jl +++ b/src/physics/land.jl @@ -7,6 +7,7 @@ function Base.show(io::IO,L::AbstractLand) print_fields(io,L,keys) end +export SeasonalLandTemperature Base.@kwdef struct SeasonalLandTemperature{NF,Grid<:AbstractGrid{NF}} <: AbstractLand{NF,Grid} "number of latitudes on one hemisphere, Equator included" @@ -42,8 +43,8 @@ function SeasonalLandTemperature(SG::SpectralGrid;kwargs...) return SeasonalLandTemperature{NF,Grid{NF}}(;nlat_half,kwargs...) end -function initialize!(land::SeasonalLandTemperature{NF,Grid}) where {NF,Grid} - load_monthly_climatology!(land.monthly_temperature,land) +function initialize!(land::SeasonalLandTemperature, model::PrimitiveEquation) + load_monthly_climatology!(land.monthly_temperature, land) end function initialize!( land::PrognosticVariablesLand, @@ -97,6 +98,7 @@ function Base.show(io::IO,S::AbstractSoil) print_fields(io,S,keys) end +export SeasonalSoilMoisture Base.@kwdef struct SeasonalSoilMoisture{NF,Grid<:AbstractGrid{NF}} <: AbstractSoil{NF,Grid} "number of latitudes on one hemisphere, Equator included" @@ -146,7 +148,7 @@ function SeasonalSoilMoisture(SG::SpectralGrid;kwargs...) return SeasonalSoilMoisture{NF,Grid{NF}}(;nlat_half,kwargs...) end -function initialize!(soil::SeasonalSoilMoisture{NF,Grid}) where {NF,Grid} +function initialize!(soil::SeasonalSoilMoisture, model::PrimitiveWet) load_monthly_climatology!(soil.monthly_soil_moisture_layer1,soil,varname=soil.varname_layer1) load_monthly_climatology!(soil.monthly_soil_moisture_layer2,soil,varname=soil.varname_layer2) end diff --git a/src/physics/longwave_radiation.jl b/src/physics/longwave_radiation.jl index e285cdf21..714e0e0ce 100644 --- a/src/physics/longwave_radiation.jl +++ b/src/physics/longwave_radiation.jl @@ -1,5 +1,6 @@ abstract type AbstractLongwave <: AbstractRadiation end +export NoLongwave struct NoLongwave <: AbstractLongwave end NoLongwave(SG::SpectralGrid) = NoLongwave() initialize!(::NoLongwave,::PrimitiveEquation) = nothing @@ -11,6 +12,7 @@ end longwave_radiation!(::ColumnVariables,::NoLongwave,::PrimitiveEquation) = nothing +export UniformCooling Base.@kwdef struct UniformCooling{NF} <: AbstractLongwave time_scale::Second = Hour(16) temp_min::NF = 207.5 diff --git a/src/physics/ocean.jl b/src/physics/ocean.jl index 5fa5d418e..89d2381ec 100644 --- a/src/physics/ocean.jl +++ b/src/physics/ocean.jl @@ -6,6 +6,7 @@ function Base.show(io::IO,O::AbstractOcean) print_fields(io,O,keys) end +export SeasonalOceanClimatology Base.@kwdef struct SeasonalOceanClimatology{NF,Grid<:AbstractGrid{NF}} <: AbstractOcean{NF,Grid} "number of latitudes on one hemisphere, Equator included" @@ -41,8 +42,8 @@ function SeasonalOceanClimatology(SG::SpectralGrid;kwargs...) return SeasonalOceanClimatology{NF,Grid{NF}}(;nlat_half,kwargs...) end -function initialize!(ocean::SeasonalOceanClimatology{NF,Grid}) where {NF,Grid} - load_monthly_climatology!(ocean.monthly_temperature,ocean) +function initialize!(ocean::SeasonalOceanClimatology, model::PrimitiveEquation) + load_monthly_climatology!(ocean.monthly_temperature, ocean) end function load_monthly_climatology!( diff --git a/src/physics/shortwave_radiation.jl b/src/physics/shortwave_radiation.jl index 6232da4d3..411f9fc11 100644 --- a/src/physics/shortwave_radiation.jl +++ b/src/physics/shortwave_radiation.jl @@ -1,6 +1,7 @@ abstract type AbstractRadiation <: AbstractParameterization end abstract type AbstractShortwave <: AbstractRadiation end +export NoShortwave struct NoShortwave <: AbstractShortwave end NoShortwave(SG::SpectralGrid) = NoShortwave() initialize!(::NoShortwave,::PrimitiveEquation) = nothing @@ -12,6 +13,7 @@ end shortwave_radiation!(::ColumnVariables,::NoShortwave,::PrimitiveEquation) = nothing +export TransparentShortwave Base.@kwdef struct TransparentShortwave{NF} <: AbstractShortwave albedo::NF = 0.3 S::Base.RefValue{NF} = Ref(zero(NF)) diff --git a/src/physics/surface_fluxes.jl b/src/physics/surface_fluxes.jl index a11b3ad72..eaf3ec97f 100644 --- a/src/physics/surface_fluxes.jl +++ b/src/physics/surface_fluxes.jl @@ -12,7 +12,7 @@ function surface_fluxes!(column::ColumnVariables,model::PrimitiveEquation) surface_wind_stress!(column, model.surface_wind) # now call other heat and humidity fluxes - sensible_heat_flux!(column, model.surface_heat_flux, model.constants) + sensible_heat_flux!(column, model.surface_heat_flux, model.atmosphere) evaporation!(column,model) end @@ -124,9 +124,11 @@ end SurfaceSensibleHeat(SG::SpectralGrid;kwargs...) = SurfaceSensibleHeat{SG.NF}(;kwargs...) -function sensible_heat_flux!( column::ColumnVariables{NF}, - heat_flux::SurfaceSensibleHeat, - atmosphere::AbstractAtmosphere) where NF +function sensible_heat_flux!( + column::ColumnVariables, + heat_flux::SurfaceSensibleHeat, + atmosphere::AbstractAtmosphere +) cₚ = atmosphere.heat_capacity (;heat_exchange_land, heat_exchange_sea, max_flux) = heat_flux diff --git a/src/physics/temperature_relaxation.jl b/src/physics/temperature_relaxation.jl index 3f3a5ab8f..391a779a1 100644 --- a/src/physics/temperature_relaxation.jl +++ b/src/physics/temperature_relaxation.jl @@ -1,21 +1,22 @@ -abstract type TemperatureRelaxation <: AbstractParameterization end +abstract type AbstractTemperatureRelaxation <: AbstractParameterization end +# function barrier to unpack model.temperature_relaxation function temperature_relaxation!(::ColumnVariables,::PrimitiveEquation) temperature_relaxation!(column, model.temperature_relaxation, model) end export NoTemperatureRelaxation -struct NoTemperatureRelaxation <: TemperatureRelaxation end +struct NoTemperatureRelaxation <: AbstractTemperatureRelaxation end NoTemperatureRelaxation(::SpectralGrid) = NoTemperatureRelaxation() -initialize!(::TemperatureRelaxation,::PrimitiveEquation) = nothing -temperature_relaxation!(::ColumnVariables,::TemperatureRelaxation,::PrimitiveEquation) = nothing +initialize!(::NoTemperatureRelaxation,::PrimitiveEquation) = nothing +temperature_relaxation!(::ColumnVariables,::NoTemperatureRelaxation,::PrimitiveEquation) = nothing export HeldSuarez """ Struct that defines the temperature relaxation from Held and Suarez, 1996 BAMS $(TYPEDFIELDS)""" -Base.@kwdef struct HeldSuarez{NF<:AbstractFloat} <: TemperatureRelaxation +Base.@kwdef struct HeldSuarez{NF<:AbstractFloat} <: AbstractTemperatureRelaxation # DIMENSIONS "number of latitude rings" nlat::Int @@ -27,11 +28,11 @@ Base.@kwdef struct HeldSuarez{NF<:AbstractFloat} <: TemperatureRelaxation "sigma coordinate below which faster surface relaxation is applied" σb::NF = 0.7 - "time scale [hrs] for slow global relaxation" - relax_time_slow::NF = 40*24 + "time scale for slow global relaxation" + relax_time_slow::Second = Day(40) - "time scale [hrs] for faster tropical surface relaxation" - relax_time_fast::NF = 4*24 + "time scale for faster tropical surface relaxation" + relax_time_fast::Second = Day(4) "minimum equilibrium temperature [K]" Tmin::NF = 200 @@ -58,8 +59,7 @@ end $(TYPEDSIGNATURES) create a HeldSuarez temperature relaxation with arrays allocated given `spectral_grid`""" function HeldSuarez(SG::SpectralGrid;kwargs...) - (;NF, Grid, nlat_half, nlev) = SG - nlat = RingGrids.get_nlat(Grid,nlat_half) + (;NF, nlat, nlev) = SG return HeldSuarez{NF}(;nlev,nlat,kwargs...) end @@ -72,14 +72,14 @@ function initialize!( scheme::HeldSuarez, (;σ_levels_full, coslat, sinlat) = model.geometry (;σb, ΔTy, Δθz, relax_time_slow, relax_time_fast, Tmax) = scheme (;temp_relax_freq, temp_equil_a, temp_equil_b) = scheme - - p₀ = model.atmosphere.pres_ref # surface reference pressure [Pa] - scheme.p₀[] = p₀ - scheme.κ[] = model.constants.κ # thermodynamic kappa + + (;pres_ref) = model.atmosphere + scheme.p₀[] = pres_ref # surface reference pressure [Pa] + scheme.κ[] = model.atmosphere.κ # thermodynamic kappa R_dry/cₚ # slow relaxation everywhere, fast in the tropics - kₐ = 1/(relax_time_slow*3600) # scale with radius as ∂ₜT is; hrs -> sec - kₛ = 1/(relax_time_fast*3600) + kₐ = 1/relax_time_slow.value + kₛ = 1/relax_time_fast.value for (j,(cosϕ,sinϕ)) = enumerate(zip(coslat,sinlat)) # use ϕ for latitude here for (k,σ) in enumerate(σ_levels_full) @@ -89,31 +89,40 @@ function initialize!( scheme::HeldSuarez, # Held and Suarez equation 3, split into max(Tmin,(a - b*ln(p))*(p/p₀)^κ) # precompute a,b to simplify online calculation - temp_equil_a[j] = Tmax - ΔTy*sinϕ^2 + Δθz*log(p₀)*cosϕ^2 + temp_equil_a[j] = Tmax - ΔTy*sinϕ^2 + Δθz*log(pres_ref)*cosϕ^2 temp_equil_b[j] = -Δθz*cosϕ^2 end end +# function barrier +function temperature_relaxation!( + column::ColumnVariables, + scheme::HeldSuarez, + model::PrimitiveEquation +) + temperature_relaxation!(column, scheme, model.atmosphere) +end + """$(TYPEDSIGNATURES) Apply temperature relaxation following Held and Suarez 1996, BAMS.""" -function temperature_relaxation!( column::ColumnVariables{NF}, - scheme::HeldSuarez) where NF - +function temperature_relaxation!( + column::ColumnVariables, + scheme::HeldSuarez, + atmosphere::AbstractAtmosphere, +) (;temp, temp_tend, pres, ln_pres) = column j = column.jring[] # latitude ring index j + (;Tmin, temp_relax_freq, temp_equil_a, temp_equil_b) = scheme - (;temp_relax_freq, temp_equil_a, temp_equil_b) = scheme - Tmin = convert(NF,scheme.Tmin) - - p₀ = scheme.p₀[] # reference surface pressure - κ = scheme.κ[] # thermodynamic kappa + # surface reference pressure [Pa] and thermodynamic kappa R_dry/cₚ + (;pres_ref, κ) = atmosphere @inbounds for k in eachlayer(column) lnp = ln_pres[k] # logarithm of pressure at level k kₜ = temp_relax_freq[k,j] # (inverse) relaxation time scale # Held and Suarez 1996, equation 3 with precomputed a,b during initilisation - Teq = max(Tmin,(temp_equil_a[j] + temp_equil_b[j]*lnp)*(pres[k]/p₀)^κ) + Teq = max(Tmin,(temp_equil_a[j] + temp_equil_b[j]*lnp)*(pres[k]/pres_ref)^κ) temp_tend[k] -= kₜ*(temp[k] - Teq) # Held and Suarez 1996, equation 2 end end @@ -123,29 +132,36 @@ export JablonowskiRelaxation """$(TYPEDSIGNATURES) HeldSuarez-like temperature relaxation, but towards the Jablonowski temperature profile with increasing temperatures in the stratosphere.""" -Base.@kwdef struct JablonowskiRelaxation{NF<:AbstractFloat} <: TemperatureRelaxation +Base.@kwdef mutable struct JablonowskiRelaxation{NF<:AbstractFloat} <: AbstractTemperatureRelaxation + # DIMENSIONS nlat::Int nlev::Int # OPTIONS - "sigma coordinate below which relax_time_fast is applied" - σb::Float64= 0.7 + "sigma coordinate below which relax_time_fast is applied [1]" + σb::NF = 0.7 + + "sigma coordinate for tropopause temperature inversion" + σ_tropopause::NF = 0.2 "conversion from σ to Jablonowski's ηᵥ-coordinates" - η₀::Float64 = 0.252 + η₀::NF = 0.252 "max amplitude of zonal wind [m/s]" - u₀::Float64 = 35 + u₀::NF = 35 "temperature difference used for stratospheric lapse rate [K]" - ΔT::Float64 = 4.8e5 + ΔT::NF = 4.8e5 - "[hours] time scale for slow global relaxation" - relax_time_slow::NF = 40*24 + "Dry-adiabatic lapse rate [K/m]" + lapse_rate::NF = 5/1000 + + "time scale for slow global relaxation" + relax_time_slow::Second = Day(40) - "[hours] time scale for fast aster tropical surface relaxation" - relax_time_fast::NF = 4*24 + "time scale for faster tropical surface relaxation" + relax_time_fast::Second = Day(4) # precomputed constants, allocate here, fill in initialize! temp_relax_freq::Matrix{NF} = zeros(NF,nlev,nlat) # (inverse) relax time scale per layer and lat @@ -156,8 +172,7 @@ end $(TYPEDSIGNATURES) create a JablonowskiRelaxation temperature relaxation with arrays allocated given `spectral_grid`""" function JablonowskiRelaxation(SG::SpectralGrid;kwargs...) - (;NF, Grid, nlat_half, nlev) = SG - nlat = RingGrids.get_nlat(Grid,nlat_half) + (;NF, nlat, nlev) = SG return JablonowskiRelaxation{NF}(;nlev,nlat,kwargs...) end @@ -167,18 +182,16 @@ equilibrium temperature Teq and the frequency (strength of relaxation).""" function initialize!( scheme::JablonowskiRelaxation, model::PrimitiveEquation) - (;σ_levels_full, radius, coslat, sinlat) = model.geometry + (;σ_levels_full, coslat, sinlat) = model.geometry (;σb, relax_time_slow, relax_time_fast, η₀, u₀, ΔT) = scheme - (;temp_relax_freq, temp_equil) = scheme - (;gravity, rotation) = model.planet - (;lapse_rate, R_dry, σ_tropopause, temp_ref) = model.atmosphere + (;temp_relax_freq, temp_equil, σ_tropopause, lapse_rate) = scheme + (;gravity) = model.planet + (;R_dry, temp_ref) = model.atmosphere + Ω = model.planet.rotation - Γ = lapse_rate/1000 # from [K/km] to [K1 = radius*rotation - Ω = rotation - - # slow relaxation everywhere, fast in the tropics - kₐ = 1/(relax_time_slow*3600) # scale with radius as ∂ₜT is; hrs -> sec - kₛ = 1/(relax_time_fast*3600) + # slow relaxation [1/s] everywhere, fast in the tropics + kₐ = 1/relax_time_slow.value + kₛ = 1/relax_time_fast.value for (j,(cosϕ,sinϕ)) = enumerate(zip(coslat,sinlat)) # use ϕ for latitude here for (k,σ) in enumerate(σ_levels_full) @@ -186,7 +199,7 @@ function initialize!( scheme::JablonowskiRelaxation, temp_relax_freq[k,j] = kₐ + (kₛ - kₐ)*max(0,(σ-σb)/(1-σb))*cosϕ^4 # vertical profile - Tη = temp_ref*σ^(R_dry*Γ/gravity) # Jablonowski and Williamson eq. 4 + Tη = temp_ref*σ^(R_dry*lapse_rate/gravity) # Jablonowski and Williamson eq. 4 if σ < σ_tropopause Tη += ΔT*(σ_tropopause-σ)^5 # Jablonowski and Williamson eq. 5 @@ -204,7 +217,17 @@ function initialize!( scheme::JablonowskiRelaxation, (8/5*cosϕ^3*(sinϕ^2 + 2/3) - π/4)*Ω) end end -end +end + +# function barrier +function temperature_relaxation!( + column::ColumnVariables, + scheme::JablonowskiRelaxation, + model::PrimitiveEquation, +) + temperature_relaxation!(column, scheme) +end + """$(TYPEDSIGNATURES) Apply HeldSuarez-like temperature relaxation to the Jablonowski and Williamson diff --git a/src/physics/thermodynamics.jl b/src/physics/thermodynamics.jl index 21d8baaae..957e3ff2f 100644 --- a/src/physics/thermodynamics.jl +++ b/src/physics/thermodynamics.jl @@ -119,8 +119,8 @@ end $(TYPEDSIGNATURES) Calculate geopotentiala and dry static energy for the primitive equation model.""" function get_thermodynamics!(column::ColumnVariables,model::PrimitiveEquation) - geopotential!(column.geopot, column.temp, model.constants) - dry_static_energy!(column, model.constants) + geopotential!(column.geopot, column.temp, model.geopotential, column.surface_geopotential) + dry_static_energy!(column, model.atmosphere) end """ diff --git a/src/physics/zenith.jl b/src/physics/zenith.jl index 6fe5fb4e5..2a52101d2 100644 --- a/src/physics/zenith.jl +++ b/src/physics/zenith.jl @@ -135,6 +135,8 @@ function Base.show(io::IO,L::AbstractZenith) print_fields(io,L,keys) end +export SolarZenith + """Solar zenith angle varying with daily and seasonal cycle. $(TYPEDFIELDS)""" Base.@kwdef struct SolarZenith{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractZenith{NF,Grid} @@ -236,6 +238,8 @@ function cos_zenith!( end end +export SolarZenithSeason + """Solar zenith angle varying with seasonal cycle only. $(TYPEDFIELDS)""" Base.@kwdef struct SolarZenithSeason{NF<:AbstractFloat,Grid<:AbstractGrid{NF}} <: AbstractZenith{NF,Grid} From 63e3d53bbdab7ff88f45cf78cbf3e923b17ca633 Mon Sep 17 00:00:00 2001 From: Milan Date: Fri, 1 Mar 2024 12:25:47 -0500 Subject: [PATCH 06/17] PrimitiveDryModel parametric (+prev unsaved changes) --- src/dynamics/tendencies.jl | 4 ++-- src/physics/temperature_relaxation.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dynamics/tendencies.jl b/src/dynamics/tendencies.jl index 36345c661..bfb6da825 100644 --- a/src/dynamics/tendencies.jl +++ b/src/dynamics/tendencies.jl @@ -441,8 +441,8 @@ function temperature_tendency!( model::PrimitiveEquation, ) (;adiabatic_conversion, atmosphere, geometry, spectral_transform, implicit) = model - temperature_tendency!(diagn, model.atmosphere, model.geometry, - model.spectral_transform, model.implicit) + temperature_tendency!(diagn, adiabatic_conversion, atmosphere, geometry, + spectral_transform, implicit) end """ diff --git a/src/physics/temperature_relaxation.jl b/src/physics/temperature_relaxation.jl index 391a779a1..f2e6bda7c 100644 --- a/src/physics/temperature_relaxation.jl +++ b/src/physics/temperature_relaxation.jl @@ -1,7 +1,7 @@ abstract type AbstractTemperatureRelaxation <: AbstractParameterization end # function barrier to unpack model.temperature_relaxation -function temperature_relaxation!(::ColumnVariables,::PrimitiveEquation) +function temperature_relaxation!(column::ColumnVariables,model::PrimitiveEquation) temperature_relaxation!(column, model.temperature_relaxation, model) end From d023a0382d04229e5e56c2b43644dff5ba2a3efb Mon Sep 17 00:00:00 2001 From: Milan Date: Fri, 1 Mar 2024 13:34:57 -0500 Subject: [PATCH 07/17] PrimitiveWet also parametric and so 20% faster!! --- src/SpeedyWeather.jl | 6 +- .../{atmospheres.jl => atmosphere.jl} | 0 src/dynamics/hole_filling.jl | 1 + src/dynamics/initial_conditions.jl | 18 +-- src/dynamics/{planets.jl => planet.jl} | 0 src/dynamics/virtual_temperature.jl | 2 +- src/models/primitive_wet.jl | 130 +++++++++++------- src/physics/convection.jl | 1 + src/physics/land.jl | 11 +- src/physics/thermodynamics.jl | 4 +- 10 files changed, 104 insertions(+), 69 deletions(-) rename src/dynamics/{atmospheres.jl => atmosphere.jl} (100%) rename src/dynamics/{planets.jl => planet.jl} (100%) diff --git a/src/SpeedyWeather.jl b/src/SpeedyWeather.jl index 85a99d405..333ad1911 100644 --- a/src/SpeedyWeather.jl +++ b/src/SpeedyWeather.jl @@ -75,8 +75,8 @@ include("dynamics/vertical_coordinates.jl") include("dynamics/spectral_grid.jl") include("dynamics/geometry.jl") include("dynamics/coriolis.jl") -include("dynamics/planets.jl") -include("dynamics/atmospheres.jl") +include("dynamics/planet.jl") +include("dynamics/atmosphere.jl") include("dynamics/adiabatic_conversion.jl") include("dynamics/orography.jl") include("physics/land_sea_mask.jl") @@ -130,5 +130,5 @@ include("models/simulation.jl") include("models/barotropic.jl") include("models/shallow_water.jl") include("models/primitive_dry.jl") -# include("models/primitive_wet.jl") +include("models/primitive_wet.jl") end \ No newline at end of file diff --git a/src/dynamics/atmospheres.jl b/src/dynamics/atmosphere.jl similarity index 100% rename from src/dynamics/atmospheres.jl rename to src/dynamics/atmosphere.jl diff --git a/src/dynamics/hole_filling.jl b/src/dynamics/hole_filling.jl index 53c43c5e4..77e68bc79 100644 --- a/src/dynamics/hole_filling.jl +++ b/src/dynamics/hole_filling.jl @@ -1,5 +1,6 @@ abstract type AbstractHoleFilling end +export ClipNegatives struct ClipNegatives <: AbstractHoleFilling end ClipNegatives(SG::SpectralGrid) = ClipNegatives() diff --git a/src/dynamics/initial_conditions.jl b/src/dynamics/initial_conditions.jl index aa3a6d273..8d8265ec6 100644 --- a/src/dynamics/initial_conditions.jl +++ b/src/dynamics/initial_conditions.jl @@ -446,14 +446,16 @@ function initialize_humidity!( progn::PrognosticVariables, return nothing end -function initialize_humidity!( progn::PrognosticVariables, - pres_surf_grid::AbstractGrid, - model::PrimitiveWet) - - (;relhumid_ref) = model.atmosphere # relative humidity reference [1] - - # ratio of scale heights [1], scale height [km], scale height for spec humidity [km] - (;scale_height, scale_height_humid) = model.atmosphere +function initialize_humidity!( + progn::PrognosticVariables{NF}, + pres_surf_grid::AbstractGrid, + model::PrimitiveWet +) where NF + + # TODO create a InitialHumidity <: InitialConditions to hold these parameters + relhumid_ref::NF = 0.7 + scale_height_humid::NF = 2.5 # scale height for specific humidity [km] + scale_height::NF = 7.5 # scale height for pressure [km] scale_height_ratio = scale_height/scale_height_humid (;nlev, σ_levels_full) = model.geometry diff --git a/src/dynamics/planets.jl b/src/dynamics/planet.jl similarity index 100% rename from src/dynamics/planets.jl rename to src/dynamics/planet.jl diff --git a/src/dynamics/virtual_temperature.jl b/src/dynamics/virtual_temperature.jl index 6968e2a52..3c1c1813c 100644 --- a/src/dynamics/virtual_temperature.jl +++ b/src/dynamics/virtual_temperature.jl @@ -73,7 +73,7 @@ function linear_virtual_temperature!( model::PrimitiveWet, lf::Integer, ) - linear_virtual_temperature!(diagn,progn,model.constants,lf) + linear_virtual_temperature!(diagn,progn,model.atmosphere,lf) end """ diff --git a/src/models/primitive_wet.jl b/src/models/primitive_wet.jl index d13f24975..e06edcf6b 100644 --- a/src/models/primitive_wet.jl +++ b/src/models/primitive_wet.jl @@ -5,59 +5,96 @@ $(SIGNATURES) The PrimitiveDryModel struct holds all other structs that contain precalculated constants, whether scalars or arrays that do not change throughout model integration. $(TYPEDFIELDS)""" -Base.@kwdef mutable struct PrimitiveWetModel{NF<:AbstractFloat, D<:AbstractDevice} <: PrimitiveWet - spectral_grid::SpectralGrid = SpectralGrid() +Base.@kwdef mutable struct PrimitiveWetModel{ + NF<:AbstractFloat, + DS<:DeviceSetup, + PL<:AbstractPlanet, + AT<:AbstractAtmosphere, + CO<:AbstractCoriolis, + GO<:AbstractGeopotential, + OR<:AbstractOrography, + AC<:AbstractAdiabaticConversion, + IC<:InitialConditions, + LS<:AbstractLandSeaMask, + OC<:AbstractOcean, + LA<:AbstractLand, + ZE<:AbstractZenith, + SO<:AbstractSoil, + VG<:AbstractVegetation, + CC<:AbstractClausiusClapeyron, + BL<:AbstractBoundaryLayer, + TR<:AbstractTemperatureRelaxation, + SE<:AbstractVerticalDiffusion, + HU<:AbstractVerticalDiffusion, + SUT<:AbstractSurfaceThermodynamics, + SUW<:AbstractSurfaceWind, + SH<:AbstractSurfaceHeat, + EV<:AbstractEvaporation, + LSC<:AbstractCondensation, + CV<:AbstractConvection, + SW<:AbstractShortwave, + LW<:AbstractLongwave, + TS<:AbstractTimeStepper, + ST<:SpectralTransform{NF}, + IM<:AbstractImplicit, + HD<:AbstractHorizontalDiffusion, + VA<:AbstractVerticalAdvection, + HF<:AbstractHoleFilling, + GE<:AbstractGeometry, + OW<:AbstractOutputWriter, + FB<:AbstractFeedback, +} <: PrimitiveWet + spectral_grid::SpectralGrid = SpectralGrid() + geometry::GE = Geometry(spectral_grid) + # DYNAMICS dynamics::Bool = true - planet::AbstractPlanet = Earth() - atmosphere::AbstractAtmosphere = EarthAtmosphere() - coriolis::AbstractCoriolis = Coriolis(spectral_grid) - initial_conditions::InitialConditions = ZonalWind() - orography::AbstractOrography{NF} = EarthOrography(spectral_grid) - geopotential::AbstractGeopotential = Geopotential(spectral_grid) - adiabatic_conversion::AbstractAdiabaticConversion = AdiabaticConversion(spectral_grid) - + planet::PL = Earth(spectral_grid) + atmosphere::AT = EarthAtmosphere(spectral_grid) + coriolis::CO = Coriolis(spectral_grid) + geopotential::GO = Geopotential(spectral_grid) + adiabatic_conversion::AC = AdiabaticConversion(spectral_grid) + initial_conditions::IC = ZonalWind() + # BOUNDARY CONDITIONS - land_sea_mask::AbstractLandSeaMask{NF} = LandSeaMask(spectral_grid) - ocean::AbstractOcean{NF} = SeasonalOceanClimatology(spectral_grid) - land::AbstractLand{NF} = SeasonalLandTemperature(spectral_grid) - soil::AbstractSoil{NF} = SeasonalSoilMoisture(spectral_grid) - vegetation::AbstractVegetation{NF} = VegetationClimatology(spectral_grid) - solar_zenith::AbstractZenith{NF} = WhichZenith(spectral_grid,planet) - + orography::OR = EarthOrography(spectral_grid) + land_sea_mask::LS = LandSeaMask(spectral_grid) + ocean::OC = SeasonalOceanClimatology(spectral_grid) + land::LA = SeasonalLandTemperature(spectral_grid) + solar_zenith::ZE = WhichZenith(spectral_grid, planet) + soil::SO = SeasonalSoilMoisture(spectral_grid) + vegetation::VG = VegetationClimatology(spectral_grid) + # PHYSICS/PARAMETERIZATIONS physics::Bool = true - clausius_clapeyron::AbstractClausiusClapeyron{NF} = ClausiusClapeyron(spectral_grid,atmosphere) - boundary_layer_drag::BoundaryLayerDrag{NF} = BulkRichardsonDrag(spectral_grid) - temperature_relaxation::TemperatureRelaxation{NF} = NoTemperatureRelaxation(spectral_grid) - static_energy_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) - humidity_diffusion::VerticalDiffusion{NF} = NoVerticalDiffusion(spectral_grid) - large_scale_condensation::AbstractCondensation{NF} = ImplicitCondensation(spectral_grid) - surface_thermodynamics::AbstractSurfaceThermodynamics{NF} = SurfaceThermodynamicsConstant(spectral_grid) - surface_wind::AbstractSurfaceWind{NF} = SurfaceWind(spectral_grid) - surface_heat_flux::AbstractSurfaceHeat{NF} = SurfaceSensibleHeat(spectral_grid) - evaporation::AbstractEvaporation{NF} = SurfaceEvaporation(spectral_grid) - convection::AbstractConvection{NF} = SimplifiedBettsMiller(spectral_grid) - shortwave_radiation::AbstractShortwave{NF} = NoShortwave(spectral_grid) - longwave_radiation::AbstractLongwave{NF} = UniformCooling(spectral_grid) - + clausius_clapeyron::CC = ClausiusClapeyron(spectral_grid, atmosphere) + boundary_layer_drag::BL = BulkRichardsonDrag(spectral_grid) + temperature_relaxation::TR = NoTemperatureRelaxation(spectral_grid) + static_energy_diffusion::SE = NoVerticalDiffusion(spectral_grid) + humidity_diffusion::HU = NoVerticalDiffusion(spectral_grid) + surface_thermodynamics::SUT = SurfaceThermodynamicsConstant(spectral_grid) + surface_wind::SUW = SurfaceWind(spectral_grid) + surface_heat_flux::SH = SurfaceSensibleHeat(spectral_grid) + evaporation::EV = SurfaceEvaporation(spectral_grid) + large_scale_condensation::LSC = ImplicitCondensation(spectral_grid) + convection::CV = SimplifiedBettsMiller(spectral_grid) + shortwave_radiation::SW = NoShortwave(spectral_grid) + longwave_radiation::LW = UniformCooling(spectral_grid) + # NUMERICS - time_stepping::TimeStepper = Leapfrog(spectral_grid) - spectral_transform::SpectralTransform{NF} = SpectralTransform(spectral_grid) - horizontal_diffusion::HorizontalDiffusion = HyperDiffusion(spectral_grid) - implicit::AbstractImplicit = ImplicitPrimitiveEquation(spectral_grid) - vertical_advection::VerticalAdvection{NF} = CenteredVerticalAdvection(spectral_grid) - hole_filling::AbstractHoleFilling = ClipNegatives() - - # INTERNALS - geometry::AbstractGeometry = Geometry(spectral_grid) - constants::DynamicsConstants{NF} = DynamicsConstants(spectral_grid,planet,atmosphere,geometry) - device_setup::DeviceSetup{D} = DeviceSetup(CPUDevice()) - + device_setup::DS = DeviceSetup(CPUDevice()) + time_stepping::TS = Leapfrog(spectral_grid) + spectral_transform::ST = SpectralTransform(spectral_grid) + implicit::IM = ImplicitPrimitiveEquation(spectral_grid) + horizontal_diffusion::HD = HyperDiffusion(spectral_grid) + vertical_advection::VA = CenteredVerticalAdvection(spectral_grid) + hole_filling::HF = ClipNegatives() + # OUTPUT - output::AbstractOutputWriter = OutputWriter(spectral_grid,PrimitiveWet) - feedback::AbstractFeedback = Feedback() + output::OW = OutputWriter(spectral_grid, PrimitiveDry) + callbacks::Vector{AbstractCallback} = AbstractCallback[] + feedback::FB = Feedback() end has(::Type{<:PrimitiveWet}, var_name::Symbol) = var_name in (:vor, :div, :temp, :pres, :humid) @@ -72,13 +109,12 @@ function initialize!(model::PrimitiveWet;time::DateTime = DEFAULT_DATE) (;spectral_grid) = model # NUMERICS (implicit is initialized later) - # slightly adjust model time step to be a convenient divisor of output timestep initialize!(model.time_stepping, model) initialize!(model.horizontal_diffusion, model) # DYNAMICS initialize!(model.coriolis, model) - initialize!(model.gepotential, model) + initialize!(model.geopotential, model) initialize!(model.adiabatic_conversion, model) # boundary conditionss diff --git a/src/physics/convection.jl b/src/physics/convection.jl index a7c78da47..47d62baf4 100644 --- a/src/physics/convection.jl +++ b/src/physics/convection.jl @@ -1,5 +1,6 @@ abstract type AbstractConvection <: AbstractParameterization end +export SimplifiedBettsMiller Base.@kwdef struct SimplifiedBettsMiller{NF} <: AbstractConvection "number of vertical layers/levels" nlev::Int diff --git a/src/physics/land.jl b/src/physics/land.jl index 55cf34db1..2b0a96d6a 100644 --- a/src/physics/land.jl +++ b/src/physics/land.jl @@ -174,14 +174,9 @@ function soil_timestep!(land::PrognosticVariablesLand{NF}, end ## SOIL MOISTURE -abstract type AbstractVegetation{NF,Grid} end - -function Base.show(io::IO,A::AbstractVegetation) - println(io,"$(typeof(A)) <: AbstractVegetation") - keys = propertynames(A) - print_fields(io,A,keys) -end +abstract type AbstractVegetation{NF,Grid} <: AbstractParameterization end +export VegetationClimatology Base.@kwdef struct VegetationClimatology{NF,Grid<:AbstractGrid{NF}} <: AbstractVegetation{NF,Grid} "number of latitudes on one hemisphere, Equator included" @@ -221,7 +216,7 @@ function VegetationClimatology(SG::SpectralGrid;kwargs...) return VegetationClimatology{NF,Grid{NF}}(;nlat_half,kwargs...) end -function initialize!(vegetation::VegetationClimatology) +function initialize!(vegetation::VegetationClimatology, model::PrimitiveEquation) # LOAD NETCDF FILE if vegetation.path == "SpeedyWeather.jl/input_data" diff --git a/src/physics/thermodynamics.jl b/src/physics/thermodynamics.jl index 957e3ff2f..962cecf33 100644 --- a/src/physics/thermodynamics.jl +++ b/src/physics/thermodynamics.jl @@ -40,8 +40,8 @@ end # generator function function ClausiusClapeyron(SG::SpectralGrid, atm::AbstractAtmosphere; kwargs...) - (;R_dry, R_vapour, latent_heat_condensation, cₚ) = atm - return ClausiusClapeyron{SG.NF}(;Lᵥ=latent_heat_condensation,R_dry,R_vapour,cₚ,kwargs...) + (;R_dry, R_vapour, latent_heat_condensation, heat_capacity) = atm + return ClausiusClapeyron{SG.NF}(;Lᵥ=latent_heat_condensation,R_dry,R_vapour,cₚ=heat_capacity,kwargs...) end """ From db16065f7bfd1c34af77d489d040142472bcc6fe Mon Sep 17 00:00:00 2001 From: Milan Date: Fri, 1 Mar 2024 15:11:16 -0500 Subject: [PATCH 08/17] docs and extending tests adapted to parameter free AbstractForcing/Drag --- docs/src/forcing_drag.md | 67 ++++++++++++++++++-------------------- test/extending.jl | 4 +-- test/surface_press_tend.jl | 6 ---- 3 files changed, 34 insertions(+), 43 deletions(-) delete mode 100644 test/surface_press_tend.jl diff --git a/docs/src/forcing_drag.md b/docs/src/forcing_drag.md index 3396b1780..ae059f42d 100644 --- a/docs/src/forcing_drag.md +++ b/docs/src/forcing_drag.md @@ -37,7 +37,7 @@ To define a new forcing type, at the most basic level you would do ```@example extending using SpeedyWeather -struct MyForcing{NF} <: AbstractForcing{NF} +struct MyForcing{NF} <: SpeedyWeather.AbstractForcing # define some parameters and work arrays here a::NF v::Vector{NF} @@ -47,7 +47,7 @@ In Julia this introduces a new (so-called compound) type that is a subtype of `A we have a bunch of these abstract super types defined and you want to piggy-back on them because of multiple-dispatch. This new type could also be a `mutable struct`, could have keywords defined with `Base.@kwdef` and can -also be parametric with respect to the number format or grid, but let's skip +also be parametric with respect to the number format `NF` or grid, but let's skip those details for now. Conceptually you include into the type any parameters (example the float `a` here) that you may need and especially those that you want to change. This type will get a user-facing interface so that one can quickly create a new @@ -163,7 +163,7 @@ will explain the details in second ```@example extend using SpeedyWeather, Dates -Base.@kwdef struct StochasticStirring{NF} <: AbstractForcing{NF} +Base.@kwdef struct StochasticStirring{NF} <: SpeedyWeather.AbstractForcing # DIMENSIONS from SpectralGrid "Spectral resolution as max degree of spherical harmonics" @@ -175,16 +175,16 @@ Base.@kwdef struct StochasticStirring{NF} <: AbstractForcing{NF} # OPTIONS "Decorrelation time scale τ [days]" - decorrelation_time::Float64 = 2 + decorrelation_time::Second = Day(2) "Stirring strength A [1/s²]" - strength::Float64 = 1e-11 + strength::NF = 7e-11 "Stirring latitude [˚N]" - latitude::Float64 = 45 + latitude::NF = 45 "Stirring width [˚]" - width::Float64 = 24 + width::NF = 24 # TO BE INITIALISED @@ -202,9 +202,10 @@ Base.@kwdef struct StochasticStirring{NF} <: AbstractForcing{NF} end ``` -So, first the scalar parameters, are added as fields of type `Float64` with some default values -as suggested in the [Vallis et al., 2004](https://doi.org/10.1175/1520-0469(2004)061%3C0264:AMASDM%3E2.0.CO;2) -paper. In order to be able to define the default values, we add the `Base.@kwdef` macro +So, first the scalar parameters, are added as fields of type `NF` (you could harcode `Float64` too) +with some default values as suggested in the +[Vallis et al., 2004](https://doi.org/10.1175/1520-0469(2004)061%3C0264:AMASDM%3E2.0.CO;2) paper. +In order to be able to define the default values, we add the `Base.@kwdef` macro before the `struct` definition. Then we need the term `S` as coefficients of the spherical harmonics, which is a `LowerTriangularMatrix`, however we want its elements to be of number format `NF`, which is also the parametric type of `StochasticStirring{NF}`, this is done because it will @@ -236,33 +237,29 @@ zero. For this we want to define a latitudinal mask `lat_mask` that is a vector the number of latitude rings. Similar to `S`, we want to allocate it with zeros (or any other value for that matter), but then precompute this mask in the `initialize!` step. For this we need to know `nlat` at creation time meaning we add this field similar as to how we added -`trunc`. This mask requires the parameters `latitude` and a `width` which are therefore also -added to the definition of `StochasticStirring`. +`trunc`. This mask requires the parameters `latitude` (it's position) and a `width` which +are therefore also added to the definition of `StochasticStirring`. ## Custom forcing: generator function -Cool. Now you could create our new `StochasticStirring` forcing with `StochasticStirring{NF}(trunc=31,nlat=48)`, -and the default values would be chosen as well as the correct size of the arrays `S` and `lat_mask` we need. -Furthermore, note that because `StochasticStirring{NF}` is parametric on the number format `NF`, these -arrays are also allocated with the correct number format that will be used throughout model integration. +Cool. Now you could create our new `StochasticStirring` forcing with `StochasticStirring{Float64}(trunc=31,nlat=48)`, +and the default values would be chosen as well as the correct size of the arrays `S` and `lat_mask` we need +and in double precision Float64. Furthermore, note that because `StochasticStirring{NF}` is parametric +on the number format `NF`, these arrays are also allocated with the correct number format that will +be used throughout model integration. But in SpeedyWeather we typically use the [SpectralGrid](@ref) object to pass on the information of the resolution (and number format) so we want a generator function like ```@example extend function StochasticStirring(SG::SpectralGrid;kwargs...) - (;trunc,Grid,nlat_half) = SG - nlat = RingGrids.get_nlat(Grid,nlat_half) + (;trunc,nlat) = SG return StochasticStirring{SG.NF}(;trunc,nlat,kwargs...) end -nothing # hide ``` Which allows us to do ```@example extend spectral_grid = SpectralGrid(trunc=42,nlev=1) -``` -and -```@example extend -stochastic_stirring = StochasticStirring(spectral_grid,latitude=30,decorrelation_time=5) +stochastic_stirring = StochasticStirring(spectral_grid,latitude=30,decorrelation_time=Day(5)) ``` So the respective resolution parameters and the number format are just pulled from the `SpectralGrid` as a first argument and the remaining parameters are just keyword arguments that one can change at creation. @@ -355,10 +352,10 @@ As you can see, for now not much is actually happening inside this function, this is what is often called a function barrier, the only thing we do in here is to unpack the model to the specific model components we actually need. You can omit this function barrier and jump straight to the definition below, -but often this is done for performance reasons: `model` has several +but often this is done for performance and clarity reasons: `model` might have abstract fields which the compiler cannot optimize for, but unpacking them -makes that possible. So we define the actual `forcing!` function that's -then called as follows +makes that possible. And it also tells you more clearly what a function depends on. +So we define the actual `forcing!` function that's then called as follows ```@example extend function forcing!( diagn::SpeedyWeather.DiagnosticVariablesLayer, @@ -378,16 +375,15 @@ function forcing!( diagn::SpeedyWeather.DiagnosticVariablesLayer, # to grid-point space S_grid = diagn.dynamics_variables.a_grid - SpeedyTransforms.gridded!(S_grid,S,spectral_transform) + SpeedyTransforms.gridded!(S_grid, S, spectral_transform) # mask everything but mid-latitudes - RingGrids._scale_lat!(S_grid,forcing.lat_mask) + RingGrids._scale_lat!(S_grid, forcing.lat_mask) # back to spectral space (;vor_tend) = diagn.tendencies - SpeedyTransforms.spectral!(vor_tend,S_grid,spectral_transform) - SpeedyTransforms.spectral_truncation!(vor_tend) # set lmax+1 to zero - + SpeedyTransforms.spectral!(vor_tend, S_grid, spectral_transform) + return nothing end ``` @@ -418,7 +414,8 @@ without overwriting other terms which, in fact, will be added to this array afterwards. In general, you can also force the momentum equations in grid-point space by writing into `u_tend_grid` and `v_tend_grid`. -Then one last detail is the call to `spectral_truncation!`. Despite the +(Not needed anymore with SpeedyWeather.jl v0.8) +~~Then one last detail is the call to `spectral_truncation!`. Despite the triangular spectral truncation in SpeedyWeather, the lower triangular matrices for the spherical harmonics are actually of size ``N+1 \times N``, because this additional degree (=row) is needed for vector quantities @@ -426,7 +423,7 @@ this additional degree (=row) is needed for vector quantities this means that the last spectral transform will compute the spherical harmonics of this last degree which, however, scalar quantities like vorticity should not make use of. We therefore set this degree to zero -with the call to `spectral_truncation!`, which does exactly that. +with the call to `spectral_truncation!`, which does exactly that.~~ ## Custom forcing: model construction @@ -466,8 +463,8 @@ This section is just to outline some differences. SpeedyWeather defines `AbstractForcing` and `AbstractDrag`, both are only conceptual supertypes, and in fact you could define a forcing as a subtype of `AbstractDrag` and vice versa. So for a drag, most straight-forwardly you would do -```julia -struct MyDrag <: AbstractDrag +```@example extend +struct MyDrag <: SpeedyWeather.AbstractDrag # parameters and arrays end ``` diff --git a/test/extending.jl b/test/extending.jl index 3a93ec020..38df0ec77 100644 --- a/test/extending.jl +++ b/test/extending.jl @@ -1,5 +1,5 @@ @testset "Extending forcing and drag" begin - Base.@kwdef struct JetDrag{NF} <: SpeedyWeather.AbstractDrag{NF} + Base.@kwdef struct JetDrag{NF} <: SpeedyWeather.AbstractDrag # DIMENSIONS from SpectralGrid "Spectral resolution as max degree of spherical harmonics" @@ -63,7 +63,7 @@ end end - Base.@kwdef struct StochasticStirring{NF} <: SpeedyWeather.AbstractForcing{NF} + Base.@kwdef struct StochasticStirring{NF} <: SpeedyWeather.AbstractForcing # DIMENSIONS from SpectralGrid "Spectral resolution as max degree of spherical harmonics" diff --git a/test/surface_press_tend.jl b/test/surface_press_tend.jl deleted file mode 100644 index 7b795e56a..000000000 --- a/test/surface_press_tend.jl +++ /dev/null @@ -1,6 +0,0 @@ -@testset "Surface pressure tendency no errors" begin - for NF in (Float32,Float64) - p,d,m = initialize_speedy(NF,PrimitiveEquation) - SpeedyWeather.surface_pressure_tendency!(p,d,1,m) - end -end \ No newline at end of file From d5a79b3f98848e859505c842ba3146b2cc80b76d Mon Sep 17 00:00:00 2001 From: Milan Date: Fri, 1 Mar 2024 15:36:16 -0500 Subject: [PATCH 09/17] tests updated --- src/dynamics/initial_conditions.jl | 18 +++++------------- test/column_variables.jl | 2 +- test/extending.jl | 13 ++++++------- test/geopotential.jl | 4 ++-- test/spectral_gradients.jl | 4 ++-- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/src/dynamics/initial_conditions.jl b/src/dynamics/initial_conditions.jl index 8d8265ec6..d9048d16a 100644 --- a/src/dynamics/initial_conditions.jl +++ b/src/dynamics/initial_conditions.jl @@ -373,14 +373,13 @@ function homogeneous_temperature!( progn::PrognosticVariables, # lapse_rate: Reference temperature lapse rate -dT/dz [K/km] # gravity: Gravitational acceleration [m/s^2] # R_dry: Specific gas constant for dry air [J/kg/K] - (;temp_ref, temp_top, lapse_rate, R_dry, σ_tropopause) = model.atmosphere + (;temp_ref, lapse_rate, R_dry) = model.atmosphere (;gravity) = model.planet (;nlev, σ_levels_full) = model.geometry - (; norm_sphere ) = model.spectral_transform # normalization of the l=m=0 spherical harmonic - n_stratosphere_levels = findfirst(σ->σ>=σ_tropopause,σ_levels_full) + (;norm_sphere) = model.spectral_transform # normalization of the l=m=0 spherical harmonic # Lapse rate scaled by gravity [K/m / (m²/s²)] - Γg⁻¹ = lapse_rate/gravity/1000 # /1000 for lapse rate [K/km] → [K/m] + Γg⁻¹ = lapse_rate/gravity # SURFACE TEMPERATURE (store in k = nlev, but it's actually surface, i.e. k=nlev+1/2) # overwrite with lowermost layer further down @@ -390,15 +389,8 @@ function homogeneous_temperature!( progn::PrognosticVariables, temp_surf[lm] -= Γg⁻¹*geopot_surf[lm] # lower temperature for higher mountains end - # TROPOPAUSE/STRATOSPHERE set the l=m=0 spectral coefficient (=mean value) only - # in uppermost levels (default: k=1,2) for lapse rate = 0 - for k in 1:n_stratosphere_levels - temp = progn.layers[k].timesteps[1].temp - temp[1] = norm_sphere*temp_top - end - - # TROPOSPHERE use lapserate and vertical coordinate σ for profile - for k in n_stratosphere_levels+1:nlev # k=nlev overwrites the surface temperature + # Use lapserate and vertical coordinate σ for profile + for k in 1:nlev # k=nlev overwrites the surface temperature # with lowermost layer temperature temp = progn.layers[k].timesteps[1].temp σₖᴿ = σ_levels_full[k]^(R_dry*Γg⁻¹) # from hydrostatic equation diff --git a/test/column_variables.jl b/test/column_variables.jl index a476422da..071a1c341 100644 --- a/test/column_variables.jl +++ b/test/column_variables.jl @@ -39,7 +39,7 @@ end column.humid_tend .= humid_tend # copy into diagn - SpeedyWeather.write_column_tendencies!(diagn,column,model.constants,1) + SpeedyWeather.write_column_tendencies!(diagn,column,model.planet,1) # and check that that worked for (k,layer) in enumerate(diagn.layers) diff --git a/test/extending.jl b/test/extending.jl index 38df0ec77..2855677d1 100644 --- a/test/extending.jl +++ b/test/extending.jl @@ -46,8 +46,8 @@ return nothing end - function SpeedyWeather.drag!( diagn::DiagnosticVariablesLayer, - progn::PrognosticVariablesLayer, + function SpeedyWeather.drag!( diagn::SpeedyWeather.DiagnosticVariablesLayer, + progn::SpeedyWeather.PrognosticVariablesLayer, drag::JetDrag, time::DateTime, model::ModelSetup) @@ -113,8 +113,7 @@ end function StochasticStirring(SG::SpectralGrid;kwargs...) - (;trunc,Grid,nlat_half) = SG - nlat = RingGrids.get_nlat(Grid,nlat_half) + (;trunc,nlat) = SG return StochasticStirring{SG.NF}(;trunc,nlat,kwargs...) end @@ -143,15 +142,15 @@ return nothing end - function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer, - progn::PrognosticVariablesLayer, + function SpeedyWeather.forcing!(diagn::SpeedyWeather.DiagnosticVariablesLayer, + progn::SpeedyWeather.PrognosticVariablesLayer, forcing::StochasticStirring, time::DateTime, model::ModelSetup) SpeedyWeather.forcing!(diagn,forcing,model.spectral_transform) end - function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer, + function SpeedyWeather.forcing!(diagn::SpeedyWeather.DiagnosticVariablesLayer, forcing::StochasticStirring{NF}, spectral_transform::SpectralTransform) where NF diff --git a/test/geopotential.jl b/test/geopotential.jl index 037a02857..1a5a810c8 100644 --- a/test/geopotential.jl +++ b/test/geopotential.jl @@ -18,7 +18,7 @@ SpeedyWeather.linear_virtual_temperature!(diagn_layer,progn_layer,model,lf) end - SpeedyWeather.geopotential!(d,model.orography,model.constants) + SpeedyWeather.geopotential!(d,model.geopotential,model.orography) # approximate heights [m] for this setup heights = [27000,18000,13000,9000,6000,3700,1800,700] @@ -46,7 +46,7 @@ end p.layers[k].timesteps[1].temp[1] = temp*m.spectral_transform.norm_sphere end - SpeedyWeather.geopotential!(d,m.orography,m.constants) + SpeedyWeather.geopotential!(d,m.geopotential,m.orography) lf = 1 for (progn_layer,diagn_layer) in zip(p.layers,d.layers) diff --git a/test/spectral_gradients.jl b/test/spectral_gradients.jl index 3b9ad05af..2cd610dbd 100644 --- a/test/spectral_gradients.jl +++ b/test/spectral_gradients.jl @@ -74,11 +74,11 @@ end G = m.geometry S = m.spectral_transform - C = m.constants + C = m.coriolis # to evaluate ∇×(uv) use curl of vorticity fluxes (=∇×(uv(ζ+f))) with ζ=1,f=0 fill!(d.layers[1].grid_variables.vor_grid,1) - fill!(C.f_coriolis,0) + fill!(m.coriolis.f,0) # calculate uω,vω in spectral space SpeedyWeather.vorticity_flux_curldiv!(d.layers[1],C,G,S,div=true) From 12723b0220e0ee54f4f761106c28b66fbc870f60 Mon Sep 17 00:00:00 2001 From: Milan Date: Fri, 1 Mar 2024 16:42:33 -0500 Subject: [PATCH 10/17] modified callbacks test --- test/callbacks.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/callbacks.jl b/test/callbacks.jl index e0a4d0971..8f8179a41 100644 --- a/test/callbacks.jl +++ b/test/callbacks.jl @@ -57,8 +57,8 @@ SpeedyWeather.finish!(::StormChaser,args...) = nothing spectral_grid = SpectralGrid() - callbacks = [NoCallback()] - model = PrimitiveWetModel(;spectral_grid,callbacks) + # callbacks = [NoCallback()] # doesn't work at the moment + model = PrimitiveWetModel(;spectral_grid) storm_chaser = StormChaser(spectral_grid) append!(model.callbacks, storm_chaser) @@ -68,8 +68,8 @@ run!(simulation) # maximum wind speed should always be non-negative - @test all(model.callbacks[2].maximum_surface_wind_speed .>= 0) + @test all(model.callbacks[1].maximum_surface_wind_speed .>= 0) # highest wind speed across all time steps should be positive - @test maximum(model.callbacks[2].maximum_surface_wind_speed) > 0 + @test maximum(model.callbacks[1].maximum_surface_wind_speed) > 0 end \ No newline at end of file From 9ed6be22a7e2ec31d4b0d7c99f6010f10d3d0f52 Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 15:29:57 -0500 Subject: [PATCH 11/17] Callbacks implemented as dictionary --- docs/src/callbacks.md | 61 ++++++++++++++++++++++++------------- src/SpeedyWeather.jl | 1 + src/models/barotropic.jl | 2 +- src/models/primitive_dry.jl | 2 +- src/models/primitive_wet.jl | 2 +- src/models/shallow_water.jl | 2 +- src/output/callbacks.jl | 59 ++++++++++++++++++++++++++++++----- test/callbacks.jl | 32 ++++++++++++++----- 8 files changed, 121 insertions(+), 40 deletions(-) diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index c7eb57066..49fbe8053 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -155,26 +155,46 @@ SpeedyWeather.finish!(::StormChaser,args...) = nothing ## Adding a callback -Every model has a field `callbacks::AbstractVector{<:AbstractCallback}` such that the `callbacks` -keyword can be used to create a model with a vector of callbacks +Every model has a field `callbacks::Dict{Symbol,AbstractCallback}` such that the `callbacks` +keyword can be used to create a model with a dictionary of callbacks. Callbacks are identified +with a `Symbol` key inside such a dictionary. We have a convenient `CallbackDict` generator function +which can be used like `Dict` but the key-value pairs have to be of type `Symbol`-`AbstractCallback`. +Let us illustrate this with the dummy callback `NoCallback` (which is a callback that returns `nothing` +on `initialize!`, `callback!` and `finish!`) ```@example callbacks -spectral_grid = SpectralGrid() -dummy_callback = [NoCallback()] # 1-element vector with dummy NoCallback only -model = PrimitiveWetModel(;spectral_grid, callbacks=dummy_callback) -model.callbacks +callbacks = CallbackDict() # empty dictionary +callbacks = CallbackDict(:my_callback => NoCallback()) # key => callback +``` +If you don't provide a key a random key will be assigned +```@example callbacks +callbacks = CallbackDict(NoCallback()) +``` +and you can add (or delete) additional callbacks +```@example callbacks +add!(callbacks,NoCallback()) # this will also pick a random key +add!(callbacks,:my_callback,NoCallback()) # use key :my_callback +delete!(callbacks,:my_callback) # remove by key +callbacks ``` +Meaning that callbacks can be added before and after model construction -but, maybe more conveniently, a callback can be added after model construction too +```@example callbacks +spectral_grid = SpectralGrid() +callbacks = CallbackDict(:callback_added_before, NoCallback()) +model = PrimitiveWetModel(;spectral_grid, callbacks) +add!(model.callbacks,:callback_added_afterwards, NoCallback()) +``` +Let us add two more meaningful callbacks ```@example callbacks storm_chaser = StormChaser(spectral_grid) record_surface_temperature = GlobalSurfaceTemperatureCallback(spectral_grid) -append!(model.callbacks, storm_chaser) -append!(model.callbacks, record_surface_temperature) +add!(model.callbacks, :storm_chaser, storm_chaser) +add!(model.callbacks, :temperature, record_surface_temperature) ``` -which means that now in the calls to `callback!` first the dummy `NoCallback` is called +which means that now in the calls to `callback!` first the two dummy `NoCallback`s are called and then our storm chaser callback and then the `GlobalSurfaceTemperatureCallback` which records the global mean surface temperature on every time step. From normal [NetCDF output](@ref) the information these callbacks analyse would not be available, @@ -183,17 +203,16 @@ and considerably slow down the simulation. Let's run the simulation and check th ```@example callbacks simulation = initialize!(model) -run!(simulation,period=Day(3)) -v = model.callbacks[2].maximum_surface_wind_speed +run!(simulation, period=Day(3)) +v = model.callbacks[:storm_chaser].maximum_surface_wind_speed maximum(v) # highest surface wind speeds in simulation [m/s] ``` -The second callback is our `storm_chaser::StormChaser` (remember the first callback was a -dummy `NoCallback`), the third is the `GlobalSurfaceTemperatureCallback` with -the field `temp` is a vector of the global mean surface temperature on every -time step while the model ran for 3 days. +Cool, our `StormChaser` callback with the key `:storm_chaser` has been recording maximum +surface wind speeds in [m/s]. And the `:temperature` callback a time series of +global mean surface temperatures in Kelvin on every time step while the model ran for 3 days. ```@example callbacks -model.callbacks[3].temp +model.callbacks[:temperature].temp ``` ## Intrusive callbacks @@ -216,11 +235,9 @@ a callback that weakens their respective strength parameter over time. Changing the diagnostic variables, however, will not have any effect. All of them are treated as work arrays, meaning that their state is completely -overwritten on every time step. - -Changing the prognostic variables in spectral space directly is not advised -though possible because this can easily lead to stability issues. It is generally -easier to implement something like this as a parameterization, forcing or +overwritten on every time step. Changing the prognostic variables in spectral space +directly is not advised though possible because this can easily lead to stability issues. +It is generally easier to implement something like this as a parameterization, forcing or drag term (which can also be made time-dependent). Overall, callbacks give the user a wide range of possibilities to diagnose diff --git a/src/SpeedyWeather.jl b/src/SpeedyWeather.jl index 333ad1911..1faaddb9e 100644 --- a/src/SpeedyWeather.jl +++ b/src/SpeedyWeather.jl @@ -20,6 +20,7 @@ import Adapt: Adapt, adapt, adapt_structure import TOML import Dates: Dates, DateTime, Period, Millisecond, Second, Minute, Hour, Day import Printf: Printf, @sprintf +import Random: randstring import NCDatasets: NCDatasets, NCDataset, defDim, defVar import JLD2: jldopen import CodecZlib diff --git a/src/models/barotropic.jl b/src/models/barotropic.jl index 5f1015df5..c5e9206eb 100644 --- a/src/models/barotropic.jl +++ b/src/models/barotropic.jl @@ -49,7 +49,7 @@ Base.@kwdef mutable struct BarotropicModel{ # OUTPUT output::OW = OutputWriter(spectral_grid,Barotropic) - callbacks::Vector{AbstractCallback} = AbstractCallback[] + callbacks::Dict{Symbol,AbstractCallback} = Dict{Symbol,AbstractCallback}() feedback::FB = Feedback() end diff --git a/src/models/primitive_dry.jl b/src/models/primitive_dry.jl index 5ee21bd80..6b0899550 100644 --- a/src/models/primitive_dry.jl +++ b/src/models/primitive_dry.jl @@ -77,7 +77,7 @@ Base.@kwdef mutable struct PrimitiveDryModel{ # OUTPUT output::OW = OutputWriter(spectral_grid, PrimitiveDry) - callbacks::Vector{AbstractCallback} = AbstractCallback[] + callbacks::Dict{Symbol,AbstractCallback} = Dict{Symbol,AbstractCallback}() feedback::FB = Feedback() end diff --git a/src/models/primitive_wet.jl b/src/models/primitive_wet.jl index e06edcf6b..b801f9e80 100644 --- a/src/models/primitive_wet.jl +++ b/src/models/primitive_wet.jl @@ -93,7 +93,7 @@ Base.@kwdef mutable struct PrimitiveWetModel{ # OUTPUT output::OW = OutputWriter(spectral_grid, PrimitiveDry) - callbacks::Vector{AbstractCallback} = AbstractCallback[] + callbacks::Dict{Symbol,AbstractCallback} = Dict{Symbol,AbstractCallback}() feedback::FB = Feedback() end diff --git a/src/models/shallow_water.jl b/src/models/shallow_water.jl index f3433343c..563a04813 100644 --- a/src/models/shallow_water.jl +++ b/src/models/shallow_water.jl @@ -50,7 +50,7 @@ Base.@kwdef mutable struct ShallowWaterModel{ # OUTPUT output::OW = OutputWriter(spectral_grid,Barotropic) - callbacks::Vector{AbstractCallback} = AbstractCallback[] + callbacks::Dict{Symbol,AbstractCallback} = Dict{Symbol,AbstractCallback}() feedback::FB = Feedback() end diff --git a/src/output/callbacks.jl b/src/output/callbacks.jl index 019d6530e..88ca5cc3d 100644 --- a/src/output/callbacks.jl +++ b/src/output/callbacks.jl @@ -1,4 +1,16 @@ abstract type AbstractCallback end +const CALLBACK_DICT = Dict{Symbol,AbstractCallback} +const RANDSTRING_LENGTH = 4 + +export CallbackDict +function CallbackDict(callbacks::AbstractCallback...) + callback_pairs = (Pair(Symbol("callback_"*randstring(RANDSTRING_LENGTH)),callback) + for callback in callbacks) + CALLBACK_DICT(callback_pairs...) +end + +CallbackDict() = CALLBACK_DICT() +CallbackDict(pairs::Pair{Symbol,<:AbstractCallback}...) = CALLBACK_DICT(pairs...) function Base.show(io::IO,C::AbstractCallback) println(io,"$(typeof(C)) <: AbstractCallback") @@ -15,20 +27,53 @@ initialize!(::NoCallback,args...) = nothing # executed once before the main callback!(::NoCallback,args...) = nothing # executed after every time step finish!(::NoCallback,args...) = nothing # executed after main time loop finishes -# simply loop over vector of callbacks +# simply loop over dict of callbacks for func in (:initialize!, :callback!, :finish!) @eval begin - function $func(callbacks::Vector{<:AbstractCallback},args...) - for callback in callbacks - $func(callback,args...) + function $func(callbacks::CALLBACK_DICT,args...) + for key in keys(callbacks) + $func(callbacks[key],args...) end end end end -# define to make append!(::AbstractVector{<:AbstractCallback},::AbstractCallback) possible -Base.length(::SpeedyWeather.AbstractCallback) = 1 -Base.iterate(c::SpeedyWeather.AbstractCallback) = (c,nothing) +export add! + +""" +$(TYPEDSIGNATURES) +Add a callback to a Dict{String,AbstractCallback} dictionary. To be used like + + add!(model.callbacks,"mycallback",callback) + +""" +function add!(D::CALLBACK_DICT,key::Symbol,callback::AbstractCallback) + D[key] = callback + return nothing +end + +function add!(D::CALLBACK_DICT,key::String,callback::AbstractCallback) + key_symbol = Symbol(key) + @info "Callback keys are Symbols. String \"$key\" converted to Symbol :$key_symbol." + add!(D,key_symbol,callback) +end + +""" +$(TYPEDSIGNATURES) +Add a callback to a Dict{Symbol,AbstractCallback} dictionary without specifying the +key which is randomly created like callback_????. To be used like + + add!(model.callbacks,callback) + +""" +function add!(D::CALLBACK_DICT,callback::AbstractCallback) + key = Symbol("callback_"*Random.randstring(4)) + @info "$(typeof(callback)) callback added with key $key" + D[key] = callback + return nothing +end + +# delete!(dict,key) already defined in Base export GlobalSurfaceTemperatureCallback diff --git a/test/callbacks.jl b/test/callbacks.jl index 8f8179a41..e47f8df33 100644 --- a/test/callbacks.jl +++ b/test/callbacks.jl @@ -1,3 +1,20 @@ +@testset "CallbackDict generator" begin + @test CallbackDict() isa SpeedyWeather.CALLBACK_DICT + @test CallbackDict(NoCallback()) isa SpeedyWeather.CALLBACK_DICT + @test CallbackDict(GlobalSurfaceTemperatureCallback()) isa SpeedyWeather.CALLBACK_DICT + @test CallbackDict(NoCallback(),NoCallback()) isa SpeedyWeather.CALLBACK_DICT + @test CallbackDict(NoCallback(),GlobalSurfaceTemperatureCallback()) isa SpeedyWeather.CALLBACK_DICT + @test CallbackDict(:a => NoCallback()) isa SpeedyWeather.CALLBACK_DICT + + d = CallbackDict() + add!(d,NoCallback()) + add!(d,:my_callback,NoCallback()) + add!(d,"my_callback",NoCallback()) + @test d isa SpeedyWeather.CALLBACK_DICT + delete!(d,:my_callback) + @test d isa SpeedyWeather.CALLBACK_DICT +end + @testset "Callbacks interface" begin Base.@kwdef mutable struct StormChaser{NF} <: SpeedyWeather.AbstractCallback @@ -57,19 +74,20 @@ SpeedyWeather.finish!(::StormChaser,args...) = nothing spectral_grid = SpectralGrid() - # callbacks = [NoCallback()] # doesn't work at the moment - model = PrimitiveWetModel(;spectral_grid) + callbacks = CallbackDict(NoCallback()) + model = PrimitiveWetModel(;spectral_grid,callbacks) storm_chaser = StormChaser(spectral_grid) - append!(model.callbacks, storm_chaser) - append!(model.callbacks, NoCallback()) # add dummy too + key = :storm_chaser + add!(model.callbacks, key, storm_chaser) # with :storm_chaser key + add!(model.callbacks, NoCallback()) # add dummy too simulation = initialize!(model) - run!(simulation) + run!(simulation,period=Day(3)) # maximum wind speed should always be non-negative - @test all(model.callbacks[1].maximum_surface_wind_speed .>= 0) + @test all(model.callbacks[key].maximum_surface_wind_speed .>= 0) # highest wind speed across all time steps should be positive - @test maximum(model.callbacks[1].maximum_surface_wind_speed) > 0 + @test maximum(model.callbacks[key].maximum_surface_wind_speed) > 0 end \ No newline at end of file From 3c7b4daa59b80fcf7a5b2d19cb799d826504fadd Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 16:01:38 -0500 Subject: [PATCH 12/17] Second.value typo --- docs/src/forcing_drag.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/forcing_drag.md b/docs/src/forcing_drag.md index ae059f42d..9b7951c24 100644 --- a/docs/src/forcing_drag.md +++ b/docs/src/forcing_drag.md @@ -280,7 +280,7 @@ function SpeedyWeather.initialize!( forcing::StochasticStirring, # precompute noise and auto-regressive factor, packed in RefValue for mutability dt = model.time_stepping.Δt_sec - τ = forcing.decorrelation_time*24*3600 # convert to seconds + τ = forcing.decorrelation_time.value # in seconds forcing.a[] = A*sqrt(1 - exp(-2dt/τ)) forcing.b[] = exp(-dt/τ) @@ -335,7 +335,7 @@ function SpeedyWeather.forcing!(diagn::SpeedyWeather.DiagnosticVariablesLayer, time::DateTime, model::SpeedyWeather.ModelSetup) # function barrier only - forcing!(diagn,forcing,model.spectral_transform) + forcing!(diagn, forcing, model.spectral_transform) end ``` The function has to be as outlined above. The first argument has to be of type From 3710e58745ea0b1d3b46dae659cfffad2446ee10 Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 16:17:04 -0500 Subject: [PATCH 13/17] wrong callbacks interface in documentation --- docs/src/callbacks.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index 49fbe8053..f7ab2360a 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -172,9 +172,9 @@ callbacks = CallbackDict(NoCallback()) ``` and you can add (or delete) additional callbacks ```@example callbacks -add!(callbacks,NoCallback()) # this will also pick a random key -add!(callbacks,:my_callback,NoCallback()) # use key :my_callback -delete!(callbacks,:my_callback) # remove by key +add!(callbacks,NoCallback()) # this will also pick a random key +add!(callbacks,:my_callback => NoCallback()) # use key :my_callback +delete!(callbacks,:my_callback) # remove by key callbacks ``` Meaning that callbacks can be added before and after model construction From 04b4010e36aa0cd15efc36ab510c90b8525cf84c Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 17:03:27 -0500 Subject: [PATCH 14/17] add! callback primarily with key-value --- docs/src/callbacks.md | 20 +++++++++++------- src/output/callbacks.jl | 46 +++++++++++++++++++++++++---------------- test/callbacks.jl | 20 +++++++++++------- 3 files changed, 53 insertions(+), 33 deletions(-) diff --git a/docs/src/callbacks.md b/docs/src/callbacks.md index f7ab2360a..dfa99109c 100644 --- a/docs/src/callbacks.md +++ b/docs/src/callbacks.md @@ -172,26 +172,32 @@ callbacks = CallbackDict(NoCallback()) ``` and you can add (or delete) additional callbacks ```@example callbacks -add!(callbacks,NoCallback()) # this will also pick a random key -add!(callbacks,:my_callback => NoCallback()) # use key :my_callback -delete!(callbacks,:my_callback) # remove by key +add!(callbacks, NoCallback()) # this will also pick a random key +add!(callbacks, :my_callback => NoCallback()) # use key :my_callback +delete!(callbacks, :my_callback) # remove by key callbacks ``` +And you can chain them too +```@example callbacks +add!(callbacks, NoCallback(), NoCallback()) # random keys +add!(callbacks, :key1 => NoCallback(), :key2 => NoCallback()) # keys provided +``` Meaning that callbacks can be added before and after model construction + ```@example callbacks spectral_grid = SpectralGrid() -callbacks = CallbackDict(:callback_added_before, NoCallback()) +callbacks = CallbackDict(:callback_added_before => NoCallback()) model = PrimitiveWetModel(;spectral_grid, callbacks) -add!(model.callbacks,:callback_added_afterwards, NoCallback()) +add!(model.callbacks,:callback_added_afterwards => NoCallback()) ``` Let us add two more meaningful callbacks ```@example callbacks storm_chaser = StormChaser(spectral_grid) record_surface_temperature = GlobalSurfaceTemperatureCallback(spectral_grid) -add!(model.callbacks, :storm_chaser, storm_chaser) -add!(model.callbacks, :temperature, record_surface_temperature) +add!(model.callbacks, :storm_chaser => storm_chaser) +add!(model.callbacks, :temperature => record_surface_temperature) ``` which means that now in the calls to `callback!` first the two dummy `NoCallback`s are called diff --git a/src/output/callbacks.jl b/src/output/callbacks.jl index 88ca5cc3d..2c2bd1305 100644 --- a/src/output/callbacks.jl +++ b/src/output/callbacks.jl @@ -9,7 +9,12 @@ function CallbackDict(callbacks::AbstractCallback...) CALLBACK_DICT(callback_pairs...) end +"""$(TYPEDSIGNATURES) +Empty Callback dictionary generator.""" CallbackDict() = CALLBACK_DICT() + +"""$(TYPEDSIGNATURES) +Create Callback dictionary like normal dictionaries.""" CallbackDict(pairs::Pair{Symbol,<:AbstractCallback}...) = CALLBACK_DICT(pairs...) function Base.show(io::IO,C::AbstractCallback) @@ -39,38 +44,43 @@ for func in (:initialize!, :callback!, :finish!) end export add! - """ $(TYPEDSIGNATURES) -Add a callback to a Dict{String,AbstractCallback} dictionary. To be used like - - add!(model.callbacks,"mycallback",callback) +Add a or several callbacks to a Dict{String,AbstractCallback} dictionary. To be used like +add!(model.callbacks,:my_callback => callback) +add!(model.callbacks,:my_callback1 => callback, :my_callback2 => other_callback) """ -function add!(D::CALLBACK_DICT,key::Symbol,callback::AbstractCallback) - D[key] = callback - return nothing +function add!(D::CALLBACK_DICT, key_callbacks::Pair{Symbol, <:AbstractCallback}...) + for key_callback in key_callbacks + key = key_callback.first + callback = key_callback.second + D[key] = callback + end end -function add!(D::CALLBACK_DICT,key::String,callback::AbstractCallback) +add!(D::CALLBACK_DICT, key::Symbol, callback::AbstractCallback) = add!(D,Pair(key,callback)) + +# also with string but flag conversion +function add!(D::CALLBACK_DICT, key::String, callback::AbstractCallback) key_symbol = Symbol(key) @info "Callback keys are Symbols. String \"$key\" converted to Symbol :$key_symbol." - add!(D,key_symbol,callback) + add!(D, key_symbol, callback) end """ $(TYPEDSIGNATURES) -Add a callback to a Dict{Symbol,AbstractCallback} dictionary without specifying the +Add a or several callbacks to a Dict{Symbol,AbstractCallback} dictionary without specifying the key which is randomly created like callback_????. To be used like - add!(model.callbacks,callback) - -""" -function add!(D::CALLBACK_DICT,callback::AbstractCallback) - key = Symbol("callback_"*Random.randstring(4)) - @info "$(typeof(callback)) callback added with key $key" - D[key] = callback - return nothing + add!(model.callbacks, callback) + add!(model.callbacks, callback1, callback2).""" +function add!(D::CALLBACK_DICT,callbacks::AbstractCallback...) + for callback in callbacks + key = Symbol("callback_"*Random.randstring(4)) + @info "$(typeof(callback)) callback added with key $key" + add!(D, key => callback) + end end # delete!(dict,key) already defined in Base diff --git a/test/callbacks.jl b/test/callbacks.jl index e47f8df33..5b30ade69 100644 --- a/test/callbacks.jl +++ b/test/callbacks.jl @@ -7,12 +7,16 @@ @test CallbackDict(:a => NoCallback()) isa SpeedyWeather.CALLBACK_DICT d = CallbackDict() - add!(d,NoCallback()) - add!(d,:my_callback,NoCallback()) - add!(d,"my_callback",NoCallback()) - @test d isa SpeedyWeather.CALLBACK_DICT - delete!(d,:my_callback) + add!(d, NoCallback()) + add!(d, NoCallback(), NoCallback()) + add!(d, :my_callback, NoCallback()) + add!(d, "my_callback2", NoCallback()) + delete!(d, :my_callback) + + add!(d,:another_callback => NoCallback()) + add!(d,:another_callback1 => NoCallback(), :another_callback2 => NoCallback()) @test d isa SpeedyWeather.CALLBACK_DICT + @test length(d) == 7 end @testset "Callbacks interface" begin @@ -79,11 +83,11 @@ end storm_chaser = StormChaser(spectral_grid) key = :storm_chaser - add!(model.callbacks, key, storm_chaser) # with :storm_chaser key - add!(model.callbacks, NoCallback()) # add dummy too + add!(model.callbacks, key => storm_chaser) # with :storm_chaser key + add!(model.callbacks, NoCallback()) # add dummy too simulation = initialize!(model) - run!(simulation,period=Day(3)) + run!(simulation,period=Day(1)) # maximum wind speed should always be non-negative @test all(model.callbacks[key].maximum_surface_wind_speed .>= 0) From ae5752f34a51b1de0f0b424010dfd5c4f12d0d41 Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 17:14:24 -0500 Subject: [PATCH 15/17] Also export DiagnosticVariablesLayer --- src/dynamics/diagnostic_variables.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/dynamics/diagnostic_variables.jl b/src/dynamics/diagnostic_variables.jl index f04671f21..3989c067b 100644 --- a/src/dynamics/diagnostic_variables.jl +++ b/src/dynamics/diagnostic_variables.jl @@ -87,6 +87,8 @@ function Base.zeros(::Type{DynamicsVariables}, return DynamicsVariables{NF,Grid{NF}}(;nlat_half,trunc) end +export DiagnosticVariablesLayer + """ All diagnostic variables for a given layer: tendencies, prognostic varibles on the grid, and intermediate dynamics variables. From adc6e085809ccdcf453d0e18acbc3c744e12a373 Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 17:15:49 -0500 Subject: [PATCH 16/17] no need for SpeedyWeather. in extending.jl tests --- test/extending.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/extending.jl b/test/extending.jl index 2855677d1..1601081c2 100644 --- a/test/extending.jl +++ b/test/extending.jl @@ -46,8 +46,8 @@ return nothing end - function SpeedyWeather.drag!( diagn::SpeedyWeather.DiagnosticVariablesLayer, - progn::SpeedyWeather.PrognosticVariablesLayer, + function SpeedyWeather.drag!( diagn::DiagnosticVariablesLayer, + progn::PrognosticVariablesLayer, drag::JetDrag, time::DateTime, model::ModelSetup) @@ -142,15 +142,15 @@ return nothing end - function SpeedyWeather.forcing!(diagn::SpeedyWeather.DiagnosticVariablesLayer, - progn::SpeedyWeather.PrognosticVariablesLayer, + function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer, + progn::PrognosticVariablesLayer, forcing::StochasticStirring, time::DateTime, model::ModelSetup) SpeedyWeather.forcing!(diagn,forcing,model.spectral_transform) end - function SpeedyWeather.forcing!(diagn::SpeedyWeather.DiagnosticVariablesLayer, + function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer, forcing::StochasticStirring{NF}, spectral_transform::SpectralTransform) where NF From 310fa3b2d580f6ce9efa8c3dfcf794da32288e35 Mon Sep 17 00:00:00 2001 From: Milan Date: Sat, 2 Mar 2024 17:18:02 -0500 Subject: [PATCH 17/17] Remove also unnecessary SpeedyWeather. in forcing_drag.md --- docs/src/forcing_drag.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/forcing_drag.md b/docs/src/forcing_drag.md index 9b7951c24..1a42f8ef3 100644 --- a/docs/src/forcing_drag.md +++ b/docs/src/forcing_drag.md @@ -272,7 +272,7 @@ Now let us have a closer look at the details of the `initialize!` function, in o actually do ```@example extend function SpeedyWeather.initialize!( forcing::StochasticStirring, - model::SpeedyWeather.ModelSetup) + model::ModelSetup) # precompute forcing strength, scale with radius^2 as is the vorticity equation (;radius) = model.spectral_grid @@ -329,11 +329,11 @@ defined for our new `StochasticStirring` forcing. But if you define it as follow then this will be called automatically with multiple dispatch. ```@example extend -function SpeedyWeather.forcing!(diagn::SpeedyWeather.DiagnosticVariablesLayer, - progn::SpeedyWeather.PrognosticVariablesLayer, +function SpeedyWeather.forcing!(diagn::DiagnosticVariablesLayer, + progn::PrognosticVariablesLayer, forcing::StochasticStirring, time::DateTime, - model::SpeedyWeather.ModelSetup) + model::ModelSetup) # function barrier only forcing!(diagn, forcing, model.spectral_transform) end @@ -358,7 +358,7 @@ makes that possible. And it also tells you more clearly what a function depends So we define the actual `forcing!` function that's then called as follows ```@example extend -function forcing!( diagn::SpeedyWeather.DiagnosticVariablesLayer, +function forcing!( diagn::DiagnosticVariablesLayer, forcing::StochasticStirring{NF}, spectral_transform::SpectralTransform) where NF @@ -443,7 +443,7 @@ and just put them together as you like, and as long as you follow some rules. spectral_grid = SpectralGrid(trunc=42,nlev=1) stochastic_stirring = StochasticStirring(spectral_grid,latitude=-45) initial_conditions = StartFromRest() -model = BarotropicModel(;spectral_grid,initial_conditions,forcing=stochastic_stirring) +model = BarotropicModel(;spectral_grid, initial_conditions, forcing=stochastic_stirring) simulation = initialize!(model) run!(simulation) ```