Symbolics.jl icon indicating copy to clipboard operation
Symbolics.jl copied to clipboard

Default output of symbolic expressions is not usable Julia code

Open orebas opened this issue 1 year ago • 8 comments

In both the REPL and when executing scripts, the display of symbolic expressions is not, unfortunately, copy and pastable Julia code. See below for a very simple MWE.
I think it is not currently promised by Symbolics, but reasonable to strive for: -Anything output in the repl as a symbolic expression should be valid Julia syntax, assignable to a new variable.

Separately, I don't really understand the below. Shouldn't a(t) and a just be synonyms in symbolic expressions?

julia> using Symbolics

julia> @variables t a(t) b(t)
3-element Vector{Num}:
    t
 a(t)
 b(t)

julia> q = a/b
a(t) / b(t)

julia> q
a(t) / b(t)

julia> r = a(t) / b(t)
ERROR: Sym a(t) is not callable. Use @syms a(t)(var1, var2,...) to create it as a callable.
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] (::SymbolicUtils.BasicSymbolic{Real})(args::SymbolicUtils.BasicSymbolic{Real})
   @ SymbolicUtils ~/.julia/packages/SymbolicUtils/65hU9/src/types.jl:918
 [3] (::Num)(args::Num)
   @ Symbolics ~/.julia/packages/Symbolics/ztabB/src/num.jl:19
 [4] top-level scope
   @ REPL[5]:1

orebas avatar Sep 01 '24 16:09 orebas

We'd need to think about this a bit. It is somewhat of an alias, but if someone does a(tau) should it error? Or would that just be nice syntax to change the time for something like a delay differential equation?

ChrisRackauckas avatar Sep 01 '24 16:09 ChrisRackauckas

Actually, now that I think about it a bit more, I'm not sure how to ask Julia Symbolics to help me with the following math problem: We have a function f(t), i.e. the variable f depends on t. How do I expand the derivative of f(t^2)?

julia> @variables t f(t)
2-element Vector{Num}:
    t
 f(t)

julia> D = Differential(t)
Differential(t)

julia> D(t^2)
Differential(t)(t^2)

julia> expand_derivatives(D(t^2))
2t

julia> expand_derivatives(D(f))
Differential(t)(f(t))

julia> expand_derivatives(D(f(t*t)))
ERROR: Sym f(t) is not callable. Use @syms f(t)(var1, var2,...) to create it as a callable.
Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:35
 [2] (::SymbolicUtils.BasicSymbolic{Real})(args::SymbolicUtils.BasicSymbolic{Real})
   @ SymbolicUtils ~/.julia/packages/SymbolicUtils/65hU9/src/types.jl:918
 [3] (::Num)(args::Num)
   @ Symbolics ~/.julia/packages/Symbolics/ztabB/src/num.jl:19
 [4] top-level scope
   @ REPL[7]:1

orebas avatar Sep 01 '24 17:09 orebas

Use a wildcard.

julia> @variables t f(..)
2-element Vector{Any}:
 t
  f⋆

julia> expand_derivatives(Differential(t)(f(t^2)))
2t*Differential(t^2)(f(t^2))

ChrisRackauckas avatar Sep 01 '24 22:09 ChrisRackauckas

That's cool. I couldn't find that syntax documented anywhere. What does it mean? (Is it a macro thing?)

What is the reason to ever declare something an f(t) as opposed to f(..)?

orebas avatar Sep 02 '24 18:09 orebas

We should probably add it to https://docs.sciml.ai/Symbolics/stable/manual/variables/#Symbolics.@variables the docstring.

What does it mean? (Is it a macro thing?)

It's a wildcard. It's a call variable, but with a lazy ask for what the call is.

What is the reason to ever declare something an f(t) as opposed to f(..)?

Simplicity. It's very common to have something that's just x(t) everywhere (for example, in a differential equation definition). It's much more rare to have expressions like x(t) + x(t^2), but the wildcard expression gives you a way to specify it. But if you do that, you do have to put x(t) everywhere, x is simply ill-defined because it's a function so it needs what it's a function of everywhere.

ChrisRackauckas avatar Sep 03 '24 10:09 ChrisRackauckas

For what it's worth, I think that in at least the case I described above, changing the default output so that a is displayed instead of a(t) resolves the issue.

To expand a bit: I'm not sold on rejecting a(t) when a is accepted and is interpreted to mean a(t). However, the value of "display" output being pastable and manipulable Julia code is evident, and I think it's the standard for many other base packages (like LinAlg etc.) So whatever input Symbolics chooses to accept, right now a small change in the output would at least fix the above case, and IMHO might improve readability of the output.

orebas avatar Sep 05 '24 02:09 orebas

For that use case though, wouldn't you build function?

ChrisRackauckas avatar Sep 07 '24 22:09 ChrisRackauckas

The "use case" here is interacting with Symbolics in the REPL. For example, I was trying to debug some code and the output of the REPL is not something I can just paste back into the REPL, for instance, to simplify it or expand it. I have to copy and paste into an editor and replace "a(t)" with "a". For 5 variables it takes a while. It's easily avoidable.

orebas avatar Sep 08 '24 04:09 orebas