Skip to content

Commit 6671527

Browse files
authored
Merge pull request #1237 from CliMA/js/point-latlon
add lat/lon constructors for Point and Column
2 parents 9292a01 + f84f84e commit 6671527

File tree

3 files changed

+397
-62
lines changed

3 files changed

+397
-62
lines changed

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ ClimaLand.jl Release Notes
33

44
main
55
-------
6-
- Create the extension LandSimulationsVisualization PR[#1199](https://github.com/CliMA/ClimaLand.jl/pull/1199)
6+
- Create the extension LandSimulationsVisualization PR[#1199](https://github.com/CliMA/ClimaLand.jl/pull/1199)
7+
- Enable constructing Point and Column domains with latitude and longitude PR[#1237](https://github.com/CliMA/ClimaLand.jl/pull/1237)
78

89
v0.17.1
910
-------

src/shared_utilities/Domains.jl

Lines changed: 175 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ module Domains
33
import ..compat_add_mask, ..compat_set_mask!
44
import ..Artifacts.landseamask_file_path
55

6-
76
using ClimaCore
87
using ClimaComms
98
using DocStringExtensions
109
using Interpolations
1110
import ClimaUtilities.Regridders: InterpolationsRegridder
1211
import ClimaUtilities.SpaceVaryingInputs: SpaceVaryingInput
12+
import StaticArrays: SMatrix
1313

1414
import ClimaCore: Meshes, Spaces, Topologies, Geometry
1515
import ClimaCore.Meshes: Uniform
@@ -73,28 +73,55 @@ struct Point{FT, NT <: NamedTuple} <: AbstractDomain{FT}
7373
end
7474

7575
"""
76-
Point(;z_sfc::FT,
77-
comms = ClimaComms.SingletonCommsContext()
78-
) where {FT}
76+
Point(; z_sfc::FT,
77+
longlat::Union{Tuple{FT, FT}, Nothing} = nothing,
78+
comms = ClimaComms.SingletonCommsContext()
79+
) where {FT}
7980
8081
Constructor for the `Point` domain using keyword arguments.
8182
82-
All other ClimaLand domains rely on default comms set internally
83-
by ClimaCore. However, the Point space is unique in this context,
84-
and does not have the same default defined in ClimaCore.
85-
Because of this, we set the default here
86-
in ClimaLand. In long term, we will repeat the same for all ClimaLand domains
87-
and not rely on any internal defaults set in ClimaCore.
83+
If `longlat` is provided, the domain's space contains those coordinates.
84+
This should be used for simulations at a point that require reading in spatial data
85+
from a file defined on a lat/long grid, such as CLM data.
86+
Note that constructing a Point with lat/long requires first constructing a 3D HybridBox
87+
domain centered at `longlat`, then extracting a point from the intermediate 3D space.
88+
89+
The latitude and longitude of the returned domain can be accessed as follows:
90+
- lat = ClimaLand.Domains.get_lat(domain.space.surface)
91+
- long = ClimaLand.Domains.get_long(domain.space.surface)
8892
"""
8993
function Point(;
9094
z_sfc::FT,
91-
comms = ClimaComms.SingletonCommsContext(),
95+
longlat::Union{Tuple{FT, FT}, Nothing} = nothing,
96+
context = ClimaComms.SingletonCommsContext(),
9297
) where {FT}
93-
coord = ClimaCore.Geometry.ZPoint(z_sfc)
94-
space = (; surface = ClimaCore.Spaces.PointSpace(comms, coord))
98+
if isnothing(longlat)
99+
coord = ClimaCore.Geometry.ZPoint(z_sfc)
100+
space = (; surface = ClimaCore.Spaces.PointSpace(context, coord))
101+
else
102+
long, lat = longlat
103+
zlim = FT.((z_sfc - 1, z_sfc))
104+
nelements = 2
105+
box_domain = HybridBox(;
106+
xlim = FT.((long, long)),
107+
ylim = FT.((lat, lat)),
108+
zlim = zlim,
109+
longlat = longlat,
110+
nelements = (1, 1, nelements),
111+
)
112+
# Extract a point at the surface from the 3D space
113+
column_space =
114+
ClimaCore.Spaces.column(box_domain.space.subsurface_face, 1, 1, 1)
115+
point_space = ClimaCore.Spaces.level(
116+
column_space,
117+
nelements + ClimaCore.Utilities.half,
118+
)
119+
space = (; surface = point_space)
120+
end
95121
return Point{FT, typeof(space)}(z_sfc, space)
96122
end
97123

124+
98125
"""
99126
Column{FT} <: AbstractDomain{FT}
100127
A struct holding the necessary information
@@ -129,50 +156,78 @@ end
129156
nelements::Int,
130157
dz_tuple::Union{Tuple{FT, FT}, Nothing} = nothing) where {FT}
131158
132-
Outer constructor for the `Column` type.
159+
Outer constructor for the 1D `Column` domain.
133160
134-
Using `ClimaCore` tools, the coordinate
135-
mesh can be stretched such that the top of the domain has finer resolution
136-
than the bottom of the domain. In order to activate this, a tuple with the
137-
target dz_bottom and dz_top should be passed via keyword argument. The default is
138-
uniform spacing. Please note that in order to use this feature, ClimaCore requires that
139-
the elements of zlim be <=0. Additionally, the dz_tuple you supply may not be compatible
140-
with the domain boundaries in some cases, in which case you may need to choose
161+
Using `ClimaCore` tools, the coordinate mesh can be stretched such that the top
162+
of the domain has finer resolution than the bottom of the domain. To activate this,
163+
a tuple with the target dz_bottom and dz_top should be passed via keyword argument.
164+
The default is uniform spacing. Please note that in order to use this feature, ClimaCore
165+
requires that the elements of zlim be <=0. Additionally, the dz_tuple you supply may not
166+
be compatible with the domain boundaries in some cases, in which case you may need to choose
141167
different values.
142168
143169
The `boundary_names` field values are used to label the boundary faces
144170
at the top and bottom of the domain.
171+
172+
If `longlat` is provided, the column is created at those coordinates.
173+
This should be used for simulations on a column that require reading in spatial data
174+
from a file defined on a lat/long grid, such as CLM data.
175+
Note that constructing a Column with lat/long requires first constructing a 3D HybridBox
176+
domain centered at `longlat`, then extracting a column from the intermediate 3D space.
177+
178+
The latitude and longitude of the returned domain can be accessed as follows:
179+
- lat = get_lat(domain.space.surface)
180+
- long = get_long(domain.space.surface)
145181
"""
146182
function Column(;
147183
zlim::Tuple{FT, FT},
148184
nelements::Int,
185+
longlat::Union{Tuple{FT, FT}, Nothing} = nothing,
149186
dz_tuple::Union{Tuple{FT, FT}, Nothing} = nothing,
150187
) where {FT}
151188
@assert zlim[1] < zlim[2]
189+
device = ClimaComms.device()
152190
boundary_names = (:bottom, :top)
153-
column = ClimaCore.Domains.IntervalDomain(
154-
ClimaCore.Geometry.ZPoint{FT}(zlim[1]),
155-
ClimaCore.Geometry.ZPoint{FT}(zlim[2]);
156-
boundary_names = boundary_names,
157-
)
158-
if dz_tuple isa Nothing
159-
mesh = ClimaCore.Meshes.IntervalMesh(column; nelems = nelements)
191+
192+
if isnothing(longlat)
193+
column = ClimaCore.Domains.IntervalDomain(
194+
ClimaCore.Geometry.ZPoint{FT}(zlim[1]),
195+
ClimaCore.Geometry.ZPoint{FT}(zlim[2]);
196+
boundary_names = boundary_names,
197+
)
198+
if isnothing(dz_tuple)
199+
mesh = ClimaCore.Meshes.IntervalMesh(column; nelems = nelements)
200+
else
201+
@assert zlim[2] <= 0
202+
mesh = ClimaCore.Meshes.IntervalMesh(
203+
column,
204+
ClimaCore.Meshes.GeneralizedExponentialStretching{FT}(
205+
dz_tuple[1],
206+
dz_tuple[2],
207+
);
208+
nelems = nelements,
209+
reverse_mode = true,
210+
)
211+
end
212+
subsurface_space =
213+
ClimaCore.Spaces.CenterFiniteDifferenceSpace(device, mesh)
160214
else
161-
@assert zlim[2] <= 0
162-
mesh = ClimaCore.Meshes.IntervalMesh(
163-
column,
164-
ClimaCore.Meshes.GeneralizedExponentialStretching{FT}(
165-
dz_tuple[1],
166-
dz_tuple[2],
167-
);
168-
nelems = nelements,
169-
reverse_mode = true,
215+
# Set limits at the longlat coordinates
216+
long, lat = longlat
217+
box_domain = HybridBox(;
218+
xlim = FT.((long, long)),
219+
ylim = FT.((lat, lat)),
220+
zlim = zlim,
221+
longlat = longlat,
222+
nelements = (1, 1, nelements),
223+
dz_tuple = dz_tuple,
170224
)
225+
# Extract a column from the 3D space
226+
colidx = ClimaCore.Grids.ColumnIndex((1, 1), 1)
227+
subsurface_space =
228+
ClimaCore.Spaces.column(box_domain.space.subsurface, colidx)
171229
end
172230

173-
device = ClimaComms.device()
174-
subsurface_space =
175-
ClimaCore.Spaces.CenterFiniteDifferenceSpace(device, mesh)
176231
surface_space = obtain_surface_space(subsurface_space)
177232
subsurface_face_space = ClimaCore.Spaces.face_space(subsurface_space)
178233
space = (;
@@ -281,8 +336,8 @@ function Plane(;
281336
@assert periodic == (false, false)
282337
radius_earth = FT(radius_earth)
283338
long, lat = longlat
284-
dxlim = xlim # long bounds
285-
dylim = ylim # lat bounds
339+
dxlim = abs.(xlim) # long bounds
340+
dylim = abs.(ylim) # lat bounds
286341
# Now make x refer to lat, and y refer to long,
287342
# for compatibility with ClimaCore
288343
xlim = (
@@ -293,6 +348,7 @@ function Plane(;
293348
long - dxlim[1] / FT(2π * radius_earth) * 360,
294349
long + dxlim[2] / FT(2π * radius_earth) * 360,
295350
)
351+
296352
@assert xlim[1] < xlim[2]
297353
@assert ylim[1] < ylim[2]
298354

@@ -771,11 +827,11 @@ function obtain_surface_space(
771827
end
772828

773829
"""
774-
obtain_surface_space(cs::ClimaCore.Spaces.CenterFiniteDifferenceSpace)
830+
obtain_surface_space(cs::ClimaCore.Spaces.FiniteDifferenceSpace)
775831
776-
Returns the top level of the face space corresponding to the CenterFiniteDifferenceSpace `cs`.
832+
Returns the top level of the face space corresponding to the input space `cs`.
777833
"""
778-
function obtain_surface_space(cs::ClimaCore.Spaces.CenterFiniteDifferenceSpace)
834+
function obtain_surface_space(cs::ClimaCore.Spaces.FiniteDifferenceSpace)
779835
fs = ClimaCore.Spaces.face_space(cs)
780836
return ClimaCore.Spaces.level(
781837
fs,
@@ -893,6 +949,66 @@ function get_Δz(z::ClimaCore.Fields.Field)
893949
return Δz_top ./ 2, Δz_bottom ./ 2, Δz_center
894950
end
895951

952+
"""
953+
get_lat(surface_space::ClimaCore.Spaces.PointSpace)
954+
get_lat(subsurface_space::ClimaCore.Spaces.FiniteDifferenceSpace)
955+
956+
Returns the latitude of provided surface space as a Field defined on the space.
957+
If the space does not have latitude information, an error is thrown.
958+
959+
This function is not implemented for subsurface spaces, since we want latitude
960+
as a 2D Field, not 3D.
961+
"""
962+
function get_lat(
963+
surface_space::Union{
964+
ClimaCore.Spaces.PointSpace,
965+
ClimaCore.Spaces.SpectralElementSpace2D,
966+
},
967+
)
968+
if hasproperty(ClimaCore.Fields.coordinate_field(surface_space), :lat)
969+
return ClimaCore.Fields.coordinate_field(surface_space).lat
970+
else
971+
error("Surface space does not have latitude information")
972+
end
973+
end
974+
get_lat(
975+
_::Union{
976+
ClimaCore.Spaces.FiniteDifferenceSpace,
977+
ClimaCore.Spaces.CenterExtrudedFiniteDifferenceSpace,
978+
ClimaCore.Spaces.ExtrudedFiniteDifferenceSpace,
979+
},
980+
) = error("`get_lat` is not implemented for subsurface spaces")
981+
982+
"""
983+
get_long(surface_space::ClimaCore.Spaces.PointSpace)
984+
get_long(subsurface_space::ClimaCore.Spaces.FiniteDifferenceSpace)
985+
986+
Returns the longitude of the provided surface space as a Field defined on the space.
987+
If the space does not have longitude information, an error is thrown.
988+
989+
This function is not implemented for subsurface spaces, since we want longitude
990+
as a 2D Field, not 3D.
991+
"""
992+
function get_long(
993+
surface_space::Union{
994+
ClimaCore.Spaces.PointSpace,
995+
ClimaCore.Spaces.SpectralElementSpace2D,
996+
},
997+
)
998+
if hasproperty(ClimaCore.Fields.coordinate_field(surface_space), :long)
999+
return ClimaCore.Fields.coordinate_field(surface_space).long
1000+
else
1001+
error("Surface space does not have longitude information")
1002+
end
1003+
end
1004+
get_long(
1005+
_::Union{
1006+
ClimaCore.Spaces.FiniteDifferenceSpace,
1007+
ClimaCore.Spaces.CenterExtrudedFiniteDifferenceSpace,
1008+
ClimaCore.Spaces.ExtrudedFiniteDifferenceSpace,
1009+
},
1010+
) = error("`get_long` is not implemented for subsurface spaces")
1011+
8961012
"""
8971013
top_face_to_surface(face_field::ClimaCore.Fields.Field, surface_space)
8981014
@@ -963,16 +1079,17 @@ will need to be modified upon the introduction of
9631079
Since the depth will be a field in this case, it should be allocated and
9641080
stored in domain.fields, which is why we store it there now even though it is not a field.
9651081
"""
966-
depth(space::ClimaCore.Spaces.CenterExtrudedFiniteDifferenceSpace) =
967-
(
968-
space.grid.vertical_grid.topology.mesh.domain.coord_max -
969-
space.grid.vertical_grid.topology.mesh.domain.coord_min
970-
).z
971-
depth(space::ClimaCore.Spaces.CenterFiniteDifferenceSpace) =
972-
(
973-
space.grid.topology.mesh.domain.coord_max -
974-
space.grid.topology.mesh.domain.coord_min
975-
).z
1082+
function depth(
1083+
space::Union{
1084+
ClimaCore.Spaces.CenterExtrudedFiniteDifferenceSpace,
1085+
ClimaCore.Spaces.CenterFiniteDifferenceSpace,
1086+
},
1087+
)
1088+
zmin, zmax = extrema(
1089+
ClimaCore.Fields.coordinate_field(ClimaCore.Spaces.face_space(space)).z,
1090+
)
1091+
return zmax - zmin
1092+
end
9761093

9771094
"""
9781095
horizontal_resolution_degrees(domain::AbstractDomain)
@@ -1179,9 +1296,9 @@ end
11791296
"""
11801297
use_lowres_clm(space)
11811298
1182-
Returns true if the simulation space is closer in resolution to the low
1183-
resolution CLM data (at 0.9x1.25 degree lat/long) compared with the
1184-
high resolution CLM data ( 0.125x0.125 degree lat/long).
1299+
Returns true if the simulation space is closer in resolution to the low
1300+
resolution CLM data (at 0.9x1.25 degree lat/long) compared with the
1301+
high resolution CLM data ( 0.125x0.125 degree lat/long).
11851302
11861303
If the simulation space is closer to the low resolution CLM data,
11871304
that data will be used, and vice versa. The high resolution data

0 commit comments

Comments
 (0)