Best Practices for converting a Mathematica expression to Julia

Thanks. Never heard of this Github Copilot.
Do you have an example of how you use it? It seems like a powerful thing.

Though it would be nice to find a julia solution…

Here is something you can copy and paste to see if it works for you:

using SymPy
const sympy_parsing_mathematica = SymPy.PyCall.pyimport("sympy.parsing.mathematica")

s = "(Sqrt[Pi])/(2*EllipticE[m])"
ex = sympy_parsing_mathematica.mathematica(s, Dict("EllipticE[x]"=>"elliptic_e(x)"))

SymPy.walk_expression(ex) #:((1 / 2) * pi ^ (1 / 2) * elliptic_e(m) ^ -1)

# or

SymPy.convert_expr(ex, use_julia_code=true) # :(sqrt(pi) ./ (2 * elliptic_e(m)))
2 Likes

That is phenomenal!

Some things still do not work as expected, though.

The walk_expression gives me something different, and I do not know why:

s = "(Sqrt[Pi])/(2*EllipticE[m])"
ex = sympy_parsing_mathematica.mathematica(s, Dict("EllipticE[x]"=>"elliptic_e(x)"))

SymPy.walk_expression(ex) # what I should get :((1 / 2) * pi ^ (1 / 2) * elliptic_e(m) ^ -1)
# what I get is
# :((1 / 2) * SymPy.__POW__(pi, 1 / 2) * SymPy.__POW__(elliptic_e(m), -1))

I can fix it using

SymPy.walk_expression(ex, fns=Dict("Pow"=>:^))
# :((1 / 2) * pi ^ (1 / 2) * elliptic_e(m) ^ -1)

The convert_expr does not work well.
First, I do not like that it returns all operations with the vectorized dot notion like so

:(sqrt(pi) ./ (2 * elliptic_e(m)))

The more problematic issue is that it does not deal with this expression for some reason

s = "Sqrt[2]/q2 EllipticF[x, 1 - q1^2/q2^2]"
ex = sympy_parsing_mathematica.mathematica(s, Dict("EllipticF[x, y]"=>"elliptic_e(x, y)"))

SymPy.convert_expr(ex, use_julia_code=true)

gives

Base.Meta.ParseError("use \"x^y\" instead of \"x**y\" for exponentiation, and \"x...\" instead of \"**x\" for splatting.")

Stacktrace:
 [1] #parse#3
   @ ./meta.jl:236 [inlined]
 [2] parse
   @ ./meta.jl:232 [inlined]
 [3] parse(str::String; raise::Bool, depwarn::Bool)
   @ Base.Meta ./meta.jl:267
 [4] parse(str::String)
   @ Base.Meta ./meta.jl:266
 [5] convert_expr(ex::Sym; fns::Dict{Any, Any}, values::Dict{Any, Any}, use_julia_code::Bool)
   @ SymPy ~/.julia/packages/SymPy/mpN0u/src/lambdify.jl:260
 [6] top-level scope
   @ In[37]:5

Sorry, convert_expr has some issues like this, hence the need for walk_expression. However, I realize I had some unpushed changes that are causing the differences you see with my example with walk_expression. I just made a PR, which I hope to tag once all the tests pass.

1 Like

Thanks again!

I see that the walk_expression does not know how to handle sqrt and it gives a power of 1/2 instead as can be seen here

Is there a way to get the sqrt from the walk_expression?
(The convert_expr gives sqrt, but as you said it has some issues).

Let me also mention for completeness that in the first time I run these functions I get the following warning:

sys:1: SymPyDeprecationWarning: 

The ``mathematica`` function for the Mathematica parser is now
deprecated. Use ``parse_mathematica`` instead.
The parameter ``additional_translation`` can be replaced by SymPy's
.replace( ) or .subs( ) methods on the output expression instead.

See https://docs.sympy.org/latest/explanation/active-deprecations.html#mathematica-parser-new
for details.

This has been deprecated since SymPy version 1.11. It
will be removed in a future version of SymPy.

Thanks. Looks like a need to update my sympy library!

Anyways, I just registered a fix to lambdify that should also give back sqrt, as you wanted.

2 Likes

Great. I saw that the sqrt was fixed!

Just mentioning that the issue with convert_expr is still there:

And also, this issue is still thereL

I extended @gangchern’s example to a package (GitHub - musoke/WolframExpr.jl). It’s bare bones, but does the basics of converting algebraic expressions from Mathematica to Julia. You can define mappings for special functions.

Example:

julia> using WolframExpr

julia> f = string_to_function("A[x,y]+y", [:A, :x, :y]);

julia> A(x, y) = x^2 + y^2;

julia> f(A, 1, 2)
7
2 Likes

Running now on Julia 1.9.3, with SymPy v2.0.1, it does not have convert_expr and walk_expression.
Does anyone know what is the alternative to these functions?

They are buried behind another module now. For ex coming from sympy_parsing_mathematica.mathematica try:

SymPy.SymPyCore.walk_expression(Sym(ex))
1 Like

Wonderful! that indeed works! thanks so much!

I have an issue with the // operation. The expression I get from sympy does not have parenthesis around the numbers, which causes then an error when I copy the expression into a function.

Here is an example:

using SymPy
const sympy_parsing_mathematica = SymPy.PyCall.pyimport("sympy.parsing.mathematica")

s = raw"1/Sqrt[(x+y)^3]"
ex = sympy_parsing_mathematica.mathematica(s)
SymPy.SymPyCore.walk_expression(Sym(ex))
# :(((x + y) ^ 3) ^ -1//2)

# my goal is to take the above output and make a function out of it.
# I want to do it often, so I want to automate it as much as possible.
# defining
ftest(x, y) = (((x + y) ^ 3) ^ -1//2)
# then results in an error
ftest(1, 1)

Here is the error:

MethodError: no method matching //(::Float64, ::Int64)

Closest candidates are:
  //(::Integer, ::Integer)
   @ Base rational.jl:62
  //(::Rational, ::Integer)
   @ Base rational.jl:64
  //(::Complex, ::Real)
   @ Base rational.jl:78
  ...


Stacktrace:
 [1] ftest(x::Int64, y::Int64)
   @ Main ./In[58]:1
 [2] top-level scope
   @ In[59]:1

Putting parenthesis around the // operation will solve it, but I want to do it automatically in the parsing. Is there a way to do that?

I have found some resolution using @generated macro:

s = raw"1/Sqrt[(x+y)^3]"
ex = sympy_parsing_mathematica.mathematica(s)
f_expr = SymPy.SymPyCore.walk_expression(Sym(ex))
# :(((x + y) ^ 3) ^ -1//2)
@generated ftest(x,y) = f_expr
ftest(1,0)
# 1.

UPDATE:
Let me mention that SymPyCore is not needed anymore, and one should use f_expr = SymPy.walk_expression(Sym(ex)) in the above code.

Looks like there’s still a long way to go, but MathLink.jl now has W2JuliaExpr. Its approach looks roughly similar to WolframExpr.jl, which certainly has lighter dependencies, but maybe closer integration with Wolfram could enable more flexible transformations.