Skip to content

Commit f0a26f2

Browse files
committed
Update speedy primitive dry coupling script
Cleaned up to only use surface temperature coupling (radiative fluxes are ignored)
1 parent 4c34544 commit f0a26f2

File tree

4 files changed

+77
-124
lines changed

4 files changed

+77
-124
lines changed

examples/Project.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
33
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
44
CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
5-
GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a"
65
GeoMakie = "db073c08-6b98-4ee5-b6a4-5efafb3259c6"
76
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
87
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
@@ -16,8 +15,8 @@ SpeedyWeatherInternals = "34489162-d270-4603-8b96-37b04f830d73"
1615
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1716
Terrarium = "80418d68-07fa-499d-ae2b-c12e531f5cd8"
1817

19-
[sources]
20-
Terrarium = {path = ".."}
21-
2218
[compat]
2319
SpeedyWeatherInternals = "0.1.2"
20+
21+
[sources.Terrarium]
22+
path = ".."

examples/speedy_dry_land.jl

Lines changed: 54 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,22 @@ using CairoMakie, GeoMakie
1010
import RingGrids
1111
import SpeedyWeather as Speedy
1212

13+
# Temporary workaround for SpeedyWeather.jl#919
14+
Speedy.SpeedyTransforms.FFTW.set_num_threads(1)
15+
1316
"""
1417
Naive implementation of a SpeedyWeather "dry" land model that couples to a Terrarium model.
1518
"""
16-
struct TerrariumDryLand{NF, TM<:ModelState{NF}} <: Speedy.AbstractDryLand
19+
struct TerrariumDryLand{NF, TMI<:Terrarium.ModelIntegrator{NF}} <: Speedy.AbstractDryLand
1720
"Speedy spectral grid"
1821
spectral_grid::Speedy.SpectralGrid
1922

20-
"Initialized Terrarium model"
21-
model::TM
23+
"Initialized Terrarium model integrator"
24+
integrator::TMI
2225

23-
function TerrariumDryLand(model::ModelState{NF, Arch, Grid}; spectral_grid_kwargs...) where {NF, Arch, Grid<:ColumnRingGrid}
24-
spectral_grid = Speedy.SpectralGrid(model.grid.rings; NF, nlayers_soil=1, spectral_grid_kwargs...)
25-
return new{eltype(model), typeof(model)}(spectral_grid, model)
26+
function TerrariumDryLand(itnegrator::Terrarium.ModelIntegrator{NF, Arch, Grid}; spectral_grid_kwargs...) where {NF, Arch, Grid<:ColumnRingGrid}
27+
spectral_grid = Speedy.SpectralGrid(integrator.model.grid.rings; NF, nlayers_soil=1, spectral_grid_kwargs...)
28+
return new{eltype(integrator), typeof(integrator)}(spectral_grid, integrator)
2629
end
2730
end
2831

@@ -33,9 +36,9 @@ function Speedy.initialize!(
3336
land::TerrariumDryLand,
3437
model::Speedy.PrimitiveEquation,
3538
) where {NF}
36-
Tsoil = interior(land.model.state.temperature)[:, 1, end]
39+
Tsoil = interior(land.integrator.state.temperature)[:, 1, end]
3740
progn_land.soil_temperature .= Tsoil .+ NF(273.15)
38-
Terrarium.initialize!(land.model)
41+
Terrarium.initialize!(land.integrator)
3942
return nothing
4043
end
4144

@@ -47,130 +50,75 @@ function Speedy.timestep!(
4750
) where {NF}
4851
# get speedy state variables
4952
Tair = @view diagn.grid.temp_grid[:, end]
50-
pres = diagn.grid.pres_grid
51-
SwIn = diagn.physics.surface_shortwave_down
52-
LwIn = diagn.physics.surface_longwave_down
53-
SwOut = diagn.physics.surface_shortwave_up
54-
LwOut = diagn.physics.surface_longwave_up
55-
H = diagn.physics.sensible_heat_flux
56-
# update Terrarium input fields
57-
inputs = land.model.state.inputs
58-
set!(inputs.air_temperature, Tair) # first set directly to avoid allocating new fields
59-
set!(inputs.air_temperature, inputs.air_temperature - 273.15) # then convert to celsius
60-
# set!(inputs.air_pressure, pres) # similar with pressure
61-
# set!(inputs.air_pressure, exp(inputs.air_pressure)) # take exp to get pressure in Pa
62-
# set!(inputs.surface_shortwave_down, SwIn)
63-
# set!(inputs.surface_longwave_down, LwIn)
64-
# set!(inputs.surface_shortwave_up, SwOut)
65-
# set!(inputs.surface_longwave_up, LwOut)
66-
# set!(inputs.sensible_heat_flux, H)
67-
# do land timestep
68-
Terrarium.timestep!(land.model, Dates.second(progn.clock.Δt))
69-
# SEB coupling:
70-
# Ts = land.model.state.skin_temperature
71-
# progn.land.soil_temperature .= Ts .+ NF(273.15)
72-
# progn.land.sensible_heat_flux .= land.model.state.sensible_heat_flux
73-
# Soil surface temperature coupling:
74-
Tsoil = land.model.state.temperature
53+
# terrarium state
54+
state = land.integrator.state
55+
set!(state.inputs.air_temperature, Tair) # first set directly to avoid allocating new fields
56+
set!(state.inputs.air_temperature, state.inputs.air_temperature - 273.15) # then convert to celsius
57+
# run land forward over speedy timestep interval;
58+
# we use a smaller actual timestep to ensure stability
59+
Terrarium.run!(land.integrator, period=progn.clock.Δt, Δt=300.0)
60+
# Get soil temperatures
61+
Tsoil = state.temperature
62+
# Get surface temperature (last z-layer in Oceananigans grids)
7563
Nx, Nz = Tsoil.grid.Nx, Tsoil.grid.Nz
7664
Tsurf = @view Tsoil[1:Nx, 1, Nz:Nz]
65+
# Update speedy soil/skin temperature
7766
progn.land.soil_temperature .= Tsurf .+ NF(273.15)
7867
return nothing
7968
end
8069

8170
ring_grid = RingGrids.FullGaussianGrid(24)
8271
Nz = 30
83-
Δz_min = 0.1 # currently the coupling is only stable with a large surface layer
84-
grid = ColumnRingGrid(CPU(), Float32, ExponentialSpacing(;N=Nz, Δz_min), ring_grid)
72+
Δz_min = 0.05 # currently the coupling is only stable with a large surface layer
73+
grid = ColumnRingGrid(CPU(), Float32, ExponentialSpacing(; N=Nz, Δz_min), ring_grid)
8574
# grid = ColumnGrid(CPU(), Float32, ExponentialSpacing(N=30))
8675
# Initial conditions
8776
soil_initializer = FieldInitializers(
8877
# steady-ish state initial condition for soil temperature
89-
temperature = (x,z) -> 10 - 0.02f0*z,
78+
temperature = (x,z) -> 0 - 0.02f0*z,
9079
# fully saturated soil
9180
saturation_water_ice = 1.0f0,
9281
)
9382

94-
# Coupled model with prescribed surface energy balance
95-
# currently not working :(
96-
# surface_energy_balance = SurfaceEnergyBalance(
97-
# eltype(grid);
98-
# radiative_fluxes = PrescribedRadiativeFluxes(),
99-
# turbulent_fluxes = PrescribedTurbulentFluxes(),
100-
# )
101-
# soil_model = SoilModel(grid, initializer=soil_initializer)
102-
# model = CoupledSoilAtmosphereModel(soil_model; surface_energy_balance)
103-
# # Quick check to make sure everything works OK
104-
# land_state = initialize(model)
105-
# set!(land_state.state.surface_shortwave_down, 200.0)
106-
# set!(land_state.state.surface_longwave_down, 100.0)
107-
# set!(land_state.state.air_temperature, 11.0)
108-
# set!(land_state.state.air_pressure, 101_325)
109-
# set!(land_state.state.specific_humidity, 0.001)
110-
# timestep!(land_state)
111-
# run!(land_state, period=Day(1))
112-
# @assert all(isfinite.(land_state.state.skin_temperature))
113-
# @assert all(isfinite.(land_state.state.latent_heat_flux))
114-
# @assert all(isfinite.(land_state.state.temperature))
115-
11683
# Soil model with prescribed surface temperautre BC
117-
soil_bcs = SoilBoundaryConditions(
118-
eltype(grid);
119-
top=PrescribedValue(:temperature, Input(:air_temperature, units=u"°C")),
120-
bottom=DefaultBoundaryConditions()
121-
)
122-
model = SoilModel(grid, initializer=soil_initializer, boundary_conditions=soil_bcs)
123-
124-
# Initialize Terrarium model
125-
land_state = initialize(model)
126-
land = TerrariumDryLand(land_state)
84+
model = SoilModel(grid, initializer=soil_initializer)
85+
Tair_input = InputSource(eltype(grid), :air_temperature)
86+
bcs = PrescribedSurfaceTemperature(:air_temperature)
87+
integrator = initialize(model, ForwardEuler(eltype(grid)), boundary_conditions=bcs, inputs=InputSources(Tair_input))
12788

89+
# Initialize Terrarium-Speedy land model
90+
land = TerrariumDryLand(integrator)
12891
# Set up coupled model
12992
land_sea_mask = Speedy.RockyPlanetMask(land.spectral_grid)
130-
# surface_heat_flux = Speedy.SurfaceHeatFlux(land=Speedy.PrescribedLandHeatFlux())
13193
output = Speedy.NetCDFOutput(land.spectral_grid, Speedy.PrimitiveDryModel, path="outputs/")
132-
primitive_dry_coupled = Speedy.PrimitiveDryModel(land.spectral_grid; land, land_sea_mask, output)
94+
time_stepping = Speedy.Leapfrog(land.spectral_grid, Δt_at_T31=Minute(15))
95+
primitive_dry_coupled = Speedy.PrimitiveDryModel(land.spectral_grid; land, land_sea_mask, time_stepping, output)
13396
# add soil temperature as output variable for Speedy simulation
13497
Speedy.add!(primitive_dry_coupled.output, Speedy.SoilTemperatureOutput())
13598
# initialize coupled simulation
13699
sim_coupled = Speedy.initialize!(primitive_dry_coupled)
137100
# Speedy.timestep!(sim)
138-
# spin up
139-
Speedy.run!(sim_coupled, period=Day(2), output=true)
140-
# run for a few more days
141-
Speedy.run!(sim_coupled, period=Day(5), output=true)
142-
143-
with_theme(fontsize=18) do
144-
Tair_fig = heatmap(sim_coupled.diagnostic_variables.grid.temp_grid[:,8] .- 273.15, title="", size=(800,400))
145-
Tsoil_fig = heatmap(RingGrids.Field(interior(land_state.state.temperature)[:,1,end-4], grid), title="", size=(800,400))
146-
save("plots/speedy_primitive_dry_coupled_tair_R48.png", Tair_fig, px_per_unit=1)
147-
save("plots/speedy_primitive_dry_coupled_tsoil_R48.png", Tsoil_fig, px_per_unit=1)
148-
Tair_fig
149-
end
150-
151-
# animate surface layer air temperature
101+
# run it
102+
Speedy.run!(sim_coupled, period=Month(1), output=true)
103+
104+
# Soil temperature in the 5th layer (~0.54 m)
105+
Tsoil_fig = heatmap(RingGrids.Field(interior(integrator.state.temperature)[:,1,end-4], grid), title="", size=(800,400))
106+
# Atmosphere variables
107+
Tair_fig = heatmap(sim_coupled.diagnostic_variables.grid.temp_grid[:,8] .- 273.15, title="Air temperature", size=(800,400))
108+
pres_fig = heatmap(exp.(sim_coupled.diagnostic_variables.grid.pres_grid), title="Surface pressure", size=(800,400))
109+
srad_fig = heatmap(exp.(sim_coupled.diagnostic_variables.physics.surface_shortwave_down), title="Surface shortwave down", size=(800,400))
110+
# Tskin_fig = heatmap(RingGrids.Field(interior(integrator.state.skin_temperature)[:,1,end], grid), title="", size=(800,400))
111+
# save("plots/speedy_primitive_dry_coupled_tair_R48.png", Tair_fig, px_per_unit=1)
112+
# save("plots/speedy_primitive_dry_coupled_tsoil_R48.png", Tsoil_fig, px_per_unit=1)
113+
114+
# animate surface air and soil temperatures
152115
Speedy.animate(sim, variable="temp", coastlines=false, level=spectral_grid.nlayers, output_file="plots/speedy_terrarium_dry_air_temperature.mp4")
153116
Speedy.animate(sim, variable="st", coastlines=false, level=1, output_file="plots/speedy_terrarium_dry_soil_temperature.mp4")
154117

155-
@show land_state.state.air_temperature
156-
@show land_state.state.skin_temperature
157-
@show sim.prognostic_variables.land.soil_temperature
158-
159-
heatmap(sim.diagnostic_variables.grid.temp_grid[:,end])
160-
heatmap(sim.prognostic_variables.land.soil_temperature[:,end])
161-
162-
# Speedy standalone
163-
164-
spectral_grid2 = Speedy.SpectralGrid(trunc=31)
165-
primitive_dry = Speedy.PrimitiveDryModel(spectral_grid2)
166-
sim2 = Speedy.initialize!(primitive_dry)
167-
Speedy.run!(sim2)
168-
169-
using BenchmarkTools
170-
171-
@profview @btime Speedy.timestep!($sim)
172-
173-
default_spectral_grid = Speedy.SpectralGrid(grid.rings)
174-
default_primitive_dry = Speedy.PrimitiveDryModel(default_spectral_grid)
175-
default_sim = Speedy.initialize!(default_primitive_dry)
176-
Speedy.run!(default_sim, period=Day(1))
118+
# pick a point somewhere in the mid-lattitudes
119+
T = interior(integrator.state.temperature)[2000,1,:]
120+
f = interior(integrator.state.liquid_water_fraction)[2000,1,:]
121+
zs = znodes(integrator.state.temperature)
122+
# Plot temperature and liquid fraction profiles in upper 15 layers
123+
Makie.scatterlines(T[end-15:end], zs[end-15:end])
124+
Makie.scatterlines(f, zs)

src/models/models.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export SoilInitializer
1111
export ConstantInitialSoilTemperature, QuasiThermalSteadyState, PiecewiseLinearInitialSoilTemperature
1212
include("soil/soil_model_init.jl")
1313

14+
# Soil-atmosphere
15+
16+
export CoupledSoilAtmosphereModel
17+
include("soil/soil_atmosphere_model.jl")
18+
1419
# Vegetation
1520

1621
export VegetationModel

src/models/soil/soil_atmosphere_model.jl

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ struct CoupledSoilAtmosphereModel{
33
GridType<:AbstractLandGrid{NF},
44
Atmosphere<:AbstractAtmosphere,
55
SurfaceEnergy<:AbstractSurfaceEnergyBalance,
6-
TimeStepper<:AbstractTimeStepper,
7-
SoilModel<:AbstractSoilModel{NF, GridType, TimeStepper},
6+
SoilModel<:AbstractSoilModel{NF, GridType},
87
Initializer<:AbstractInitializer,
9-
} <: AbstractSoilModel{NF, GridType, TimeStepper}
8+
} <: AbstractSoilModel{NF, GridType}
109
"Spatial discretization"
1110
grid::GridType
1211

@@ -33,20 +32,9 @@ function CoupledSoilAtmosphereModel(
3332
constants::PhysicalConstants = PhysicalConstants(NF),
3433
initializer::AbstractInitializer = DefaultInitializer()
3534
) where {NF}
36-
surface_fluxes = SoilBC(energy=GroundHeatFlux(), hydrology=DefaultBoundaryConditions())
37-
coupled_soil_bcs = SoilBoundaryConditions(
38-
NF,
39-
top = surface_fluxes,
40-
bottom = DefaultBoundaryConditions() # temporary hack
41-
)
42-
soil = setproperties(soil, boundary_conditions=coupled_soil_bcs)
4335
return CoupledSoilAtmosphereModel(soil.grid, atmosphere, surface_energy_balance, soil, constants, initializer)
4436
end
4537

46-
get_boundary_conditions(model::CoupledSoilAtmosphereModel) = get_boundary_conditions(model.soil)
47-
48-
get_time_stepping(model::CoupledSoilAtmosphereModel) = model.soil.time_stepping
49-
5038
get_soil_energy_balance(model) = model.soil.energy
5139

5240
get_soil_hydrology(model) = model.soil.hydrology
@@ -70,6 +58,19 @@ function variables(model::CoupledSoilAtmosphereModel)
7058
)
7159
end
7260

61+
function initialize(
62+
model::CoupledSoilAtmosphereModel{NF};
63+
clock = Clock(time=zero(NF)),
64+
boundary_conditions = (;),
65+
fields = (;),
66+
external_variables = ()
67+
) where {NF}
68+
vars = Variables(variables(model)..., external_variables...)
69+
surface_fluxes = GroundHeatFlux()
70+
bcs = merge_recursive(boundary_conditions, surface_fluxes)
71+
return StateVariables(vars, model.grid, clock, boundary_conditions=bcs)
72+
end
73+
7374
function initialize!(state, model::CoupledSoilAtmosphereModel)
7475
initialize!(state, model, model.initializer)
7576
initialize!(state, model.soil)

0 commit comments

Comments
 (0)