Developer API

The names on this page are public extension points for packages that build on SymbolicUtils, including Symbolics.jl. They are intentionally not generally exported. User code should prefer the higher-level symbolic constructors, rewriters, and substitution APIs documented on the main API page.

Expression and cache interfaces

SymbolicUtils.:<ₑFunction
a <ₑ b

Compare symbolic expressions and their degree representations using the canonical expression ordering. The operator is intended for deterministic ordering of terms, not for mathematical less-than comparisons.

source
SymbolicUtils.@cacheMacro
@cache [options...] function foo(arg1::Type, arg2::Type; kwargs...)::ReturnType
    # ...
end

Create a cached version of the function foo. This is typically useful for recursive functions that descend through an expression tree.

The return type of the function should be annotated to avoid warnings. If any of the argument types is a BasicSymbolic, uses a special caching for efficiency. If an argument has Any type or a Union containing BasicSymbolic, a runtime check is performed to handle it. This can be avoided if the type is annotated with BasicSymbolic. The maximum number of entries in the cache can be set using the limit option by providing an integer size. This defaults to 100_000. When this limit is hit, a fraction of the entries in the cache will be cleared at random. The fraction of entries retained is given by the retain_fraction option, which defaults to 0.5.

Multiple methods of the same function cannot be cached and will lead to an error. This should be avoided by creating a wrapper function which calls the one with ,multiple methods, and caching the wrapper. The function with multiple methods should recursively call the wrapper. Caching a single method is valid.

The cache is thread-safe and uses TaskLocalValues.jl to maintain a task-specific cache.

The caching behavior for this function is enabled by default. Use the enabled option to toggle this.

See also: SymbolicUtils.get_limit, SymbolicUtils.set_limit!, SymbolicUtils.get_retain_fraction, SymbolicUtils.set_retain_fraction!, SymbolicUtils.toggle_caching!, SymbolicUtils.is_caching_enabled, SymbolicUtils.get_stats, SymbolicUtils.clear_cache!, SymbolicUtils.reset_stats!.

source
SymbolicUtils.DivType
Div{T}(n, d, simplified; type = promote_symtype(/, symtype(n), symtype(d)), kw...) where {T}

High-level constructor for division expressions with simplification.

Arguments

  • n: The numerator
  • d: The denominator
  • simplified::Bool: Whether simplification has been attempted
  • type: The result type (default: inferred using promote_symtype)
  • kw...: Additional keyword arguments (e.g., shape, metadata, unsafe)

Returns

  • BasicSymbolic{T}: An optimized representation of n / d

Details

This constructor creates symbolic division expressions with extensive simplification:

  • Zero numerator returns zero
  • Unit denominator returns the numerator
  • Zero denominator returns Const{T}(1 // 0) (infinity). Any infinity may be returned.
  • Nested divisions are flattened
  • Constant divisions are evaluated
  • Rational coefficients are simplified
  • Multiplications in numerator/denominator are handled specially

For non-SafeReal variants, automatic cancellation is attempted using quick_cancel. The simplified flag prevents infinite simplification loops.

source
SymbolicUtils.OperatorType
Operator

Abstract supertype for symbolic callable operators. An operator is treated as an atomic function-like object by expression traversal and supplies its own symbolic type and shape promotion through promote_symtype and promote_shape.

Developer implementations should subtype Operator and define those promotion methods before constructing operator terms.

source
SymbolicUtils.SubstituterType
Substituter{Fold}

An abstract supertype for functors that perform substitution operations on symbolic expressions. This can also be used as a constructor for the functor used by substitute. Fold corresponds to the fold keyword of substitute. Passing fold = Val(true) corresponds to Substituter{true} (and similarly for Val(false)). To define substitution rules for custom types that wrap/contain BasicSymbolic, define methods for this abstract type. For example,

struct Equation
    lhs::BasicSymbolic{SymReal}
    rhs::BasicSymbolic{SymReal}
end

function (subst::Substituter)(eq::Equation)
    return Equation(subst(eq.lhs), subst(eq.rhs))
end

Custom substitution algorithms should define functors that subtype Substituter. For example, a functor may be defined for substituting until the expression reaches a fixpoint. These functors should then implement:

  • (s::Substituter{Fold})(ex::BasicSymbolic{T}) where {T} to perform the appropriate substitution on the given symbolic expression.
  • get_substitution_dict(::Substituter) returning an AbstractDict of the substitution rules.

Instead of repeatedly calling substitute with the same rules, it is usually more efficient to build a Substituter and reuse it.

Substituter is also allowed to cache intermediate results as necessary. When constructing Substituter with an AbstractDict, it will alias the provided mapping. Mutating the map such that the identity of the substitution rules changes invalidates the substituter. It can be reused by clearing the cache using SymbolicUtils.clear_cache!.

The caching is only available when the SymbolicUtils.vartype of the expressions is inferable from the substitution rules, or explicitly specified. As long as either the keys or values of the substitution rules are all BasicSymbolic{T} (for some T) the automatic inference will work. To allow the inference to work for your custom wrapper type, implement SymbolicUtils.infer_vartype. For example:

struct Num <: Real
  inner::BasicSymbolic{SymReal}
end

SymbolicUtils.infer_vartype(::Type{Num}) = SymReal

Alternatively, the vartype can be provided as the third positional argument to the Substituter constructor.

Extended help

The following is internal details of Substituter and should not be relied on as public API.

Fields

source
SymbolicUtils._isoneFunction
_isone(x) -> Bool

Check if a value is one, with caching for performance.

Arguments

  • x: The value to check (can be a number, array, or symbolic expression)

Returns

  • true if the unwrapped value is one, false otherwise

Details

This cached function efficiently checks if a value is one by first unwrapping any constant wrappers. Handles both numeric values and arrays. Returns false for symbolic expressions that are not constant one. The @cache macro improves performance by memoizing results for previously seen values.

source
SymbolicUtils._iszeroFunction
_iszero(x) -> Bool

Check if a value is zero, with caching for performance.

Arguments

  • x: The value to check (can be a number, array, or symbolic expression)

Returns

  • true if the unwrapped value is zero, false otherwise

Details

This cached function efficiently checks if a value is zero by first unwrapping any constant wrappers. Handles both numeric values and arrays. Returns false for symbolic expressions that are not constant zero. The @cache macro improves performance by memoizing results for previously seen values.

source
SymbolicUtils.default_is_atomicFunction
default_is_atomic(
    ex::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T}
) -> Any

The default is_atomic predicate for search_variables!. ex is considered atomic if one of the following conditions is true:

  • It is a Sym and not an internal index variable for an arrayop
  • It is a Term, the operation is a BasicSymbolic and the operation represents a dependent variable according to is_function_symbolic.
  • It is a Term, the operation is getindex and the variable being indexed is atomic.
source
SymbolicUtils.default_substitute_filterFunction
default_substitute_filter(
    ex::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T}
) -> Any

The default filter function used by substitute to determine whether to substitute within an expression. Returns false for expressions that are Terms with an Operator as the operation (preventing substitution within operator calls), and true otherwise.

Arguments

  • ex::BasicSymbolic{T}: The expression to check.

Returns

  • Bool: false if the expression should not be substituted into, true otherwise.
source
SymbolicUtils.evaluateFunction
evaluate(expr; filterer = default_substitute_filter)

Evaluate a symbolic expression using the standard substitution and folding machinery without replacing any variables. filterer controls which symbolic nodes may be folded. The result is the folded expression or value returned by substitute with fold = Val(true).

source
SymbolicUtils.hashconsFunction
hashcons(expr::BasicSymbolic; reregister = false)

Intern expr in the vartype-specific weak hash-cons table and return the canonical object. If reregister is false, an expression with an existing identifier is returned unchanged; setting it to true permits registration in the current table. Hash-consing is a developer optimization and preserves isequal semantics, including symbolic metadata.

source
SymbolicUtils.is_array_shapeFunction
is_array_shape(shape)

Return true when a symbolic shape represents an array rather than a scalar. Unknown is considered an array shape because its rank may be nonzero; an empty ShapeVecT is the scalar shape.

source
SymbolicUtils.is_function_symbolicFunction
is_function_symbolic(
    x::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T} where T
) -> Any

Check if x is a symbolic representing a function (as opposed to a dependent variable). A symbolic function either has a defined signature or the function type defined. For example, all of the below are considered symbolic functions:

@syms f(::Real, ::Real) g(::Real)::Integer h(::Real)[1:2]::Integer (ff::MyCallableT)(..)

However, the following is considered a dependent variable with unspecified independent variable:

@syms x(..)

See also: SymbolicUtils.is_function_symtype.

source
SymbolicUtils.isarrayopFunction
isarrayop(
    x::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T} where T
) -> Bool

Check if a value is an ArrayOp variant of BasicSymbolic.

Arguments

  • x: Value to check (for BasicSymbolic input returns true if ArrayOp, for others returns false).

Returns

  • true if x is a BasicSymbolic with ArrayOp variant, false otherwise.

Details

Array operations represent vectorized computations created by the @arrayop macro.

source
SymbolicUtils.isbinopFunction
isbinop(x)

Return whether x is recognized as a binary symbolic operator. This extension point defaults to false for non-symbolic values and can be specialized by symbolic operator implementations.

source
SymbolicUtils.one_of_vartypeFunction
one_of_vartype(
    _::Type{SymReal}
) -> SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}

Return a Const representing 1 with the provided vartype.

source
SymbolicUtils.operator_to_termFunction
operator_to_term(operator::Operator, ex::BasicSymbolic)

Return the symbolic term representing an operator application. The default implementation returns ex; custom Operator subtypes may override it when their printed or canonical term differs from the original expression.

source
SymbolicUtils.promote_symtypeFunction
promote_symtype(
    f,
    Ts...
) -> Type{SymbolicUtils.FnType{_A, _B, Nothing}} where {_A, _B}

The result of applying f to arguments of SymbolicUtils.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 expressions without an explicit symtype, promote_symtype is used to figure out the symtype of the Term.

It is recommended that all type arguments be annotated with SymbolicUtils.TypeT and one method be implemented for any combination of f and the number of arguments. For example, one method is implemented for unary - and one method for binary -. Each method has an if..elseif chain to handle possible types. Any call to promote_type should be typeasserted with ::TypeT.

source
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.

source

promotesymtype(f::ComposedFunction, argsymtypes::TypeT...)

Compute the symbolic type of applying a composed function to arguments.

For a composed function f ∘ g ∘ h, this computes the result type by propagating type information from the innermost function outward:

  1. Apply h to the input argument types
  2. Use that result type as input to g
  3. Use that result type as input to f

This implementation assumes each function returns a single value that becomes the argument to the next function. Multi-argument returns (tuples) are not currently supported but could be added if needed.

source
SymbolicUtils.queryFunction
query(
    predicate,
    expr::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T};
    recurse,
    default
) -> Any

Recursively search an expression tree to determine if any subexpression satisfies a given predicate. This function traverses the expression tree and returns true if the predicate returns true for any node in the tree.

Arguments

  • predicate::F: A function that takes an expression and returns a Bool.
  • expr::BasicSymbolic: The expression to search.

Keyword Arguments

  • recurse::G=iscall: A function determining whether to recurse into a subexpression.
  • default::Bool=false: The default value to return if the expression is not a call or recursion is prevented.

Returns

  • Bool: true if any subexpression satisfies the predicate, false otherwise.
source
query(
    predicate,
    ir::IRStructure{T},
    expr::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T};
    recurse
) -> Bool

Optimized version of SymbolicUtils.query that leverages the provided SymbolicUtils.IRStructure. Requires that expr is present in ir.

source
SymbolicUtils.scalarizeFunction
scalarize(
    x::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T}
) -> Any
scalarize(
    x::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T},
    v::Val{toplevel}
) -> Any

Convert a symbolic expression with array operations into a fully scalarized form. This function expands array operations into element-wise operations, converting symbolic array expressions into arrays of scalar symbolic expressions.

For ArrayOp expressions, this function reduces eliminated indices and substitutes concrete values for output indices to generate scalar expressions for each array element.

Arguments

  • x::BasicSymbolic{T}: The symbolic expression to scalarize.
  • ::Val{toplevel}=Val{false}(): Whether to evaluate constant expressions at the top level. When true, constant subexpressions are evaluated; when false, they are recursively scalarized.

Returns

  • The scalarized expression. For array-shaped expressions, returns an array of scalar expressions. For scalar expressions, returns the expression unchanged or with recursively scalarized subexpressions.
source
SymbolicUtils.search_variablesFunction
search_variables(expr; is_atomic = default_is_atomic, recurse = iscall)

Return a set-like collection containing the atomic symbolic variables found in expr. The keyword arguments are forwarded to search_variables!, so custom wrappers can define both the atomic predicate and recursion policy.

source
SymbolicUtils.search_variables!Function
search_variables!(
    buffer,
    expr::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T};
    is_atomic,
    recurse,
    _seen
)

Find all variables used in expr and add them to buffer. A variable is identified by the predicate is_atomic. The predicate recurse determines whether to search further inside expr if it is not a variable. Note that recurse must at least return false if iscall returns false.

Wrappers for BasicSymbolic should implement this function by unwrapping.

See also: default_is_atomic.

source
search_variables!(
    buffer::AbstractSet{SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T}},
    ir::IRStructure{T},
    expr::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T};
    is_atomic,
    recurse
)

Optimized version of SymbolicUtils.search_variables! that leverages the provided SymbolicUtils.IRStructure. May also add expr to ir in the process.

source
SymbolicUtils.show_callFunction
show_call(io, f, expr; kwargs...)

Render a symbolic call expr to io using operation f. This is the developer printing hook used by the symbolic display machinery. Keyword arguments are accepted for specialized printers and are otherwise ignored.

source
SymbolicUtils.stable_eachindexFunction
stable_eachindex(
    x::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T} where T
) -> SymbolicUtils.StableIndices

Returns a type-stable iterator over all indices of a symbolic array x.

This function provides an efficient, allocation-friendly way to iterate over multi-dimensional symbolic arrays. Unlike Base.eachindex, which returns CartesianIndices with type parameters that may be uninferrable for symbolic arrays, stable_eachindex returns a StableIndices iterator that produces StableIndex values in a fully type-stable manner.

Note that the returned iterator does not match the shape of x. In other words, collect(stable_eachindex(x)) will be a vector regardless of the shape of x.

Arguments

  • x::BasicSymbolic: A symbolic array expression with a known concrete shape.

Returns

  • StableIndices: An iterator that yields StableIndex values for each position in the array.

Throws

  • This function assumes x has a concrete shape (i.e., shape(x) is a ShapeVecT, not Unknown). If the shape is unknown, it will error.

Examples

using SymbolicUtils

# Create a symbolic 2×3 matrix
@variables x[1:2, 1:3]

# Iterate over all indices in a type-stable manner
for idx in stable_eachindex(x)
    println("Index: ", idx, " -> Value: ", x[idx])
end

# Compare with regular eachindex
for idx in eachindex(x)  # Returns CartesianIndices
    println("Index: ", idx, " -> Value: ", x[idx])
end

See also

  • StableIndices: The iterator type returned by this function
  • StableIndex: The index type produced by StableIndices
  • Base.eachindex: The standard Julia function for iterating over array indices
source
SymbolicUtils.zero_of_vartypeFunction
zero_of_vartype(
    _::Type{SymReal}
) -> SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}

Return a Const representing 0 with the provided vartype.

source
SymbolicUtils.zeropolyFunction
zeropoly(

) -> DynamicPolynomials.Polynomial{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, MultivariatePolynomials.Graded{MultivariatePolynomials.Reverse{MultivariatePolynomials.InverseLexOrder}}, Number}

Create a zero polynomial with empty monomial vector.

Returns

  • A PolynomialT representing the zero polynomial
source

Parsing and polynomial conversion

SymbolicUtils.MonomialOrderType
MonomialOrder

The canonical monomial ordering used by SymbolicUtils polynomial conversion. It is graded reverse lexicographic ordering with the reverse tie-breaker used by DynamicPolynomials.

source
SymbolicUtils.PolyCoeffTType
PolyCoeffT

The coefficient type accepted by the developer polynomial interface. SymbolicUtils uses Number so integer, rational, and floating-point coefficients can share the same conversion routines.

source
SymbolicUtils.PolyVarOrderType
PolyVarOrder

The DynamicPolynomials variable-order type used by PolyVarT and PolynomialT. Variables are ordered by their creation order.

source
SymbolicUtils.PolyVarTType
PolyVarT

The concrete polynomial-variable type used for symbolic expressions converted to polynomial form. Values are created with DynamicPolynomials.@polyvar-compatible constructors and are not themselves symbolic terms.

source
SymbolicUtils._indexed_ndimsFunction
_indexed_ndims(index_types...)

Return the number of array dimensions selected by a tuple of index types. Integer and scalar indices consume no dimensions, while Colon and integer vectors consume one. Invalid index types throw ArgumentError.

This helper is part of the developer indexing interface used by promote_symtype(getindex, ...).

source
SymbolicUtils.basicsymbolic_to_polyvarFunction
basicsymbolic_to_polyvar(
    bs_to_poly::AbstractDict,
    x::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T} where T
) -> DynamicPolynomials.Variable{DynamicPolynomials.Commutative{DynamicPolynomials.CreationOrder}, MultivariatePolynomials.Graded{MultivariatePolynomials.Reverse{MultivariatePolynomials.InverseLexOrder}}}

Convert a BasicSymbolic expression to a polynomial variable, caching the result.

Arguments

  • bs_to_poly::AbstractDict: Dictionary cache mapping BasicSymbolic to PolyVarT
  • x::BasicSymbolic: The symbolic expression to convert

Returns

  • A PolyVarT polynomial variable representing x, created or retrieved from cache
source
SymbolicUtils.from_polyFunction
from_poly(poly_to_bs, poly)

Reconstruct a BasicSymbolic expression from a polynomial produced by to_poly!. poly_to_bs must contain an entry for every polynomial variable in poly.

Returns

A BasicSymbolic expression with the same polynomial value.

source
SymbolicUtils.parse_variableFunction
parse_variable(x; default_type) -> Dict{Symbol, Any}

Parse an Expr or Symbol representing a variable in the syntax of the @syms macro. Returns a Dict{Symbol, Any} with the following keys guaranteed to exist:

  • :name: The name of the variable. nothing if not specified.
  • :type: The type of the variable. default_type if not specified.
  • :shape: The shape of the variable.
  • :isruntime: Whether the name is a runtime value (comes from a $name interpolation syntax).

This does not attempt to eval to interpret types. Values in the above keys are concrete values when possible and Exprs when not.

If the variable is a function, it contains additional keys:

  • :head: A Dict{Symbol, Any} containing the name and type of the function.
  • :args: A list of Dict{Symbol, Any} corresponding to each argument of the function. If there is a single argument .., the only Dict{Symbol, Any} in :args will only contain :name => :... For arguments of the form ::T (type annotation without a name) the name will be nothing.

Refer to the docstring for @syms for a description of the grammar accepted by this function.

source
SymbolicUtils.sym_from_parse_resultFunction
sym_from_parse_result(
    result::Dict{Symbol, Any},
    vartype;
    do_esc
) -> Expr

Return an Expr which constructs a Sym for the given parsed variable result. vartype is the vartype of the variable to be constructed. do_esc controls whether this function is responsible for escing necessary values, or the caller will manually sanitize and esc the expression.

source
SymbolicUtils.to_poly!Function
to_poly!(poly_to_bs, bs_to_poly, expr, recurse = true)

Convert a BasicSymbolic expression into a sparse polynomial representation. poly_to_bs maps generated polynomial variables back to symbolic expressions, while bs_to_poly caches the reverse mapping. With recurse = false, non-polynomial subexpressions become single polynomial variables.

Returns

A PolyVarT or PolynomialT representing expr.

source

Rewriting and broadcasting helpers

SymbolicUtils.@map_methodsMacro
@map_methods T argument_transform result_transform

Generate Base.map methods for a symbolic array-like type. argument_transform converts the symbolic container to the arguments passed to map, and result_transform wraps the mapped result. This is a developer interface and must be used while defining the corresponding symbolic type.

source
SymbolicUtils.@mapreduce_methodsMacro
@mapreduce_methods T argument_transform result_transform

Generate Base.mapreduce methods for a symbolic array-like type. The transform expressions receive the mapped input and the result transform reconstructs the symbolic representation.

source
SymbolicUtils.@number_methodsMacro
@number_methods T unary_body binary_body [options]

Emit arithmetic methods for T using the supplied unary and binary expression bodies. See number_methods for the generated dispatch combinations and supported options values.

source
SymbolicUtils.number_methodsFunction
@number_methods T unary_body binary_body [options]

Generate the standard arithmetic methods needed for a symbolic type T. unary_body and binary_body are expressions evaluated with the generated function and binary-operation variables in scope. options may be nothing, :skipbasics, :onlybasics, or a vector expression naming operations to skip.

This is a developer macro for symbolic-type implementations. It performs no runtime dispatch by itself; it emits methods for the numeric and symbolic promotion combinations supported by T.

source
SymbolicUtils.RuleType
Rule{L, M, R}

Compiled representation of a rewrite rule created by @rule or @acrule.

Fields

  • expr: the original rule expression used for display.
  • lhs: the pattern matched by the rule.
  • matcher: the compiled matcher for lhs.
  • rhs: the replacement expression or function.
  • depth: the maximum expression depth inspected by the rule.
source
SymbolicUtils.SymBroadcastType
SymBroadcast{T}

Broadcast style for BasicSymbolic{T} expressions. It keeps symbolic arrays in the symbolic broadcast path and rejects broadcasts that combine incompatible symbolic variants.

source

Code generation

SymbolicUtils.CodeModule

SymbolicUtils.jl

Join the chat at https://julialang.zulipchat.com #sciml-bridged Global Docs

codecov Build Status Build status

ColPrac: Contributor's Guide on Collaborative Practices for Community Packages SciML Code Style

Tutorials and Documentation

For information on using the package, see the stable documentation. Use the in-development documentation for the version of the documentation, which contains the unreleased features.

SymbolicUtils.jl provides various utilities for symbolic computing. SymbolicUtils.jl is what one would use to build a Computer Algebra System (CAS). If you're looking for a complete CAS, similar to SymPy or Mathematica, see Symbolics.jl. If you want to build a crazy CAS for your weird Octonian algebras, you've come to the right place.

Symbols in SymbolicUtils carry type information. Operations on them propagate this information. A rule-based rewriting language can be used to find subexpressions that satisfy arbitrary conditions and apply arbitrary transformations on the matches. The library also contains a set of useful simplification rules for expressions of numeric symbols and numbers. These can be remixed and extended for special purposes.

If you are a Julia package developer in need of a rule rewriting system for your own types, have a look at the interfacing guide.

"I don't want to read your manual, just show me some cool code"

julia> using SymbolicUtils

julia> SymbolicUtils.show_simplified[] = true

julia> @syms x::Real y::Real z::Complex{Real} f(::Number)::Real
(x, y, z, f(::Number)::Real)

julia> 2x^2 - y + x^2
(3 * (x ^ 2)) + (-1 * y)

julia> f(sin(x)^2 + cos(x)^2) + z
f(1) + z

julia> r = @rule sinh(im * ~x) => sin(~x)
sinh(im * ~x) => sin(~x)

julia> r(sinh(im * y))
sin(y)

julia> simplify(cos(y)^2 + sinh(im*y)^2, RuleSet([r]))
1

Citations

  • The pattern matcher is an adaption of the one by Gerald Jay Sussman (as seen in 6.945 at MIT), his use of symbolic programming in the book SICM inspired this package.
  • Rewrite.jl and Simplify.jl by Harrison Grodin also inspired this package.

Developer-facing intermediate-representation and code-generation helpers for SymbolicUtils. The public names in this module are stable extension points for packages that generate code from symbolic expressions; ordinary callers should prefer SymbolicUtils.build_function-style high-level APIs.

source
SymbolicUtils.Code.LazyStateType
LazyState()

Lazily initialized rewrite state used while generating code from a symbolic expression. The state exposes the same rewrite dictionary interface as NameState after its first access.

source
SymbolicUtils.Code.fast_toexprFunction
fast_toexpr(
    sym::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T},
    rewrites::Dict{Any, Any}
) -> Expr

Replacement of SymbolicUtils.Code.toexpr using IR-based codegen.

source
fast_toexpr(
    sym::SymbolicUtils.Code.CodegenPrimitive,
    ir::IRStructure{T},
    rewrites::Dict{Any, Any}
) -> Any
source
SymbolicUtils.Code.function_to_exprFunction
function_to_expr(op, expr, state)

Lower one symbolic operation to a Julia expression during code generation.
This is a developer extension point used by `SymbolicUtils.Code.toexpr` and
the IR-based code generator. `state` must be the active code-generation
state; custom operation handlers should preserve the expression and state
invariants expected by the surrounding generator.
source
SymbolicUtils.Code.get_rewritesFunction
get_rewrites(expr)

Collect symbolic subexpressions that need rewrite bindings during code
generation. This developer hook returns an empty collection for literals
and recursively visits arrays, tuples, and destructured arguments.
source
SymbolicUtils.Code.supports_with_allocatorFunction
supports_with_allocator(ex::BasicSymbolic)

Return `true` when `ex` is an allocating symbolic expression accepted by
[`with_allocator`](@ref). The supported forms are `array_literal`,
`SymbolicUtils.@arrayop`, and `SymbolicUtils.@makearray`, including array
operations backed by `Fill`.
source
SymbolicUtils.Code.with_allocatorFunction
with_allocator(
    alloc,
    ex::SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T}
) -> SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{T} where T

Associate an allocating expression with an allocator. Expressions such as SymbolicUtils.@arrayop, SymbolicUtils.@makearray, and SymbolicUtils.array_literal need to allocate an array for their results. By default, the generated code will allocate an array using any method it deems appropriate (such as zeros). To use a specific allocation method, or perhaps provide a preallocated array of the appropriate size and eltype, with_allocator can be used. The first argument is an allocator (API described below), and the second argument is an expression which allocates an array. The array allocation will use the provided allocator. Note that this only applies to immediate next allocation in ex. For example, an array_literal containing an @arrayop has two allocations. with_allocator on the outer array_literal will only control the allocation used for array_literal, and not @arrayop. Each allocating expression needs its own with_allocator.

Allocator API

The API for the allocator is relatively simple. It should be a callable accepting an NTuple{N, Int} indicating the size of the required array, and returning an array of the appropriate eltype and indicated size. The array should be mutable, allowing setindex!.

Note that the eltype of the array is not provided, and is the responsibility of the allocator to identify. This is intentional, since figuring this out ahead of time can be prohibitively expensive. For example, to obtain a value representing the eltype of an @arrayop can require running the entire set of reduction loops. The symtype of the expression is also typically very wide - it is usually a type like Real or Number. The provider of the allocator typically has more information about the required eltype, since they know how the expression is involved in the larger code, where the arguments come from, and the concrete types of the buffers.

source