SymbolicUtils.jl: typed symbols and rewrite rules for Julia
Symbolic expressions, rewriting and simplification
At a glance
- What is it?
- SymbolicUtils.jl is the Julia package that carries type information on symbolic expressions, applies a rule based rewriting language to subexpressions, and ships simplification rules you can remix. It suits people building a computer algebra system or a bespoke symbolic domain, and it is not a ready made CAS, since the README points you to Symbolics.jl for that.
- Who is it for?
- Adopt SymbolicUtils.jl if you are building symbolic machinery in Julia, whether a domain specific algebra, a rule system over your own types, or the front half of a larger CAS, and you want type information carried on the expression rather than bolted on afterwards. Do not adopt it expecting a finished algebra system, because the README states it is what one would use to build a CAS and points to Symbolics.jl for a complete one.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Julia, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What SymbolicUtils.jl is, and what it deliberately is not
SymbolicUtils.jl provides various utilities for symbolic computing. The important sentence in the README is the one that follows: it is what one would use to build a Computer Algebra System, and if you are looking for a complete CAS similar to SymPy or Mathematica, you should see Symbolics.jl instead.
That boundary shapes everything about the project. This is a library of primitives, not an application. There is no solver, no plotting, no notebook, no `simplify` that magically does the right thing for your domain. What it offers is the machinery: a representation for expressions, a way to match parts of an expression, and a way to rewrite them.
The audience that fits is therefore specific. It is for a Julia package developer who needs symbolic manipulation over their own types, and for a person who wants to build an algebra system for a domain that SymPy and Mathematica know nothing about, the Octonian algebras the README jokes about being a legitimate use case.
It is also the foundation layer under the wider SciML ecosystem. The package carries a SciML code style badge, a ColPrac community packages guide badge, a codecov badge and both GitHub Actions and Buildkite CI badges, and the chat badge points at the `sciml-bridged` channel on the Julia Zulip. The documentation lives under the SciML docs site, with a stable version and a dev version for unreleased features.
Typed symbols and how operations propagate type information
The mechanism that distinguishes this library from a generic term rewriting engine is that symbols in SymbolicUtils carry type information, and operations on them propagate that information.
The `@syms` macro is how you create them, and the type is part of the declaration.
julia> @syms x::Real y::Real z::Complex{Real} f(::Number)::Real
(x, y, z, f(::Number)::Real)So `x` and `y` are reals, `z` is a complex number with real components, and `f` is a function symbol that accepts any number and returns a real. That declaration is not decoration carried along for later inspection. It is what lets the simplification rules fire selectively.
The example in the README shows the payoff. With `SymbolicUtils.show_simplified[]` set to `true`, an expression like `2x^2 - y + x^2` prints as `(3 * (x ^ 2)) + (-1 * y)`, so the two `x^2` terms were already combined when the expression was built, which is the point of carrying type information through operations rather than simplifying later.
The second example is the identity that proves the type information is doing work. With `f` accepting a number and `x` a real, `f(sin(x)^2 + cos(x)^2) + z` evaluates to `f(1) + z`. The trigonometric identity was applied because the argument was known to be a real number, which a system that treated `f` as an untyped application could not have done.
The trade-off is that you have to decide the types up front, and a wrong type declaration produces wrong simplifications rather than an error. A symbol declared as `Complex{Real}` when the physics only makes sense over reals will not warn you.
The @rule rewriting language and its tilde placeholders
The rewriting language is where the package earns its keep. It finds subexpressions that satisfy arbitrary conditions and applies arbitrary transformations on the matches.
A rule is written with `@rule`, and the pattern uses tildes to mark the parts that should match anything and be captured.
julia> r = @rule sinh(im * ~x) => sin(~x)
sinh(im * ~x) => sin(~x)The rule is an object, and it is applied by calling it on an expression.
julia> r(sinh(im * y))
sin(y)Two properties are worth calling out. First, the rule does not fire on `x` specifically, it fires on anything, and `y` is substituted for the captured `~x`. Second, and more interesting, the rule is passed into the simplification machinery as part of a rule set, so it composes with the built-in rules rather than replacing them.
julia> simplify(cos(y)^2 + sinh(im*y)^2, RuleSet([r]))
1That last line is the whole point of the design. The expression contains a term `sinh(im*y)^2` and a term `cos(y)^2`. The custom rule rewrites `sinh(im*y)` into `sin(y)`, and the built-in rules then turn `cos(y)^2 + sin(y)^2` into `1`. Neither the custom rule nor the standard rules could have done it alone, and the user wrote one small transformation to get there.
The pattern matcher behind this is credited in the README as an adaptation of Gerald Jay Sussman's, as seen in the MIT 6.945 course notes, with his use of symbolic programming in SICM as the inspiration for the package. Rewrite.jl and Simplify.jl by Harrison Grodin are also cited as inspirations.
Simplification rules for numeric symbols, and remixing them
The library ships a set of simplification rules for expressions of numeric symbols and numbers. The README is explicit that these can be remixed and extended for special purposes, which tells you how to treat the built-in set: it is a starting point rather than a contract.
The remixing is not a metaphor. A `RuleSet` takes a list of rules and can be passed to `simplify` alongside or instead of the defaults, as the `RuleSet([r])` call in the previous section shows. So the workflow for a domain with its own algebra is to take the numeric rules as a base, add the handful of transformations specific to your domain, and pass the combined set.
This is also the mechanism that handles the type information. A rule can be conditioned on the type of a symbol, and the typed symbol declarations are what let the matcher know whether a condition holds. In practice that means the built-in numeric rules fire on `x::Real` and your own rules fire on your own type, in the same pass, and the order in which they are tried is under your control through the set you build.
For a Julia package developer working over their own types, the README points at a separate interfacing guide. That is the document for the case where the symbols are not numbers at all, and it exists because the library was designed to be extended into a domain rather than to be used only for arithmetic. The `ext/` directory in the repository tree is where the package extensions live, and `src/` holds the implementation, with `test/` and a `benchmark/` directory beside them.
Getting started, and where the installation instructions are not
The README does not give a package installation command. What it points to instead is the stable documentation for using the package, and the dev documentation for the version that contains unreleased features. The package manifest in the repository is `Project.toml`, which is the standard Julia dependency file, and the name recorded there is the one you would add to an environment.
The quick tour in the README is the fastest way to see the whole feature set at once, and it is short enough to read in a minute. It opens with loading the package, turns on the simplified display, declares typed symbols, does some algebra, applies a trigonometric identity through a function symbol, and then writes and uses a rewrite rule.
julia> using SymbolicUtils
julia> SymbolicUtils.show_simplified[] = trueTwo details in that snippet are easy to miss. `SymbolicUtils.show_simplified[]` is a mutable global flag indexed by square brackets, and it changes how expressions print, not how they are computed. And the whole session is REPL driven, with the `julia>` prompts showing that this is an interactive session rather than a script.
The pattern for the type declarations also deserves a second look. `f(::Number)::Real` declares a function symbol, with the argument type given in the signature position and the return type after the double colon. That is the form that makes the `f(sin(x)^2 + cos(x)^2)` simplification possible, and it is the same mechanism a package author would use for their own callable symbols.
For a version reference, the last push to the tree was on 2026-10-20, and the releases are frequent, with v4.53.0 on 2026-10-20, v4.52.0 on 2026-10-19 and v4.51.0 on 2026-10-14. The repository is not archived.
Where this library is the wrong choice
There are two clear cases where SymbolicUtils.jl is the wrong tool, and both are stated in the README rather than discovered later.
The first is wanting a CAS. If your goal is to differentiate, integrate, solve and plot, you want Symbolics.jl, and the effort of assembling that from primitives is not worth it. This library supplies the representation and the rewriting, not the calculus.
The second is doing numeric work. Nothing here computes a value; it manipulates expressions. If you need a number, you have already left the right tool.
There is also a subtler cost to the type system. Because types drive which simplifications fire, the correctness of the output depends on the correctness of the type declarations you wrote. Declare a symbol as `Real` when it may carry an imaginary part and you get an identity applied in the wrong place, and unlike a type error, that failure is silent. A team adopting this needs a convention about who owns the type declarations, and the interfacing guide for custom types is where that convention has to be written down.
A fourth limitation is the size of the surface. The README itself offers a shortcut, a section titled as a request to skip the manual and show some code, which tells you that the real documentation is long. The tutorial, the rewriting manual, the interfacing guide and the dev documentation are separate documents, and the project is explicit that the dev docs contain unreleased features, so anything read there may change.
The licence is a custom one that GitHub cannot classify, held in `LICENSE.md` at the repository root, and there is a `CITATION.bib` file for academic citation. A team that needs a standard permissive licence to satisfy a policy will have to read that file directly.
Editorial conclusion
Adopt SymbolicUtils.jl if you are building symbolic machinery in Julia, whether a domain specific algebra, a rule system over your own types, or the front half of a larger CAS, and you want type information carried on the expression rather than bolted on afterwards. Do not adopt it expecting a finished algebra system, because the README states it is what one would use to build a CAS and points to Symbolics.jl for a complete one. Before building on it, read the interfacing guide for your own types, then confirm the simplifications you rely on behave as you expect, since the shipped rules target numeric symbols and numbers and are meant to be remixed and extended.
Frequently asked questions
What is SymbolicUtils.jl used for?
It provides the utilities you would use to build a Computer Algebra System, including typed symbolic expressions, a rule based rewriting language and simplification rules for numeric symbols and numbers. If you want a complete CAS, the README points to Symbolics.jl instead.
How do I create typed symbols in SymbolicUtils.jl?
You use the `@syms` macro with type annotations, for example `@syms x::Real y::Real z::Complex{Real} f(::Number)::Real`. The types are carried on the symbol and propagate through operations, which is what lets the library apply rules selectively.
How do I write a rewrite rule in SymbolicUtils.jl?
You write it with `@rule` and use tildes to mark the parts to capture, for example `@rule sinh(im * ~x) => sin(~x)`. The rule is an object you can call on an expression, and you can pass it into `simplify` inside a `RuleSet` so it composes with the built-in rules.
Can SymbolicUtils.jl be used with my own types?
Yes. The README points Julia package developers who need a rule rewriting system for their own types to the interfacing guide, and describes the package as suitable for building a CAS for unusual algebras. The `ext/` directory in the repository holds the package extensions.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/juliasymbolics-symbolicutils-jl)Was this useful?