Skip to content

Commit 24225cc

Browse files
committed
use SIC instead of area fraction [skip ci]
1 parent bbb1305 commit 24225cc

File tree

3 files changed

+68
-59
lines changed

3 files changed

+68
-59
lines changed

experiments/ClimaEarth/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ ClimaAtmos = "0.27, 0.28, 0.29, 0.30, 0.31"
4141
ClimaCalibrate = "0.1"
4242
ClimaDiagnostics = "0.2.6"
4343
ClimaLand = "1.0"
44-
ClimaOcean = "0.8"
44+
ClimaOcean = "0.8.6"
4545
ClimaParams = "1.0"
4646
ClimaSeaIce = "0.3"
4747
ClimaTimeSteppers = "0.7, 0.8"

experiments/ClimaEarth/components/ocean/clima_seaice.jl

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,11 @@ can be found in the documentation for `ClimaOcean.ocean_simulation`.
5252
"""
5353
function ClimaSeaIceSimulation(land_fraction, ocean; output_dir)
5454
# Initialize the sea ice with the same grid as the ocean
55-
grid = ocean.ocean.model.grid # TODO can't use lat/lon grid at poles for ice, need to fill with bucket
55+
grid = ocean.ocean.model.grid
5656
arch = OC.Architectures.architecture(grid)
5757
advection = ocean.ocean.model.advection.T
5858
top_heat_boundary_condition = CSI.MeltingConstrainedFluxBalance()
5959

60-
# TODO use ClimaOcean 0.8.6
6160
ice = CO.sea_ice_simulation(grid, ocean.ocean; advection, top_heat_boundary_condition)
6261

6362
melting_speed = 1e-4
@@ -262,6 +261,12 @@ end
262261
Compute the fluxes between the ocean and sea ice, storing them in the `ocean_ice_fluxes`
263262
fields of the ocean and sea ice simulations.
264263
264+
This function assumes both simulations share the same grid, so no remapping is done.
265+
266+
Both simulations have had their atmospheric fluxes updated already in this timestep
267+
(see `update_sim!` and `update_turbulent_fluxes!`), so we add the contributions from the
268+
ocean-sea ice interactions to the existing fluxes, rather than overwriting all fluxes.
269+
265270
!!! note
266271
This function must be called after the turbulent fluxes have been updated in both
267272
simulations. Here only the contributions from the sea ice/ocean interactions
@@ -275,11 +280,10 @@ function FluxCalculator.ocean_seaice_fluxes!(
275280
ocean_properties = ocean_sim.ocean_properties
276281
ice_concentration = Interfacer.get_field(ice_sim, Val(:ice_concentration))
277282

283+
# Update the sea ice concentration in the ocean simulation
284+
ocean_sim.ice_concentration .= ice_concentration
285+
278286
# Compute the fluxes and store them in the both simulations
279-
ocean_properties = (;
280-
reference_density = ocean_properties.ocean_reference_density,
281-
heat_capacity = ocean_properties.ocean_heat_capacity,
282-
) # TODO rename in constructor
283287
CO.OceanSeaIceModels.InterfaceComputations.compute_sea_ice_ocean_fluxes!(
284288
ice_sim.ocean_ice_fluxes,
285289
ocean_sim.ocean,
@@ -297,8 +301,8 @@ function FluxCalculator.ocean_seaice_fluxes!(
297301
bottom_heat_flux .= Qf .+ Qi
298302

299303
## Update the internals of the ocean model
300-
ρₒ⁻¹ = 1 / ocean_sim.ocean_properties.ocean_reference_density
301-
cₒ = ocean_sim.ocean_properties.ocean_heat_capacity
304+
ρₒ⁻¹ = 1 / ocean_sim.ocean_properties.reference_density
305+
cₒ = ocean_sim.ocean_properties.heat_capacity
302306

303307
# Compute fluxes for u, v, T, and S from momentum, heat, and freshwater fluxes
304308
oc_flux_u = surface_flux(ocean_sim.ocean.model.velocities.u)

experiments/ClimaEarth/components/ocean/oceananigans.jl

Lines changed: 55 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ using KernelAbstractions: @kernel, @index, @inbounds
1010
include("climaocean_helpers.jl")
1111

1212
"""
13-
OceananigansSimulation{SIM, A, OPROP, REMAP}
13+
OceananigansSimulation{SIM, A, OPROP, REMAP, SIC}
1414
1515
The ClimaCoupler simulation object used to run with Oceananigans.
1616
This type is used by the coupler to indicate that this simulation
@@ -21,12 +21,14 @@ It contains the following objects:
2121
- `area_fraction::A`: A ClimaCore Field representing the surface area fraction of this component model on the exchange grid.
2222
- `ocean_properties::OPROP`: A NamedTuple of ocean properties and parameters
2323
- `remapping::REMAP`: Objects needed to remap from the exchange (spectral) grid to Oceananigans spaces.
24+
- `ice_concentration::SIC`: An Oceananigans Field representing the sea ice concentration on the ocean/sea ice grid.
2425
"""
25-
struct OceananigansSimulation{SIM, A, OPROP, REMAP} <: Interfacer.OceanModelSimulation
26+
struct OceananigansSimulation{SIM, A, OPROP, REMAP, SIC} <: Interfacer.OceanModelSimulation
2627
ocean::SIM
2728
area_fraction::A
2829
ocean_properties::OPROP
2930
remapping::REMAP
31+
ice_concentration::SIC
3032
end
3133

3234
"""
@@ -154,13 +156,11 @@ function OceananigansSimulation(
154156
scratch_arr2 = ArrayType(zeros(FT, interpolated_values_dim...))
155157
scratch_arr3 = ArrayType(zeros(FT, interpolated_values_dim...))
156158

157-
remapping = (; remapper_cc, scratch_cc1, scratch_cc2, scratch_arr1, scratch_arr2, scratch_arr3)
159+
remapping =
160+
(; remapper_cc, scratch_cc1, scratch_cc2, scratch_arr1, scratch_arr2, scratch_arr3)
158161

159-
ocean_properties = (;
160-
ocean_reference_density = 1020,
161-
ocean_heat_capacity = 3991,
162-
ocean_fresh_water_density = 999.8,
163-
)
162+
ocean_properties =
163+
(; reference_density = 1020, heat_capacity = 3991, fresh_water_density = 999.8)
164164

165165
# Before version 0.96.22, the NetCDFWriter was broken on GPU
166166
if arch isa OC.CPU || pkgversion(OC) >= v"0.96.22"
@@ -178,8 +178,17 @@ function OceananigansSimulation(
178178
ocean.output_writers[:diagnostics] = netcdf_writer
179179
end
180180

181-
sim = OceananigansSimulation(ocean, area_fraction, ocean_properties, remapping)
182-
return sim
181+
# Initialize with 0 ice concentration; this will be updated in `resolve_ocean_ice_fractions!`
182+
# if the ocean is coupled to a non-prescribed sea ice model.
183+
ice_concentration = OC.Field{OC.Center, OC.Center, Nothing}(grid)
184+
185+
return OceananigansSimulation(
186+
ocean,
187+
area_fraction,
188+
ocean_properties,
189+
remapping,
190+
ice_concentration,
191+
)
183192
end
184193

185194
"""
@@ -193,6 +202,9 @@ degrees latitude, and make sure the ocean fraction is 0 there.
193202
194203
The land fraction is expected to be set to 1 at the poles before calling this function,
195204
and doesn't need to be set again since its fraction is static.
205+
206+
This function also updates the ice concentration field in the ocean simulation
207+
so that it can be used for weighting flux updates.
196208
"""
197209
function FieldExchanger.resolve_ocean_ice_fractions!(
198210
ocean_sim::OceananigansSimulation,
@@ -215,6 +227,9 @@ function FieldExchanger.resolve_ocean_ice_fractions!(
215227
@. ice_fraction = ifelse.(polar_mask == FT(1), FT(1) - land_fraction, ice_fraction)
216228
@. ocean_fraction = ifelse.(polar_mask == FT(1), FT(0), ocean_fraction)
217229
end
230+
231+
# Update the ice concentration field in the ocean simulation
232+
ocean_sim.ice_concentration .= Interfacer.get_field(ice_sim, Val(:ice_concentration))
218233
return nothing
219234
end
220235

@@ -254,7 +269,11 @@ Interfacer.get_field(sim::OceananigansSimulation, ::Val{:surface_temperature}) =
254269
Update the turbulent fluxes in the simulation using the values computed at this time step.
255270
These include latent heat flux, sensible heat flux, momentum fluxes, and moisture flux.
256271
257-
The input `fields` are already area-weighted, so there's no need to weight them again.
272+
Rather than setting the surface fluxes and overwriting previous values, this function adds only
273+
the contributions from the turbulent fluxes. `update_sim!` sets the surface fluxes due to
274+
radiation and precipitation. Additional contributions may be made in `ocean_seaice_fluxes!`.
275+
An exception is the momentum fluxes, which are set directly here since they are not updated
276+
in `update_sim!`.
258277
259278
A note on sign conventions:
260279
SurfaceFluxes and Oceananigans both use the convention that a positive flux is an upward flux.
@@ -264,18 +283,9 @@ and Oceananigans represents moisture moving from atmosphere to ocean as a positi
264283
so a sign change is needed when we convert from moisture to salinity flux.
265284
"""
266285
function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fields)
267-
# TODO multiply all fluxes by 1 - SIC
268-
# TODO clarify where we need to add and where we set fluxes directly
269286
(; F_lh, F_sh, F_turb_ρτxz, F_turb_ρτyz, F_turb_moisture) = fields
270287
grid = sim.ocean.model.grid
271-
272-
# Remap the area fraction from the boundary space to the Oceananigans grid
273-
CC.Remapping.interpolate!(
274-
sim.remapping.scratch_arr3,
275-
sim.remapping.remapper_cc,
276-
sim.area_fraction,
277-
)
278-
area_fraction = sim.remapping.scratch_arr3
288+
ice_concentration = sim.ice_concentration
279289

280290
# Remap momentum fluxes onto reduced 2D Center, Center fields using scratch arrays and fields
281291
CC.Remapping.interpolate!(
@@ -295,6 +305,13 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
295305
F_turb_ρτxz_cc = sim.remapping.scratch_cc1
296306
F_turb_ρτyz_cc = sim.remapping.scratch_cc2
297307

308+
# Weight by (1 - sea ice concentration)
309+
# TODO does this work with OC fields?
310+
OC.interior(F_turb_ρτxz_cc, :, :, 1) .=
311+
OC.interior(F_turb_ρτxz_cc, :, :, 1) .* (1.0 .- ice_concentration)
312+
OC.interior(F_turb_ρτyz_cc, :, :, 1) .=
313+
OC.interior(F_turb_ρτyz_cc, :, :, 1) .* (1.0 .- ice_concentration)
314+
298315
# Set the momentum flux BCs at the correct locations using the remapped scratch fields
299316
oc_flux_u = surface_flux(sim.ocean.model.velocities.u)
300317
oc_flux_v = surface_flux(sim.ocean.model.velocities.v)
@@ -303,10 +320,9 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
303320
grid,
304321
F_turb_ρτxz_cc,
305322
F_turb_ρτyz_cc,
306-
) # TODO multiply by area_fraction?
323+
)
307324

308-
(; ocean_reference_density, ocean_heat_capacity, ocean_fresh_water_density) =
309-
sim.ocean_properties
325+
(; reference_density, heat_capacity, fresh_water_density) = sim.ocean_properties
310326

311327
# Remap the latent and sensible heat fluxes using scratch arrays
312328
CC.Remapping.interpolate!(sim.remapping.scratch_arr1, sim.remapping.remapper_cc, F_lh) # latent heat flux
@@ -322,8 +338,8 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
322338
oc_flux_T = surface_flux(sim.ocean.model.tracers.T)
323339
OC.interior(oc_flux_T, :, :, 1) .=
324340
OC.interior(oc_flux_T, :, :, 1) .+
325-
area_fraction .* (remapped_F_lh .+ remapped_F_sh) ./
326-
(ocean_reference_density * ocean_heat_capacity)
341+
(1.0 .- ice_concentration) .* (remapped_F_lh .+ remapped_F_sh) ./
342+
(reference_density * heat_capacity)
327343

328344
# Add the part of the salinity flux that comes from the moisture flux, we also need to
329345
# add the component due to precipitation (that was done with the radiative fluxes)
@@ -332,12 +348,12 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
332348
sim.remapping.remapper_cc,
333349
F_turb_moisture,
334350
)
335-
moisture_fresh_water_flux = sim.remapping.scratch_arr1 ./ ocean_fresh_water_density
351+
moisture_fresh_water_flux = sim.remapping.scratch_arr1 ./ fresh_water_density
336352
oc_flux_S = surface_flux(sim.ocean.model.tracers.S)
337353
surface_salinity = OC.interior(sim.ocean.model.tracers.S, :, :, 1)
338354
OC.interior(oc_flux_S, :, :, 1) .=
339355
OC.interior(oc_flux_S, :, :, 1) .-
340-
area_fraction .* surface_salinity .* moisture_fresh_water_flux
356+
(1.0 .- ice_concentration) .* surface_salinity .* moisture_fresh_water_flux
341357
return nothing
342358
end
343359

@@ -355,9 +371,8 @@ by the coupler.
355371
Update the portion of the surface_fluxes for T and S that is due to radiation and
356372
precipitation. The rest will be updated in `update_turbulent_fluxes!`.
357373
358-
Unlike the turbulent fluxes, the radiative and precipitation fluxes need to be
359-
weighted by the ocean area fraction, since they provided from the atmosphere
360-
without any weighting.
374+
This function sets the surface fluxes directly, overwriting any previous values.
375+
Additional contributions will be made in `update_turbulent_fluxes!` and `ocean_seaice_fluxes!`.
361376
362377
A note on sign conventions:
363378
ClimaAtmos and Oceananigans both use the convention that a positive flux is an upward flux.
@@ -367,16 +382,8 @@ Oceananigans represents precipitation as a positive salinity flux,
367382
so a sign change is needed when we convert from precipitation to salinity flux.
368383
"""
369384
function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
370-
(; ocean_reference_density, ocean_heat_capacity, ocean_fresh_water_density) =
371-
sim.ocean_properties
372-
# TODO use SIC instead?
373-
# Remap the area fraction from the boundary space to the Oceananigans grid
374-
CC.Remapping.interpolate!(
375-
sim.remapping.scratch_arr3,
376-
sim.remapping.remapper_cc,
377-
sim.area_fraction,
378-
)
379-
area_fraction = sim.remapping.scratch_arr3
385+
(; reference_density, heat_capacity, fresh_water_density) = sim.ocean_properties
386+
ice_concentration = sim.ice_concentration
380387

381388
# Remap radiative flux onto scratch array; rename for clarity
382389
CC.Remapping.interpolate!(
@@ -401,36 +408,34 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
401408
α = Interfacer.get_field(sim, Val(:surface_direct_albedo)) # scalar
402409
ϵ = Interfacer.get_field(sim, Val(:emissivity)) # scalar
403410
OC.interior(oc_flux_T, :, :, 1) .=
404-
area_fraction .* (
411+
(1.0 .- ice_concentration) .* (
405412
-(1 - α) .* remapped_SW_d .-
406413
ϵ * (
407414
remapped_LW_d .-
408415
σ .* (273.15 .+ OC.interior(sim.ocean.model.tracers.T, :, :, 1)) .^ 4
409416
)
410-
) ./ (ocean_reference_density * ocean_heat_capacity)
417+
) ./ (reference_density * heat_capacity)
411418

412419
# Remap precipitation fields onto scratch arrays; rename for clarity
413420
CC.Remapping.interpolate!(
414421
sim.remapping.scratch_arr1,
415422
sim.remapping.remapper_cc,
416-
sim.area_fraction .* csf.P_liq,
423+
csf.P_liq,
417424
)
418425
CC.Remapping.interpolate!(
419426
sim.remapping.scratch_arr2,
420427
sim.remapping.remapper_cc,
421-
sim.area_fraction .* csf.P_snow,
428+
csf.P_snow,
422429
)
423430
remapped_P_liq = sim.remapping.scratch_arr1
424431
remapped_P_snow = sim.remapping.scratch_arr2
425432

426433
# Virtual salt flux
427434
oc_flux_S = surface_flux(sim.ocean.model.tracers.S)
428-
precipitating_fresh_water_flux =
429-
area_fraction .* (remapped_P_liq .+ remapped_P_snow) ./ ocean_fresh_water_density
430-
surface_salinity_flux =
431-
OC.interior(sim.ocean.model.tracers.S, :, :, 1) .* precipitating_fresh_water_flux
432435
OC.interior(oc_flux_S, :, :, 1) .=
433-
OC.interior(oc_flux_S, :, :, 1) .- surface_salinity_flux
436+
OC.interior(oc_flux_S, :, :, 1) .-
437+
OC.interior(sim.ocean.model.tracers.S, :, :, 1) .* (1.0 .- ice_concentration) .*
438+
(remapped_P_liq .+ remapped_P_snow) ./ fresh_water_density
434439
return nothing
435440
end
436441

0 commit comments

Comments
 (0)