Skip to content

Commit 141ecab

Browse files
feat: add non-mutating set_scalar_constants
1 parent 68d60d4 commit 141ecab

4 files changed

Lines changed: 61 additions & 2 deletions

File tree

src/DynamicExpressions.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,8 @@ import .NodeModule:
7373
has_constants,
7474
count_scalar_constants,
7575
get_scalar_constants,
76-
set_scalar_constants!
76+
set_scalar_constants!,
77+
set_scalar_constants
7778
@reexport import .StringsModule: string_tree, print_tree
7879
import .StringsModule: get_op_name, get_pretty_op_name
7980
@reexport import .OperatorEnumModule: AbstractOperatorEnum

src/Expression.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import ..NodeUtilsModule:
1919
has_constants,
2020
count_scalar_constants,
2121
get_scalar_constants,
22-
set_scalar_constants!
22+
set_scalar_constants!,
23+
set_scalar_constants
2324
import ..NodePreallocationModule: copy_into!, allocate_container
2425
import ..EvaluateModule: eval_tree_array, differentiable_eval_tree_array
2526
import ..EvaluateDerivativeModule: eval_grad_tree_array
@@ -327,6 +328,9 @@ end
327328
function set_scalar_constants!(ex::Expression{T}, constants, refs) where {T}
328329
return set_scalar_constants!(get_tree(ex), constants, refs)
329330
end
331+
function set_scalar_constants(ex::Expression, constants)
332+
return Expression(set_scalar_constants(get_tree(ex), constants), get_metadata(ex))
333+
end
330334
function extract_gradient(
331335
gradient::@NamedTuple{tree::NT, metadata::Nothing}, ex::Expression{T,N}
332336
) where {T,N<:AbstractExpressionNode{T},NT<:NodeTangent{T,N}}

src/NodeUtils.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ..NodeModule:
88
Node,
99
preserve_sharing,
1010
constructorof,
11+
with_type_parameters,
1112
set_children!,
1213
copy_node,
1314
count_nodes,
@@ -142,6 +143,29 @@ function set_scalar_constants!(tree::AbstractExpressionNode{T}, constants, refs)
142143
return tree
143144
end
144145

146+
"""
147+
set_scalar_constants(tree::AbstractExpressionNode{T}, constants) where {T}
148+
149+
Return a *new* tree with scalar constants set (non-mutating).
150+
151+
This is equivalent to `copy(tree)` followed by [`set_scalar_constants!`](@ref),
152+
but will also promote the tree's number type to accommodate `eltype(constants)`.
153+
This makes it possible to forward-mode differentiate through constant-setting
154+
(e.g. with `ForwardDiff.Dual` constants).
155+
"""
156+
function set_scalar_constants(tree::AbstractExpressionNode{T}, constants) where {T}
157+
Tc = eltype(constants)
158+
Tout = promote_type(T, Tc)
159+
newtree = if Tout === T
160+
copy(tree)
161+
else
162+
convert(with_type_parameters(typeof(tree), Tout), tree)
163+
end
164+
_, refs = get_scalar_constants(newtree)
165+
set_scalar_constants!(newtree, constants, refs)
166+
return newtree
167+
end
168+
145169
## Assign index to nodes of a tree
146170
# This will mirror a Node struct, rather
147171
# than adding a new attribute to Node.

test/test_utils.jl

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using DynamicExpressions
22
using DynamicExpressions.UtilsModule: fill_similar
3+
using ForwardDiff
34
using Test
45

56
operators = OperatorEnum(2 => (+, *, -, /, ^), 1 => (exp, sin))
@@ -36,6 +37,35 @@ tree = x1 + Node(; val=0.0) - sin(x2 - Node(; val=0.5))
3637
set_scalar_constants!(tree, [1.0, 2.0], [Ref(tree.l.r), Ref(tree.r.l.r)])
3738
@test repr(tree) == "(x1 + 1.0) - sin(x2 - 2.0)"
3839

40+
# Non-mutating set constants (and ForwardDiff friendliness):
41+
let
42+
x1 = Node("x1")
43+
tree = x1 * Node(; val=0.0) + Node(; val=0.0)
44+
@test get_scalar_constants(tree)[1] == [0.0, 0.0]
45+
X = reshape([1.0, 2.0, 3.0], 1, :)
46+
operators = OperatorEnum(2 => (+, *), 1 => (sin,))
47+
48+
f(c) = begin
49+
t2 = set_scalar_constants(tree, c)
50+
return sum(eval_tree_array(t2, X, operators)[1])
51+
end
52+
53+
g = ForwardDiff.gradient(f, [2.0, 3.0])
54+
@test g [6.0, 3.0]
55+
56+
# Original tree unchanged.
57+
@test get_scalar_constants(tree)[1] == [0.0, 0.0]
58+
59+
# Expression wrapper also works (including promotion):
60+
ex = Expression(tree; operators=operators, variable_names=["x1"])
61+
f_ex(c) = begin
62+
ex2 = set_scalar_constants(ex, c)
63+
return sum(eval_tree_array(get_tree(ex2), X, operators)[1])
64+
end
65+
g_ex = ForwardDiff.gradient(f_ex, [2.0, 3.0])
66+
@test g_ex [6.0, 3.0]
67+
end
68+
3969
# Ensure that fill_similar is type stable
4070
x = randn(Float32, 3, 10)
4171
@inferred fill_similar(0.5f0, x, axes(x, 1))

0 commit comments

Comments
 (0)