From 00b7982166efe0443e76668d154ab9301ef6a7b2 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 24 Aug 2023 14:15:53 -0400 Subject: [PATCH 1/8] work on extensions --- Project.toml | 12 +++- ext/SymPyPythonCallSymbolicsExt.jl | 93 ++++++++++++++++++++++++++ ext/SymPyPythonCallTermInterfaceExt.jl | 52 ++++++++++++++ src/introspection.jl | 32 +++++++++ 4 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 ext/SymPyPythonCallSymbolicsExt.jl create mode 100644 ext/SymPyPythonCallTermInterfaceExt.jl diff --git a/Project.toml b/Project.toml index 128e9d0..3a88ecb 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "SymPyPythonCall" uuid = "bc8888f7-b21e-4b7c-a06a-5d9c9496438c" authors = ["jverzani and contributors"] -version = "0.1.0" +version = "0.1.1" [deps] CommonEq = "3709ef60-1bee-4518-9f2f-acd86f176c50" @@ -14,6 +14,14 @@ PythonCall = "6099a3de-0909-46bc-b1f4-468b9a2dfc0d" RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" +[weakdeps] +TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" + +[extensions] +SymPyPythonCallTermInterfaceExt = "TermInterface" +SymPyPythonCallSymbolicsExt = "Symbolics" + [compat] julia = "1.6.1" CommonEq = "0.2" @@ -26,6 +34,8 @@ SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" +Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" [targets] test = ["Test"] diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl new file mode 100644 index 0000000..c73f098 --- /dev/null +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -0,0 +1,93 @@ +module SymPyPythonCallSymbolicsExt + +# from https://github.com/JuliaSymbolics/Symbolics.jl/pull/957/ +# by @jClugstor +using SymPyPythonCall +const PythonCall = SymPyPythonCall.PythonCall +import SymPyPythonCall.PythonCall: Py, pyisinstance, pyconvert + +import Symbolics +import Symbolics: @variables +const sp = SymPyPythonCall.sympy + +# rule functions +function pyconvert_rule_sympy_symbol(::Type{Symbolics.Num}, x::Py) + if !pyisinstance(x,sp.Symbol) + return PythonCall.pyconvert_unconverted() + end + name = PythonCall.pyconvert(Symbol,x.name) + return PythonCall.pyconvert_return(Symbolics.variable(name)) +end + +function pyconvert_rule_sympy_pow(::Type{Symbolics.Num}, x::Py) + if !pyisinstance(x,sp.Pow) + return PythonCall.pyconvert_unconverted() + end + 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) + if !pyisinstance(x,sp.Mul) + return PythonCall.pyconvert_unconverted() + end + mult = reduce(*,PythonCall.pyconvert.(Symbolics.Num,x.args)) + return PythonCall.pyconvert_return(mult) +end + +function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x::Py) + if !pyisinstance(x,sp.Add) + return PythonCall.pyconvert_unconverted() + end + sum = reduce(+, PythonCall.pyconvert.(Symbolics.Num,x.args)) + return PythonCall.pyconvert_return(sum) +end + +function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x::Py) + if !pyisinstance(x,sp.Equality) + return PythonCall.pyconvert_unconverted() + end + 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) + if !pyisinstance(x,sp.Derivative) + return PythonCall.pyconvert_unconverted() + end + 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) + if !pyisinstance(x,sp.Function) + return PythonCall.pyconvert_unconverted() + end + 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) + + + +end diff --git a/ext/SymPyPythonCallTermInterfaceExt.jl b/ext/SymPyPythonCallTermInterfaceExt.jl new file mode 100644 index 0000000..55c22b3 --- /dev/null +++ b/ext/SymPyPythonCallTermInterfaceExt.jl @@ -0,0 +1,52 @@ +module SymPyPythonCallTermInterfaceExt + +import SymPyPythonCall +import TermInterface + +#== +Check if x represents an expression tree. If returns true, it will be assumed that operation(::T) and arguments(::T) methods are defined. Definining these three should allow use of SymbolicUtils.simplify on custom types. Optionally symtype(x) can be defined to return the expected type of the symbolic expression. +==# +function TermInterface.istree(x::SymPyPythonCall.SymbolicObject) + !(convert(Bool, x.is_Atom)) +end + +#== +f x is a term as defined by istree(x), exprhead(x) must return a symbol, corresponding to the head of the Expr most similar to the term x. If x represents a function call, for example, the exprhead is :call. If x represents an indexing operation, such as arr[i], then exprhead is :ref. Note that exprhead is different from operation and both functions should be defined correctly in order to let other packages provide code generation and pattern matching features. +==# +function TermInterface.exprhead(x::SymPyPythonCall.SymbolicObject) + :call # this is not right +end + +#== +Returns the head (a function object) performed by an expression tree. Called only if istree(::T) is true. Part of the API required for simplify to work. Other required methods are arguments and istree +==# +function TermInterface.operation(x::SymPyPythonCall.SymbolicObject) + @assert TermInterface.istree(x) + nm = Symbol(SymPyPythonCall.Introspection.funcname(x)) + + λ = get(SymPyPythonCall.Introspection.funcname2function, nm, nothing) + if isnothing(λ) + return getfield(Main, nm) + else + return λ + end +end + + +#== +Returns the arguments (a Vector) for an expression tree. Called only if istree(x) is true. Part of the API required for simplify to work. Other required methods are operation and istree +==# +function TermInterface.arguments(x::SymPyPythonCall.SymbolicObject) + collect(SymPyPythonCall.Introspection.args(x)) +end + +#== +Construct a new term with the operation f and arguments args, the term should be similar to t in type. if t is a SymbolicUtils.Term object a new Term is created with the same symtype as t. If not, the result is computed as f(args...). Defining this method for your term type will reduce any performance loss in performing f(args...) (esp. the splatting, and redundant type computation). T is the symtype of the output term. You can use SymbolicUtils.promote_symtype to infer this type. The exprhead keyword argument is useful when creating Exprs. +==# +function TermInterface.similarterm(t::SymPyPythonCall.SymbolicObject, f, args, symtype=nothing; + metadata=nothing, exprhead=TermInterface.exprhead(t)) + f(args...) # default +end + + +end diff --git a/src/introspection.jl b/src/introspection.jl index 627e4b0..f0cd70f 100644 --- a/src/introspection.jl +++ b/src/introspection.jl @@ -51,4 +51,36 @@ classname(x::T) where {T <: Union{Sym, Py}} = (cls = class(x); isnothing(cls) ? # Dict(u=>v for (u,v) in inspect.getmembers(x)) #end +## Map to get function object from type information +const funcname2function = ( + Add = +, + Sub = -, + Mul = *, + Div = /, + Pow = ^, + re = real, + im = imag, + Abs = abs, + Min = min, + Max = max, + Poly = identity, + Piecewise = error, # replace + Order = (as...) -> 0, + And = (as...) -> all(as), + Or = (as...) -> any(as), + Less = <, + LessThan = <=, + StrictLessThan = <, + Equal = ==, + Equality = ==, + Unequality = !==, + StrictGreaterThan = >, + GreaterThan = >=, + Greater = >, + conjugate = conj, + atan2 = atan, + TupleArg = tuple, + Heaviside = (a...) -> (a[1] < 0 ? 0 : (a[1] > 0 ? 1 : (length(a) > 1 ? a[2] : NaN))), +) + end From fc40a8aa64a118762382dfff8a8eabb58b9861a8 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 24 Aug 2023 15:56:34 -0400 Subject: [PATCH 2/8] SymbolicUtils, not TermInterface; use .py --- Project.toml | 6 +++--- ...t.jl => SymPyPythonCallSymbolicUtilsExt.jl} | 18 +++++++++--------- ext/SymPyPythonCallSymbolicsExt.jl | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) rename ext/{SymPyPythonCallTermInterfaceExt.jl => SymPyPythonCallSymbolicUtilsExt.jl} (83%) diff --git a/Project.toml b/Project.toml index 3a88ecb..7cd531e 100644 --- a/Project.toml +++ b/Project.toml @@ -15,12 +15,12 @@ RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" [weakdeps] -TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" [extensions] -SymPyPythonCallTermInterfaceExt = "TermInterface" SymPyPythonCallSymbolicsExt = "Symbolics" +SymPyPythonCallSymbolicUtilsExt = "SymbolicUtils" [compat] julia = "1.6.1" @@ -34,8 +34,8 @@ SpecialFunctions = "0.8, 0.9, 0.10, 1.0, 2" [extras] Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" -TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" +SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" [targets] test = ["Test"] diff --git a/ext/SymPyPythonCallTermInterfaceExt.jl b/ext/SymPyPythonCallSymbolicUtilsExt.jl similarity index 83% rename from ext/SymPyPythonCallTermInterfaceExt.jl rename to ext/SymPyPythonCallSymbolicUtilsExt.jl index 55c22b3..cac0afe 100644 --- a/ext/SymPyPythonCallTermInterfaceExt.jl +++ b/ext/SymPyPythonCallSymbolicUtilsExt.jl @@ -1,27 +1,27 @@ -module SymPyPythonCallTermInterfaceExt +module SymPyPythonCallSymbolicUtilsExt import SymPyPythonCall -import TermInterface +import SymbolicUtils #== Check if x represents an expression tree. If returns true, it will be assumed that operation(::T) and arguments(::T) methods are defined. Definining these three should allow use of SymbolicUtils.simplify on custom types. Optionally symtype(x) can be defined to return the expected type of the symbolic expression. ==# -function TermInterface.istree(x::SymPyPythonCall.SymbolicObject) +function SymbolicUtils.istree(x::SymPyPythonCall.SymbolicObject) !(convert(Bool, x.is_Atom)) end #== f x is a term as defined by istree(x), exprhead(x) must return a symbol, corresponding to the head of the Expr most similar to the term x. If x represents a function call, for example, the exprhead is :call. If x represents an indexing operation, such as arr[i], then exprhead is :ref. Note that exprhead is different from operation and both functions should be defined correctly in order to let other packages provide code generation and pattern matching features. -==# function TermInterface.exprhead(x::SymPyPythonCall.SymbolicObject) :call # this is not right end +==# #== Returns the head (a function object) performed by an expression tree. Called only if istree(::T) is true. Part of the API required for simplify to work. Other required methods are arguments and istree ==# -function TermInterface.operation(x::SymPyPythonCall.SymbolicObject) - @assert TermInterface.istree(x) +function SymbolicUtils.operation(x::SymPyPythonCall.SymbolicObject) + @assert SymbolicUtils.istree(x) nm = Symbol(SymPyPythonCall.Introspection.funcname(x)) λ = get(SymPyPythonCall.Introspection.funcname2function, nm, nothing) @@ -36,15 +36,15 @@ end #== Returns the arguments (a Vector) for an expression tree. Called only if istree(x) is true. Part of the API required for simplify to work. Other required methods are operation and istree ==# -function TermInterface.arguments(x::SymPyPythonCall.SymbolicObject) +function SymbolicUtils.arguments(x::SymPyPythonCall.SymbolicObject) collect(SymPyPythonCall.Introspection.args(x)) end #== Construct a new term with the operation f and arguments args, the term should be similar to t in type. if t is a SymbolicUtils.Term object a new Term is created with the same symtype as t. If not, the result is computed as f(args...). Defining this method for your term type will reduce any performance loss in performing f(args...) (esp. the splatting, and redundant type computation). T is the symtype of the output term. You can use SymbolicUtils.promote_symtype to infer this type. The exprhead keyword argument is useful when creating Exprs. ==# -function TermInterface.similarterm(t::SymPyPythonCall.SymbolicObject, f, args, symtype=nothing; - metadata=nothing, exprhead=TermInterface.exprhead(t)) +function SymbolicUtils.similarterm(t::SymPyPythonCall.SymbolicObject, f, args, symtype=nothing; + metadata=nothing, exprhead=:call) f(args...) # default end diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl index c73f098..c9fae33 100644 --- a/ext/SymPyPythonCallSymbolicsExt.jl +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -3,12 +3,12 @@ module SymPyPythonCallSymbolicsExt # from https://github.com/JuliaSymbolics/Symbolics.jl/pull/957/ # by @jClugstor using SymPyPythonCall +const sp = SymPyPythonCall.sympy.py const PythonCall = SymPyPythonCall.PythonCall import SymPyPythonCall.PythonCall: Py, pyisinstance, pyconvert import Symbolics import Symbolics: @variables -const sp = SymPyPythonCall.sympy # rule functions function pyconvert_rule_sympy_symbol(::Type{Symbolics.Num}, x::Py) From 072ad2d75d8ab2ce2379ac08fb319cf04235d0e4 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 24 Aug 2023 17:42:55 -0400 Subject: [PATCH 3/8] WIP --- ext/SymPyPythonCallSymbolicsExt.jl | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl index c9fae33..4cd0dd1 100644 --- a/ext/SymPyPythonCallSymbolicsExt.jl +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -2,10 +2,10 @@ module SymPyPythonCallSymbolicsExt # from https://github.com/JuliaSymbolics/Symbolics.jl/pull/957/ # by @jClugstor -using SymPyPythonCall +import SymPyPythonCall const sp = SymPyPythonCall.sympy.py const PythonCall = SymPyPythonCall.PythonCall -import SymPyPythonCall.PythonCall: Py, pyisinstance, pyconvert +import PythonCall: Py, pyisinstance, pyconvert import Symbolics import Symbolics: @variables @@ -38,7 +38,7 @@ end function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x::Py) if !pyisinstance(x,sp.Add) - return PythonCall.pyconvert_unconverted() + return PythonCall.pyconvert_unconverted() end sum = reduce(+, PythonCall.pyconvert.(Symbolics.Num,x.args)) return PythonCall.pyconvert_return(sum) @@ -46,13 +46,12 @@ end function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x::Py) if !pyisinstance(x,sp.Equality) - return PythonCall.pyconvert_unconverted() + return PythonCall.pyconvert_unconverted() end 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) if !pyisinstance(x,sp.Derivative) return PythonCall.pyconvert_unconverted() @@ -74,10 +73,10 @@ function pyconvert_rule_sympy_function(::Type{Symbolics.Num}, x::Py) 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.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) + 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) From a5e0b11a1f805c6be96438d3bed1f4757a8b4b85 Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 24 Aug 2023 17:55:22 -0400 Subject: [PATCH 4/8] WIP --- ext/SymPyPythonCallSymbolicsExt.jl | 32 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl index 4cd0dd1..23fe1de 100644 --- a/ext/SymPyPythonCallSymbolicsExt.jl +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -3,69 +3,83 @@ module SymPyPythonCallSymbolicsExt # from https://github.com/JuliaSymbolics/Symbolics.jl/pull/957/ # by @jClugstor import SymPyPythonCall -const sp = SymPyPythonCall.sympy.py +#const sp = SymPyPythonCall.sympy.py const PythonCall = SymPyPythonCall.PythonCall -import PythonCall: Py, pyisinstance, pyconvert +import PythonCall: pyconvert import Symbolics import Symbolics: @variables # rule functions -function pyconvert_rule_sympy_symbol(::Type{Symbolics.Num}, x::Py) +function pyconvert_rule_sympy_symbol(::Type{Symbolics.Num}, x) + #= if !pyisinstance(x,sp.Symbol) return PythonCall.pyconvert_unconverted() end + =# name = PythonCall.pyconvert(Symbol,x.name) return PythonCall.pyconvert_return(Symbolics.variable(name)) end -function pyconvert_rule_sympy_pow(::Type{Symbolics.Num}, x::Py) +function pyconvert_rule_sympy_pow(::Type{Symbolics.Num}, x) + #= if !pyisinstance(x,sp.Pow) return PythonCall.pyconvert_unconverted() end + =# 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) +function pyconvert_rule_sympy_mul(::Type{Symbolics.Num}, x) + #= if !pyisinstance(x,sp.Mul) return PythonCall.pyconvert_unconverted() end + =# mult = reduce(*,PythonCall.pyconvert.(Symbolics.Num,x.args)) return PythonCall.pyconvert_return(mult) end -function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x::Py) +function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x) + #= if !pyisinstance(x,sp.Add) return PythonCall.pyconvert_unconverted() end + =# sum = reduce(+, PythonCall.pyconvert.(Symbolics.Num,x.args)) return PythonCall.pyconvert_return(sum) end -function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x::Py) +function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x) + #= if !pyisinstance(x,sp.Equality) return PythonCall.pyconvert_unconverted() end + =# 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) +function pyconvert_rule_sympy_derivative(::Type{Symbolics.Num}, x) + #= if !pyisinstance(x,sp.Derivative) return PythonCall.pyconvert_unconverted() end + =# 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) +function pyconvert_rule_sympy_function(::Type{Symbolics.Num}, x) + #= if !pyisinstance(x,sp.Function) return PythonCall.pyconvert_unconverted() end + =# name = pyconvert(Symbol,x.name) args = pyconvert.(Symbolics.Num,x.args) func = @variables $name(..) From 13595758bae5fbdf97e8ff293025bd06a1fbb36c Mon Sep 17 00:00:00 2001 From: jverzani Date: Thu, 24 Aug 2023 21:56:17 -0400 Subject: [PATCH 5/8] needed init! --- ext/SymPyPythonCallSymbolicsExt.jl | 38 +++++++++++------------------- 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl index 23fe1de..fed4ee4 100644 --- a/ext/SymPyPythonCallSymbolicsExt.jl +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -3,71 +3,61 @@ module SymPyPythonCallSymbolicsExt # from https://github.com/JuliaSymbolics/Symbolics.jl/pull/957/ # by @jClugstor import SymPyPythonCall -#const sp = SymPyPythonCall.sympy.py +sp = SymPyPythonCall.sympy.py const PythonCall = SymPyPythonCall.PythonCall -import PythonCall: pyconvert +import PythonCall: pyconvert, pyimport, pyisinstance import Symbolics import Symbolics: @variables # rule functions +function pyconvert_rule_sympy_symbolX(::Type{Symbolics.Num}, x) +end function pyconvert_rule_sympy_symbol(::Type{Symbolics.Num}, x) - #= if !pyisinstance(x,sp.Symbol) return PythonCall.pyconvert_unconverted() end - =# name = PythonCall.pyconvert(Symbol,x.name) return PythonCall.pyconvert_return(Symbolics.variable(name)) end function pyconvert_rule_sympy_pow(::Type{Symbolics.Num}, x) - #= if !pyisinstance(x,sp.Pow) return PythonCall.pyconvert_unconverted() end - =# 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) - #= if !pyisinstance(x,sp.Mul) return PythonCall.pyconvert_unconverted() end - =# mult = reduce(*,PythonCall.pyconvert.(Symbolics.Num,x.args)) return PythonCall.pyconvert_return(mult) end function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x) - #= if !pyisinstance(x,sp.Add) return PythonCall.pyconvert_unconverted() end - =# sum = reduce(+, PythonCall.pyconvert.(Symbolics.Num,x.args)) return PythonCall.pyconvert_return(sum) end function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x) - #= if !pyisinstance(x,sp.Equality) return PythonCall.pyconvert_unconverted() end - =# 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) - #= if !pyisinstance(x,sp.Derivative) return PythonCall.pyconvert_unconverted() end - =# variables = pyconvert.(Symbolics.Num,x.variables) derivatives = prod(var -> Differential(var), variables) expr = pyconvert(Symbolics.Num, x.expr) @@ -75,32 +65,32 @@ function pyconvert_rule_sympy_derivative(::Type{Symbolics.Num}, x) end function pyconvert_rule_sympy_function(::Type{Symbolics.Num}, x) - #= if !pyisinstance(x,sp.Function) return PythonCall.pyconvert_unconverted() end - =# 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.symbol:Symbol", Symbolics.Num, pyconvert_rule_sympy_symbol) -PythonCall.pyconvert_add_rule("sympy.core.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) +function __init__() + # added rules + 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.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) -PythonCall.pyconvert_add_rule("sympy.core.add:Add", Symbolics.Num, pyconvert_rule_sympy_add) + PythonCall.pyconvert_add_rule("sympy.core.mul:Mul", Symbolics.Num, pyconvert_rule_sympy_mul) -PythonCall.pyconvert_add_rule("sympy.core.relational:Equality", Symbolics.Equation, pyconvert_rule_sympy_equality) + PythonCall.pyconvert_add_rule("sympy.core.add:Add", Symbolics.Num, pyconvert_rule_sympy_add) -PythonCall.pyconvert_add_rule("sympy.core.function:Derivative",Symbolics.Num, pyconvert_rule_sympy_derivative) + PythonCall.pyconvert_add_rule("sympy.core.relational:Equality", Symbolics.Equation, pyconvert_rule_sympy_equality) -PythonCall.pyconvert_add_rule("sympy.core.function:Function",Symbolics.Num, pyconvert_rule_sympy_function) + 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) +end end From 3ab988ab4e5950d353788debacac504060bc89dc Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 25 Aug 2023 10:01:17 -0400 Subject: [PATCH 6/8] add core numbers --- Project.toml | 2 +- ext/SymPyPythonCallSymbolicsExt.jl | 28 +++++++++++++++++++++++++--- test/runtests.jl | 4 ++++ test/symbolics-integration.jl | 4 ++++ 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 test/symbolics-integration.jl diff --git a/Project.toml b/Project.toml index 7cd531e..98aad17 100644 --- a/Project.toml +++ b/Project.toml @@ -38,4 +38,4 @@ Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7" SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b" [targets] -test = ["Test"] +test = ["Symbolics", "Test"] diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl index fed4ee4..fcc3758 100644 --- a/ext/SymPyPythonCallSymbolicsExt.jl +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -68,8 +68,10 @@ function pyconvert_rule_sympy_function(::Type{Symbolics.Num}, x) if !pyisinstance(x,sp.Function) return PythonCall.pyconvert_unconverted() end - name = pyconvert(Symbol,x.name) - args = pyconvert.(Symbolics.Num,x.args) + nm = PythonCall.pygetattr(x, "func", nothing) + isnothing(nm) && return PythonCall.pyconvert_unconverted() # XXX + name = pyconvert(Symbol, nm) + args = pyconvert.(Symbolics.Num, x.args) func = @variables $name(..) return PythonCall.pyconvert_return(first(func)(args...)) end @@ -77,6 +79,8 @@ end function __init__() # added rules + add_pyconvert_rule(f, cls) = PythonCall.pyconvert_add_rule(cls, Symbolics.Num, f) + PythonCall.pyconvert_add_rule("sympy.core.symbol:Symbol", Symbolics.Num, pyconvert_rule_sympy_symbol) PythonCall.pyconvert_add_rule("sympy.core.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) @@ -90,7 +94,25 @@ function __init__() 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) -end + # core numbers + add_pyconvert_rule("sympy.core.numbers:Pi") do T::Type{Symbolics.Num}, x + PythonCall.pyconvert_return(Symbolics.Num(pi)) + end + add_pyconvert_rule("sympy.core.numbers:Exp1") do T::Type{Symbolics.Num}, x + PythonCall.pyconvert_return(Symbolics.Num(ℯ)) + end + add_pyconvert_rule("sympy.core.numbers:Infinity") do T::Type{Symbolics.Num}, x + PythonCall.pyconvert_return(Symbolics.Num(Inf)) + end + #= complex numbers and Num needs some workaround + add_pyconvert_rule("sympy.core.numbers:ImaginaryUnit") do T::Type{Symbolics.Num}, x + PythonCall.pyconvert_return(Symbolics.Num(im)) + end + add_pyconvert_rule("sympy.core.numbers:ComplexInfinity") do T::Type{Symbolics.Num}, x + PythonCall.pyconvert_return(Symbolics.Num(Inf)) # errors: Complex(Inf,Inf))) + end + =# +end end diff --git a/test/runtests.jl b/test/runtests.jl index a4d56b2..d8d065d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -13,3 +13,7 @@ include("test-specialfuncs.jl") #include("test-physics.jl") #include("test-external-module.jl") include("test-latexify.jl") + +if VERSION >= v"1.9.0-" + @testset "Symbolics integration" begin include("symbolics-integration.jl") end +end diff --git a/test/symbolics-integration.jl b/test/symbolics-integration.jl new file mode 100644 index 0000000..808fc86 --- /dev/null +++ b/test/symbolics-integration.jl @@ -0,0 +1,4 @@ +using SymPyPythonCall +using Symbolics + +@test isa(SymPyPythonCall.PythonCall.pyconvert(Symbolics.Num, sympy.sympify("x")), Symbolics.Num) From f355167f6756e3e3b89d98f925f310266ddb3b32 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 25 Aug 2023 17:01:01 -0400 Subject: [PATCH 7/8] edit --- ext/SymPyPythonCallSymbolicsExt.jl | 35 ++++++++++++++++-------------- test/symbolics-integration.jl | 2 +- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/ext/SymPyPythonCallSymbolicsExt.jl b/ext/SymPyPythonCallSymbolicsExt.jl index fcc3758..7f5076f 100644 --- a/ext/SymPyPythonCallSymbolicsExt.jl +++ b/ext/SymPyPythonCallSymbolicsExt.jl @@ -46,14 +46,6 @@ function pyconvert_rule_sympy_add(::Type{Symbolics.Num}, x) return PythonCall.pyconvert_return(sum) end -function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x) - if !pyisinstance(x,sp.Equality) - return PythonCall.pyconvert_unconverted() - end - 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) if !pyisinstance(x,sp.Derivative) return PythonCall.pyconvert_unconverted() @@ -76,26 +68,37 @@ function pyconvert_rule_sympy_function(::Type{Symbolics.Num}, x) return PythonCall.pyconvert_return(first(func)(args...)) end +function pyconvert_rule_sympy_equality(::Type{Symbolics.Equation}, x) + if !pyisinstance(x,sp.Equality) + return PythonCall.pyconvert_unconverted() + end + rhs = pyconvert(Symbolics.Num,x.rhs) + lhs = pyconvert(Symbolics.Num,x.lhs) + return PythonCall.pyconvert_return(rhs ~ lhs) +end + function __init__() # added rules - add_pyconvert_rule(f, cls) = PythonCall.pyconvert_add_rule(cls, Symbolics.Num, f) - + # T = Symbolics.Num PythonCall.pyconvert_add_rule("sympy.core.symbol:Symbol", Symbolics.Num, pyconvert_rule_sympy_symbol) - PythonCall.pyconvert_add_rule("sympy.core.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) + PythonCall.pyconvert_add_rule("sympy.core.power:Pow", Symbolics.Num, pyconvert_rule_sympy_pow) - PythonCall.pyconvert_add_rule("sympy.core.mul:Mul", Symbolics.Num, pyconvert_rule_sympy_mul) + 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.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:Derivative",Symbolics.Num, pyconvert_rule_sympy_derivative) + PythonCall.pyconvert_add_rule("sympy.core.function:Function", Symbolics.Num, pyconvert_rule_sympy_function) - PythonCall.pyconvert_add_rule("sympy.core.function:Function",Symbolics.Num, pyconvert_rule_sympy_function) + # T = Symbolics.Equation + PythonCall.pyconvert_add_rule("sympy.core.relational:Equality", Symbolics.Equation, pyconvert_rule_sympy_equality) # core numbers + add_pyconvert_rule(f, cls) = PythonCall.pyconvert_add_rule(cls, Symbolics.Num, f) + add_pyconvert_rule("sympy.core.numbers:Pi") do T::Type{Symbolics.Num}, x PythonCall.pyconvert_return(Symbolics.Num(pi)) end diff --git a/test/symbolics-integration.jl b/test/symbolics-integration.jl index 808fc86..5146356 100644 --- a/test/symbolics-integration.jl +++ b/test/symbolics-integration.jl @@ -1,4 +1,4 @@ using SymPyPythonCall -using Symbolics +import Symbolics @test isa(SymPyPythonCall.PythonCall.pyconvert(Symbolics.Num, sympy.sympify("x")), Symbolics.Num) From e7cf818bbd76a4ce02a246220bcab41f5c239332 Mon Sep 17 00:00:00 2001 From: jverzani Date: Fri, 25 Aug 2023 17:14:14 -0400 Subject: [PATCH 8/8] work on docs --- README.md | 13 ++++++++----- docs/make.jl | 13 ++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index fa5b904..9a478b4 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,19 @@ [![Coverage](https://codecov.io/gh/jverzani/SymPyPythonCall.jl/branch/main/graph/badge.svg)](https://codecov.io/gh/jverzani/SymPyPythonCall.jl) -This is a start on what is needed to use `PythonCall` instead of `PyCall` for `SymPy.jl`. -At the moment, the expectation is that *if* that change proves desirable, this would become `SymPy`. +This package allows access to the [SymPy](https://www.sympy.org/en/index.html) Python library to `Julia` users through [PythonCall](https://github.com/cjdoris/PythonCall.jl). -For now, there are some small design decisions from `SymPy` reflected here: +(The more established [SymPy.jl](https://github.com/JuliaPy/SymPy.jl) uses [PyCall.jl](https://github.com/JuliaPy/PyCall.jl).) -There would be a few deprecations: +At the moment, the expectation is that *if* that change proves desirable, this would become `SymPy`, but for now this is a standalone package. This may be or interest for those having difficulty installing the underlying `sympy` library using `PyCall`. + +---- + +Though nearly the same as `SymPy.jl`, for now, there are some small design decisions differing from `SymPy`: * `@vars` would be deprecated; use `@syms` only -* `elements` for sets would be removed (convert to a `Set` by default) +* `elements` for sets is deprecated (conversion to a `Set` is the newdefault) * `sympy.poly` *not* `sympy.Poly` diff --git a/docs/make.jl b/docs/make.jl index 9f3f8d6..69cc6ab 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -5,7 +5,18 @@ ENV["GKSwstype"] = "100" using SymPyPythonCall using Documenter -makedocs(sitename="My Documentation") +makedocs( + sitename = "SymPyPythonCall", + format = Documenter.HTML(), + modules = [SymPyPythonCall] +) + +# Documenter can also automatically deploy documentation to gh-pages. +# See "Hosting Documentation" and deploydocs() in the Documenter manual +# for more information. +deploydocs( + repo = "github.com/jverzani/SymPyPythonCall.jl.git" +) #DocMeta.setdocmeta!(SymPyPythonCall, :DocTestSetup, :(using SymPyPythonCall); recursive=true)