# Getting Started with Symbolics.jl

Symbolics.jl is a symbolic modeling language. In this tutorial, we will walk you through
the process of getting Symbolics.jl up and running, and start doing our first symbolic
calculations.

## Installing Symbolics.jl

Installing Symbolics.jl is as simple as using the Julia package manager. This is done via
the command `]add Symbolics`. After precompilation is complete, do `using Symbolics`
in the terminal (REPL) and when that command is completed, you're ready to start!

## Building Symbolic Expressions

The way to define symbolic variables is via the `@variables` macro:

```@example symbolic_basics
using Symbolics
@variables x y
```

After defining variables as symbolic, symbolic expressions, which we call a
`iscall` object, can be generated by utilizing Julia expressions. For example:

```@example symbolic_basics
z = x^2 + y
```

Here, `z` is an expression tree for “square `x` and add `y`”. To make an array
of symbolic expressions, simply make an array of symbolic expressions:

```@example symbolic_basics
A = [x^2 + y 0 2x
     0       0 2y
     y^2 + x 0 0]
```

Note that by default, `@variables` returns `Sym` or `iscall` objects wrapped in
`Num` to make them behave like subtypes of `Real`. Any operation on
these `Num` objects will return a new `Num` object, wrapping the result of
computing symbolically on the underlying values.

If you are following this tutorial in the Julia REPL, `A` will not be shown with LaTeX equations.
To get these equations, we can use [Latexify.jl](https://github.com/korsbo/Latexify.jl).
Symbolics.jl comes with Latexify recipes, so it works automatically:

```@example symbolic_basics
using Latexify
latexify(A)
```

Normal Julia functions work on Symbolics expressions, so if we
want to create the sparse version of `A` we would just call `sparse`:

```@example symbolic_basics
using SparseArrays
spA = sparse(A)
latexify(A)
```

We can thus use normal Julia functions as generators for sparse
expressions. For example, here we will define

```@example symbolic_basics
function f(u)
  [u[1] - u[3], u[1]^2 - u[2], u[3] + u[2]]
end
f([x, y, z]) # Recall that z = x^2 + y
```

Or we can build an array of variables and use it to trace the function:

```@example symbolic_basics
u = Symbolics.variables(:u, 1:5)
f(u)
```

!!! note
    `Symbolics.variables(:u, 1:5)` creates a Julia array of symbolic variables. This uses
    O(n) compute and memory but is a very general representation. Symbolics.jl also has the
    ability to represent symbolic arrays which gives an O(1) representation but is more
    limited in its functionality. For more information, see the
    [Symbolic Arrays](@ref symbolic_arrays) page.

## Derivatives

One common thing to compute in a symbolic system is derivatives. In
Symbolics.jl, derivatives are represented lazily via operations,
just like any other function. To build a differential operator, use
`Differential` like:

```@example symbolic_basics
@variables t
D = Differential(t)
```

This is the differential operator ``D = \frac{\partial}{\partial t}``. We can
compose the differential operator by `*`, e.g.
`Differential(t) * Differential(x)` or `Differential(t)^2`.
Now let's write down the derivative of some expression:

```@example symbolic_basics
z = t + t^2
D(z)
```

Notice that this hasn't computed anything yet: `D` is a lazy operator
because it lets us symbolically represent “The derivative of ``z`` with
respect to ``t``”, which is useful for example when representing our
favorite thing in the world, differential equations. However, if we
want to expand the derivative operators, we'd use `expand_derivatives`:

```@example symbolic_basics
expand_derivatives(D(z))
```

The variable, that you are taking the derivative with respect to, is accessed with:

```@example symbolic_basics
D.x
```

We can also have simplified functions for multivariable calculus.
For example, we can compute the Jacobian of an array of expressions
like:

```@example symbolic_basics
Symbolics.jacobian([x + x*y, x^2 + y], [x, y])
```

and similarly we can do Hessians, gradients, and define whatever other
derivatives you want.

## Simplification and Substitution

Symbolics interfaces with [SymbolicUtils.jl](https://github.com/JuliaSymbolics/SymbolicUtils.jl)
to allow for simplifying symbolic expressions. This is done simply
through the `simplify` command:

```@example symbolic_basics
simplify(2x + 2y)
```

This can be applied to arrays by using Julia's broadcast mechanism:

```@example symbolic_basics
B = simplify.([t + t^2 + t + t^2  2t + 4t
               x + y + y + 2t     x^2 - x^2 + y^2])
```

We can then use `substitute` to change values of an expression around:

```@example symbolic_basics
simplify.(substitute.(B, (Dict(x => y^2),)))
```

and we can use this to interactively evaluate expressions without
generating and compiling Julia functions:

```@example symbolic_basics
V = substitute.(B, (Dict(x => 2.0, y => 3.0, t => 4.0),))
```

Where we can reference the values via:

```@example symbolic_basics
Symbolics.value.(V)
```

## Non-Interactive Development

Note that the macros are for the high-level case where you're doing symbolic
computation on your own code. If you want to do symbolic computation on someone
else's code, like in a macro, you may not want to do `@variables x` because you
might want the name “x” to come from the user's code. For these cases, you can
use the interpolation operator to interpolate the runtime value of `x`, i.e.
`@variables $x`. Check the documentation of `@variables` for more details.

```@example symbolic_basics
a, b, c = :runtime_symbol_value, :value_b, :value_c
```
```@example symbolic_basics
vars = @variables t $a $b(t) $c(t)[1:3]
```
```@example symbolic_basics
(t, a, b, c)
```

One could also use `variable` and `variables`. Read their documentation for more
details.

If we need to use this to generate new Julia code, we can simply
convert the output to an `Expr`:

```@example symbolic_basics
Symbolics.toexpr(x + y^2)
```

The other way around is also possible, parsing Julia expressions into symbolic expressions

```@example symbolic_basics
ex = [:(v ~ w)
      :(w ~ -v)]
eqs = parse_expr_to_symbolic.(ex, (Main,))
eqs_lhs = [eq.lhs for eq in eqs]
eqs_rhs = [eq.rhs for eq in eqs]
Symbolics.jacobian(eqs_rhs, eqs_lhs)
```

## `Sym`s and callable `Sym`s

In the definition

```@example symbolic_basics
@variables t x(t) y(t)
```

`t` is of type `Sym{Real}`, but the name `x` refers to an object that represents the `Term` `x(t)`. The operation of this expression is itself the object `Sym{FnType{Tuple{Real}, Real}}(:x)`. The type `Sym{FnType{...}}` represents a callable object. In this case specifically, it's a function that takes 1 Real argument (noted by `Tuple{Real}`) and returns a `Real` result. You can call such a callable `Sym` with either a number or a symbolic expression of a permissible type.

This expression also defines `t` as an independent variable, while `x(t)` and `y(t)` are
dependent variables. This is accounted for in differentiation:

```@example symbolic_basics
z = x + y*t
expand_derivatives(D(z))
```

Since `x` and `y` are time-dependent, they are not automatically eliminated
from the expression, and thus the `D(x)` and `D(y)` operations still
exist in the expanded derivatives for correctness.

We can also define unrestricted functions:

```@example symbolic_basics
@variables g(..)
```

Here, `g` is a variable that is a function of other variables. Any time
that we reference `g` we have to utilize it as a function:

```@example symbolic_basics
z = g(x) + g(y)
```

## Registering Functions

One of the benefits of a one-language Julia symbolic stack is that the
primitives are all written in Julia, and therefore it's trivially
extendable from Julia itself. By default, new functions are traced
to the primitives and the symbolic expressions are written on the
primitives. However, we can expand the allowed primitives by registering
new functions. For example, let's register a new function `h`:

```@example symbolic_basics
h(x, y) = x^2 + y
@register_symbolic h(x, y)
```

When we use `h(x, y)`, it is a symbolic expression and doesn't expand:

```@example symbolic_basics
h(x, y) + y^2
```

To use it with the differentiation system, we need to register
its derivatives. We would do it like this:

```@example symbolic_basics
# Derivative w.r.t. the first argument
Symbolics.derivative(::typeof(h), args::NTuple{2,Any}, ::Val{1}) = 2args[1]
# Derivative w.r.t. the second argument
Symbolics.derivative(::typeof(h), args::NTuple{2,Any}, ::Val{2}) = 1
```

and now it works with the rest of the system:

```@example symbolic_basics
Symbolics.derivative(h(x, y) + y^2, x)
```
```@example symbolic_basics
Symbolics.derivative(h(x, y) + y^2, y)
```

!!! note
    `@register_symbolic` only allows for scalar outputs. If full array functions are needed,
    then see `@register_array_symbolic` for registering functions of symbolic arrays.

## Building Functions

The function for building functions from symbolic expressions is the aptly-named `build_function`.
The first argument is the symbolic expression or the array of symbolic
expressions to compile, and the trailing arguments are the arguments
for the function. For example:

```@example symbolic_basics
to_compute = [x^2 + y, y^2 + x]
f_expr = build_function(to_compute, [x, y])
Base.remove_linenums!.(f_expr)
```

gives back two codes. The first is a function `f([x, y])` that computes
and builds an output vector `[x^2 + y, y^2 + x]`. Because this tool
was made to be used by all the cool kids writing fast Julia codes, it
is specialized to Julia and supports features like StaticArrays. For
example:

```@example symbolic_basics
using StaticArrays
myf = eval(f_expr[1])
myf(SA[2.0, 3.0])
```

The second function is an in-place non-allocating mutating function
which mutates its first value. Thus, we'd use it like the following:

```@example symbolic_basics
myf! = eval(f_expr[2])
out = zeros(2)
myf!(out, [2.0, 3.0])
out
```

To save the symbolic calculations for later, we can take this expression
and save it out to a file:

```@example symbolic_basics
write("function.jl", string(f_expr[2]))
```

Note that if we need to avoid `eval`, for example to avoid world-age
issues, one could do `expression = Val{false}`:

```@example symbolic_basics
Base.remove_linenums!(build_function(to_compute, [x, y], expression=Val{false})[1])
```

which will use [RuntimeGeneratedFunctions.jl](https://github.com/SciML/RuntimeGeneratedFunctions.jl)
to build Julia functions which avoid world-age issues.

## Building Non-Allocating Parallel Functions for Sparse Matrices

Now let's show off a little bit. `build_function` is kind of like if
[`lambdify`](https://docs.sympy.org/latest/modules/utilities/lambdify.html)
ate its spinach. To show this, **let's build a non-allocating
function that fills sparse matrices in a multithreaded manner**. To do
this, we just have to represent the operation that we're turning into
a function via a sparse matrix. For example:

```@example symbolic_basics
using LinearAlgebra
N = 8
A = sparse(Tridiagonal([x^i for i in 1:N-1], [x^i * y^(8-i) for i in 1:N], [y^i for i in 1:N-1]))
show(A)
```

Now we call `build_function`:

```@example symbolic_basics
Base.remove_linenums!(build_function(A,[x,y],parallel=Symbolics.MultithreadedForm())[2])
```
Let's unpack what that's doing. It's using `A.nzval` to linearly
index through the sparse matrix, avoiding the `A[i,j]` form because that
is a more expensive way to index a sparse matrix if you know exactly the
order that the data is stored. Then, it's splitting up the calculation
into Julia threads, so they can be operated on in parallel. It synchronizes
after spawning all the tasks, so the computation is ensured to be
complete before moving on. And it does this with all in-place operations
to the original matrix instead of generating arrays. This is the fanciest
way you could fill a sparse matrix, and Symbolics makes this
dead simple.

(Note: this example may be slower with multithreading due to the
thread spawning overhead, but the full version was not included in the
documentation for brevity. It will be the faster version if `N` is
sufficiently large!)

Importantly, when using the in-place functions generated by
[`build_function`](@ref), it is important that the mutating argument matches the
sparse structure of the original function, as demonstrated below.
```@example symbolic_basics
using Symbolics, SparseArrays, LinearAlgebra

N = 10
_S = sprand(N, N, 0.1)
_Q = sprand(N, N, 0.1)

F(z) = [ # some complicated sparse amenable function
    _S * z
    _Q * z.^2
]

Symbolics.@variables z[1:N]

sj = Symbolics.sparsejacobian(F(z), z) # sparse jacobian

f_expr = build_function(sj, z)

rows, cols, _ = findnz(sj)
out = sparse(rows, cols, zeros(length(cols)), size(sj)...) # pre-allocate, and correct structure
myf = eval(last(f_expr))
myf(out, rand(N)) # note that out matches the sparsity structure of sj
out
```
