Skip to content

Commit 70d344b

Browse files
committed
docs: create Expression example
1 parent 51ded2a commit 70d344b

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

docs/make.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,11 @@ makedocs(;
6161
),
6262
pages=[
6363
"Home" => "index.md",
64-
"Examples" => ["examples/base_operations.md", "examples/structured_expression.md"],
64+
"Examples" => [
65+
"examples/base_operations.md",
66+
"examples/expression.md",
67+
"examples/structured_expression.md",
68+
],
6569
"Eval" => "eval.md",
6670
"Utils" => "utils.md",
6771
"API" => "api.md",

test/test_expressions.jl

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,118 @@ end
249249
tree = get_tree(ex)
250250
@test_throws ArgumentError get_operators(tree, nothing)
251251
end
252+
253+
@testitem "Expression Literate examples" begin
254+
#literate_begin file="src/examples/expression.md"
255+
#=
256+
# `Expression` example
257+
258+
`Expression` is a fundamental type in DynamicExpressions that represents
259+
a mathematical expression as a tree structure. It combines an
260+
`AbstractExpressionNode` (typically a `Node`) with metadata like operators
261+
and variable names.
262+
263+
Let's explore how to create and work with `Expression` objects:
264+
=#
265+
using DynamicExpressions, Random
266+
267+
# First, let's define our operators and variable names:
268+
269+
operators = OperatorEnum(;
270+
binary_operators=[+, -, *, /], unary_operators=[sin, cos, exp]
271+
)
272+
variable_names = ["x", "y"]
273+
274+
# Now, let's create a simple Expression manually:
275+
x = Node{Float64}(; feature=1)
276+
x_expr = Expression(x; operators, variable_names)
277+
278+
# We can build up more complex expressions using these basic building blocks:
279+
y = Node{Float64}(; feature=2)
280+
c = Node{Float64}(; val=2.0)
281+
complex_node = Node(; op=3, l=x, r=Node(; op=1, l=y, r=c))
282+
# where the `3` indicates `*` and `1` indicates `+`.
283+
complex_expr = Expression(complex_node; operators, variable_names)
284+
285+
#=
286+
This expression includes its own metadata: the operators and variable names,
287+
and so there are no scope issues as with raw `AbstractExpressionNode` types
288+
which depend on the last-used metadata for convenience functions like printing.
289+
In other words, you can print this expression, or evaluate it, directly:
290+
=#
291+
rng = Random.MersenneTwister(0)
292+
complex_expr(randn(rng, 2, 5))
293+
294+
#=
295+
While creating expressions manually is possible, it can be cumbersome for more
296+
complex expressions. DynamicExpressions provides a more convenient way to create
297+
expressions using the `parse_expression` function:
298+
=#
299+
parsed_expr = parse_expression(
300+
:(sin(2.0 * x + exp(y + 5.0))); operators=operators, variable_names=variable_names
301+
)
302+
303+
# We can convert an expression into the primitive `AbstractExpressionNode` type
304+
# with [`get_tree`](@ref):
305+
tree = get_tree(parsed_expr)
306+
@test tree isa AbstractExpressionNode #src
307+
308+
# Some `AbstractExpression` types may choose to store their expression in
309+
# a different way than simply saving it as one of the fields. For any expression,
310+
# you can get the raw contents with [`get_contents`](@ref):
311+
get_contents(parsed_expr)
312+
313+
#=
314+
Similarly, you can get the metadata for an expression with [`get_metadata`](@ref):
315+
=#
316+
get_metadata(parsed_expr)
317+
318+
#=
319+
These can be used with [`with_contents`](@ref) and [`with_metadata`](@ref) to
320+
create new expressions based on the original:
321+
=#
322+
with_contents(parsed_expr, Node(; op=2, l=get_contents(parsed_expr)))
323+
324+
#=
325+
One of the key features of `Expression` is that it can be evaluated
326+
on data. Let's create some random input data and evaluate our expression:
327+
=#
328+
rng = Random.MersenneTwister(0)
329+
X = rand(rng, 2, 5) # 2 variables, 5 data points
330+
331+
result = parsed_expr(X)
332+
@test size(result) == (1, 5) #src
333+
334+
# We can verify this result against a direct calculation:
335+
expected = @. sin(2.0 * X[1, :] + exp(X[2, :] + 5.0))
336+
@test result expected #src
337+
338+
#=
339+
`Expression` objects also support various tree operations.
340+
For example, we can count the number of nodes:
341+
=#
342+
node_count = count_nodes(parsed_expr)
343+
println("Number of nodes: $node_count")
344+
345+
# Or find the depth of the expression tree:
346+
depth = count_depth(parsed_expr)
347+
println("Tree depth: $depth")
348+
349+
#=
350+
We can also perform more complex operations, like simplification:
351+
=#
352+
complex_expr = parse_expression(
353+
:((2.0 + x) + 3.0); operators=operators, variable_names=["x"]
354+
)
355+
simplified_expr = combine_operators(complex_expr)
356+
println("Original: ", complex_expr)
357+
println("Simplified: ", simplified_expr)
358+
359+
#=
360+
These examples demonstrate some of the key features of `Expression` objects.
361+
They provide a powerful way to represent, evaluate, and manipulate
362+
mathematical expressions in DynamicExpressions.
363+
=#
364+
365+
#literate_end
366+
end

0 commit comments

Comments
 (0)