@@ -16,6 +16,7 @@ Constuct a 1D mesh on `domain` with `nelems` elements, using `stretching`. Possi
16
16
- [`Uniform()`](@ref)
17
17
- [`ExponentialStretching(H)`](@ref)
18
18
- [`GeneralizedExponentialStretching(dz_bottom, dz_top)`](@ref)
19
+ - [`HyperbolicTangentStretching(dz_bottom)`](@ref)
19
20
"""
20
21
struct IntervalMesh{I <: IntervalDomain , V <: AbstractVector } <: AbstractMesh1D
21
22
domain:: I
@@ -165,6 +166,8 @@ model configurations).
165
166
Then, the user can define a stretched mesh via
166
167
167
168
ClimaCore.Meshes.IntervalMesh(interval_domain, ExponentialStretching(H); nelems::Int, reverse_mode = false)
169
+
170
+ `faces` contain reference z without any warping.
168
171
"""
169
172
struct ExponentialStretching{FT} <: StretchingRule
170
173
H:: FT
@@ -213,6 +216,8 @@ For land configurations, use `reverse_mode` = `true` (default value `false`).
213
216
Then, the user can define a generalized stretched mesh via
214
217
215
218
ClimaCore.Meshes.IntervalMesh(interval_domain, GeneralizedExponentialStretching(dz_bottom, dz_top); nelems::Int, reverse_mode = false)
219
+
220
+ `faces` contain reference z without any warping.
216
221
"""
217
222
struct GeneralizedExponentialStretching{FT} <: StretchingRule
218
223
dz_bottom:: FT
@@ -241,9 +246,9 @@ function IntervalMesh(
241
246
throw (ArgumentError (" dz_top must be ≤ dz_bottom" ))
242
247
end
243
248
244
- # bottom coord height value, always min, for both atmos and land, since z-axis does not change
249
+ # bottom coord height value is always min and top coord height value is always max
250
+ # since the vertical coordinate is positive upward
245
251
z_bottom = Geometry. component (domain. coord_min, 1 )
246
- # top coord height value, always max, for both atmos and land, since z-axis does not change
247
252
z_top = Geometry. component (domain. coord_max, 1 )
248
253
# but in case of reverse_mode, we temporarily swap them together with dz_bottom and dz_top
249
254
# so that the following root solve algorithm does not need to change
@@ -256,7 +261,7 @@ function IntervalMesh(
256
261
# define the inverse σ⁻¹ exponential stretching function
257
262
exp_stretch (ζ, h) = ζ == 1 ? ζ : - h * log (1 - (1 - exp (- 1 / h)) * ζ)
258
263
259
- # nondimensional vertical coordinate (] 0.0, 1.0])
264
+ # nondimensional vertical coordinate ([ 0.0, 1.0])
260
265
ζ_n = LinRange (one (FT_solve), nelems, nelems) / nelems
261
266
262
267
# find bottom height variation
@@ -292,7 +297,7 @@ function IntervalMesh(
292
297
find_top,
293
298
RootSolvers. SecantMethod (guess₋, guess₊),
294
299
RootSolvers. CompactSolution (),
295
- RootSolvers. ResidualTolerance (FT_solve (1e-3 )),
300
+ RootSolvers. ResidualTolerance (FT_solve (tol )),
296
301
)
297
302
if h_top_sol. converged != = true
298
303
error (
@@ -305,7 +310,7 @@ function IntervalMesh(
305
310
h =
306
311
h_bottom .+
307
312
(ζ_n .- ζ_n[1 ]) * (h_top - h_bottom) / (ζ_n[end - 1 ] - ζ_n[1 ])
308
- faces = ( z_bottom + (z_top - z_bottom) ) * exp_stretch .(ζ_n, h)
313
+ faces = z_bottom . + (z_top - z_bottom) * exp_stretch .(ζ_n, h)
309
314
310
315
# add the bottom level
311
316
faces = FT_solve[z_bottom; faces... ]
@@ -318,6 +323,94 @@ function IntervalMesh(
318
323
IntervalMesh (domain, CT .(faces))
319
324
end
320
325
326
+ """
327
+ HyperbolicTangentStretching(dz_surface::FT)
328
+
329
+ Apply a hyperbolic tangent stretching to the domain when constructing elements.
330
+ `dz_surface` is the target element grid spacing at the surface. In typical atmosphere
331
+ configuration, it is the grid spacing at the bottom of the
332
+ vertical column domain (m). On the other hand, for typical land configurations,
333
+ it is the grid spacing at the top of the vertical column domain.
334
+
335
+ For an interval ``[z_0,z_1]``, this makes the elements uniformally spaced in
336
+ ``\\ zeta``, where
337
+ ```math
338
+ \\ eta = 1 - \\ frac{tanh[\\ gamma(1-\\ zeta)]}{tanh(\\ gamma)},
339
+ ```
340
+ where ``\\ eta = \\ frac{z - z_0}{z_1-z_0}``. The stretching parameter ``\\ gamma``
341
+ is chosen to achieve a given resolution `dz_surface` at the surface.
342
+
343
+ Then, the user can define a stretched mesh via
344
+
345
+ ClimaCore.Meshes.IntervalMesh(interval_domain, HyperbolicTangentStretching(dz_surface); nelems::Int, reverse_mode)
346
+
347
+ `reverse_mode` is default to false for atmosphere configurations. For land configurations,
348
+ use `reverse_mode` = `true`.
349
+
350
+ `faces` contain reference z without any warping.
351
+ """
352
+ struct HyperbolicTangentStretching{FT} <: StretchingRule
353
+ dz_surface:: FT
354
+ end
355
+
356
+ function IntervalMesh (
357
+ domain:: IntervalDomain{CT} ,
358
+ stretch:: HyperbolicTangentStretching{FT} ;
359
+ nelems:: Int ,
360
+ FT_solve = Float64,
361
+ tol:: Union{FT, Nothing} = nothing ,
362
+ reverse_mode:: Bool = false ,
363
+ ) where {CT <: Geometry.Abstract1DPoint{FT} } where {FT}
364
+ if nelems ≤ 1
365
+ throw (ArgumentError (" `nelems` must be ≥ 2" ))
366
+ end
367
+
368
+ dz_surface = FT_solve (stretch. dz_surface)
369
+ tol === nothing && (tol = dz_surface * FT_solve (1e-6 ))
370
+
371
+ # bottom coord height value is always min and top coord height value is always max
372
+ # since the vertical coordinate is positive upward
373
+ z_bottom = Geometry. component (domain. coord_min, 1 )
374
+ z_top = Geometry. component (domain. coord_max, 1 )
375
+ # but in case of reverse_mode, we temporarily swap them
376
+ # so that the following root solve algorithm does not need to change
377
+ if reverse_mode
378
+ z_bottom, z_top = Geometry. component (domain. coord_max, 1 ),
379
+ - Geometry. component (domain. coord_min, 1 )
380
+ end
381
+
382
+ # define the hyperbolic tangent stretching function
383
+ tanh_stretch (ζ, γ) = 1 - tanh (γ * (1 - ζ)) / tanh (γ)
384
+
385
+ # nondimensional vertical coordinate ([0.0, 1.0])
386
+ ζ_n = LinRange (one (FT_solve), nelems, nelems) / nelems
387
+
388
+ # find the stretching parameter given the grid spacing at the surface
389
+ find_surface (γ) = dz_surface - z_top * tanh_stretch (ζ_n[1 ], γ)
390
+ γ_sol = RootSolvers. find_zero (
391
+ find_surface,
392
+ RootSolvers. NewtonsMethodAD (FT_solve (1.0 )),
393
+ RootSolvers. CompactSolution (),
394
+ RootSolvers. ResidualTolerance (FT_solve (tol)),
395
+ )
396
+ if γ_sol. converged != = true
397
+ error (
398
+ " gamma root failed to converge for dz_surface: $dz_surface on domain ($z_bottom , $z_top )" ,
399
+ )
400
+ end
401
+
402
+ faces = z_bottom .+ (z_top - z_bottom) * tanh_stretch .(ζ_n, γ_sol. root)
403
+
404
+ # add the bottom level
405
+ faces = FT_solve[z_bottom; faces... ]
406
+ if reverse_mode
407
+ reverse! (faces)
408
+ faces = map (f -> eltype (faces)(- f), faces)
409
+ faces[end ] = faces[end ] == - z_bottom ? z_bottom : faces[1 ]
410
+ end
411
+ monotonic_check (faces)
412
+ IntervalMesh (domain, CT .(faces))
413
+ end
321
414
322
415
"""
323
416
truncate_mesh(
0 commit comments