API Reference
Symbols and Terms
Creating Symbols and Terms
SymbolicUtils.@syms
— Macro@syms <lhs_expr>[::T1] <lhs_expr>[::T2]...
For instance:
@syms foo::Real bar baz(x, y::Real)::Complex
Create one or more variables. <lhs_expr>
can be just a symbol in which case it will be the name of the variable, or a function call in which case a function-like variable which has the same name as the function being called. The Sym type, or in the case of a function-like Sym, the output type of calling the function can be set using the ::T
syntax.
Examples:
@syms foo bar::Real baz::Int
will create
variable foo
of symtype Number
(the default), bar
of symtype Real
and baz
of symtype Int
@syms f(x) g(y::Real, x)::Int h(a::Int, f(b))
creates 1-argf
2-argg
and 2 arg h
. The second argument to h
must be a one argument function-like variable. So, h(1, g)
will fail and h(1, f)
will work.
Formal syntax
Following is a semi-formal CFG of the syntax accepted by this macro:
# any variable accepted by this macro must be a `var`.
# `var` can represent a quantity (`value`) or a function `(fn)`.
var = value | fn
# A `value` is represented as a name followed by a suffix
value = name suffix
# A `name` can be a valid Julia identifier
name = ident |
# Or it can be an interpolated variable, in which case `ident` is assumed to refer to
# a variable in the current scope of type `Symbol` containing the name of this variable.
# Note that in this case the created symbolic variable will be bound to a randomized
# Julia identifier.
"$" ident
# The `suffix` can be empty (no suffix) which defaults the type to `Number`
suffix = "" |
# or it can be a type annotation (setting the type of the prefix). The shape of the result
# is inferred from the type as best it can be. In particular, `Array{T, N}` is inferred
# to have shape `Unknown(N)` and `Array{T}` is inferred to have shape `Unknown(-1)`.
"::" type |
# or it can be a shape annotation, which sets the shape to the one specified by `ranges`.
# The type defaults to `Array{Number, length(ranges)}`
"[" ranges "]" |
# lastly, it can be a combined shape and type annotation. Here, the type annotation
# sets the `eltype` of the symbolic array.
"[" ranges "]::" type
# `ranges` is either a single `range` or a single range followed by one or more `ranges`.
ranges = range | range "," ranges
# A `range` is simply two bounds separated by a colon, as standard Julia ranges work.
# The range must be non-empty. Each bound can be a literal integer or an identifier
# representing an integer in the current scope.
range = (int | ident) ":" (int | ident) |
# Alternatively, a range can be a Julia expression that evaluates to a range. All identifiers
# used in `expr` are assumed to exist in the current scope.
expr |
# Alternatively, a range can be a Julia expression evaluating to an iterable of ranges,
# followed by the splat operator.
expr "..."
# A function is represented by a function-call syntax `fncall` followed by the `suffix`
# above. The type and shape from `suffix` represent the type and shape of the value
# returned by the symbolic function.
fn = fncall suffix
# a function call is a call `head` followed by a parenthesized list of arguments.
fncall = head "(" args ")"
# A function call head can be a name, representing the name of the symbolic function.
head = ident |
# Alternatively, it can be a parenthesized type-annotated name, where the type annotation
# represents the intended supertype of the function. In other words, if this symbolic
# function were to be replaced by an "actual" function, the type-annotation constrains the
# type of the "actual" function.
"(" ident "::" type ")"
# Arguments to a function is a list of one or more arguments
args = arg | arg "," args
# An argument can take the syntax of a variable (which means we can represent functions of
# functions of functions of...). The type of the variable constrains the type of the
# corresponding argument of the function. The name and shape information is discarded.
arg = var |
# Or an argument can be an unnamed type-annotation, which constrains the type without
# requiring a name.
"::" type |
# Or an argument can be the identifier `..`, which is used as a stand-in for `Vararg{Any}`
".." |
# Or an argument can be a type-annotated `..`, representing `Vararg{type}`. Note that this
# and the previous version of `arg` can only be the last element in `args` due to Julia's
# `Tuple` semantics.
"(..)::" type |
# Or an argument can be a Julia expression followed by a splat operator. This assumes the
# expression evaluates to an iterable of symbolic variables whose `symtype` should be used
# as the argument types. Note that `expr` may be evaluated multiple times in the macro
# expansion.
expr "..."
SymbolicUtils.term
— Functionterm(f, args...; type = nothing)
Create a symbolic term with operation f
and arguments args
.
Arguments
f
: The operation or function head of the termargs...
: The arguments to the operationtype
: Optional type specification for the term. If not provided, the type is inferred usingpromote_symtype
.
Examples
julia> @syms x y
(x, y)
julia> term(+, x, y)
x + y
julia> term(sin, x)
sin(x)
julia> term(^, x, 2)
x^2
Inspecting Terms
Missing docstring for SymbolicUtils.issym
. Check Documenter's build log for details.
Missing docstring for SymbolicUtils.symtype
. Check Documenter's build log for details.
TermInterface.iscall
— Functioniscall(expr)
Check if a symbolic expression expr
represents a function call. Returns true
if the expression is a composite expression with an operation and arguments, false
otherwise.
This function is fundamental for traversing and analyzing symbolic expressions. In SymbolicUtils.jl, an expression is considered a "call" if it represents a function application (including operators like +, -, *, etc.).
Examples
using SymbolicUtils
@variables x y
# Basic variables are not calls
iscall(x) # false
# Function calls are calls
expr = sin(x + y)
iscall(expr) # true
# Arithmetic expressions are calls
iscall(x + y) # true
iscall(x * y) # true
TermInterface.operation
— Functionoperation(expr)
Extract the operation (function) from a symbolic function call expression. Only valid for expressions where iscall(expr)
returns true
.
Returns the function/operator that is being applied in the expression. For basic arithmetic, this returns the operator function (+, -, *, /, ^). For function calls like sin(x)
, this returns the function sin
.
Examples
using SymbolicUtils
@variables x y
# Arithmetic operations
expr1 = x + y
operation(expr1) # returns +
expr2 = x * y
operation(expr2) # returns *
# Function calls
expr3 = sin(x)
operation(expr3) # returns sin
# Nested expressions
expr4 = sin(x + y)
operation(expr4) # returns sin
operation(arguments(expr4)[1]) # returns +
TermInterface.arguments
— Functionarguments(expr)
Extract the arguments from a symbolic function call expression. Only valid for expressions where iscall(expr)
returns true
.
Returns a collection (typically a vector) containing the arguments passed to the operation. For binary operations like +
or *
, this returns a collection of all operands. For function calls, this returns the function arguments.
Examples
using SymbolicUtils
@variables x y z
# Binary arithmetic operations
expr1 = x + y
arguments(expr1) # returns collection containing x and y
expr2 = x * y * z
arguments(expr2) # returns collection containing x, y, and z
# Function calls
expr3 = sin(x)
arguments(expr3) # returns collection containing x
# Nested expressions
expr4 = sin(x + y)
arguments(expr4) # returns collection containing (x + y)
arguments(arguments(expr4)[1]) # returns collection containing x and y
TermInterface.sorted_arguments
— Functionsorted_arguments(x::BasicSymbolic)
Get the arguments of a symbolic expression in canonical sorted order.
For commutative operations like addition and multiplication, the arguments are sorted according to a canonical ordering. This ensures that equivalent expressions have the same representation.
Arguments
x::BasicSymbolic
: The symbolic expression
Returns
A vector of the arguments in sorted order. For non-commutative operations, returns the arguments in their original order.
Examples
julia> @syms x y z
(x, y, z)
julia> expr = x + z + y
x + y + z
julia> sorted_arguments(expr)
3-element Vector{Any}:
x
y
z
SymbolicUtils.showraw
— Functionshowraw([io::IO], t)
Display the raw structure of a symbolic expression without simplification.
This function shows the internal structure of symbolic expressions without applying any simplification rules, which is useful for debugging and understanding the exact form of an expression.
Arguments
io::IO
: Optional IO stream to write to (defaults to stdout)t
: The symbolic expression to display
Examples
julia> @syms x
x
julia> expr = x + x + x
3x
julia> showraw(expr) # Shows the unsimplified structure
x + x + x
Metadata
SymbolicUtils.hasmetadata
— Functionhasmetadata(s::Symbolic, ctx)
Check if a symbolic expression has metadata for a given context.
Arguments
s::Symbolic
: The symbolic expression to checkctx
: The metadata context key (typically a DataType)
Returns
true
if the expression has metadata for the given context,false
otherwise
Examples
julia> @syms x
x
julia> hasmetadata(x, Float64)
false
SymbolicUtils.getmetadata
— Functiongetmetadata(s::Symbolic, ctx)
Retrieve metadata associated with a symbolic expression for a given context.
Arguments
s::Symbolic
: The symbolic expressionctx
: The metadata context key (typically a DataType)
Returns
The metadata value associated with the given context
Throws
ArgumentError
if the expression does not have metadata for the given context
Examples
julia> @syms x::Float64
x
julia> getmetadata(x, symtype) # Get the type metadata
Float64
getmetadata(s::Symbolic, ctx, default)
Retrieve metadata associated with a symbolic expression for a given context, returning a default value if not found.
Arguments
s::Symbolic
: The symbolic expressionctx
: The metadata context key (typically a DataType)default
: The default value to return if metadata is not found
Returns
The metadata value associated with the given context, or default
if not found
Examples
julia> @syms x
x
julia> getmetadata(x, Float64, "no type")
"no type"
SymbolicUtils.setmetadata
— Functionsetmetadata(s::Symbolic, ctx::DataType, val)
Set metadata for a symbolic expression in a given context.
Arguments
s::Symbolic
: The symbolic expressionctx::DataType
: The metadata context keyval
: The metadata value to set
Returns
A new symbolic expression with the updated metadata
Examples
julia> @syms x
x
julia> x_with_meta = setmetadata(x, Float64, "custom value")
x
julia> getmetadata(x_with_meta, Float64)
"custom value"
Type Promotion
SymbolicUtils.promote_symtype
— Functionpromote_symtype(f, Ts...)
The result of applying f
to arguments of symtype
Ts...
julia> promote_symtype(+, Real, Real)
Real
julia> promote_symtype(+, Complex, Real)
Number
julia> @syms f(x)::Complex
(f(::Number)::Complex,)
julia> promote_symtype(f, Number)
Complex
When constructing Term
s without an explicit symtype, promote_symtype
is used to figure out the symtype of the Term.
promote_symtype(f::FnType{X,Y}, arg_symtypes...)
The output symtype of applying variable f
to arguments of symtype arg_symtypes...
. if the arguments are of the wrong type then this function will error.
Rewriting System
Rule Creation
SymbolicUtils.@rule
— Macro@rule LHS => RHS
Creates a Rule
object. A rule object is callable, and takes an expression and rewrites it if it matches the LHS pattern to the RHS pattern, returns nothing
otherwise. The rule language is described below.
LHS can be any possibly nested function call expression where any of the arguments can optionally be a Slot (~x
), Default Value Slot (~!x
also called DefSlot) or a Segment (~~x
) (described below).
If an expression matches LHS entirely, then it is rewritten to the pattern in the RHS. Slot, DefSlot and Segment variables on the RHS will substitute the result of the matches found for these variables in the LHS.
Slot:
A Slot variable is written as ~x
and matches a single expression. x
is the name of the variable. If a slot appears more than once in an LHS expression then expression matched at every such location must be equal (as shown by isequal
).
Example:
Simple rule to turn any sin
into cos
:
julia> @syms a b c
(a, b, c)
julia> r = @rule sin(~x) => cos(~x)
sin(~x) => cos(~x)
julia> r(sin(1+a))
cos((1 + a))
A rule with 2 segment variables
julia> r = @rule sin(~x + ~y) => sin(~x)*cos(~y) + cos(~x)*sin(~y)
sin(~x + ~y) => sin(~x) * cos(~y) + cos(~x) * sin(~y)
julia> r(sin(a + b))
cos(a)*sin(b) + sin(a)*cos(b)
A rule that matches two of the same expressions:
julia> r = @rule sin(~x)^2 + cos(~x)^2 => 1
sin(~x) ^ 2 + cos(~x) ^ 2 => 1
julia> r(sin(2a)^2 + cos(2a)^2)
1
julia> r(sin(2a)^2 + cos(a)^2)
# nothing
DefSlot:
A DefSlot variable is written as ~!x
. Works like a normal slot, but can also take default values if not present in the expression.
Example in power:
julia> r_pow = @rule (~x)^(~!m) => ~m
(~x) ^ ~(!m) => ~m
julia> r_pow(x^2)
2
julia> r_pow(x)
1
Example in sum:
julia> r_sum = @rule ~x + ~!y => ~y
~x + ~(!y) => ~y
julia> r_sum(x+2)
x
julia> r_sum(x)
0
Currently DefSlot is implemented in:
Operation | Default value<br> |
---|---|
* | 1 |
+ | 0 |
2nd argument of ^ | 1 |
Segment:
A Segment variable is written as ~~x
and matches zero or more expressions in the function call.
Example:
This implements the distributive property of multiplication: +(~~ys)
matches expressions like a + b
, a+b+c
and so on. On the RHS ~~ys
presents as any old julia array.
julia> r = @rule ~x * +((~~ys)) => sum(map(y-> ~x * y, ~~ys));
julia> r(2 * (a+b+c))
((2 * a) + (2 * b) + (2 * c))
Predicates:
There are two kinds of predicates, namely over slot variables and over the whole rule. For the former, predicates can be used on both ~x
and ~~x
by using the ~x::f
or ~~x::f
. Here f
can be any julia function. In the case of a slot the function gets a single matched subexpression, in the case of segment, it gets an array of matched expressions.
The predicate should return true
if the current match is acceptable, and false
otherwise.
julia> two_πs(x::Number) = abs(round(x/(2π)) - x/(2π)) < 10^-9
two_πs (generic function with 1 method)
julia> two_πs(x) = false
two_πs (generic function with 2 methods)
julia> r = @rule sin(~~x + ~y::two_πs + ~~z) => sin(+(~~x..., ~~z...))
sin(~(~x) + ~(y::two_πs) + ~(~z)) => sin(+(~(~x)..., ~(~z)...))
julia> r(sin(a+3π))
julia> r(sin(a+6π))
sin(a)
julia> r(sin(a+6π+c))
sin((a + c))
Predicate function gets an array of values if attached to a segment variable (~~x
).
For the predicate over the whole rule, use @rule <LHS> => <RHS> where <predicate>
:
julia> @syms a b;
julia> predicate(x) = x === a;
julia> r = @rule ~x => ~x where predicate(~x);
julia> r(a)
a
julia> r(b) === nothing
true
Note that this is syntactic sugar and that it is the same as something like @rule ~x => f(~x) ? ~x : nothing
.
Debugging Rules: Note that if the RHS is a single tilde ~
, then the rule returns a a dictionary of all [slot variable, expression matched], this is useful for debugging.
Example:
julia> r = @rule (~x + (~y)^(~m)) => ~
~x + (~y) ^ ~m => (~)
julia> r(a + b^2)
Base.ImmutableDict{Symbol, Any} with 5 entries:
:MATCH => a + b^2
:m => 2
:y => b
:x => a
:____ => nothing
Context:
In predicates: Contextual predicates are functions wrapped in the Contextual
type. The function is called with 2 arguments: the expression and a context object passed during a call to the Rule object (maybe done by passing a context to simplify
or a RuleSet
object).
The function can use the inputs however it wants, and must return a boolean indicating whether the predicate holds or not.
In the consequent pattern: Use (@ctx)
to access the context object on the right hand side of an expression.
SymbolicUtils.@acrule
— Macro@acrule(lhs => rhs)
Create an associative-commutative rule that matches all permutations of the arguments.
This macro creates a rule that can match patterns regardless of the order of arguments in associative and commutative operations like addition and multiplication.
Arguments
lhs
: The pattern to match (left-hand side)rhs
: The replacement expression (right-hand side)
Examples
julia> @syms x y z
(x, y, z)
julia> r = @acrule x + y => 2x # Matches both x + y and y + x
ACRule(x + y => 2x)
julia> r(x + y)
2x
julia> r(y + x)
2x
See also: @rule
, @ordered_acrule
Rewriters
SymbolicUtils.Rewriters
— ModuleA rewriter is any function which takes an expression and returns an expression or nothing
. If nothing
is returned that means there was no changes applicable to the input expression.
The Rewriters
module contains some types which create and transform rewriters.
Empty()
is a rewriter which always returnsnothing
Chain(itr)
chain an iterator of rewriters into a single rewriter which applies each chained rewriter in the given order. If a rewriter returnsnothing
this is treated as a no-change.RestartedChain(itr)
likeChain(itr)
but restarts from the first rewriter once on the first successful application of one of the chained rewriters.IfElse(cond, rw1, rw2)
runs thecond
function on the input, appliesrw1
if cond returns true,rw2
if it returns falseIf(cond, rw)
is the same asIfElse(cond, rw, Empty())
Prewalk(rw; threaded=false, thread_cutoff=100)
returns a rewriter which does a pre-order traversal of a given expression and applies the rewriterrw
. Note that ifrw
returnsnothing
when a match is not found, thenPrewalk(rw)
will also return nothing unless a match is found at every level of the walk.threaded=true
will use multi threading for traversal.thread_cutoff
is the minimum number of nodes in a subtree which should be walked in a threaded spawn.Postwalk(rw; threaded=false, thread_cutoff=100)
similarly does post-order traversal.Fixpoint(rw)
returns a rewriter which appliesrw
repeatedly until there are no changes to be made.FixpointNoCycle
behaves likeFixpoint
but instead it appliesrw
repeatedly only while it is returning new results.PassThrough(rw)
returns a rewriter which ifrw(x)
returnsnothing
will instead returnx
otherwise will returnrw(x)
.
SymbolicUtils.Rewriters.Empty
— TypeEmpty()
A rewriter that always returns nothing
, indicating no rewrite occurred.
This is useful as a placeholder or for conditional rewriting patterns.
Examples
julia> Empty()(x)
nothing
SymbolicUtils.Rewriters.IfElse
— TypeIfElse(cond, yes, no)
A conditional rewriter that applies yes
if cond(x)
is true, otherwise applies no
.
Arguments
cond
: A function that returns true or false for the inputyes
: The rewriter to apply if the condition is trueno
: The rewriter to apply if the condition is false
Examples
julia> r = IfElse(x -> x > 0, x -> -x, x -> x)
julia> r(5) # Returns -5
julia> r(-3) # Returns -3
See also: If
SymbolicUtils.Rewriters.If
— FunctionIf(cond, yes)
A conditional rewriter that applies yes
if cond(x)
is true, otherwise returns the input unchanged.
This is equivalent to IfElse(cond, yes, Empty())
.
Arguments
cond
: A function that returns true or false for the inputyes
: The rewriter to apply if the condition is true
Examples
julia> r = If(x -> x > 0, x -> -x)
julia> r(5) # Returns -5
julia> r(-3) # Returns -3 (unchanged)
SymbolicUtils.Rewriters.Chain
— TypeChain(rws; stop_on_match=false)
Apply a sequence of rewriters to an expression, chaining the results.
Each rewriter in the chain receives the result of the previous rewriter. If a rewriter returns nothing
, the input is passed unchanged to the next rewriter.
Arguments
rws
: A collection of rewriters to apply in sequencestop_on_match
: If true, stop at the first rewriter that produces a change
Examples
julia> r1 = @rule sin(~x)^2 + cos(~x)^2 => 1
julia> r2 = @rule sin(2*(~x)) => 2*sin(~x)*cos(~x)
julia> chain = Chain([r1, r2])
julia> chain(sin(x)^2 + cos(x)^2) # Returns 1
SymbolicUtils.Rewriters.RestartedChain
— TypeRestartedChain(rws)
Apply rewriters in sequence, restarting the chain when any rewriter produces a change.
When any rewriter in the chain produces a non-nothing result, the entire chain is restarted with that result as the new input.
Arguments
rws
: A collection of rewriters to apply
Examples
julia> r1 = @rule ~x + ~x => 2 * ~x
julia> r2 = @rule 2 * ~x => ~x * 2
julia> chain = RestartedChain([r1, r2])
julia> chain(x + x) # Applies r1, then restarts and applies r2
SymbolicUtils.Rewriters.Fixpoint
— TypeFixpoint(rw)
Apply a rewriter repeatedly until a fixed point is reached.
The rewriter is applied repeatedly until the output equals the input (either by identity or by isequal
), indicating a fixed point has been reached.
Arguments
rw
: The rewriter to apply repeatedly
Examples
julia> r = @rule ~x + ~x => 2 * ~x
julia> fp = Fixpoint(r)
julia> fp(x + x + x + x) # Keeps applying until no more changes
See also: FixpointNoCycle
SymbolicUtils.Rewriters.FixpointNoCycle
— TypeFixpointNoCycle(rw)
FixpointNoCycle
behaves like Fixpoint
, but returns a rewriter which applies rw
repeatedly until it produces a result that was already produced before, for example, if the repeated application of rw
produces results a, b, c, d, b
in order, FixpointNoCycle
stops because b
has been already produced.
SymbolicUtils.Rewriters.Postwalk
— FunctionPostwalk(rw; threaded=false, thread_cutoff=100, maketerm=maketerm)
Apply a rewriter to a symbolic expression tree in post-order (bottom-up).
Post-order traversal visits child nodes before their parents, allowing for simplification of subexpressions before the containing expression.
Arguments
rw
: The rewriter to apply at each nodethreaded
: If true, use multi-threading for large expressionsthread_cutoff
: Minimum node count to trigger threadingmaketerm
: Function to construct terms (defaults tomaketerm
)filter
: Function which returns whether to search into a subtree
Examples
julia> r = @rule ~x + ~x => 2 * ~x
julia> pw = Postwalk(r)
julia> pw((x + x) * (y + y)) # Simplifies both additions
2x * 2y
See also: Prewalk
SymbolicUtils.Rewriters.Prewalk
— FunctionPrewalk(rw; threaded=false, thread_cutoff=100, maketerm=maketerm)
Apply a rewriter to a symbolic expression tree in pre-order (top-down).
Pre-order traversal visits parent nodes before their children, allowing for transformation of the overall structure before processing subexpressions.
Arguments
rw
: The rewriter to apply at each nodethreaded
: If true, use multi-threading for large expressionsthread_cutoff
: Minimum node count to trigger threadingmaketerm
: Function to construct terms (defaults tomaketerm
)filter
: Function which returns whether to search into a subtree
Examples
julia> r = @rule sin(~x) => cos(~x)
julia> pw = Prewalk(r)
julia> pw(sin(sin(x))) # Transforms outer sin first
cos(cos(x))
See also: Postwalk
SymbolicUtils.Rewriters.PassThrough
— TypePassThrough(rw)
A rewriter that returns the input unchanged if the wrapped rewriter returns nothing
.
This is useful for making rewriters that preserve the input when no rule applies.
Arguments
rw
: The rewriter to wrap
Examples
julia> r = @rule sin(~x) => cos(~x)
julia> pt = PassThrough(r)
julia> pt(sin(x)) # Returns cos(x)
julia> pt(tan(x)) # Returns tan(x) unchanged
Simplification and Transformation
SymbolicUtils.simplify
— Functionsimplify(x; expand=false,
threaded=false,
thread_subtree_cutoff=100,
rewriter=nothing)
Simplify an expression (x
) by applying rewriter
until there are no changes. expand=true
applies expand
in the beginning of each fixpoint iteration.
By default, simplify will assume denominators are not zero and allow cancellation in fractions. Pass simplify_fractions=false
to prevent this.
SymbolicUtils.expand
— Functionexpand(expr)
Expand expressions by distributing multiplication over addition, e.g., a*(b+c)
becomes ab+ac
.
expand
uses replace symbols and non-algebraic expressions by variables of type variable_type
to compute the distribution using a specialized sparse multivariate polynomials implementation. variable_type
can be any subtype of MultivariatePolynomials.AbstractVariable
.
SymbolicUtils.substitute
— Functionsubstitute(expr, dict; fold=true)
substitute any subexpression that matches a key in dict
with the corresponding value. If fold=false
, expressions which can be evaluated won't be evaluated.
julia> substitute(1+sqrt(y), Dict(y => 2), fold=true)
2.414213562373095
julia> substitute(1+sqrt(y), Dict(y => 2), fold=false)
1 + sqrt(2)
Polynomial Forms
SymbolicUtils.simplify_fractions
— Functionsimplify_fractions(x; polyform=false)
Find Div
nodes and simplify them by cancelling a set of factors of numerators and denominators.
SymbolicUtils.quick_cancel
— Functionquick_cancel(d)
Cancel out matching factors from numerator and denominator. This is not as effective as simplify_fractions
, for example, it wouldn't simplify (x^2 + 15 - 8x) / (x - 5)
to (x - 3)
. But it will simplify (x - 5)^2*(x - 3) / (x - 5)
to (x - 5)*(x - 3)
. Has optimized processes for Mul
and Pow
terms.
SymbolicUtils.flatten_fractions
— Functionflatten_fractions(x)
Flatten nested fractions that are added together.
julia> flatten_fractions((1+(1+1/a)/a)/a)
(1 + a + a^2) / (a^3)
Code Generation
Core Functions
SymbolicUtils.Code.toexpr
— Functiontoexpr(ex, [st,])
Convert a symbolic expression into an Expr
, suitable to be passed into eval
.
For example,
julia> @syms a b
(a, b)
julia> toexpr(a+b)
:((+)(a, b))
julia> toexpr(a+b) |> dump
Expr
head: Symbol call
args: Array{Any}((3,))
1: + (function of type typeof(+))
2: Symbol a
3: Symbol b
Note that the function is an actual function object.
For more complex expressions, see other code-related combinators,
Namely Assignment
, Let
, Func
, SetArray
, MakeArray
, MakeSparseArray
and MakeTuple
.
To make your own type convertible to Expr using toexpr
define toexpr(x, st)
and forward the state st
in internal calls to toexpr
. st
is state used to know when to leave something like y(t)
as it is or when to make it var"y(t)"
. E.g. when y(t)
is itself the argument of a function rather than y
.
SymbolicUtils.Code.cse
— Functioncse(expr) -> Any
Perform common subexpression elimination on an expression.
This optimization identifies repeated subexpressions and replaces them with variables to avoid redundant computation.
Arguments
expr
: The expression to optimize
Returns
An optimized expression with common subexpressions eliminated
Examples
julia> expr = :(sin(x) + sin(x) * cos(y))
julia> cse(expr) # sin(x) is computed only once
Code Generation Types
SymbolicUtils.Code.Assignment
— TypeAssignment(lhs, rhs)
An assignment expression. Shorthand lhs ← rhs
(\leftarrow
)
SymbolicUtils.Code.Let
— TypeLet(assignments, body[, let_block])
A Let block.
assignments
is a vector ofAssignment
sbody
is the body of the let blocklet_block
boolean (default=true) – do not create a let block if false.
SymbolicUtils.Code.Func
— TypeFunc(args, kwargs, body[, pre])
A function.
args
is a vector of expressionskwargs
is a vector ofAssignment
sbody
is the body of the functionpre
a vector of expressions to be prepended to the function body, for example, it could be[Expr(:meta, :inline), Expr(:meta, :propagate_inbounds)]
to create an@inline @propagate_inbounds
function definition.
Special features in args
:
- args can contain
DestructuredArgs
- call expressions
For example,
julia> @syms a b c t f(d) x(t) y(t) z(t)
(a, b, c, t, f(::Number)::Number, x(::Number)::Number, y(::Number)::Number, z(::Number)::Number)
julia> func = Func([a,x(t), DestructuredArgs([b, y(t)]), f], # args
[c ← 2, z(t) ← 42], # kwargs
f((a + b + c) / x(t) + y(t) + z(t)));
julia> toexpr(func)
:(function (a, var"x(t)", var"##arg#255", f; c = 2, var"z(t)" = 42)
let b = var"##arg#255"[1], var"y(t)" = var"##arg#255"[2]
f((+)(var"y(t)", var"z(t)", (*)((+)(a, b, c), (inv)(var"x(t)"))))
end
end)
- the second argument is a
DestructuredArgs
, in theExpr
form, it is given a random name, and is expected to receive a vector or tuple of size 2 containing the values ofb
andy(t)
. The let block that is automatically generated "destructures" these arguments. x(t)
andy(t)
have been replaced withvar"x(t)"
andvar"y(t)"
symbols throughout
the generated Expr. This makes sure that we are not actually calling the expressions x(t)
or y(t)
but instead passing the right values in place of the whole expression.
f
is also a function-like symbol, same asx
andy
, but since theargs
array containsf
as itself rather than as say,f(t)
, it does not become avar"f(t)"
. The generated function expects a function of one argument to be passed in the position off
.
An example invocation of this function is:
julia> executable = eval(toexpr(func))
#10 (generic function with 1 method)
julia> executable(1, 2.0, [2,3.0], x->string(x); var"z(t)" = sqrt(42))
"11.98074069840786"
SymbolicUtils.Code.DestructuredArgs
— TypeDestructuredArgs(elems, [name=gensym("arg")])
elems
is a vector of symbols or call expressions. When it appears as an argument in Func
, it expects a vector of the same length and de-structures the vector into its named components. See example in Func
for more information.
name
is the name to be used for the argument in the generated function Expr.
SymbolicUtils.Code.LiteralExpr
— TypeLiteralExpr(ex)
Literally ex
, an Expr
. toexpr
on LiteralExpr
recursively calls toexpr
on any interpolated symbolic expressions.
SymbolicUtils.Code.ForLoop
— TypeForLoop(itervar, range, body)
Generate a for
loop of the form
for itervar in range
body
end
Array Operations
SymbolicUtils.Code.SetArray
— TypeSetArray(inbounds::Bool, arr, elems[, return_arr::Bool])
An expression representing setting of elements of arr
.
By default, every element of elems
is copied over to arr
,
but if elems
contains AtIndex(i, val)
objects, then arr[i] = val
is performed in its place.
inbounds
is a boolean flag, true
surrounds the resulting expression in an @inbounds
.
return_arr
is a flag which controls whether the generated begin..end
block returns the arr
. Defaults to false
, in which case the block returns nothing
.
SymbolicUtils.Code.MakeArray
— TypeMakeArray(elems, similarto, [output_eltype=nothing])
An expression which constructs an array.
elems
is the output arraysimilarto
can either be a type, or some symbol that is an array whose type needs to be emulated. Ifsimilarto
is a StaticArrays.SArray, then the output array is also created as anSArray
, similarly, anArray
will result in anArray
, and aLabelledArrays.SLArray
will result in a labelled static array.output_eltype
: if set, then forces the element type of the output array to be this. by default, the output type is inferred automatically.
You can define:
@inline function create_array(A::Type{<:MyArray},a
::Nothing, d::Val{dims}, elems...) where dims
# and
@inline function create_array(::Type{<:MyArray}, T, ::Val{dims}, elems...) where dims
which creates an array of size dims
using the elements elems
and eltype T
, to allow MakeArray
to create arrays similarto MyArray
s.
SymbolicUtils.Code.MakeSparseArray
— TypeMakeSpaseArray(array)
An expression which creates a SparseMatrixCSC
or a SparseVector
.
The generated expression contains the sparsity information of array
,
it only creates the nzval
field at run time.
SymbolicUtils.Code.MakeTuple
— TypeMakeTuple(tup)
Make a Tuple from a tuple of expressions.
Parallelism
SymbolicUtils.Code.SpawnFetch
— TypeSpawnFetch{ParallelType}(funcs [, args], reduce)
Run every expression in funcs
in its own task, the expression should be a Func
object and is passed to Threads.Task(f)
. If Func
takes arguments, then the arguments must be passed in as args
–a vector of vector of arguments to each function in funcs
. We don't use @spawn
in order to support RuntimeGeneratedFunctions which disallow closures, instead we interpolate these functions or closures as smaller RuntimeGeneratedFunctions.
reduce
function is used to combine the results of executing exprs
. A SpawnFetch expression returns the reduced result.
Use Symbolics.MultithreadedForm
ParallelType from the Symbolics.jl package to get the RuntimeGeneratedFunction version SpawnFetch.
ParallelType
can be used to define more parallelism types SymbolicUtils supports Multithreaded
type. Which spawns threaded tasks.
SymbolicUtils.Code.Multithreaded
— TypeMultithreaded
A parallelism type for SpawnFetch
that uses Julia's threading system.
When used with SpawnFetch{Multithreaded}
, expressions are executed in parallel using Threads.@spawn
.
Examples
julia> SpawnFetch{Multithreaded}([func1, func2], combine_func)
See also: SpawnFetch