[ANN] Symbolics.jl: A Modern Computer Algebra System for a Modern Language

Thanks for your contributions! I think it was a good choice to strip out Symbolics as a separate package. I can’t wait to start playing with it.

There are a few things that is not clear for me in the documentation; perhaps I am looking in the wrong place? The concept of variables is straightforward, but the concept of parameters is not really described?? The concept parameter is used differently in different fields, e.g., in Modelica it means a constant. I seem to recall to have seen a suggestion that for ModelingToolkit (and probably Symbolics), “parameter” includes constants, independent variables, and known functions of independent variables (??).

Let me highlight the problem by including some code from the Symbolics documentation – there is some code that is presented both via macros and via a non-macro description… First the macro description:

@parameters t α σ(..) β[1:2]
@variables w(..) x(t) y z(t, α, x)

expr = β₁* x + y^α + σ(3) * (z - t) - β₂ * w(t - 1)

[It is not clear to me what an uncalled function implies. Here, I am guessing that α and β are constants, that t is an independent variable (time), and that σ is defined to be some function; it is not clear what the argument should be.
Furthermore, I am guessing that x(t) and z(t,α,x) are *dependent variables, it is not clear to me what type of variable y is – an algebraic variable for which you do not take derivatives??, while w again is some sort of uncalled variable – from expr, we can clearly make w a function of time.]

It is interesting to contrast this with the non-macro version…

σ = Num(Variable{Symbolics.FnType{Tuple{Any},Real}}(:σ)) # left uncalled, since it is used as a function
w = Num(Variable{Symbolics.FnType{Tuple{Any},Real}}(:w)) # unknown, left uncalled
x = Num(Variable{Symbolics.FnType{Tuple{Any},Real}}(:x))(t)  # unknown, depends on `t`
y = Num(Variable(:y))   # unknown, no dependents
z = Num(Variable{Symbolics.FnType{NTuple{3,Any},Real}}(:z))(t, α, x)  # unknown, multiple arguments
β₁ = Num(Variable(:β, 1)) # with index 1
β₂ = Num(Variable(:β, 2)) # with index 2

expr = β₁ * x + y^α + σ(3) * (z - t) - β₂ * w(t - 1)

Here, I observe the following:

  1. t and α are used in expr, but have not been defined individually in the non-macro version. Perhaps not needed, as these are given as arguments in the definitions of x and z??
  2. Quantities that are not constants or independent variables ( y, β) are defined simply using the Num(Variable(:y)) syntax, i.e., there is no mentioning of Symbolics and FnType.
  3. All functions/dependent variables are constructed with the inclusion of the Symbolics.FnType type.
  4. Uncalled functions (σ, w) are given without the specification of an argument list, while dependent variables/“called” functions are given with a list of arguments.

By contrasting the macro version and the non-macro version, it seems like there is no real difference between parameters and variables: there is no obvious difference between σ (“parameter”) and w (variable) in the no-macro description.

Also, in some of the code for symbolically defining differential equations, t seems sometimes to be given as a parameter, while other times it is given as a variable.

→ Anyway, some clarification of this would be helpful :slight_smile:

4 Likes

Parameter is just a ModelingToolkit.jl thing. It shouldn’t be in the Symbolics.jl documentation at all. Variables can have arbitrary metadata (where there’s a neat trick for doing without allocating on operations). So a ModelingToolkit @parameter is just a variable with metadata attributes saying it’s a parameter for automated detection.

Because that examples needs to update the top portion. They should all just be @variable there. Can you open a PR?

6 Likes

Will do that.

2 Likes

Hi, thanks for this great comment!

Your first example is possible with the kind of symbolic arrays we are working on: https://github.com/JuliaSymbolics/SymbolicUtils.jl/pull/123

It does a best-effort shape propagation, but you can leave out the shape if you don’t have it.

So your example could be simply encoded by something like

@variables x::Vector

maximize(sum(1//2 .* log.(x)), st=sum(x) == I)

Where log.(x) just returns a symbolic broadcasted term.

The second one:

differentiate(1//2 .* log.(x), x)

This would require adding rules for differentiating broadcast. This is tricky but possible!

Also we could easily make

@variables n::Int x[1:n]

Work so that you can have arrays that share dimension and match them when possible…

10 Likes

How will “assumptions” be dealt with in Symbolics.jl?

I have some priliminary work on this GitHub - JuliaSymbolics/SymbolicSAT.jl: for you with the good questions

It basically wraps the z3 theorem prover, calling out to it to prove expressions. :slight_smile: It’s a simple wrapper but the outcome is potentially really powerful!

I would appreciate any issues fleshing out people’s requirements on that package!

12 Likes

A couple of bugs/strange behavior:

julia> @variables x
julia> sinc(x)
TypeError: non-boolean (Num) used in boolean context

Stacktrace:
 [1] sinc(::Num) at .\special\trig.jl:944
 [2] top-level scope at In[74]:1
 [3] include_string(::Function, ::Module, ::String, ::String) at .\loading.jl:1091

This problem was also found in ModelingToolkit.

In ModelingToolkit, I got around this by defining mysinc(x) = sin(pi*x)/(pi*x), which worked.

However, in Symbolics, this triggers another error:

julia> @variables x
julia> mysinc(x) = sin(pi*x)/(pi*x)
julia> mysinc(x)
DomainError with -1:
Cannot raise an integer x to a negative power -1.
Convert input to float.

Stacktrace:
 [1] throw_domerr_powbysq(::Float64, ::Int64) at .\intfuncs.jl:225
 [2] power_by_squaring(::Irrational{:π}, ::Int64) at .\intfuncs.jl:249
....

Any idea what causes this problem?

2 Likes

Missing a registered function. We should add it. Open an issue.

4 Likes

Done.

2 Likes

This is great! What about the opposite direction (the examples here are with integrals which are not yet implemented in Symbolic.jl, but they are most ubiquitous place where an assumption has to be generated as part of the answer)
image

6 Likes

I wish I could go back and retake my undergrad calculus sequence in light of these new tools.

11 Likes

Is it possible yet to symbolically do integration or find expectations given variables on a known distribution? I can use Expectations.jl to numerically estimate the value, but I would love to be able to simplify these expressions analytically:

4 Likes

Not yet. That falls under integrals.

7 Likes

Thanks!!!

  1. is there a place we can post some common CAS “test” problems that we would like to solve w/ Symbolics.jl from our respective fields?
  2. Can we be more specific/general than @variables x::Vector?
    A vector can be an element of \frak{R}^{N}, \frak{C}^{N}, C\left(\frak{R}^{N}, \frak{R}\right) \dots
    Can we specify: x is a vector in \frak{R}^{N} where N is a symbolic parameter N \in Z_{+}?
  3. Btw, the slightly more general version of the above problem
    \max_{x_{i}} \text{ } \sum_{i=1}^{N} \alpha_{i} \log(x_{i}) \text{ s.t. } \sum_{i=1}^{N} p_{i}x_{i} = I
    Here: x, \alpha, p \in \frak{R}^{N}
    Assumptions (variables): N \in Z_{+}, x_{i}>0 for i \in \{1,\dots, N\}
    Assumptions (parameters): \alpha_{i} > 0, p_{i} >0, I>0 for i \in \{1,\dots, N\}
    Solution: x_{j}^{*}(\alpha, p, I)= \frac{\alpha_{j}}{ \sum_{i=1}^{N} \alpha_{i}} \frac{I}{ p_{j} }
    Mathematica struggles @ solving this for general parameters even if we set N=2.
    image
  4. Adding one parameter \rho \in (-\infty, 1] to the utility function makes Mathematica struggle even more
    \max_{x_{i}} \text{ } \left( \sum_{i=1}^{N} \alpha_{i} x_{i}^{\rho} \right)^{\frac{1}{\rho}} \text{ s.t. } \sum_{i=1}^{N} p_{i}x_{i} = I
    Very routine solution by hand: x_i =image
7 Likes

Write tests for the library and PR them. Tests don’t need to b unit tests: CI is cheap. Having a big robust test suite is always good.

You will be able to. Didn’t Shashi just demo that? I don’t know if he did it here though. But there’s a metadata system for arbitrary metadata. From there, the whole game is just coming up with all of the rules needed to handle all of the combinations of the metadata.

4 Likes

In Xmaxima, it can do basic expansion, for example
Screen Shot 2021-03-03 at 12.01.16 PM

How do we do expansion in Symbolics.jl? The tutorials do not give any examples.

7 Likes

inverse laplace transforms ?
edit: also, this is awesome !

Is there a reason why there is no Sym{Complex} in Symbolics.jl? Will it be implemented in the future?

One of the people working with us is handling complex numbers:

https://github.com/JuliaSymbolics/Symbolics.jl/pull/70

4 Likes

This is the first note I see where someone is comparing some other computer algebra system (CAS) with the proposed Julia program.
As an old hand in CAS programs, let me suggest that studying the previous systems, and especially what difficulties were encountered, would be time well spent.
There are (at least) the following: Sagemath, which would seem to be close in spirit to Symbolics.jl, except using python, and borrowing other implemented stuff. There’s Maxima (continuation of Macsyma) which would seem to be close also, but in Common Lisp. There’s also Axiom, Fricas, Reduce, and commercial systems Maple and Mathematica.

The item above about assumptions might be more relevant if it addressed the real problem that is the multiplicity of roots. sqrt(9) is the set {-3,+3}.
Even if you declare the obvious that -3<0,
sqrt( (-3)^2) is still not -3. It is {-3,3}. or in many of the systems mentioned above, it would be 3.
To insist that sqrt( (-x)^2) is -x, … well, you figure out the implications of that.
Let me voice my concern up front: Please don’t start by thinking that the various troubles in previous CAS implementations resulted from the wrong choice of an implementation language. There have been very sophisticated language designs explicitly to support the building of a CAS (see the history of Axiom). There have been strenuous efforts to elaborate on constructive versions of various algebras. There have been numerous breakthroughs in algebraic algorithms. There have been variations on memory management, big-number arithmetic, multi-programming, data-structure ideas including trees, hash-tables, cache management, etc.
I have only a slight passing familiarity with Julia, and no familiarity with the cited precursor efforts mentioned in the notes here and there, so perhaps some of these efforts have incorporated all the major technology developed in the CAS world to date. That would be great, and I wish you much luck.
Richard Fateman
fateman@cs.berkeley.edu

37 Likes

The Matrix Calculus paper is pretty nice:

6 Likes