JuliaPy / SymPy.jl

Julia interface to SymPy via PyCall
http://juliapy.github.io/SymPy.jl/
MIT License
266 stars 61 forks source link

Error when converting expressions to julia functions #501

Closed flmuk closed 1 year ago

flmuk commented 1 year ago

Hello,

I work with:

I am new to Julia, and I encountered the following problem when trying to follow the documentation on how to make performant Julia functions from symbolic expressions (from sympy documentation here).

I work with sympy's hypergeometric functions (wikipedia, SymPy Python, SymPy.jl), so here is a minimal example of my code:

using SymPy

@vars x y
ex = sympy.hyper((2,2),(3,3),x) * y
body = convert(Expr, ex)
println(body)
syms = Symbol.(free_symbols(ex))
fn = eval(Expr(:function, Expr(:call, gensym(), syms...), body));
fn(1,1)

When executing this code, I get the following error, first the print statement yields: y * hyper(TupleArg(2, 2), TupleArg(3, 3), x), but then the following error appears:


ERROR: UndefVarError: TupleArg not defined
Stacktrace:
 [1] var"##322"(x::Int64, y::Int64)
   @ Main ./none:0
 [2] top-level scope
   @ ~/Documents/github-sympy.jl:8
```.

From the `println(body)` statement I deduce that there is an issue with how the hypergeometric functions are accessed. Can someone help? I know there is a package for hypergeometric functions in Julia, i.e. [HypergeometricFunctions.jl](https://github.com/JuliaMath/HypergeometricFunctions.jl). Is there a way to integrate the two?

Is there a way to substitute the `hyper()` call in `body` with a call to HypergeometricFunctions.jl?
fabioveneto commented 1 year ago

I had the same problem recently. Don't know how to solve it

jverzani commented 1 year ago

Yes, a good question. This example has two sympy functions that need counterparts. This works:

julia> using HypergeometricFunctions
julia> using SymPy
julia> d = Dict("hyper" => :pFq, "TupleArg" => :tuple)
julia> using SymPy
julia> @vars x y
julia> ex = sympy.hyper((2,2),(3,3),x) * y
julia> body = SymPy.walk_expression(ex, fns=d)
julia> syms = Symbol.(free_symbols(ex))
julia> fn = eval(Expr(:function, Expr(:call, gensym(), syms...), body));
julia> fn(1,1)
1.6015187080185656

I can make it so TupleArg resolves to tuple, but as I don't want to add a dependency to HypergeometricFunctions I'd be reluctant to make that happen without intervention on the user's part. I should make the needed usage above better documented.

jverzani commented 1 year ago

502 documents how to do this and automatically converts TupleArg to tuple. Thanks for the feedback.

flmuk commented 1 year ago

thank you so much for the quick reply, this really helps!

flmuk commented 1 year ago

I still have a question on this, however: if I change the expression to ex = sympy.hyper((2,2),(3,3),x) ^ y (note power operator instead of product) in the above code, I will get an expression that still depends on a SymPy routine, i.e. SymPy.__POW__:


using HypergeometricFunctions
using SymPy
d = Dict("hyper" => :pFq, "TupleArg" => :tuple)
@vars x y
ex = sympy.hyper((2,2),(3,3),x) ^ y
body = SymPy.walk_expression(ex, fns=d)
syms = Symbol.(free_symbols(ex))
fn = eval(Expr(:function, Expr(:call, gensym(), syms...), body));

The code evaluates fine, but I would like the resulting expression not to use references to SymPy, i.e. have it in pure julia. As it stands here body reads

julia> body
:(SymPy.__POW__(pFq(tuple(2, 2), tuple(3, 3), x), y))

Is there a workaround for this, too? I tried d = Dict("hyper" => :pFq, "TupleArg" => :tuple, :(SymPy.__POW__) => :(^)) but that did not have any effect on the final body expression.

jverzani commented 1 year ago

Close, try the key "Pow" => :^ in your dictionary. (There are a few mappings that get overridden for minor reasons, I forget why this one is there, but likely I thought there would be a small performance gain.)