@@ -3,13 +3,13 @@ module Domains
3
3
import .. compat_add_mask, .. compat_set_mask!
4
4
import .. Artifacts. landseamask_file_path
5
5
6
-
7
6
using ClimaCore
8
7
using ClimaComms
9
8
using DocStringExtensions
10
9
using Interpolations
11
10
import ClimaUtilities. Regridders: InterpolationsRegridder
12
11
import ClimaUtilities. SpaceVaryingInputs: SpaceVaryingInput
12
+ import StaticArrays: SMatrix
13
13
14
14
import ClimaCore: Meshes, Spaces, Topologies, Geometry
15
15
import ClimaCore. Meshes: Uniform
@@ -73,28 +73,55 @@ struct Point{FT, NT <: NamedTuple} <: AbstractDomain{FT}
73
73
end
74
74
75
75
"""
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}
79
80
80
81
Constructor for the `Point` domain using keyword arguments.
81
82
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)
88
92
"""
89
93
function Point (;
90
94
z_sfc:: FT ,
91
- comms = ClimaComms. SingletonCommsContext (),
95
+ longlat:: Union{Tuple{FT, FT}, Nothing} = nothing ,
96
+ context = ClimaComms. SingletonCommsContext (),
92
97
) 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
95
121
return Point {FT, typeof(space)} (z_sfc, space)
96
122
end
97
123
124
+
98
125
"""
99
126
Column{FT} <: AbstractDomain{FT}
100
127
A struct holding the necessary information
@@ -129,50 +156,78 @@ end
129
156
nelements::Int,
130
157
dz_tuple::Union{Tuple{FT, FT}, Nothing} = nothing) where {FT}
131
158
132
- Outer constructor for the `Column` type .
159
+ Outer constructor for the 1D `Column` domain .
133
160
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
141
167
different values.
142
168
143
169
The `boundary_names` field values are used to label the boundary faces
144
170
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)
145
181
"""
146
182
function Column (;
147
183
zlim:: Tuple{FT, FT} ,
148
184
nelements:: Int ,
185
+ longlat:: Union{Tuple{FT, FT}, Nothing} = nothing ,
149
186
dz_tuple:: Union{Tuple{FT, FT}, Nothing} = nothing ,
150
187
) where {FT}
151
188
@assert zlim[1 ] < zlim[2 ]
189
+ device = ClimaComms. device ()
152
190
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)
160
214
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 ,
170
224
)
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)
171
229
end
172
230
173
- device = ClimaComms. device ()
174
- subsurface_space =
175
- ClimaCore. Spaces. CenterFiniteDifferenceSpace (device, mesh)
176
231
surface_space = obtain_surface_space (subsurface_space)
177
232
subsurface_face_space = ClimaCore. Spaces. face_space (subsurface_space)
178
233
space = (;
@@ -281,8 +336,8 @@ function Plane(;
281
336
@assert periodic == (false , false )
282
337
radius_earth = FT (radius_earth)
283
338
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
286
341
# Now make x refer to lat, and y refer to long,
287
342
# for compatibility with ClimaCore
288
343
xlim = (
@@ -293,6 +348,7 @@ function Plane(;
293
348
long - dxlim[1 ] / FT (2 π * radius_earth) * 360 ,
294
349
long + dxlim[2 ] / FT (2 π * radius_earth) * 360 ,
295
350
)
351
+
296
352
@assert xlim[1 ] < xlim[2 ]
297
353
@assert ylim[1 ] < ylim[2 ]
298
354
@@ -771,11 +827,11 @@ function obtain_surface_space(
771
827
end
772
828
773
829
"""
774
- obtain_surface_space(cs::ClimaCore.Spaces.CenterFiniteDifferenceSpace )
830
+ obtain_surface_space(cs::ClimaCore.Spaces.FiniteDifferenceSpace )
775
831
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`.
777
833
"""
778
- function obtain_surface_space (cs:: ClimaCore.Spaces.CenterFiniteDifferenceSpace )
834
+ function obtain_surface_space (cs:: ClimaCore.Spaces.FiniteDifferenceSpace )
779
835
fs = ClimaCore. Spaces. face_space (cs)
780
836
return ClimaCore. Spaces. level (
781
837
fs,
@@ -893,6 +949,66 @@ function get_Δz(z::ClimaCore.Fields.Field)
893
949
return Δz_top ./ 2 , Δz_bottom ./ 2 , Δz_center
894
950
end
895
951
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
+
896
1012
"""
897
1013
top_face_to_surface(face_field::ClimaCore.Fields.Field, surface_space)
898
1014
@@ -963,16 +1079,17 @@ will need to be modified upon the introduction of
963
1079
Since the depth will be a field in this case, it should be allocated and
964
1080
stored in domain.fields, which is why we store it there now even though it is not a field.
965
1081
"""
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
976
1093
977
1094
"""
978
1095
horizontal_resolution_degrees(domain::AbstractDomain)
@@ -1179,9 +1296,9 @@ end
1179
1296
"""
1180
1297
use_lowres_clm(space)
1181
1298
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).
1185
1302
1186
1303
If the simulation space is closer to the low resolution CLM data,
1187
1304
that data will be used, and vice versa. The high resolution data
0 commit comments