Skip to content

Commit 18e1bb1

Browse files
authored
Modify polar mask to be on cell centers only (#1800) (#1873)
Additionally: - Convert `ifelse` statements to pointwise multiplication when applying the mask - Switch the 1's and 0's in the boundary mask to be consistent with the ocean mask - Rename some polar mask variables/functions to reflect changes
1 parent 6517bec commit 18e1bb1

File tree

2 files changed

+46
-70
lines changed

2 files changed

+46
-70
lines changed

ext/ClimaCouplerCMIPExt/clima_seaice.jl

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -510,14 +510,15 @@ function FluxCalculator.ocean_seaice_fluxes!(
510510
# Compute fluxes for u, v, T, and S from momentum, heat, and freshwater fluxes
511511
oc_flux_u = surface_flux(ocean_sim.ocean.model.velocities.u)
512512
oc_flux_v = surface_flux(ocean_sim.ocean.model.velocities.v)
513-
# polar-exclusion mask
514-
polar_excl_centers = ocean_sim.remapping.polar_exclusion_flux_mask_centers
515-
polar_excl_u = ocean_sim.remapping.polar_exclusion_flux_mask_u
516-
polar_excl_v = ocean_sim.remapping.polar_exclusion_flux_mask_v
517513

518514
ρτxio = ice_sim.ocean_ice_interface.fluxes.x_momentum # sea_ice - ocean zonal momentum flux
519515
ρτyio = ice_sim.ocean_ice_interface.fluxes.y_momentum # sea_ice - ocean meridional momentum flux
520516

517+
# mask out the poles
518+
polar_mask = ocean_sim.remapping.polar_mask
519+
@. ρτxio = polar_mask * ρτxio
520+
@. ρτyio = polar_mask * ρτyio
521+
521522
# Update the momentum flux contributions from ocean/sea ice fluxes
522523
arch = OC.Architectures.architecture(grid)
523524
OC.Utils.launch!(
@@ -533,24 +534,21 @@ function FluxCalculator.ocean_seaice_fluxes!(
533534
ρₒ⁻¹,
534535
ice_concentration,
535536
)
536-
# polar-exclusion mask
537-
flux_u = OC.interior(oc_flux_u, :, :, 1)
538-
flux_v = OC.interior(oc_flux_v, :, :, 1)
539-
flux_u .= ifelse.(polar_excl_u .≈ 0, zero(flux_u), flux_u)
540-
flux_v .= ifelse.(polar_excl_v .≈ 0, zero(flux_v), flux_v)
541537

542538
# The heat and salt fluxes already include the SIC masking, so we don't need to
543539
# multiply by SIC here.
544540
oc_flux_T = surface_flux(ocean_sim.ocean.model.tracers.T)
545541
heat_flux = OC.interior(ocean_sim.remapping.scratch_cc1, :, :, 1)
546542
heat_flux .= OC.interior(Qi, :, :, 1) .* ρₒ⁻¹ ./ cₒ
547-
heat_flux .= ifelse.(polar_excl_centers .≈ 0, zero(heat_flux), heat_flux)
543+
# mask out the poles
544+
@. heat_flux = polar_mask * heat_flux
548545
OC.interior(oc_flux_T, :, :, 1) .+= heat_flux
549546

550547
oc_flux_S = surface_flux(ocean_sim.ocean.model.tracers.S)
551548
salt_contrib = OC.interior(ocean_sim.remapping.scratch_cc2, :, :, 1)
552549
salt_contrib .= OC.interior(ice_sim.ocean_ice_interface.fluxes.salt, :, :, 1)
553-
salt_contrib .= ifelse.(polar_excl_centers .≈ 0, zero(salt_contrib), salt_contrib)
550+
# mask out the poles
551+
@. salt_contrib = polar_mask * salt_contrib
554552
OC.interior(oc_flux_S, :, :, 1) .+= salt_contrib
555553

556554
return nothing

ext/ClimaCouplerCMIPExt/oceananigans.jl

Lines changed: 37 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -198,15 +198,10 @@ function OceananigansSimulation(
198198
# Allocate space for a Field of UVVectors, which we need for remapping momentum fluxes
199199
temp_uv_vec = CC.Fields.Field(CC.Geometry.UVVector{FT}, boundary_space)
200200

201-
# polar-exclusion mask
202-
# Precompute polar-exclusion flux masks once (zeros ocean surface fluxes where |lat| ≥ 78° to avoid instability).
203-
# _centers = cell-centered (T, S); _u = Face/Center (u); _v = Center/Face (v).
204-
polar_exclusion_flux_mask_centers =
205-
ocean_flux_highlat_mask(grid; location = (OC.Center(), OC.Center(), OC.Center()))
206-
polar_exclusion_flux_mask_u =
207-
ocean_flux_highlat_mask(grid; location = (OC.Face(), OC.Center(), OC.Center()))
208-
polar_exclusion_flux_mask_v =
209-
ocean_flux_highlat_mask(grid; location = (OC.Center(), OC.Face(), OC.Center()))
201+
# Precompute mask (zero where |lat| ≥ 78°) for removing ocean surface fluxes at the poles
202+
# This mask lives on cell centers
203+
# (Will be removed when we switch to other grids)
204+
polar_mask = ocean_polar_mask(grid; location = (OC.Center(), OC.Center(), OC.Center()))
210205

211206
remapping = (;
212207
remapper_cc,
@@ -216,10 +211,7 @@ function OceananigansSimulation(
216211
scratch_arr2,
217212
scratch_arr3,
218213
temp_uv_vec,
219-
# polar-exclusion mask
220-
polar_exclusion_flux_mask_centers,
221-
polar_exclusion_flux_mask_u,
222-
polar_exclusion_flux_mask_v,
214+
polar_mask,
223215
)
224216

225217
# COARE3 roughness params (allocated once, reused each timestep)
@@ -310,17 +302,17 @@ function FieldExchanger.resolve_area_fractions!(
310302
ocean_fraction = Interfacer.get_field(ocean_sim, Val(:area_fraction))
311303
ice_fraction = Interfacer.get_field(ice_sim, Val(:area_fraction))
312304

313-
# Polar mask: 1 where |lat| ≥ 78° (same band used to zero ocean fluxes)
305+
# Polar mask: 0 where |lat| ≥ 78° (same band used to zero ocean fluxes)
314306
boundary_space = axes(ocean_fraction)
315307
FT = CC.Spaces.undertype(boundary_space)
316308
lat = CC.Fields.coordinate_field(boundary_space).lat
317309
polar_mask = CC.Fields.zeros(boundary_space)
318-
polar_mask .= abs.(lat) .>= FT(78)
310+
polar_mask .= abs.(lat) .< FT(78)
319311

320-
# Set land fraction to 1 and ice/ocean fraction to 0 where polar_mask is 1
321-
@. land_fraction = ifelse.(polar_mask == FT(1), FT(1), land_fraction)
322-
@. ice_fraction = ifelse.(polar_mask == FT(1), FT(0), ice_fraction)
323-
@. ocean_fraction = ifelse.(polar_mask == FT(1), FT(0), ocean_fraction)
312+
# Set land fraction to 1 and ice/ocean fraction to 0 where polar_mask is 0
313+
@. land_fraction = polar_mask * land_fraction + (1 - polar_mask)
314+
@. ice_fraction = polar_mask * ice_fraction
315+
@. ocean_fraction = polar_mask * ocean_fraction
324316
end
325317

326318
# Update the ice concentration field in the ocean simulation
@@ -361,15 +353,14 @@ Interfacer.get_field(sim::OceananigansSimulation, ::Val{:surface_temperature}) =
361353
sim.ocean.model.tracers.T + sim.ocean_properties.C_to_K # convert from Celsius to Kelvin
362354

363355
"""
364-
ocean_flux_highlat_mask(grid; location)
356+
ocean_polar_mask(grid; location)
365357
366-
Build the ocean flux high latitude mask once at setup.
358+
Build the ocean polar mask once at setup.
367359
Currently we define ocean between 80°S to 80°N with 2 degree overlap in the coupler mask.
368360
Returns a 2D mask (1.0 where |lat| < 78°, 0.0 elsewhere). This mask is on the ocean grid
369361
(unlike the polar mask which is defined on the boundary_space)
370362
"""
371-
# polar-exclusion mask
372-
function ocean_flux_highlat_mask(grid; location = (OC.Center(), OC.Center(), OC.Center()))
363+
function ocean_polar_mask(grid; location = (OC.Center(), OC.Center(), OC.Center()))
373364
polar_flux_lat_deg = 78.0 # zero fluxes where |lat| ≥ this (same band as polar_mask for atmosphere)
374365

375366
# latitude nodes: a StepRangeLen of size grid.Ny *in degrees*
@@ -409,8 +400,8 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
409400
(; reference_density, heat_capacity) = sim.ocean_properties
410401
grid = sim.ocean.model.grid
411402
ice_concentration_field = sim.ice_concentration
412-
# polar-exclusion mask
413-
polar_excl_centers = sim.remapping.polar_exclusion_flux_mask_centers
403+
# for masking out the poles
404+
polar_mask = sim.remapping.polar_mask
414405

415406
# Convert the momentum fluxes from contravariant to Cartesian basis
416407
contravariant_to_cartesian!(sim.remapping.temp_uv_vec, F_turb_ρτxz, F_turb_ρτyz)
@@ -435,7 +426,11 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
435426
F_turb_ρτxz_cell = sim.remapping.scratch_cc1
436427
F_turb_ρτyz_cell = sim.remapping.scratch_cc2
437428

438-
# Weight by (1 - sea ice concentration); polar-exclusion mask applied via ifelse below
429+
# mask out the poles
430+
@. F_turb_ρτxz_cell = polar_mask * F_turb_ρτxz_cell
431+
@. F_turb_ρτyz_cell = polar_mask * F_turb_ρτyz_cell
432+
433+
# Weight by (1 - sea ice concentration)
439434
ice_concentration = OC.interior(ice_concentration_field, :, :, 1)
440435
OC.interior(F_turb_ρτxz_cell, :, :, 1) .=
441436
OC.interior(F_turb_ρτxz_cell, :, :, 1) .* (1.0 .- ice_concentration) ./
@@ -453,13 +448,6 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
453448
F_turb_ρτxz_cell,
454449
F_turb_ρτyz_cell,
455450
)
456-
# polar-exclusion mask
457-
polar_excl_u = sim.remapping.polar_exclusion_flux_mask_u
458-
polar_excl_v = sim.remapping.polar_exclusion_flux_mask_v
459-
flux_u = OC.interior(oc_flux_u, :, :, 1)
460-
flux_v = OC.interior(oc_flux_v, :, :, 1)
461-
flux_u .= ifelse.(polar_excl_u .≈ 0, zero(flux_u), flux_u)
462-
flux_v .= ifelse.(polar_excl_v .≈ 0, zero(flux_v), flux_v)
463451

464452
# Remap the latent and sensible heat fluxes using scratch arrays
465453
CC.Remapping.interpolate!(sim.remapping.scratch_arr1, sim.remapping.remapper_cc, F_lh) # latent heat flux
@@ -473,9 +461,9 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
473461
# everything on the surface, but later we will need to account for this.
474462
# One way we can do this is using directly ClimaOcean
475463
oc_flux_T = surface_flux(sim.ocean.model.tracers.T)
476-
# polar-exclusion mask
477-
@. remapped_F_lh = ifelse(polar_excl_centers .≈ 0, zero(remapped_F_lh), remapped_F_lh)
478-
@. remapped_F_sh = ifelse(polar_excl_centers .≈ 0, zero(remapped_F_sh), remapped_F_sh)
464+
# mask out the poles
465+
@. remapped_F_lh = polar_mask * remapped_F_lh
466+
@. remapped_F_sh = polar_mask * remapped_F_sh
479467
OC.interior(oc_flux_T, :, :, 1) .+=
480468
(1.0 .- ice_concentration) .* (remapped_F_lh .+ remapped_F_sh) ./
481469
(reference_density * heat_capacity)
@@ -490,12 +478,8 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
490478
moisture_fresh_water_flux = sim.remapping.scratch_arr1 ./ reference_density
491479
oc_flux_S = surface_flux(sim.ocean.model.tracers.S)
492480
surface_salinity = OC.interior(sim.ocean.model.tracers.S, :, :, grid.Nz)
493-
# polar-exclusion mask
494-
@. moisture_fresh_water_flux = ifelse(
495-
polar_excl_centers .≈ 0,
496-
zero(moisture_fresh_water_flux),
497-
moisture_fresh_water_flux,
498-
)
481+
# mask out the poles
482+
@. moisture_fresh_water_flux = polar_mask * moisture_fresh_water_flux
499483
OC.interior(oc_flux_S, :, :, 1) .-=
500484
(1.0 .- ice_concentration) .* surface_salinity .* moisture_fresh_water_flux
501485
return nothing
@@ -537,8 +521,8 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
537521
oc_flux_S = surface_flux(sim.ocean.model.tracers.S)
538522
OC.interior(oc_flux_S, :, :, 1) .= 0
539523

540-
# polar-exclusion mask
541-
polar_excl_centers = sim.remapping.polar_exclusion_flux_mask_centers
524+
# for masking out the poles
525+
polar_mask = sim.remapping.polar_mask
542526

543527
# Remap radiative flux onto scratch array; rename for clarity
544528
CC.Remapping.interpolate!(
@@ -561,7 +545,7 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
561545
α = Interfacer.get_field(sim, Val(:surface_direct_albedo)) # scalar
562546
ϵ = Interfacer.get_field(sim, Val(:emissivity)) # scalar
563547

564-
# Compute radiative contribution; polar-exclusion mask applied via ifelse
548+
# Compute radiative contribution
565549
rad_T_flux = sim.remapping.scratch_arr3
566550
rad_T_flux .=
567551
(1.0 .- ice_concentration) .* (
@@ -571,7 +555,8 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
571555
σ .* (C_to_K .+ OC.interior(sim.ocean.model.tracers.T, :, :, Nz)) .^ 4
572556
)
573557
) ./ (reference_density * heat_capacity)
574-
@. rad_T_flux = ifelse(polar_excl_centers .≈ 0, zero(rad_T_flux), rad_T_flux)
558+
# mask out the poles
559+
@. rad_T_flux = polar_mask * rad_T_flux
575560
OC.interior(oc_flux_T, :, :, 1) .+= rad_T_flux
576561

577562
# Remap precipitation fields onto scratch arrays; rename for clarity
@@ -585,20 +570,13 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf)
585570
sim.remapping.remapper_cc,
586571
csf.P_snow,
587572
)
588-
remapped_P_liq =
589-
ifelse.(
590-
polar_excl_centers .≈ 0,
591-
zero(sim.remapping.scratch_arr1),
592-
sim.remapping.scratch_arr1,
593-
)
594-
remapped_P_snow =
595-
ifelse.(
596-
polar_excl_centers .≈ 0,
597-
zero(sim.remapping.scratch_arr2),
598-
sim.remapping.scratch_arr2,
599-
)
573+
remapped_P_liq = sim.remapping.scratch_arr1
574+
remapped_P_snow = sim.remapping.scratch_arr2
575+
# mask out the poles
576+
@. remapped_P_liq = polar_mask * remapped_P_liq
577+
@. remapped_P_snow = polar_mask * remapped_P_snow
600578

601-
# Update salinity flux with precipitation contribution, including polar masking
579+
# Update salinity flux with precipitation contribution
602580
OC.interior(oc_flux_S, :, :, 1) .-=
603581
OC.interior(sim.ocean.model.tracers.S, :, :, Nz) .* (1.0 .- ice_concentration) .*
604582
(remapped_P_liq .+ remapped_P_snow) ./ reference_density

0 commit comments

Comments
 (0)