@@ -105,7 +105,7 @@ Let us see the simplest example to accomplish this
105105import ClimaDiagnostics: DiagnosticVariable, ScheduledDiagnostic
106106import ClimaDiagnostics. Writers: DictWriter
107107
108- myvar = DiagnosticVariable (; compute! = (out, u, p, t) -> u. var1)
108+ myvar = DiagnosticVariable (; compute = (u, p, t) -> u. var1)
109109
110110myschedule = (integrator) -> maximum (integrator. u. var2) > 10.0
111111
@@ -148,7 +148,7 @@ const ALL_DIAGNOSTICS = Dict{String, DiagnosticVariable}()
148148 standard_name,
149149 units,
150150 description,
151- compute! )
151+ compute)
152152
153153
154154Add a new variable to the `ALL_DIAGNOSTICS` dictionary (this function mutates the state of
@@ -173,27 +173,25 @@ Keyword arguments
173173- `comments`: More verbose explanation of what the variable is, or comments related to how
174174 it is defined or computed.
175175
176- - `compute!`: Function that compute the diagnostic variable from the state. It has to take
177- two arguments: the `integrator`, and a pre-allocated area of memory where to
178- write the result of the computation. It the no pre-allocated area is
179- available, a new one will be allocated. To avoid extra allocations, this
180- function should perform the calculation in-place (i.e., using `.=`).
181-
176+ - `compute`: Function that computes the diagnostic variable from the state, cache, and time. The function
177+ should return a `Field` or a `Base.Broadcast.Broadcasted` expression. It should not allocate
178+ new `Field`: if you find yourself using a dot, that is a good indication you should be using
179+ `lazy`.
182180"""
183181function add_diagnostic_variable! (;
184182 short_name,
185183 long_name,
186184 standard_name = " " ,
187185 units,
188186 comments = " " ,
189- compute! ,
187+ compute,
190188)
191189 haskey (ALL_DIAGNOSTICS, short_name) && @warn (
192190 " overwriting diagnostic `$short_name ` entry containing fields\n " *
193191 " $(map (
194192 field -> " $(getfield (ALL_DIAGNOSTICS[short_name], field)) " ,
195193 # We cannot really compare functions...
196- filter (field -> field != :compute! , fieldnames (DiagnosticVariable)),
194+ filter (field -> ! ( field in ( :compute! , :compute )) , fieldnames (DiagnosticVariable)),
197195 )) "
198196 )
199197
@@ -203,7 +201,7 @@ function add_diagnostic_variable!(;
203201 standard_name,
204202 units,
205203 comments,
206- compute! ,
204+ compute,
207205 )
208206
209207"""
@@ -236,15 +234,30 @@ add_diagnostic_variable!(
236234 long_name = " Air Density" ,
237235 standard_name = " air_density" ,
238236 units = " kg m^-3" ,
239- compute! = (out, state, cache, time) -> begin
240- if isnothing (out)
241- return state. c. ρ
242- else
243- out .= state. c. ρ
244- end
245- end ,
237+ compute = (state, cache, time) -> state. c. ρ,
238+ )
239+ ```
240+
241+ When writing compute functions, make them lazy with
242+ [ LazyBroadcast.jl] ( https://github.com/CliMA/LazyBroadcast.jl ) to improve
243+ performance and avoid intermediate allocations. To do that, add ` LazyBroadcast `
244+ to your dependencies and import ` lazy ` . A slight variation of the previous
245+ example would look like
246+
247+ ``` julia
248+ # ##
249+ # Density (3d)
250+ # ##
251+ add_diagnostic_variable! (
252+ short_name = " rhoa" ,
253+ long_name = " Air Density" ,
254+ standard_name = " air_density" ,
255+ units = " kg m^-3" ,
256+ compute = (state, cache, time) -> lazy .(1000 .* state. c. ρ),
246257)
247258```
259+ Where we added the ` 1000 ` to simulate a more complex expression. If you didn't have
260+ ` lazy ` , the diagnostic would allocate an intermediate ` Field ` , severly hurting performance.
248261
249262It is a good idea to put safeguards in place to ensure that your users will not
250263be allowed to call diagnostics that do not make sense for the simulation they
@@ -254,26 +267,21 @@ can dispatch over that and return an error. A simple example might be
254267# ##
255268# Specific Humidity
256269# ##
257- compute_hus! (out, state, cache, time) =
258- compute_hus! (out, state, cache, time, cache. atmos. moisture_model)
270+ compute_hus ( state, cache, time) =
271+ compute_hus ( state, cache, time, cache. atmos. moisture_model)
259272
260- compute_hus! (out, state, cache, time) =
261- compute_hus! (out, state, cache, time, cache. model. moisture_model)
262- compute_hus! (_, _, _, _, model:: T ) where {T} =
273+ compute_hus ( state, cache, time) =
274+ compute_hus! (state, cache, time, cache. model. moisture_model)
275+ compute_hus ( _, _, _, model:: T ) where {T} =
263276 error (" Cannot compute hus with $model " )
264277
265- function compute_hus! (
266- out,
278+ function compute_hus (
267279 state,
268280 cache,
269281 time,
270282 moisture_model:: T ,
271283) where {T <: Union{EquilMoistModel, NonEquilMoistModel} }
272- if isnothing (out)
273- return state. c. ρq_tot ./ state. c. ρ
274- else
275- out .= state. c. ρq_tot ./ state. c. ρ
276- end
284+ return lazy .(state. c. ρq_tot ./ state. c. ρ)
277285end
278286
279287add_diagnostic_variable! (
@@ -282,7 +290,7 @@ add_diagnostic_variable!(
282290 standard_name = " specific_humidity" ,
283291 units = " kg kg^-1" ,
284292 comments = " Mass of all water phases per mass of air" ,
285- compute! = compute_hus! ,
293+ compute = compute_hus,
286294)
287295```
288296This relies on dispatching over ` moisture_model ` . If ` model ` is not in
0 commit comments