Skip to content

Commit 4f18f08

Browse files
committed
working on ClimaSeaIce [skip ci]
1 parent ae04351 commit 4f18f08

File tree

4 files changed

+155
-108
lines changed

4 files changed

+155
-108
lines changed

experiments/ClimaEarth/components/ocean/clima_seaice.jl

Lines changed: 97 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -39,64 +39,77 @@ a surface area fraction field.
3939
This type is used to indicate that this simulation is an ocean simulation for
4040
dispatch in coupling.
4141
42+
Initially, no sea ice is present.
43+
44+
Since this model does not solve for prognostic temperature, we use a
45+
prescribed heat flux boundary condition at the top, which is used to solve for
46+
temperature at the surface of the sea ice. The surface temperature from the
47+
previous step is provided to the coupler to be used in computing fluxes.
48+
4249
Specific details about the default model configuration
4350
can be found in the documentation for `ClimaOcean.ocean_simulation`.
4451
"""
45-
function ClimaSeaIceSimulation(area_fraction, ocean; output_dir)
52+
function ClimaSeaIceSimulation(land_fraction, ocean; output_dir)
4653
# Initialize the sea ice with the same grid as the ocean
47-
# Initially no sea ice is present
4854
grid = ocean.ocean.model.grid
4955
arch = grid.architecture
5056
advection = ocean.ocean.model.advection.T
51-
sea_ice = CO.sea_ice_simulation(grid, ocean.ocean; advection)
57+
top_heat_boundary_condition = CO.MeltingConstrainedFluxBalance()
58+
59+
# TODO use branch ss-js/top-heat-bc for this constructor
60+
sea_ice =
61+
CO.sea_ice_simulation(grid, ocean.ocean; advection, top_heat_boundary_condition)
5262

5363
melting_speed = 1e-4
5464

5565
# Since ocean and sea ice share the same grid, we can also share the remapping objects
5666
remapping = ocean.remapping
5767

5868
# Before version 0.96.22, the NetCDFWriter was broken on GPU
59-
# TODO this fails with OC Field MethodError
60-
# if arch isa OC.CPU || pkgversion(OC) >= v"0.96.22"
61-
# # Save all tracers and velocities to a NetCDF file at daily frequency
62-
# outputs = OC.prognostic_fields(sea_ice.model)
63-
# netcdf_writer = OC.NetCDFWriter(
64-
# sea_ice.model,
65-
# outputs;
66-
# schedule = OC.TimeInterval(86400), # Daily output
67-
# filename = joinpath(output_dir, "seaice_diagnostics.nc"),
68-
# indices = (:, :, grid.Nz),
69-
# overwrite_existing = true,
70-
# array_type = Array{Float32},
71-
# )
72-
# sea_ice.output_writers[:diagnostics] = netcdf_writer
73-
# end
69+
if arch isa OC.CPU || pkgversion(OC) >= v"0.96.22"
70+
# Save all tracers and velocities to a NetCDF file at daily frequency
71+
outputs = OC.prognostic_fields(sea_ice.model)
72+
jld_writer = OC.JLDWriter(
73+
sea_ice.model,
74+
outputs;
75+
schedule = OC.TimeInterval(86400), # Daily output
76+
filename = joinpath(output_dir, "seaice_diagnostics.jld2"),
77+
overwrite_existing = true,
78+
array_type = Array{Float32},
79+
)
80+
sea_ice.output_writers[:diagnostics] = jld_writer
81+
end
7482

7583
# Allocate space for the sea ice-ocean (io) fluxes
76-
io_bottom_heat_flux = OC.Field{Center, Center, Nothing}(grid)
77-
io_frazil_heat_flux = OC.Field{Center, Center, Nothing}(grid)
78-
io_salt_flux = OC.Field{Center, Center, Nothing}(grid)
79-
x_momentum = OC.Field{Face, Center, Nothing}(grid)
80-
y_momentum = OC.Field{Center, Face, Nothing}(grid)
81-
82-
ocean_sea_ice_fluxes = (
83-
interface_heat = io_bottom_heat_flux,
84-
frazil_heat = io_frazil_heat_flux,
85-
salt = io_salt_flux,
86-
x_momentum = x_momentum,
87-
y_momentum = y_momentum,
88-
)
84+
ocean_sea_ice_fluxes = ocean.ocean_sea_ice_fluxes
85+
86+
# TODO clean this up
87+
# Get the area fraction from the fractional sea ice concentration
88+
area_fraction = sea_ice.model.ice_concentration
89+
90+
# Overwrite ice fraction with the static land area fraction anywhere we have nonzero land area
91+
# max needed to avoid Float32 errors (see issue #271; Heisenbug on HPC)
92+
boundary_space = axes(ocean.area_fraction)
93+
FT = CC.Spaces.undertype(boundary_space)
94+
95+
area_fraction_boundary_space = Interfacer.remap(area_fraction, boundary_space)
96+
@. area_fraction_boundary_space =
97+
max(min(area_fraction_boundary_space, FT(1) - land_fraction), FT(0))
8998

9099
sim = ClimaSeaIceSimulation(
91100
sea_ice,
92-
area_fraction,
101+
area_fraction_boundary_space,
93102
melting_speed,
94103
remapping,
95104
ocean_sea_ice_fluxes,
96105
)
97106
return sim
98107
end
99108

109+
function update_sic!(area_fraction, sea_ice)
110+
# TODO
111+
end
112+
100113
###############################################################################
101114
### Functions required by ClimaCoupler.jl for a SurfaceModelSimulation
102115
###############################################################################
@@ -106,8 +119,9 @@ Interfacer.step!(sim::ClimaSeaIceSimulation, t) =
106119
OC.time_step!(sim.sea_ice, float(t) - sim.sea_ice.model.clock.time)
107120

108121
Interfacer.get_field(sim::ClimaSeaIceSimulation, ::Val{:area_fraction}) = sim.area_fraction
122+
Interfacer.get_field(sim::ClimaSeaIceSimulation, Val(:sea_ice_concentration)) =
123+
sim.sea_ice.model.ice_concentration
109124

110-
# TODO: Better values for this
111125
# At the moment, we return always Float32. This is because we always want to run
112126
# Oceananingans with Float64, so we have no way to know the float type here. Sticking with
113127
# Float32 ensures that nothing is accidentally promoted to Float64. We will need to change
@@ -118,22 +132,25 @@ Interfacer.get_field(sim::ClimaSeaIceSimulation, ::Val{:roughness_momentum}) =
118132
Float32(5.8e-5)
119133
Interfacer.get_field(sim::ClimaSeaIceSimulation, ::Val{:beta}) = Float32(1)
120134
Interfacer.get_field(sim::ClimaSeaIceSimulation, ::Val{:surface_direct_albedo}) =
121-
Float32(0.011)
135+
Float32(0.7)
122136
Interfacer.get_field(sim::ClimaSeaIceSimulation, ::Val{:surface_diffuse_albedo}) =
123-
Float32(0.069)
137+
Float32(0.7)
124138

125-
# Approximate the sea ice surface temperature from the bulk temperature and ocean surface temperature,
126-
# assuming a linear profile in the top layer
127-
# TODO how to get ocean surface temp here? Is it in the sea ice or do we need to pass the ocean sim too?
139+
# Approximate the sea ice surface temperature as the temperature computed from the
140+
# fluxes at the previous timestep.
128141
Interfacer.get_field(sim::ClimaSeaIceSimulation, ::Val{:surface_temperature}) =
129-
273.15 + sim.ocean.model.tracers.T
142+
273.15 .+
143+
OC.interior(sim.sea_ice.model.ice_thermodynamics.top_surface_temperature, :, :, 1)
130144

131145
"""
132146
FluxCalculator.update_turbulent_fluxes!(sim::ClimaSeaIceSimulation, fields)
133147
134148
Update the turbulent fluxes in the simulation using the values stored in the coupler fields.
135149
These include latent heat flux, sensible heat flux, momentum fluxes, and moisture flux.
136150
151+
Note that currently the moisture flux has no effect on the sea ice model, which has
152+
constant salinity.
153+
137154
A note on sign conventions:
138155
SurfaceFluxes and ClimaSeaIce both use the convention that a positive flux is an upward flux.
139156
No sign change is needed during the exchange, except for moisture/salinity fluxes:
@@ -166,10 +183,11 @@ function FluxCalculator.update_turbulent_fluxes!(sim::ClimaSeaIceSimulation, fie
166183
F_turb_ρτyz_cc = sim.remapping.scratch_cc2
167184

168185
# Set the momentum flux BCs at the correct locations using the remapped scratch fields
169-
oc_flux_u = surface_flux(sim.sea_ice.model.velocities.u)
170-
oc_flux_v = surface_flux(sim.sea_ice.model.velocities.v)
186+
# Note that this requires the sea ice model to always be run with dynamics turned on
187+
si_flux_u = sim.sea_ice.model.dynamics.external_stresses.top.u
188+
si_flux_v = sim.sea_ice.model.dynamics.external_stresses.top.v
171189
set_from_extrinsic_vectors!(
172-
(; u = oc_flux_u, v = oc_flux_v),
190+
(; u = si_flux_u, v = si_flux_v),
173191
grid,
174192
F_turb_ρτxz_cc,
175193
F_turb_ρτyz_cc,
@@ -183,27 +201,14 @@ function FluxCalculator.update_turbulent_fluxes!(sim::ClimaSeaIceSimulation, fie
183201
remapped_F_lh = sim.remapping.scratch_arr1
184202
remapped_F_sh = sim.remapping.scratch_arr2
185203

204+
# Update the sea ice only where the concentration is greater than zero.
205+
si_flux_heat = ice_sim.sea_ice.model.external_heat_fluxes.top
206+
OC.interior(si_flux_heat, :, :, 1) .=
207+
OC.interior(si_flux_heat, :, :, 1) .+ (
208+
(OC.interior(sim.sea_ice.model.ice_concentration, :, :, 1) .> 0) .*
209+
(remapped_F_lh .+ remapped_F_sh)
210+
)
186211

187-
# TODO update this for sea ice
188-
# TODO what is sim.sea_ice.model.ice_thermodynamics.top_surface_temperature? where is it set?
189-
# TODO ocean_reference_density -> sea_ice.model.ice_density ?
190-
oc_flux_T = surface_flux(sim.ocean.model.tracers.T)
191-
OC.interior(oc_flux_T, :, :, 1) .=
192-
OC.interior(oc_flux_T, :, :, 1) .+
193-
(remapped_F_lh .+ remapped_F_sh) ./ (ocean_reference_density * ocean_heat_capacity)
194-
195-
# Add the part of the salinity flux that comes from the moisture flux. We also need to
196-
# add the component due to precipitation (that was done with the radiative fluxes)
197-
CC.Remapping.interpolate!(
198-
sim.remapping.scratch_arr1,
199-
sim.remapping.remapper_cc,
200-
F_turb_moisture,
201-
)
202-
moisture_fresh_water_flux = sim.remapping.scratch_arr1 ./ ocean_fresh_water_density # TODO do we need this for sea ice too?
203-
oc_flux_S = surface_flux(sim.sea_ice.model.tracers.S)
204-
surface_salinity = OC.interior(sim.sea_ice.model.tracers.S, :, :, 1)
205-
OC.interior(oc_flux_S, :, :, 1) .=
206-
OC.interior(oc_flux_S, :, :, 1) .- surface_salinity .* moisture_fresh_water_flux
207212
return nothing
208213
end
209214

@@ -221,6 +226,9 @@ by the coupler.
221226
Update the portion of the surface_fluxes for T and S that is due to radiation and
222227
precipitation. The rest will be updated in `update_turbulent_fluxes!`.
223228
229+
Note that currently precipitation has no effect on the sea ice model, which has
230+
constant salinity.
231+
224232
A note on sign conventions:
225233
ClimaAtmos and ClimaSeaIce both use the convention that a positive flux is an upward flux.
226234
No sign change is needed during the exchange, except for precipitation/salinity fluxes.
@@ -229,10 +237,6 @@ ClimaSeaIce represents precipitation as a positive salinity flux,
229237
so a sign change is needed when we convert from precipitation to salinity flux.
230238
"""
231239
function FieldExchanger.update_sim!(sim::ClimaSeaIceSimulation, csf, area_fraction)
232-
# TODO update this for sea ice
233-
(; ocean_reference_density, ocean_heat_capacity, ocean_fresh_water_density) =
234-
sim.ocean_properties
235-
236240
# Remap radiative flux onto scratch array; rename for clarity
237241
CC.Remapping.interpolate!(
238242
sim.remapping.scratch_arr1,
@@ -243,31 +247,12 @@ function FieldExchanger.update_sim!(sim::ClimaSeaIceSimulation, csf, area_fracti
243247

244248
# Update only the part due to radiative fluxes. For the full update, the component due
245249
# to latent and sensible heat is missing and will be updated in update_turbulent_fluxes.
246-
oc_flux_T = surface_flux(sim.ocean.model.tracers.T)
247-
OC.interior(oc_flux_T, :, :, 1) .=
248-
remapped_F_radiative ./ (ocean_reference_density * ocean_heat_capacity)
250+
# Update the sea ice only where the concentration is greater than zero.
251+
si_flux_heat = sim.sea_ice.model.external_heat_fluxes.top
252+
OC.interior(si_flux_heat, :, :, 1) .=
253+
(OC.interior(sim.sea_ice.model.ice_concentration, :, :, 1) .> 0) .*
254+
remapped_F_radiative
249255

250-
# Remap precipitation fields onto scratch arrays; rename for clarity
251-
CC.Remapping.interpolate!(
252-
sim.remapping.scratch_arr1,
253-
sim.remapping.remapper_cc,
254-
csf.P_liq,
255-
)
256-
CC.Remapping.interpolate!(
257-
sim.remapping.scratch_arr2,
258-
sim.remapping.remapper_cc,
259-
csf.P_snow,
260-
)
261-
remapped_P_liq = sim.remapping.scratch_arr1
262-
remapped_P_snow = sim.remapping.scratch_arr2
263-
264-
# Virtual salt flux
265-
oc_flux_S = surface_flux(sim.ocean.model.tracers.S)
266-
precipitating_fresh_water_flux =
267-
(remapped_P_liq .+ remapped_P_snow) ./ ocean_fresh_water_density
268-
surface_salinity_flux =
269-
OC.interior(sim.ocean.model.tracers.S, :, :, 1) .* precipitating_fresh_water_flux
270-
OC.interior(oc_flux_S, :, :, 1) .= .-surface_salinity_flux
271256
return nothing
272257
end
273258

@@ -286,7 +271,7 @@ function FluxCalculator.ocean_seaice_fluxes!(
286271
ocean_properties = ocean_sim.ocean_properties
287272

288273
# Compute the fluxes and store them in the both simulations
289-
OC.compute_sea_ice_ocean_fluxes!(
274+
CO.compute_sea_ice_ocean_fluxes!(
290275
ice_sim.ocean_sea_ice_fluxes,
291276
ocean_sim.ocean,
292277
ice_sim.sea_ice,
@@ -295,7 +280,27 @@ function FluxCalculator.ocean_seaice_fluxes!(
295280
)
296281
ocean_sim.ocean_sea_ice_fluxes = ice_sim.ocean_sea_ice_fluxes
297282

298-
# TODO what do we do with these fluxes now? They need to be passed to the component sims somehow
283+
## Update the internals of the sea ice model
284+
# Set the bottom heat flux to the sum of the frazil and interface heat fluxes
285+
bottom_heat_flux = ice_sim.sea_ice.model.external_heat_fluxes.bottom
286+
287+
Qf = sea_ice_ocean_fluxes.frazil_heat # frazil heat flux
288+
Qi = sea_ice_ocean_fluxes.interface_heat # interfacial heat flux
289+
bottom_heat_flux .= Qf .+ Qi
290+
291+
## Update the internals of the ocean model
292+
ρₒ⁻¹ = 1 / ocean_sim.ocean_properties.reference_density
293+
cₒ = ocean_sim.ocean_properties.heat_capacity
294+
295+
Jᵀio = Qio * ρₒ⁻¹ / cₒ
296+
Jˢio = sea_ice_ocean_fluxes.salt[i, j, 1] * ℵᵢ
297+
298+
# ℑxᶠᵃᵃ: interpolate faces to centers
299+
τxio = ρτxio[i, j, 1] * ρₒ⁻¹ * ℑxᶠᵃᵃ(i, j, 1, grid, ℵ)
300+
τyio = ρτyio[i, j, 1] * ρₒ⁻¹ * ℑyᵃᶠᵃ(i, j, 1, grid, ℵ)
301+
302+
# TODO finish this
303+
299304
return nothing
300305
end
301306

0 commit comments

Comments
 (0)