@@ -15,15 +15,6 @@ const EXPERIMENTAL_WARNING = """
1515 This API is experimental and may change in a future non-breaking release.
1616"""
1717
18- """
19- Treat a derivative of an array-valued expression as a leaf, so that
20- [`expand_array_derivatives!`](@ref) collects `D(u[2:4])` itself rather than descending into
21- it. Scalar variables are not atomic here, so nothing else is collected.
22- """
23- function array_derivative_is_atomic (ex:: SymbolicT )
24- return isdifferential (ex) && SU. is_array_shape (SU. shape (ex))
25- end
26-
2718"""
2819 $(TYPEDSIGNATURES)
2920
@@ -43,25 +34,7 @@ function expand_array_derivatives!(rhss::Vector{SymbolicT}, ir::IRStructure{Vart
4334 end
4435 isempty (terms) && return rhss
4536
46- subs = Dict {SymbolicT, SymbolicT} ()
47- for term in terms
48- op = operation (term)
49- arg = only (arguments (term))
50- sh = SU. shape (arg):: SU.ShapeVecT
51- # Preserve the shape: a derivative of a 2D slice must expand to a 2D array of
52- # scalar derivatives, or it will not broadcast against the surrounding slices.
53- arrargs = Symbolics. SArgsT ()
54- sizehint! (arrargs, prod (length, sh; init = 1 ) + 1 )
55- push! (arrargs, SU. Const {VartypeT} (size (arg)))
56- for idx in SU. stable_eachindex (arg)
57- push! (arrargs, op (arg[idx]))
58- end
59- subs[term] = Symbolics. STerm (
60- SU. array_literal, arrargs; type = symtype (arg), shape = sh
61- )
62- end
63-
64- subber = SU. IRSubstituter {false} (ir, subs)
37+ subber = SU. IRSubstituter {false} (ir, array_derivative_expansion_map (terms))
6538 map! (subber, rhss, rhss)
6639 return rhss
6740end
@@ -100,6 +73,88 @@ function array_residual_maker(rhss::Vector{SymbolicT})
10073 return SU. ArrayMaker {VartypeT} (regions, values)
10174end
10275
76+ """
77+ residual_eltype(x)
78+
79+ Return the numeric element type contributed by `x`, or `Union{}`.
80+ """
81+ function residual_eltype (x)
82+ x = SciMLBase. unwrap_parameters (x)
83+ if x isa Number
84+ return typeof (x)
85+ elseif x isa AbstractArray
86+ T = eltype (x)
87+ return T === Any ? Union{} : T
88+ elseif SciMLStructures. isscimlstructure (x)
89+ tun = first (SciMLStructures. canonicalize (SciMLStructures. Tunable (), x))
90+ return residual_eltype (tun)
91+ else
92+ return Union{}
93+ end
94+ end
95+
96+ """
97+ similar_for_residual(prototype, extras...)
98+
99+ Return an allocator using `prototype` and the promoted runtime element type.
100+ """
101+ function similar_for_residual (prototype, extras... )
102+ T = residual_eltype (prototype)
103+ T === Union{} && (T = Float64)
104+ for x in extras
105+ T = promote_type (T, residual_eltype (x))
106+ end
107+ return sz -> similar (prototype, T, sz)
108+ end
109+
110+ function residual_allocator_arg (arg)
111+ name = arg isa DestructuredArgs ? arg. name : arg
112+ name isa SymbolicT && return name
113+ name isa Symbol || throw (
114+ ArgumentError (
115+ " Cannot form a residual allocator argument from $(typeof (arg)) ."
116+ )
117+ )
118+ return SSym (name; type = Any, shape = SU. ShapeVecT ())
119+ end
120+
121+ function residual_allocator_term (args)
122+ return STerm (
123+ similar_for_residual, SArgsT ((map (residual_allocator_arg, args)... ,));
124+ type = SU. FnType{Tuple, Any, Any},
125+ shape = SU. ShapeVecT (),
126+ )
127+ end
128+
129+ function inject_similar_for_residual (body, alloc_term)
130+ if body isa Let
131+ return Let (
132+ body. pairs, inject_similar_for_residual (body. body, alloc_term),
133+ body. let_block
134+ )
135+ elseif body isa SymbolicT && Code. supports_with_allocator (body)
136+ # The public helper would wrap the symbolic allocator in `Const`.
137+ return STerm (
138+ Code. with_allocator,
139+ SArgsT ((alloc_term, body));
140+ type = SU. symtype (body),
141+ shape = SU. shape (body),
142+ )
143+ else
144+ return body
145+ end
146+ end
147+
148+ function wrap_oop_similar_for_residual (fn)
149+ fn isa Func || return fn
150+ isempty (fn. args) && return fn
151+ alloc_term = residual_allocator_term (fn. args)
152+ return Func (
153+ fn. args, fn. kwargs, inject_similar_for_residual (fn. body, alloc_term),
154+ fn. pre
155+ )
156+ end
157+
103158"""
104159 $(TYPEDSIGNATURES)
105160
@@ -121,6 +176,10 @@ $GENERATE_X_KWARGS
121176 by default, which leaves the standard codegen path byte-identical.
122177
123178All other keyword arguments are forwarded to [`build_function_wrapper`](@ref).
179+
180+ Time-independent systems use `_iszero(lhs) ? rhs : rhs - lhs` and
181+ `array_residual_maker`. No `du`. Out-of-place ArrayMaker residuals promote
182+ their element type from the function arguments.
124183"""
125184function generate_rhs (
126185 sys:: System , opts:: GeneratedFunctionOptions ;
@@ -175,6 +234,9 @@ function generate_rhs(
175234 expand_array_derivatives! (rhss, get_irstructure (sys))
176235 assemble_residuals = true
177236 end
237+ elseif ! is_time_dependent (sys)
238+ rhss = SymbolicT[_iszero (eq. lhs) ? eq. rhs : eq. rhs - eq. lhs for eq in eqs]
239+ assemble_residuals = true
178240 else
179241 if ! override_discrete && ! is_discrete_system (sys)
180242 check_operator_variables (eqs, Differential)
@@ -223,10 +285,20 @@ function generate_rhs(
223285 (; p_end = (t === nothing ? length (args) : length (args) - 1 ) - length (extra_args))
224286
225287 u_arg = scalar ? - 1 : (implicit_dae ? 2 : 1 )
288+ codegen_opts = opts. codegen
289+ if ! implicit_dae && assemble_residuals && rhss isa SymbolicT &&
290+ Code. supports_with_allocator (rhss)
291+ # `ArrayMaker` otherwise allocates a `Float64` buffer out of place.
292+ oop_wrap, iip_wrap = codegen_opts. wrap_code
293+ codegen_opts = setproperties (
294+ codegen_opts,
295+ (; wrap_code = (wrap_oop_similar_for_residual ∘ oop_wrap, iip_wrap))
296+ )
297+ end
226298 res = build_function_wrapper (
227299 sys, rhss, collect (Any, args), BuildFunctionWrapperOptions (;
228300 p_start, extra_assignments, u_arg, n_param_buffers, p_end_kw... ,
229- codegen_function_options = opts . codegen
301+ codegen_function_options = codegen_opts
230302 )
231303 )
232304 nargs = length (args) - length (p) + 1
0 commit comments