Skip to content

Commit 32011f8

Browse files
committed
Clean up convenience interpolate
I don't know if I lost a commit or something, but `Remapping.interpolate` was missing some methods needed to make it useful. In this commit, I go over the methods for all the spaces and add what is missing. I also change the default behavior for `default_target_zcoords` to interpolate on levels instead of linear interpolation (operation that more common). With this commit, plotting `Field`s becomes very easy. For example, plotting the surface field: ```julia using CairoMakie import ClimaCore.Remapping: interpolate heatmap(interpolate(field)[:, :, 1]) ``` This commit also ads the defaults to the constructors for the `Remapper` too, so that creating a `Remapper` becomes easier: ```julia import ClimaCore.Remapping: Remapper remapper = Remapper(space) ``` (If one is fine with the defaults otherwise they can still pass the coordinates)
1 parent 757bf01 commit 32011f8

File tree

5 files changed

+287
-113
lines changed

5 files changed

+287
-113
lines changed

NEWS.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,37 @@ ClimaCore.jl Release Notes
44
main
55
-------
66

7+
### ![][badge-✨feature/enhancement] Improvements to `Remapping.interpolate` PR [2272](https://github.com/CliMA/ClimaCore.jl/pull/2272)
8+
9+
PR [2272](https://github.com/CliMA/ClimaCore.jl/pull/2272) introduces
10+
convenience `interpolate` functions that are optimally suited for plotting.
11+
12+
Assuming `field` is a 3D `Field`,
13+
```julia
14+
import ClimaCore.Remapping: interpolate
15+
16+
array3D = interpolate(fields)
17+
```
18+
19+
`array3D` will be an 3D Array with values along the three dimensions.
20+
21+
By default, no interpolation in the vertical direction is performed. Linear
22+
interpolation can be obtained by passing `zresolution`
23+
```julia
24+
array3D_linear_in_z = interpolate(fields; zresolution)
25+
```
26+
27+
Controlling the specific interpolation points is also possible, as shown in this
28+
example:
29+
```julia
30+
import ClimaCore.Geometry: LatLongPoint, ZPoint
31+
import ClimaCore.Remapping: interpolate
32+
33+
target_hcoords = [LatLongPoint(45., 23.)]
34+
target_zcoords = ZPoint.([100., 200., 1000.])
35+
36+
array3D_specific = interpolate(fields; target_hcoords, target_zcoords)
37+
```
738

839
v0.14.29
940
-------

docs/src/remapping.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ given `field`. To obtain such coordinates, you can call the
4545
functions. These functions return an `Array` with the coordinates over which
4646
interpolation will occur. These arrays are of type `Geometry.Point`s.
4747

48+
By default, vertical interpolation is switched off and the `field` is evaluated
49+
directly on the levels.
50+
4851
`ClimaCore.Remapping.interpolate` allocates new output arrays. As such, it is
4952
not suitable for performance-critical applications.
5053
`ClimaCore.Remapping.interpolate!` performs interpolation in-place. When using
@@ -80,6 +83,12 @@ If the default target coordinates are being used, it is possible to broadcast
8083
`ClimaCore.Geometry.components` to extract them as a vector of tuples (and then
8184
broadcast `getindex` to extract the respective coordinates as vectors).
8285

86+
This also provides the simplest way to plot a `Field`. Suppose `field` is a 2D `Field`:
87+
```julia
88+
using CairoMakie
89+
heatmap(ClimaCore.Remapping.interpolate(field))
90+
```
91+
8392
### The `Remapper` object
8493

8594
A `Remapping.Remapper` is an object that is tied to a specified `Space` and can
@@ -88,15 +97,17 @@ The grid does not have to be regular, but it has to be defined as a Cartesian
8897
product between some horizontal and vertical coordinates (meaning, for each
8998
horizontal point, there is a fixed column of vertical coordinates).
9099

91-
Let us create our first remapper, assuming we have `space` defined on the surface of the sphere
100+
Let us create our first remapper, assuming we have `space` defined on the
101+
surface of the sphere
92102
```julia
93103
import ClimaCore.Geometry: LatLongPoint, ZPoint
94104
import ClimaCore.Remapping: Remapper
95105

96106
hcoords = [Geometry.LatLongPoint(lat, long) for long in -180.:180., lat in -90.:90.]
97107
remapper = Remapper(space, target_hcoords)
98108
```
99-
This `remapper` object knows can interpolate `Field`s defined on `space` with the same `interpolate` and `interpolate!` functions.
109+
This `remapper` object knows can interpolate `Field`s defined on `space` with
110+
the same `interpolate` and `interpolate!` functions.
100111
```julia
101112
import ClimaCore.Fields: coordinate_field
102113
import ClimaCore.Remapping: interpolate, interpolate!
@@ -118,7 +129,8 @@ When interpolating multiple fields, greater performance can be achieved by
118129
creating the `Remapper` with a larger internal buffer to store intermediate
119130
values for interpolation. Effectively, this controls how many fields can be
120131
remapped simultaneously in `interpolate`. When more fields than `buffer_length`
121-
are passed, the remapper will batch the work in sizes of `buffer_length`. The optimal number of fields passed is the `buffer_length` of the `remapper`. If
132+
are passed, the remapper will batch the work in sizes of `buffer_length`. The
133+
optimal number of fields passed is the `buffer_length` of the `remapper`. If
122134
more fields are passed, the `remapper` will batch work with size up to its
123135
`buffer_length`.
124136

src/Remapping/distributed_remapping.jl

Lines changed: 62 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ The `Remapper` is designed to not be tied to any particular `Field`. You can use
231231
232232
If you want to quickly remap something, you can call directly `interpolate`.
233233
234+
By default, [`default_target_zcoords`](@ref) [`default_target_hcoords`](@ref) are used to
235+
determine the coordinates.
236+
234237
Keyword arguments
235238
=================
236239
@@ -241,31 +244,51 @@ the work in sizes of `buffer_length`.
241244
"""
242245
function Remapper end
243246

244-
# 3D case, everything passed as a keyword argument
247+
# General case
248+
#
249+
# We have Union{AbstractArray, Nothing} because we want to allow for a single interface that
250+
# capture every case.
245251
Remapper(
246252
space::Spaces.AbstractSpace;
247-
target_hcoords::Union{AbstractArray, Nothing} = nothing,
248-
target_zcoords::Union{AbstractArray, Nothing} = nothing,
253+
target_hcoords::Union{AbstractArray, Nothing} = default_target_hcoords(
254+
space,
255+
),
256+
target_zcoords::Union{AbstractArray, Nothing} = default_target_zcoords(
257+
space,
258+
),
249259
buffer_length::Int = 1,
250260
) = _Remapper(space; target_zcoords, target_hcoords, buffer_length)
251261

252-
# 3D case, horizontal coordinate passed as position argument (backward compatibility)
262+
# General case, everything passed as positional
253263
Remapper(
254264
space::Spaces.AbstractSpace,
255265
target_hcoords::Union{AbstractArray, Nothing},
256266
target_zcoords::Union{AbstractArray, Nothing};
257267
buffer_length::Int = 1,
258-
) = _Remapper(space; target_hcoords, target_zcoords, buffer_length)
259-
268+
) = _Remapper(space; target_zcoords, target_hcoords, buffer_length)
260269

261270
# Purely vertical case
262271
Remapper(
263272
space::Spaces.FiniteDifferenceSpace;
264-
target_zcoords::AbstractArray,
273+
target_zcoords::AbstractArray = default_target_zcoords(space),
274+
buffer_length::Int = 1,
275+
) = _Remapper(space; target_zcoords, target_hcoords = nothing, buffer_length)
276+
277+
# Purely vertical, positional
278+
Remapper(
279+
space::Spaces.FiniteDifferenceSpace,
280+
target_zcoords::AbstractArray;
265281
buffer_length::Int = 1,
266282
) = _Remapper(space; target_zcoords, target_hcoords = nothing, buffer_length)
267283

268284
# Purely horizontal case
285+
Remapper(
286+
space::Spaces.AbstractSpectralElementSpace;
287+
target_hcoords::AbstractArray = default_target_hcoords(space),
288+
buffer_length::Int = 1,
289+
) = _Remapper(space; target_zcoords = nothing, target_hcoords, buffer_length)
290+
291+
# Purely horizontal case, positional
269292
Remapper(
270293
space::Spaces.AbstractSpectralElementSpace,
271294
target_hcoords::AbstractArray;
@@ -922,13 +945,17 @@ end
922945
"""
923946
interpolate(field::ClimaCore.Fields;
924947
hresolution = 180,
925-
zresolution = 50,
948+
zresolution = nothing,
926949
target_hcoords = default_target_hcoords(space; hresolution),
927-
target_zcoords = default_target_vcoords(space; zresolution)
950+
target_zcoords = default_target_zcoords(space; zresolution)
928951
)
929952
930953
Interpolate the given fields on the Cartesian product of `target_hcoords` with
931-
`target_zcoords` (if not empty).
954+
`target_zcoords`. For `Space`s without horizontal/vertical component, the relevant
955+
`target_*coords` are ignored.
956+
957+
When `zresolution` is set to `nothing`, do not perform any interpolation in the vertical
958+
direction. When `zresolution`, perform linear interpolation with uniformly spaced levels.
932959
933960
Coordinates have to be `ClimaCore.Geometry.Points`.
934961
@@ -965,7 +992,7 @@ julia> interpolate(field, target_hcoords, target_zcoords)
965992
```
966993
967994
If you need the array of coordinates, you can call `default_target_hcoords` (or
968-
`default_target_vcoords`) passing `axes(field)`. This will return an array of
995+
`default_target_zcoords`) passing `axes(field)`. This will return an array of
969996
`Geometry.Point`s. The functions `Geometry.Components` and `Geometry.Component`
970997
can be used to extract the components as numeric values. For example,
971998
```julia
@@ -990,115 +1017,45 @@ This can be used directly for plotting.
9901017
"""
9911018
function interpolate(
9921019
field::Fields.Field;
993-
zresolution = 50,
1020+
zresolution = nothing,
9941021
hresolution = 180,
9951022
target_hcoords = default_target_hcoords(axes(field); hresolution),
9961023
target_zcoords = default_target_zcoords(axes(field); zresolution),
9971024
)
998-
return interpolate(field, axes(field); hresolution, zresolution)
1025+
return interpolate(field, axes(field); target_hcoords, target_zcoords)
9991026
end
10001027

1028+
# interpolate, positional
10011029
function interpolate(field::Fields.Field, target_hcoords, target_zcoords)
1002-
remapper = Remapper(axes(field), target_hcoords, target_zcoords)
1003-
return interpolate(remapper, field)
1004-
end
1005-
1006-
"""
1007-
default_target_hcoords(space::Spaces.AbstractSpace; hresolution)
1008-
1009-
Return an Array with the Geometry.Points to interpolate uniformly the horizontal
1010-
component of the given `space`.
1011-
"""
1012-
function default_target_hcoords(space::Spaces.AbstractSpace; hresolution = 180)
1013-
return default_target_hcoords(Spaces.horizontal_space(space); hresolution)
1030+
return interpolate(field, axes(field); target_hcoords, target_zcoords)
10141031
end
10151032

1016-
"""
1017-
default_target_hcoords_as_vectors(space::Spaces.AbstractSpace; hresolution)
1018-
1019-
Return an Vectors with the coordinate to interpolate uniformly the horizontal
1020-
component of the given `space`.
1021-
"""
1022-
function default_target_hcoords_as_vectors(
1033+
function interpolate(
1034+
field::Fields.Field,
10231035
space::Spaces.AbstractSpace;
1024-
hresolution = 180,
1036+
target_hcoords,
1037+
target_zcoords,
10251038
)
1026-
return default_target_hcoords_as_vectors(
1027-
Spaces.horizontal_space(space);
1028-
hresolution,
1029-
)
1030-
end
1031-
1032-
function default_target_hcoords(
1033-
space::Spaces.SpectralElementSpace2D;
1034-
hresolution = 180,
1035-
)
1036-
topology = Spaces.topology(space)
1037-
domain = Meshes.domain(topology.mesh)
1038-
xrange, yrange = default_target_hcoords_as_vectors(space; hresolution)
1039-
PointType =
1040-
domain isa Domains.SphereDomain ? Geometry.LatLongPoint :
1041-
Topologies.coordinate_type(topology)
1042-
return [PointType(x, y) for x in xrange, y in yrange]
1043-
end
1044-
1045-
function default_target_hcoords_as_vectors(
1046-
space::Spaces.SpectralElementSpace2D;
1047-
hresolution = 180,
1048-
)
1049-
FT = Spaces.undertype(space)
1050-
topology = Spaces.topology(space)
1051-
domain = Meshes.domain(topology.mesh)
1052-
if domain isa Domains.SphereDomain
1053-
return FT.(range(-180.0, 180.0, hresolution)),
1054-
FT.(range(-90.0, 90.0, hresolution))
1055-
else
1056-
x1min = Geometry.component(domain.interval1.coord_min, 1)
1057-
x2min = Geometry.component(domain.interval2.coord_min, 1)
1058-
x1max = Geometry.component(domain.interval1.coord_max, 1)
1059-
x2max = Geometry.component(domain.interval2.coord_max, 1)
1060-
return FT.(range(x1min, x1max, hresolution)),
1061-
FT.(range(x2min, x2max, hresolution))
1062-
end
1039+
remapper = Remapper(space; target_hcoords, target_zcoords)
1040+
return interpolate(remapper, field)
10631041
end
10641042

1065-
function default_target_hcoords(
1066-
space::Spaces.SpectralElementSpace1D;
1067-
hresolution = 180,
1043+
function interpolate(
1044+
field::Fields.Field,
1045+
space::Spaces.FiniteDifferenceSpace;
1046+
target_zcoords,
1047+
target_hcoords,
10681048
)
1069-
topology = Spaces.topology(space)
1070-
PointType = Topologies.coordinate_type(topology)
1071-
return PointType.(default_target_hcoords_as_vectors(space; hresolution))
1049+
remapper = Remapper(space; target_zcoords)
1050+
return interpolate(remapper, field)
10721051
end
10731052

1074-
function default_target_hcoords_as_vectors(
1075-
space::Spaces.SpectralElementSpace1D;
1076-
hresolution = 180,
1053+
function interpolate(
1054+
field::Fields.Field,
1055+
space::Spaces.AbstractSpectralElementSpace;
1056+
target_hcoords,
1057+
target_zcoords,
10771058
)
1078-
FT = Spaces.undertype(space)
1079-
topology = Spaces.topology(space)
1080-
domain = Meshes.domain(topology.mesh)
1081-
xmin = Geometry.component(domain.coord_min, 1)
1082-
xmax = Geometry.component(domain.coord_max, 1)
1083-
return FT.(range(xmin, xmax, hresolution))
1084-
end
1085-
1086-
1087-
"""
1088-
default_target_zcoords(space::Spaces.AbstractSpace; zresolution)
1089-
1090-
Return an Array with the Geometry.Points to interpolate uniformly the vertical component of
1091-
the given `space`.
1092-
"""
1093-
function default_target_zcoords(space; zresolution = 50)
1094-
return Geometry.ZPoint.(
1095-
default_target_zcoords_as_vectors(space; zresolution)
1096-
)
1097-
1098-
end
1099-
1100-
function default_target_zcoords_as_vectors(space; zresolution = 50)
1101-
return collect(
1102-
range(Domains.z_min(space), Domains.z_max(space), zresolution),
1103-
)
1059+
remapper = Remapper(space; target_hcoords)
1060+
return interpolate(remapper, field)
11041061
end

0 commit comments

Comments
 (0)