diff --git a/Project.toml b/Project.toml index 8c085d797..bdc1f0985 100644 --- a/Project.toml +++ b/Project.toml @@ -35,9 +35,11 @@ TreeViews = "a2a6695c-b41b-5b7d-aed9-dbfdeacea5d7" [weakdeps] SymPy = "24249f21-da20-56a4-8eb1-6a02cf4ae2e6" +PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" [extensions] SymbolicsSymPyExt = "SymPy" +SymPySymbolicsExt = "PythonCall" [compat] ArrayInterface = "6, 7" diff --git a/ext/SymPySymbolicsExt.jl b/ext/SymPySymbolicsExt.jl new file mode 100644 index 000000000..d46866a49 --- /dev/null +++ b/ext/SymPySymbolicsExt.jl @@ -0,0 +1,76 @@ +module SymPySymbolics +if isdefined(Base,:get_extension) + using Symbolics + using PythonCall + using CondaPkg +else + using ..Symbolics + using ..PythonCall + using ..CondaPkg +end + +# rule functions +function pyconvert_rule_sympy_symbol(::Type{Symbolics.Num}, x::Py) + name = PythonCall.pyconvert(Symbol,x.name) + return PythonCall.pyconvert_return(Symbolics.variable(name)) +end + +function pyconvert_rule_sympy_pow(::Type{Symbolics.Num}, x::Py) + expbase = pyconvert(Symbolics.Num,x.base) + exp = pyconvert(Symbolics.Num,x.exp) + return PythonCall.pyconvert_return(expbase^exp) +end + +function pyconvert_rule_sympy_mul(::Type{Symbolics.Num}, x::Py) + mult = reduce(*,PythonCall.pyconvert.(Symbolics.Num,x.args)) + return PythonCall.pyconvert_return(mult) +end + +function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x::Py) + sum = reduce(+, PythonCall.pyconvert.(Symbolics.Num,x.args)) + return PythonCall.pyconvert_return(sum) +end + +function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x::Py) + rhs = pyconvert(Symbolics.Num,x.rhs) + lhs = pyconvert(Symbolics.Num,x.lhs) + return PythonCall.pyconvert_return(rhs ~ lhs) +end + +function pyconvert_rule_sympy_derivative(::Type{Symbolics.Num}, x::Py) + variables = pyconvert.(Symbolics.Num,x.variables) + derivatives = prod(var -> Differential(var), variables) + expr = pyconvert(Symbolics.Num, x.expr) + return PythonCall.pyconvert_return(derivatives(expr)) +end + +function pyconvert_rule_sympy_function(::Type{Symbolics.Num}, x::Py) + name = pyconvert(Symbol,x.name) + args = pyconvert.(Symbolics.Num,x.args) + func = @variables $name(..) + return PythonCall.pyconvert_return(first(func)(args...)) +end + +# added rules +PythonCall.pyconvert_add_rule("sympy.core.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) + +PythonCall.pyconvert_add_rule("sympy.core.symbol:Symbol", Symbolics.Num, pyconvert_rule_sympy_symbol) + +PythonCall.pyconvert_add_rule("sympy.core.mul:Mul", Symbolics.Num, pyconvert_rule_sympy_mul) + +PythonCall.pyconvert_add_rule("sympy.core.add:Add", Symbolics.Num, pyconvert_rule_sympy_add) + +PythonCall.pyconvert_add_rule("sympy.core.relational:Equality", Symbolics.Equation, pyconvert_rule_sympy_equality) + +PythonCall.pyconvert_add_rule("sympy.core.function:Derivative",Symbolics.Num, pyconvert_rule_sympy_derivative) + +PythonCall.pyconvert_add_rule("sympy.core.function:Function",Symbolics.Num, pyconvert_rule_sympy_function) + +function sympy_to_symbolics(expr::Py) + if pyconvert(Bool,pytype(expr) == sp.core.relational.Equality) + return pyconvert(Symbolics.Equation,expr) + end + return pyconvert(Symbolics.Num,expr) +end + +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 530e23af1..60d41f7e6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -41,6 +41,7 @@ if GROUP == "All" || GROUP == "Core" @safetestset "Latexify Test" begin include("latexify.jl") end @safetestset "Domain Test" begin include("domains.jl") end @safetestset "SymPy Test" begin include("sympy.jl") end + @safetestset "SymPy to Symbolics Test" begin include("sympy_to_symbolics.jl") end @safetestset "Inequality Test" begin include("inequality.jl") end @safetestset "Integral Test" begin include("integral.jl") end @safetestset "CartesianIndex Test" begin include("cartesianindex.jl") end diff --git a/test/sympy_to_symbolics.jl b/test/sympy_to_symbolics.jl new file mode 100644 index 000000000..e813882ea --- /dev/null +++ b/test/sympy_to_symbolics.jl @@ -0,0 +1,33 @@ +using Test +using PythonCall +using Symbolics + +CondaPkg.add("sympy") + +sp = pyimport("sympy") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("t"))) == Symbol("t") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("t**t"))) == Symbol("^(t, t)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("t + z + d"))) == Symbol("+(d, t, z)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("t*z*9"))) == Symbol("*(9, t, z)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("5*t*z + 3*d + h/(b*5)"))) == Symbol("+(3d, ((1//5)*h) / b, 5t*z)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("t * n/z * t**4 * h**z + l*h - j"))) == Symbol("+(-j, h*l, (n*(h^z)*(t^5)) / z)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("Eq(2,5, evaluate = False)"))) + +@test Symbol(sympy_to_symbolics(sp.sympify("Eq(t*x + 5**x + 20/t, 90/t + t**4 - t*z)"))) == Symbol("t^4 + 90 / t - t*z ~ 20 / t + t*x + 5^x") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("Function('f')(x)"))) == Symbol("f(x)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("f(x,y)"))) == Symbol("f(x, y)") + +@test Symbol((sympy_to_symbolics(sp.sympify("Eq(f(x), 2*x +1)")))) == Symbol("1 + 2x ~ f(x)") + +@test Symbolics.tosymbol(sympy_to_symbolics(sp.sympify("Derivative(f(x),x)"))) == Symbol("fˍx(x)") + +@test Symbol(sympy_to_symbolics(sp.sympify("Eq(Derivative(f(x),x), x)"))) == Symbol("x ~ Differential(x)(f(x))")