Skip to content

Commit 7f1a73b

Browse files
committed
initial ClimaSeaIce infrastructure changes
1 parent 85413fa commit 7f1a73b

File tree

9 files changed

+184
-142
lines changed

9 files changed

+184
-142
lines changed

docs/src/fieldexchanger.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The specific fields that are exchanged depend on the requirements of the compone
1212
The fields imported from the atmosphere to the coupler are specified by extending `FieldExchanger.import_atmos_fields!`
1313
The default `import_atmos_fields!` imports radiative fluxes, liquid precipitation, and snow precipitation.
1414

15-
The fields of a component model that get updated by the coupler are specified by extending `FieldExchanger.update_sim!`
15+
The fields of a component model that get updated by the coupler are specified by extending `FieldExchanger.update_sim!`.
1616
The default `update_sim!` for an atmosphere model updates the direct and diffuse surface albedos,
1717
the surface temperature, and the turbulent fluxes.
1818
The default `update_sim!` for a surface model updates the air density, radiative fluxes,
@@ -21,18 +21,24 @@ These updates are done via the `update_field!` function, which must be extended
2121
particular variable and component model.
2222
If an `update_field!` function is not defined for a particular component model, it will be ignored.
2323

24+
Note that turbulent fluxes are not updated in `update_sim!`, but rather via
25+
`FluxCalculator.update_turbulent_fluxes!`, where fluxes are computed between
26+
the atmosphere and each surface model.
27+
2428
## FieldExchanger API
2529

2630
```@docs
31+
ClimaCoupler.FieldExchanger.exchange!
2732
ClimaCoupler.FieldExchanger.update_sim!
2833
ClimaCoupler.FieldExchanger.step_model_sims!
2934
ClimaCoupler.FieldExchanger.update_surface_fractions!
30-
ClimaCoupler.FieldExchanger.exchange!
3135
ClimaCoupler.FieldExchanger.set_caches!
3236
```
3337

3438
## FieldExchanger Internal Functions
3539

3640
```@docs
3741
ClimaCoupler.FieldExchanger.combine_surfaces!
42+
ClimaCoupler.FieldExchanger.resolve_ocean_ice_fractions!
43+
ClimaCoupler.FieldExchanger.import_atmos_fields!
3844
```

docs/src/fluxcalculator.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,23 @@ turbulent fluxes and ancillary quantities, such as the Obukhov length, using
1111
function is called at the end of each coupling step.
1212

1313
All the quantities computed in `turbulent_fluxes!` are calculated
14-
separately for each surface type using the
14+
separately for each surface model using the
1515
[`FluxCalculator.compute_surface_fluxes!`](@ref) function. This function can be
1616
extended by component models if they need specific type of flux calculation, and
1717
a default is provided for models that can use the standard flux calculation.
18-
The final result of `turbulent_fluxes!` is an area-weighted sum of
19-
all the contributions of the various surfaces.
2018

2119
The default method of [`FluxCalculator.compute_surface_fluxes!`](@ref), in turn,
2220
calls [`FluxCalculator.get_surface_fluxes`](@ref). This function uses a thermal
2321
state obtained by using the model surface temperature, extrapolates atmospheric
2422
density adiabatically to the surface, and with the surface humidity (if
2523
available, if not, assuming a saturation specific humidity for liquid phase).
26-
`compute_surface_fluxes!` also updates the component internal fluxes fields with
24+
`compute_surface_fluxes!` also updates the component internal fluxes fields via
2725
[`FluxCalculator.update_turbulent_fluxes!`](@ref), and adds the area-weighted
2826
contribution from this component model to the `CoupledSimulation` fluxes fields.
29-
Any extension of this function for a particular surface model is also expected
30-
to update both the component models' internal fluxes and the CoupledSimulation
31-
object's fluxes fields.
27+
28+
Any extension of `FluxCalculator.compute_surface_fluxes!` for a particular
29+
surface model is also expected to update both the component models' internal
30+
fluxes and the CoupledSimulation object's fluxes fields.
3231

3332
[`FluxCalculator.compute_surface_fluxes!`](@ref) sets:
3433
- the flux of momenta, `F_turb_ρτxz`, `F_turb_ρτyz`;

docs/src/interfacer.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,11 +223,26 @@ for the following properties:
223223
| `surface_direct albedo` | bulk direct surface albedo | |
224224
| `surface_diffuse albedo` | bulk diffuse surface albedo | |
225225
| `surface_temperature` | surface temperature | K |
226+
| `ice_concentration` | sea ice concentration (*sea ice models only*) | |
226227

227228
!!! note
228229
`area_fraction` is expected to be defined on the boundary space of the simulation,
229230
while all other fields will likely be on the simulation's own space.
230231

232+
!!! note "Sea ice concentration vs. area fraction"
233+
Sea ice models are expected to provide both `area_fraction` and `ice_concentration`.
234+
This may seem redundant, but there are subtle differences between the two.
235+
`ice_concentration` is internal to the ice model and may be determined
236+
via a prognostic variable, prescribed data, etc. `area_fraction` is defined
237+
at the coupler level and may follow some constraints that don't apply to `ice_concentration`.
238+
For example, we require that surface model area fractions sum to 1 at all points;
239+
this constraint is enforced for `area_fraction`, but not for `ice_concentration`.
240+
Additionally, since `area_fraction` is a coupler-defined concept, it is defined on
241+
the coupler boundary space, whereas `ice_concentration` exists on the model's space.
242+
Generally, `ice_concentration` and `area_fraction` should largely agree,
243+
with differences only arising from `area_fraction` corrections and
244+
the fields existing on different spaces.
245+
231246
- `update_field!(::SurfaceModelSimulation, ::Val{property}, field)`:
232247
A function to update the value of property in the component model
233248
simulation, using the values in `field` passed from the coupler
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""
2+
to_node(pt::CC.Geometry.LatLongPoint)
3+
4+
Transform `LatLongPoint` into a tuple (long, lat, 0), where the 0 is needed because we only
5+
care about the surface.
6+
"""
7+
@inline to_node(pt::CC.Geometry.LatLongPoint) = pt.long, pt.lat, zero(pt.lat)
8+
# This next one is needed if we have "LevelGrid"
9+
@inline to_node(pt::CC.Geometry.LatLongZPoint) = pt.long, pt.lat, zero(pt.lat)
10+
11+
"""
12+
map_interpolate(points, oc_field::OC.Field)
13+
14+
Interpolate the given 3D field onto the target points.
15+
16+
If the underlying grid does not contain a given point, return 0 instead.
17+
18+
TODO: Use a non-allocating version of this function (simply replace `map` with `map!`)
19+
"""
20+
function map_interpolate(points, oc_field::OC.Field)
21+
loc = map(L -> L(), OC.Fields.location(oc_field))
22+
grid = oc_field.grid
23+
data = oc_field.data
24+
25+
# TODO: There has to be a better way
26+
min_lat, max_lat = extrema(OC.φnodes(grid, OC.Center(), OC.Center(), OC.Center()))
27+
28+
map(points) do pt
29+
FT = eltype(pt)
30+
31+
# The oceananigans grid does not cover the entire globe, so we should not
32+
# interpolate outside of its latitude bounds. Instead we return 0
33+
min_lat < pt.lat < max_lat || return FT(0)
34+
35+
fᵢ = OC.Fields.interpolate(to_node(pt), data, loc, grid)
36+
convert(FT, fᵢ)::FT
37+
end
38+
end
39+
40+
"""
41+
surface_flux(f::OC.AbstractField)
42+
43+
Extract the top boundary conditions for the given field.
44+
"""
45+
function surface_flux(f::OC.AbstractField)
46+
top_bc = f.boundary_conditions.top
47+
if top_bc isa OC.BoundaryCondition{<:OC.BoundaryConditions.Flux}
48+
return top_bc.condition
49+
else
50+
return nothing
51+
end
52+
end
53+
54+
function Interfacer.remap(field::OC.Field, target_space)
55+
return map_interpolate(CC.Fields.coordinate_field(target_space), field)
56+
end
57+
58+
function Interfacer.remap(operation::OC.AbstractOperations.AbstractOperation, target_space)
59+
evaluated_field = OC.Field(operation)
60+
OC.compute!(evaluated_field)
61+
return Interfacer.remap(evaluated_field, target_space)
62+
end
63+
64+
"""
65+
set_from_extrinsic_vectors!(vectors, grid, u_cc, v_cc)
66+
67+
Given the extrinsic vector components `u_cc` and `v_cc` as `Center, Center`
68+
fields, rotate them onto the target grid and remap to `Face, Center` and
69+
`Center, Face` fields, respectively.
70+
"""
71+
function set_from_extrinsic_vectors!(vectors, grid, u_cc, v_cc)
72+
arch = OC.Architectures.architecture(grid)
73+
74+
# Rotate vectors onto the grid
75+
OC.Utils.launch!(arch, grid, :xy, _rotate_vectors!, u_cc, v_cc, grid)
76+
77+
# Fill halo regions with the rotated vectors so we can use them to interpolate
78+
OC.fill_halo_regions!(u_cc)
79+
OC.fill_halo_regions!(v_cc)
80+
81+
# Interpolate the vectors to face/center and center/face respectively
82+
OC.Utils.launch!(
83+
arch,
84+
grid,
85+
:xy,
86+
_interpolate_vectors!,
87+
vectors.u,
88+
vectors.v,
89+
grid,
90+
u_cc,
91+
v_cc,
92+
)
93+
return nothing
94+
end
95+
96+
"""
97+
_rotate_vectors!(τx, τy, grid)
98+
99+
Rotate the velocities from the extrinsic coordinate system to the intrinsic
100+
coordinate system.
101+
"""
102+
@kernel function _rotate_vectors!(τx, τy, grid)
103+
# Use `k = 1` to index into the reduced Fields
104+
i, j = @index(Global, NTuple)
105+
# Rotate u, v from extrinsic to intrinsic coordinate system
106+
τxr, τyr = OC.Operators.intrinsic_vector(i, j, 1, grid, τx, τy)
107+
@inbounds begin
108+
τx[i, j, 1] = τxr
109+
τy[i, j, 1] = τyr
110+
end
111+
end
112+
113+
"""
114+
_interpolate_vectors!(τx, τy, grid, τx_cc, τy_cc)
115+
116+
Interpolate the input fluxes `τx_cc` and `τy_cc`, which are Center/Center
117+
Fields to Face/Center and Center/Face coordinates, respectively.
118+
"""
119+
@kernel function _interpolate_vectors!(τx, τy, grid, τx_cc, τy_cc)
120+
# Use `k = 1` to index into the reduced Fields
121+
i, j = @index(Global, NTuple)
122+
@inbounds begin
123+
τx[i, j, 1] = OC.Operators.ℑxᶠᵃᵃ(i, j, 1, grid, τx_cc)
124+
τy[i, j, 1] = OC.Operators.ℑyᵃᶠᵃ(i, j, 1, grid, τy_cc)
125+
end
126+
end

experiments/ClimaEarth/components/ocean/oceananigans.jl

Lines changed: 2 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import Thermodynamics as TD
77
import ClimaOcean.EN4: download_dataset
88
using KernelAbstractions: @kernel, @index, @inbounds
99

10+
include("climaocean_helpers.jl")
11+
1012
"""
1113
OceananigansSimulation{SIM, A, OPROP, REMAP}
1214
@@ -207,70 +209,6 @@ end
207209
Interfacer.step!(sim::OceananigansSimulation, t) =
208210
OC.time_step!(sim.ocean, float(t) - sim.ocean.model.clock.time)
209211

210-
# We always want the surface, so we always set zero(pt.lat) for z
211-
"""
212-
to_node(pt::CA.ClimaCore.Geometry.LatLongPoint)
213-
214-
Transform `LatLongPoint` into a tuple (long, lat, 0), where the 0 is needed because we only
215-
care about the surface.
216-
"""
217-
@inline to_node(pt::CA.ClimaCore.Geometry.LatLongPoint) = pt.long, pt.lat, zero(pt.lat)
218-
# This next one is needed if we have "LevelGrid"
219-
@inline to_node(pt::CA.ClimaCore.Geometry.LatLongZPoint) = pt.long, pt.lat, zero(pt.lat)
220-
221-
"""
222-
map_interpolate(points, oc_field::OC.Field)
223-
224-
Interpolate the given 3D field onto the target points.
225-
226-
If the underlying grid does not contain a given point, return 0 instead.
227-
228-
TODO: Use a non-allocating version of this function (simply replace `map` with `map!`)
229-
"""
230-
function map_interpolate(points, oc_field::OC.Field)
231-
loc = map(L -> L(), OC.Fields.location(oc_field))
232-
grid = oc_field.grid
233-
data = oc_field.data
234-
235-
# TODO: There has to be a better way
236-
min_lat, max_lat = extrema(OC.φnodes(grid, OC.Center(), OC.Center(), OC.Center()))
237-
238-
map(points) do pt
239-
FT = eltype(pt)
240-
241-
# The oceananigans grid does not cover the entire globe, so we should not
242-
# interpolate outside of its latitude bounds. Instead we return 0
243-
min_lat < pt.lat < max_lat || return FT(0)
244-
245-
fᵢ = OC.Fields.interpolate(to_node(pt), data, loc, grid)
246-
convert(FT, fᵢ)::FT
247-
end
248-
end
249-
250-
"""
251-
surface_flux(f::OC.AbstractField)
252-
253-
Extract the top boundary conditions for the given field.
254-
"""
255-
function surface_flux(f::OC.AbstractField)
256-
top_bc = f.boundary_conditions.top
257-
if top_bc isa OC.BoundaryCondition{<:OC.BoundaryConditions.Flux}
258-
return top_bc.condition
259-
else
260-
return nothing
261-
end
262-
end
263-
264-
function Interfacer.remap(field::OC.Field, target_space)
265-
return map_interpolate(CC.Fields.coordinate_field(target_space), field)
266-
end
267-
268-
function Interfacer.remap(operation::OC.AbstractOperations.AbstractOperation, target_space)
269-
evaluated_field = OC.Field(operation)
270-
OC.compute!(evaluated_field)
271-
return Interfacer.remap(evaluated_field, target_space)
272-
end
273-
274212
Interfacer.get_field(sim::OceananigansSimulation, ::Val{:area_fraction}) = sim.area_fraction
275213

276214
# TODO: Better values for this
@@ -375,70 +313,6 @@ function FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fi
375313
return nothing
376314
end
377315

378-
"""
379-
set_from_extrinsic_vectors!(vectors, grid, u_cc, v_cc)
380-
381-
Given the extrinsic vector components `u_cc` and `v_cc` as `Center, Center`
382-
fields, rotate them onto the target grid and remap to `Face, Center` and
383-
`Center, Face` fields, respectively.
384-
"""
385-
function set_from_extrinsic_vectors!(vectors, grid, u_cc, v_cc)
386-
arch = grid.architecture
387-
388-
# Rotate vectors onto the grid
389-
OC.Utils.launch!(arch, grid, :xy, _rotate_velocities!, u_cc, v_cc, grid)
390-
391-
# Fill halo regions with the rotated vectors so we can use them to interpolate
392-
OC.fill_halo_regions!(u_cc)
393-
OC.fill_halo_regions!(v_cc)
394-
395-
# Interpolate the vectors to face/center and center/face respectively
396-
OC.Utils.launch!(
397-
arch,
398-
grid,
399-
:xy,
400-
_interpolate_velocities!,
401-
vectors.u,
402-
vectors.v,
403-
grid,
404-
u_cc,
405-
v_cc,
406-
)
407-
return nothing
408-
end
409-
410-
"""
411-
_rotate_velocities!(u, v, grid)
412-
413-
Rotate the velocities from the extrinsic coordinate system to the intrinsic
414-
coordinate system.
415-
"""
416-
@kernel function _rotate_velocities!(u, v, grid)
417-
# Use `k = 1` to index into the reduced Fields
418-
i, j = @index(Global, NTuple)
419-
# Rotate u, v from extrinsic to intrinsic coordinate system
420-
ur, vr = OC.Operators.intrinsic_vector(i, j, 1, grid, u, v)
421-
@inbounds begin
422-
u[i, j, 1] = ur
423-
v[i, j, 1] = vr
424-
end
425-
end
426-
427-
"""
428-
_interpolate_velocities!(u, v, grid, u_cc, v_cc)
429-
430-
Interpolate the input velocities `u_cc` and `v_cc`, which are Center/Center
431-
Fields to Face/Center and Center/Face coordinates, respectively.
432-
"""
433-
@kernel function _interpolate_velocities!(u, v, grid, u_cc, v_cc)
434-
# Use `k = 1` to index into the reduced Fields
435-
i, j = @index(Global, NTuple)
436-
@inbounds begin
437-
u[i, j, 1] = OC.Operators.ℑxyᶠᶜᵃ(i, j, 1, grid, u_cc)
438-
v[i, j, 1] = OC.Operators.ℑxyᶜᶠᵃ(i, j, 1, grid, v_cc)
439-
end
440-
end
441-
442316
function Interfacer.update_field!(sim::OceananigansSimulation, ::Val{:area_fraction}, field)
443317
sim.area_fraction .= field
444318
return nothing

experiments/ClimaEarth/components/ocean/prescr_seaice.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ end
165165
# extensions required by Interfacer
166166
Interfacer.get_field(sim::PrescribedIceSimulation, ::Val{:area_fraction}) =
167167
sim.integrator.p.area_fraction
168+
Interfacer.get_field(sim::PrescribedIceSimulation, ::Val{:ice_concentration}) =
169+
sim.integrator.p.area_fraction
168170
Interfacer.get_field(sim::PrescribedIceSimulation, ::Val{:roughness_buoyancy}) =
169171
sim.integrator.p.params.z0b
170172
Interfacer.get_field(sim::PrescribedIceSimulation, ::Val{:roughness_momentum}) =

0 commit comments

Comments
 (0)