JuliaSymbolics / Symbolics.jl

Symbolic programming for the next generation of numerical software
https://docs.sciml.ai/Symbolics/stable/
Other
1.35k stars 151 forks source link

Default output of symbolic expressions is not usable Julia code #1253

Open orebas opened 2 weeks ago

orebas commented 2 weeks ago

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
ChrisRackauckas commented 2 weeks ago

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?

orebas commented 2 weeks ago

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
ChrisRackauckas commented 2 weeks ago

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))
orebas commented 1 week ago

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(..)?

ChrisRackauckas commented 1 week ago

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.

orebas commented 1 week ago

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.

ChrisRackauckas commented 1 week ago

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

orebas commented 1 week ago

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.