@@ -39,64 +39,77 @@ a surface area fraction field.
3939This type is used to indicate that this simulation is an ocean simulation for
4040dispatch 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+
4249Specific details about the default model configuration
4350can 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
98107end
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
108121Interfacer. 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 )
119133Interfacer. get_field (sim:: ClimaSeaIceSimulation , :: Val{:beta} ) = Float32 (1 )
120134Interfacer. get_field (sim:: ClimaSeaIceSimulation , :: Val{:surface_direct_albedo} ) =
121- Float32 (0.011 )
135+ Float32 (0.7 )
122136Interfacer. 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.
128141Interfacer. 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
134148Update the turbulent fluxes in the simulation using the values stored in the coupler fields.
135149These 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+
137154A note on sign conventions:
138155SurfaceFluxes and ClimaSeaIce both use the convention that a positive flux is an upward flux.
139156No 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
208213end
209214
@@ -221,6 +226,9 @@ by the coupler.
221226Update the portion of the surface_fluxes for T and S that is due to radiation and
222227precipitation. 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+
224232A note on sign conventions:
225233ClimaAtmos and ClimaSeaIce both use the convention that a positive flux is an upward flux.
226234No sign change is needed during the exchange, except for precipitation/salinity fluxes.
@@ -229,10 +237,6 @@ ClimaSeaIce represents precipitation as a positive salinity flux,
229237so a sign change is needed when we convert from precipitation to salinity flux.
230238"""
231239function 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
272257end
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
300305end
301306
0 commit comments