JuliaSymbolics / Symbolics.jl

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

Thoughts on interaction with SymPy #201

Open jverzani opened 3 years ago

jverzani commented 3 years ago

Building a CAS is time consuming process, but wouldn't it be nice to be able to leverage some SymPy operations while this happens? It might not be impossible, this toy model shows one way to go half way there, though at a loss of type information:

julia> import PyCall

julia> import SymPy: sympy

julia> using Symbolics

julia> function λ(ex)
          build_function(ex, Symbolics.get_variables(ex)..., expression=Val{false})
       end;

julia> function PyCall.PyObject(ex::Num)
           xs = Symbolics.get_variables(ex)
           xs′ = sympy.sympify.(string.(xs))
           λ(ex)(xs′...).__pyobject__
       end;

julia> @variables x a
(x, a)

julia> sympy.limit(sin(a*x)/x, x, 0)

a

The PyObject method is the big conversion from Symbolics to the python SymPy using SymPy.jl. The use of build_function is a hacky way to get that done. The round trip needs more thought. The best way would be to integrate in PyCall's pytype_mapping for behind the scenes conversion, but a quick an easy means might be achievable by turning the sympy value into an expression and incorporating that back into Symbolics.

My question, is there any appetite in pursing this?

┆Issue is synchronized with this Trello card by Unito

shashi commented 3 years ago

Nice! But this seems more apt as a blog post or an appendix than something this package would need to support. It would be good to use any CAS, not just SymPy.

ChrisRackauckas commented 3 years ago

No, I think it would be good to have this code somewhere in JuliaSymbolics. I think it would be good to keep it off of Symbolics.jl because Python dependencies always cause issues (like always, they are the bane of my existence). But I think it would be really good to capture this code in an @requires. This would do two things:

  1. It would make it so we can point users to it when we don't have the feature implemented.
  2. It would be an easy way to make benchmarks.

So there's a lot of value there. So please go ahead and let's get this code and document it, and if it becomes too big we can move it to SymPySymbolics.jl but for now it should be a quick @requires block with one documentation page showing the link.

jverzani commented 3 years ago

I think @shashi is right here. Doing this reasonably well requires more work than this simple proof of concept. Including this would bring on more ownership issues than what it is worth. The return trip needs doing as well. I believe the approach taken by Symata, which works at the AST level, might be worth pursuing (https://github.com/jlapeyre/Symata.jl/blob/master/src/sympy.jl) were there any spare time by any one working on Symbolics.

ChrisRackauckas commented 3 years ago

I think contributing a proof of concept is fine though. Giving a little bit and nurturing the code's evolution is what needs to happen, not necessarily someone doing 100% of it in a single code drop.

jverzani commented 3 years ago

Here is a hacky solution that gets simple things done, but not nuanced ones, when called at the command line:

using Symbolics
import PyCall
const sympy = PyCall.pyimport_conda("sympy", "sympy")

function PyCall.PyObject(ex::Num)
    sympy.sympify(string(ex)) # lose type detail of variables
end;
Symbolics.Num(o::PyCall.PyObject) = eval(Meta.parse(sympy.julia_code(o)))

Which gives, for example:

julia> @variables a b x

(a, b, x)

julia> o = sympy.limit(sin(a*x)/sin(b*x), x, 0)
PyObject a/b

julia> Num(o)
a*(b^-1)

But it seems far too brittle to put into a package, though I should post somewhere as an example. Here it falls apart due to a Piecewise condition (Ne)

julia> o = sympy.integrate(1/x^n, x)
PyObject Piecewise((x**(1 - n)/(1 - n), Ne(n, 1)), (log(x), True))

julia> Num(o)
ERROR: MethodError: no method matching !(::Num)