@@ -53,64 +53,128 @@ Arguments:
53
53
return ρa == 0 ? ρχ / ρ : weight * ρaχ / ρa + (1 - weight) * ρχ / ρ
54
54
end
55
55
56
+ # Internal method that checks if its input is @name(ρχ) for some variable χ.
57
+ @generated is_ρ_weighted_name (
58
+ :: MatrixFields.FieldName{name_chain} ,
59
+ ) where {name_chain} =
60
+ length (name_chain) == 1 && startswith (string (name_chain[1 ]), " ρ" )
61
+
62
+ # Internal method that converts @name(ρχ) to @name(χ) for some variable χ.
63
+ @generated function specific_tracer_name (
64
+ :: MatrixFields.FieldName{ρχ_name_chain} ,
65
+ ) where {ρχ_name_chain}
66
+ χ_symbol = Symbol (string (ρχ_name_chain[1 ])[(ncodeunits (" ρ" ) + 1 ): end ])
67
+ return :(@name ($ χ_symbol))
68
+ end
69
+
56
70
"""
57
- tracer_names(field )
71
+ gs_tracer_names(Y )
58
72
59
- Filters and returns the names of the variables from a given state
60
- vector component, excluding `ρ`, `ρe_tot`, and `uₕ` and SGS fields.
73
+ `Tuple` of `@name`s for the grid-scale tracers in the center field `Y.c`
74
+ (excluding `ρ`, `ρe_tot`, velocities, and SGS fields).
75
+ """
76
+ gs_tracer_names (Y) =
77
+ unrolled_filter (MatrixFields. top_level_names (Y. c)) do name
78
+ is_ρ_weighted_name (name) && ! (name in (@name (ρ), @name (ρe_tot)))
79
+ end
61
80
62
- Arguments:
81
+ """
82
+ specific_gs_tracer_names(Y)
83
+
84
+ `Tuple` of the specific tracer names `@name(χ)` that correspond to the
85
+ density-weighted tracer names `@name(ρχ)` in `gs_tracer_names(Y)`.
86
+ """
87
+ specific_gs_tracer_names (Y) =
88
+ unrolled_map (specific_tracer_name, gs_tracer_names (Y))
89
+
90
+ """
91
+ ᶜempty(Y)
92
+
93
+ Lazy center `Field` of empty `NamedTuple`s.
94
+ """
95
+ ᶜempty (Y) = lazy .(Returns ((;)).(Y. c))
63
96
64
- - `field`: A component of the state vector `Y.c`.
97
+ """
98
+ ᶜgs_tracers(Y)
65
99
66
- Returns:
100
+ Lazy center `Field` of `NamedTuple`s that contain the values of all grid-scale
101
+ tracers given by `gs_tracer_names(Y)`.
102
+ """
103
+ function ᶜgs_tracers (Y)
104
+ isempty (gs_tracer_names (Y)) && return ᶜempty (Y)
105
+ ρχ_symbols = unrolled_map (MatrixFields. extract_first, gs_tracer_names (Y))
106
+ ρχ_fields = unrolled_map (gs_tracer_names (Y)) do ρχ_name
107
+ MatrixFields. get_field (Y. c, ρχ_name)
108
+ end
109
+ return @. lazy (NamedTuple {ρχ_symbols} (tuple (ρχ_fields... )))
110
+ end
67
111
68
- - A `Tuple` of `ClimaCore.MatrixFields.FieldName`s corresponding to the tracers.
69
112
"""
70
- tracer_names (field) =
71
- unrolled_filter (MatrixFields. top_level_names (field)) do name
72
- ! (
73
- name in
74
- (@name (ρ), @name (ρe_tot), @name (uₕ), @name (sgs⁰), @name (sgsʲs))
75
- )
113
+ ᶜspecific_gs_tracers(Y)
114
+
115
+ Lazy center `Field` of `NamedTuple`s that contain the values of all specific
116
+ grid-scale tracers given by `specific_gs_tracer_names(Y)`.
117
+ """
118
+ function ᶜspecific_gs_tracers (Y)
119
+ isempty (gs_tracer_names (Y)) && return ᶜempty (Y)
120
+ χ_symbols =
121
+ unrolled_map (MatrixFields. extract_first, specific_gs_tracer_names (Y))
122
+ χ_fields = unrolled_map (gs_tracer_names (Y)) do ρχ_name
123
+ ρχ_field = MatrixFields. get_field (Y. c, ρχ_name)
124
+ @. lazy (specific (ρχ_field, Y. c. ρ))
76
125
end
126
+ return @. lazy (NamedTuple {χ_symbols} (tuple (χ_fields... )))
127
+ end
77
128
78
129
"""
79
- foreach_gs_tracer(f::F, Yₜ, Y) where {F}
130
+ foreach_gs_tracer(f, Y_or_similar_values...)
131
+
132
+ Applies a function `f` to each grid-scale tracer in the state `Y` or any similar
133
+ value like the tendency `Yₜ`. This is used to implement performant loops over
134
+ all tracers given by `gs_tracer_names(Y)`.
80
135
81
- Applies a given function `f` to each grid-scale scalar variable (except `ρ` and `ρe_tot`)
82
- in the state `Y` and its corresponding tendency `Yₜ`.
83
- This utility abstracts the process of iterating over all scalars. It uses
84
- `tracer_names` to identify the relevant variables and `unrolled_foreach` to
85
- ensure a performant loop. For each tracer, it calls the provided function `f`
86
- with the tendency field, the state field, and a boolean flag indicating if
87
- the current tracer is `ρq_tot` (to allow for special handling).
136
+ Although the first input value needs to be similar to `Y`, the remaining values
137
+ can also be center `Field`s similar to `Y.c`, and they can use specific tracers
138
+ given by `specific_gs_tracer_names(Y)` instead of density-weighted tracers.
88
139
89
140
Arguments:
90
141
91
- - `f`: A function to apply to each grid-scale scalar. It must have the signature `f
92
- (ᶜρχₜ, ᶜρχ, ρχ_name)`, where `ᶜρχₜ` is the tendency field, `ᶜρχ`
93
- is the state field, and `ρχ_name` is a `MatrixFields.@name` object.
94
- - `Yₜ`: The tendency state vector .
95
- - `Y `: The current state vector .
142
+ - `f`: The function applied to each grid-scale tracer, which must have the
143
+ signature `f(ρχ_or_χ_fields..., ρχ_name)`, where `ρχ_or_χ_fields` are
144
+ grid-scale tracer subfields (either density-weighted or specific) and
145
+ `ρχ_name` is the `MatrixFields.FieldName` of the tracer .
146
+ - `Y_or_similar_values `: The state `Y` or similar values like the tendency `Yₜ` .
96
147
97
- # Example
148
+ # Examples
98
149
99
150
```julia
100
151
foreach_gs_tracer(Yₜ, Y) do ᶜρχₜ, ᶜρχ, ρχ_name
101
- # Apply some operation, e.g., a sponge layer
102
- @. ᶜρχₜ += some_sponge_function(ᶜρχ)
152
+ ᶜρχₜ .+= tendency_of_ρχ(ᶜρχ)
103
153
if ρχ_name == @name(ρq_tot)
104
- # Perform an additional operation only for ρq_tot
154
+ ᶜρχₜ .+= additional_tendency_of_ρq_tot(ᶜρχ)
155
+ end
156
+ end
157
+ ```
158
+
159
+ ```julia
160
+ foreach_gs_tracer(Yₜ, Base.materialize(ᶜspecific_gs_tracers(Y))) do ᶜρχₜ, ᶜχ, ρχ_name
161
+ ᶜρχₜ .+= Y.c.ρ .* tendency_of_χ(ᶜχ)
162
+ if ρχ_name == @name(ρq_tot)
163
+ ᶜρχₜ .+= Y.c.ρ .* additional_tendency_of_q_tot(ᶜχ)
105
164
end
106
165
end
107
166
```
108
167
"""
109
- foreach_gs_tracer (f:: F , Yₜ, Y) where {F} =
110
- unrolled_foreach (tracer_names (Y. c)) do scalar_name
111
- ᶜρχₜ = MatrixFields. get_field (Yₜ. c, scalar_name)
112
- ᶜρχ = MatrixFields. get_field (Y. c, scalar_name)
113
- f (ᶜρχₜ, ᶜρχ, scalar_name)
168
+ foreach_gs_tracer (f:: F , Y_or_similar_values... ) where {F} =
169
+ unrolled_foreach (gs_tracer_names (Y_or_similar_values[1 ])) do ρχ_name
170
+ ρχ_or_χ_fields = unrolled_map (Y_or_similar_values) do value
171
+ field = value isa Fields. Field ? value : value. c
172
+ ρχ_or_χ_name =
173
+ MatrixFields. has_field (field, ρχ_name) ? ρχ_name :
174
+ specific_tracer_name (ρχ_name)
175
+ MatrixFields. get_field (field, ρχ_or_χ_name)
176
+ end
177
+ f (ρχ_or_χ_fields... , ρχ_name)
114
178
end
115
179
116
180
"""
@@ -206,94 +270,6 @@ function divide_by_ρa(ρaχ, ρa, ρχ, ρ, turbconv_model)
206
270
return ρa == 0 ? ρχ / ρ : weight * ρaχ / ρa + (1 - weight) * ρχ / ρ
207
271
end
208
272
209
- # Helper functions for manipulating symbols in the generated functions:
210
- has_prefix (symbol, prefix_symbol) =
211
- startswith (string (symbol), string (prefix_symbol))
212
- remove_prefix (symbol, prefix_symbol) =
213
- Symbol (string (symbol)[(ncodeunits (string (prefix_symbol)) + 1 ): end ])
214
- # Note that we need to use ncodeunits instead of length because prefix_symbol
215
- # can contain non-ASCII characters like 'ρ'.
216
-
217
- """
218
- specific_gs(gs)
219
-
220
- Converts every variable of the form `ρχ` in the grid-scale state `gs` into the
221
- specific variable `χ` by dividing it by `ρ`. All other variables in `gs` are
222
- omitted from the result.
223
- """
224
- @generated function specific_gs (gs)
225
- gs_names = Base. _nt_names (gs)
226
- relevant_gs_names =
227
- filter (name -> has_prefix (name, :ρ ) && name != :ρ , gs_names)
228
- specific_gs_names = map (name -> remove_prefix (name, :ρ ), relevant_gs_names)
229
- specific_gs_values = map (name -> :(gs.$ name / gs. ρ), relevant_gs_names)
230
- return :(NamedTuple {$specific_gs_names} (($ (specific_gs_values... ),)))
231
- end
232
-
233
- """
234
- specific_sgs(sgs, gs, turbconv_model)
235
-
236
- Converts every variable of the form `ρaχ` in the sub-grid-scale state `sgs` into
237
- the specific variable `χ` by dividing it by `ρa`. All other variables in `sgs`
238
- are omitted from the result. The division is computed as
239
- `divide_by_ρa(ρaχ, ρa, ρχ, ρ, turbconv_model)`, which is preferable to simply
240
- calling `ρaχ / ρa` because it avoids numerical issues that arise when `a` is
241
- small. The values of `ρ` and `ρχ` are taken from `gs`, but, when `ρχ` is not
242
- available in `gs` (e.g., when `χ` is a second moment variable like `tke`), its
243
- value is assumed to be equal to the value of `ρaχ` in `sgs`.
244
- """
245
- @generated function specific_sgs (sgs, gs, turbconv_model)
246
- sgs_names = Base. _nt_names (sgs)
247
- gs_names = Base. _nt_names (gs)
248
- relevant_sgs_names =
249
- filter (name -> has_prefix (name, :ρa ) && name != :ρa , sgs_names)
250
- specific_sgs_names =
251
- map (name -> remove_prefix (name, :ρa ), relevant_sgs_names)
252
- relevant_gs_names = map (name -> Symbol (:ρ , name), specific_sgs_names)
253
- specific_sgs_values = map (
254
- (sgs_name, gs_name) -> :(divide_by_ρa (
255
- sgs.$ sgs_name,
256
- sgs. ρa,
257
- $ (gs_name in gs_names ? :(gs.$ gs_name) : :(sgs.$ sgs_name)),
258
- gs. ρ,
259
- turbconv_model,
260
- )),
261
- relevant_sgs_names,
262
- relevant_gs_names,
263
- )
264
- return :(NamedTuple {$specific_sgs_names} (($ (specific_sgs_values... ),)))
265
- end
266
-
267
- """
268
- matching_subfields(tendency_field, specific_field)
269
-
270
- Given a field that contains the tendencies of variables of the form `ρχ` or
271
- `ρaχ` and another field that contains the values of specific variables `χ`,
272
- returns all tuples `(tendency_field.<ρχ or ρaχ>, specific_field.<χ>, :<χ>)`.
273
- Variables in `tendency_field` that do not have matching variables in
274
- `specific_field` are omitted, as are variables in `specific_field` that do not
275
- have matching variables in `tendency_field`. This function is needed to avoid
276
- allocations due to failures in type inference, which are triggered when the
277
- `propertynames` of these fields are manipulated during runtime in order to pick
278
- out the matching subfields (as of Julia 1.8).
279
- """
280
- @generated function matching_subfields (tendency_field, specific_field)
281
- tendency_names = Base. _nt_names (eltype (tendency_field))
282
- specific_names = Base. _nt_names (eltype (specific_field))
283
- prefix = :ρa in tendency_names ? :ρa : :ρ
284
- relevant_specific_names =
285
- filter (name -> Symbol (prefix, name) in tendency_names, specific_names)
286
- subfield_tuples = map (
287
- name -> :((
288
- tendency_field.$ (Symbol (prefix, name)),
289
- specific_field.$ name,
290
- $ (QuoteNode (name)),
291
- )),
292
- relevant_specific_names,
293
- )
294
- return :(($ (subfield_tuples... ),))
295
- end
296
-
297
273
"""
298
274
ρa⁺(gs)
299
275
@@ -401,19 +377,6 @@ u₃⁰(ρaʲs, u₃ʲs, ρ, u₃, turbconv_model) = divide_by_ρa(
401
377
turbconv_model,
402
378
)
403
379
404
- """
405
- remove_energy_var(specific_state)
406
-
407
- Creates a copy of `specific_state` with the energy variable
408
- removed, where `specific_state` is the result of calling, e.g., `specific_gs`,
409
- `specific_sgsʲs`, or `specific_sgs⁰`.
410
- """
411
- remove_energy_var (specific_state:: NamedTuple ) =
412
- Base. structdiff (specific_state, NamedTuple{(:e_tot ,)})
413
- remove_energy_var (specific_state:: Tuple ) =
414
- map (remove_energy_var, specific_state)
415
-
416
-
417
380
import ClimaCore. RecursiveApply: ⊞ , ⊠ , rzero, rpromote_type
418
381
function mapreduce_with_init (f, op, iter... )
419
382
r₀ = rzero (rpromote_type (typeof (f (map (first, iter)... ))))
0 commit comments