Skip to content

Commit

Permalink
adjust to use getproperty over getindex with PyObject values (#254)
Browse files Browse the repository at this point in the history
* adjust to use getproperty over getindex with PyObject values

* adjust to getproperty interface
  • Loading branch information
jverzani authored Feb 28, 2019
1 parent 3da7674 commit ff91a83
Show file tree
Hide file tree
Showing 25 changed files with 252 additions and 257 deletions.
76 changes: 33 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[![SymPy](http://pkg.julialang.org/badges/SymPy_0.7.svg)](http://pkg.julialang.org/?pkg=SymPy&ver=0.7)
[![SymPy](http://pkg.julialang.org/badges/SymPy_0.7.svg)](http://pkg.julialang.org/?pkg=SymPy&ver=0.7)

Linux: [![Build Status](https://travis-ci.org/JuliaPy/SymPy.jl.svg?branch=master)](https://travis-ci.org/JuliaPy/SymPy.jl)
 
Expand All @@ -10,7 +10,7 @@ Windows: [![Build Status](https://ci.appveyor.com/api/projects/status/github/Jul



The `SymPy` package (`http://sympy.org/`) is a Python library for symbolic mathematics.
The `SymPy` package (`http://sympy.org/`) is a Python library for symbolic mathematics.

With the excellent `PyCall` package of `julia`, one has access to the
many features of `SymPy` from within a `Julia` session.
Expand Down Expand Up @@ -44,37 +44,37 @@ The only point to this package is that using `PyCall` to access
a symbolic value `x`, take its sine, then evaluate at `pi`, say:

```
using PyCall
@pyimport sympy
using PyCall
sympy = pyimport("sympy")
x = sympy.Symbol("x")
y = sympy.sin(x)
y[:subs](x, sympy.pi) |> float
z = y.subs(x, sympy.pi)
convert(Float64, z)
```

The `Symbol` and `sin` function of `SymPy` are found within the
imported `sympy` object. They may be referenced with `Python`'s dot
notation. However, the `subs` method of the `y` object is accessed
differently, using indexing notation with a symbol. The call above
substitutes a value of `sympy.pi` for `x`. This leaves the object as a
`PyObject` storing a number which can be brought back into `julia`
through conversion, in this case with the `float` function.
notation. Similarly, the `subs` method of the `y` object may be
accessed with Python's dot nottation using PyCall's `getproperty`
overload to call the method. The call above substitutes a value of
`sympy.pi` for `x`. This leaves the object as a `PyObject` storing a
number which can be brought back into `julia` through conversion, in
this case through an explicit `convert` call.

The above isn't so awkward, but even more cumbersome is the similarly
simple task of finding `sin(pi*x)`. As this multiplication is done at
the python level and is not a method of `sympy` or the `x` object, we
need to evaluate python code. Here is one solution:

Alternatively, `PyCall` now has a `*` method, so this could be done with

```
x = sympy.Symbol("x")
y = pycall(sympy.Mul, PyAny, sympy.pi, x)
z = sympy.sin(y)
z[:subs](x, 1) |> float
y = sympy.pi * x
z = sympy.sin(y)
convert(Float64, z.subs(x, 1))
```

This gets replaced by a more `julia`n syntax:
With `SymPy` this gets replaced by a more `julia`n syntax:

```
using SymPy
using SymPy
x = symbols("x") # or @vars x, Sym("x"), or Sym(:x)
y = sin(pi*x)
y(1) # Does subs(y, x, 1). Use y(x=>1) to be specific as to which symbol to substitute
Expand All @@ -86,31 +86,26 @@ that working with symbolic expressions can use natural `julia`
idioms. The final result here is a symbolic value of `0`, which
prints as `0` and not `PyObject 0`. To convert it into a numeric value
within `Julia`, the `N` function may be used, which acts like the
`float` call, only there is an attempt to preserve the variable type.
float conversion, only there is an attempt to preserve the variable type.

(There is a subtlety, the value of `pi` here (an `Irrational` in `Julia`) is converted to the
symbolic `PI`, but in general won't be if the math constant is coerced
to a floating point value before it encounters a symbolic object. It
is better to just use the symbolic value `PI`, an alias for `sympy.pi`
used above. A similar comment applies for `e`, where `E` should be
used.)
(There is a subtlety, the value of `pi` here (an `Irrational` in
`Julia`) is converted to the symbolic `PI`, but in general won't be if
the math constant is coerced to a floating point value before it
encounters a symbolic object. It is better to just use the symbolic
value `PI`, an alias for `sympy.pi` used above. A similar comment
applies for `e`, where `E` should be used.)

However, for some tasks the `PyCall` interface is still needed, as
only a portion of the `SymPy` interface is exposed. To call an
underlying SymPy method, the `getindex` method is overloaded for
`symbol` indices so that `ex[:meth_name](...)` dispatches to either to
SymPy's `ex.meth_name(...)` or `meth_name(ex, ...)`, as possible.

underlying SymPy method, the `getproperty` method is overloaded so
that `ex.meth_name(...)` dispatches the method of the object and
`sympy.meth_name(...)` calls a function in the SymPy module.

There is a `sympy` string macro to simplify this a bit, with the call
looking like: `sympy"meth_name"(...)`, for example
`sympy"harmonic"(10)`. For another example, the above could also be done
through:
For example, we could have been more explicit and used:

```
@vars x
y = sympy"sin"(pi * x)
y(1)
y = sympy.sin(pi * x)
y.subs(x, 1)
```

As calling the underlying SymPy function is not difficult, the
Expand All @@ -136,14 +131,9 @@ det(a)
Can be replaced with

```
sympy"det"(a)
sympy.det(a)
```

Similarly for `trace`, `eigenvects`, ... . Note these are `sympy`
methods, not `Julia` methods that have been ported. (Hence,
`eigenvects` and not `eigvecs`.)





2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
julia 0.7.0
PyCall 1.7.1
PyCall 1.90.0
RecipesBase 0.5.0
SpecialFunctions 0.3.4
21 changes: 11 additions & 10 deletions src/SymPy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ for prop in union(core_object_properties,
polynomial_predicates)

prop_name = string(prop)
@eval ($prop)(ex::SymbolicObject) = PyObject(ex)[Symbol($prop_name)]
@eval ($prop)(ex::SymbolicObject) = getproperty(PyObject(ex), Symbol($prop_name))
eval(Expr(:export, prop))
end

Expand All @@ -258,7 +258,7 @@ global call_sympy_fun(fn::PyCall.PyObject, args...; kwargs...) = PyCall.pycall(f
## These get coverted to PyAny for conversion via
## PyCall, others directly call `Sym`, as it is faster
function sympy_meth(meth, args...; kwargs...)
ans = call_sympy_fun(sympy[string(meth)], args...; kwargs...)
ans = call_sympy_fun(getproperty(sympy, Symbol(string(meth))), args...; kwargs...)
## make nicer...
try
if isa(ans, Vector)
Expand All @@ -277,7 +277,7 @@ end
# pybuiltin(:int) and iterables
# ## also PyObject(pybuiltin(:None))
function _sympy_meth(meth, args...; kwargs...)
out = PyCall.pycall(sympy[string(meth)], PyCall.PyObject, args...; kwargs...)
out = PyCall.pycall(getproperty(sympy, Symbol(string(meth))), PyCall.PyObject, args...; kwargs...)
Sym(out)
end

Expand Down Expand Up @@ -333,7 +333,7 @@ end


global object_meth(object::SymbolicObject, meth, args...; kwargs...) = begin
meth_or_prop = PyObject(object)[Symbol(meth)]
meth_or_prop = getproperty(PyObject(object),Symbol(meth))
if isa(meth_or_prop, PyCall.PyObject)
call_sympy_fun(meth_or_prop, args...; kwargs...) # method
else
Expand All @@ -353,19 +353,20 @@ function __init__()
## mappings from PyObjects to types.

copy!(combinatorics, PyCall.pyimport_conda("sympy.combinatorics", "sympy"))
pytype_mapping(combinatorics["permutations"]["Permutation"], SymPermutation)
pytype_mapping(combinatorics["perm_groups"]["PermutationGroup"], SymPermutationGroup)
polytype = sympy["polys"]["polytools"]["Poly"]
pytype_mapping(combinatorics."permutations"."Permutation", SymPermutation)
pytype_mapping(combinatorics."perm_groups"."PermutationGroup", SymPermutationGroup)
polytype = sympy.polys.polytools.Poly
pytype_mapping(polytype, Sym)

try
pytype_mapping(sympy["Matrix"], Array{Sym})
pytype_mapping(sympy["matrices"]["MatrixBase"], Array{Sym})
pytype_mapping(sympy."Matrix", Array{Sym})
pytype_mapping(sympy."matrices"."MatrixBase", Array{Sym})
catch e
end


basictype = sympy["basic"]["Basic"]
## need "" here, as basictype = sympy.basic.Basic will not convert
basictype = sympy."basic"."Basic"
pytype_mapping(basictype, Sym)


Expand Down
18 changes: 9 additions & 9 deletions src/assumptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ ask(Q.positive(1 + x^2) # true -- must be postive now.
The ask function uses tri-state logic, returning one of 3 values:
`true`; `false`; or `nothing`, when the query is indeterminate.
The construction of predicates is done through `Q` methods. These can
be combined logically. For example, this will be `true`:
Expand All @@ -85,7 +85,7 @@ The above use `&` as an infix operation for the binary operator
Note: As `SymPy.jl` converts symbolic matrices into Julia's `Array`
type and not as matrices within Python, the predicate functions from SymPy for
matrices are not used, though a replacement is given.
"""
"""
module Q
import SymPy
import PyCall
Expand Down Expand Up @@ -143,7 +143,7 @@ for meth in Q_predicates
# `$($nm)`: a SymPy function.
# The SymPy documentation can be found through: http://docs.sympy.org/latest/search.html?q=$($nm)
# """ ->
($meth)(x) = PyCall.pycall(SymPy.sympy["Q"][$nm], SymPy.Sym, x)::SymPy.Sym
($meth)(x) = PyCall.pycall(SymPy.sympy.Q.$nm, SymPy.Sym, x)::SymPy.Sym
end
end

Expand Down Expand Up @@ -190,7 +190,7 @@ function orthogonal(M::Array{T,2}) where {T <: SymPy.Sym}
no_nothing > 0 && return nothing
return true
end


function unitary(M::Array{T,2}) where {T <: SymPy.Sym}
vals = SymPy.simplify.(SymPy.simplify.(M*ctranspose(M)) .== one(T))
Expand All @@ -216,7 +216,7 @@ function normal(M::Array{T,2}) where {T <: SymPy.Sym}
for val in vals
a = SymPy.ask(val)
if a == nothing
no_nothing += 1
no_nothing += 1
elseif a == false
return false
end
Expand Down Expand Up @@ -250,7 +250,7 @@ end


upper_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istriu(M)
lower_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istril(M)
lower_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istril(M)
diagonal(M::Array{T,2}) where {T <: SymPy.Sym} = upper_triangular(M) && lower_triangular(M)
triangular(M::Array{T,2}) where {T <: SymPy.Sym} = upper_triangular(M) || lower_triangular(M)

Expand All @@ -260,7 +260,7 @@ function full_rank(M::Array{T,2}) where {T <: SymPy.Sym}
m,n = size(M)
m <= n || return full_rank(transpose(M))


rr, p = SymPy.rref(M)
lr = rr[end, :] # is this zero?
no_nothing = 0
Expand All @@ -281,7 +281,7 @@ function full_rank(M::Array{T,2}) where {T <: SymPy.Sym}
else
return true
end

end


Expand All @@ -304,7 +304,7 @@ end
function complex_elements(M::Array{T,2}) where {T <: SymPy.Sym}
vals = real.(M)
for val in vals
a = SymPy.ask(SymPy.sympy["Q"][:complex](val))
a = SymPy.ask(SymPy.sympy."Q".complex(val))
(a == nothing || a == false) && return false
end
return true
Expand Down
11 changes: 5 additions & 6 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
core_object_methods = (:as_poly, :atoms,
:compare, :compare_pretty,
:doit, :dummy_eq, # :count,
:has,
:has,
:sort_key,
:args_cnc,
:as_coeff_Add, :as_coeff_Mul, :as_coeff_add,
Expand Down Expand Up @@ -55,8 +55,8 @@ x = Sym("x")
Eq(x, sin(x)) |> rhs ## sin(x)
```
"""
rhs(ex::Sym, args...; kwargs...) = PyObject(ex)[:rhs]
lhs(ex::Sym, args...; kwargs...) = PyObject(ex)[:lhs]
rhs(ex::Sym, args...; kwargs...) = PyObject(ex).rhs
lhs(ex::Sym, args...; kwargs...) = PyObject(ex).lhs



Expand All @@ -65,9 +65,9 @@ lhs(ex::Sym, args...; kwargs...) = PyObject(ex)[:lhs]
Return a vector of free symbols in an expression
"""
function free_symbols(ex::Union{T, Vector{T}}) where {T<:SymbolicObject}
fs = PyObject(ex)[:free_symbols]
fs = PyObject(ex).free_symbols
## are these a set?
if fs[:__class__][:__name__] == "set"
if fs.__class__.__name__ == "set"
convert(Vector{Sym}, collect(fs))
else
Sym[]
Expand All @@ -77,4 +77,3 @@ end
free_symbols(exs::Tuple) = free_symbols(Sym[ex for ex in exs])

export free_symbols

10 changes: 5 additions & 5 deletions src/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ Examples
@vars x
import Base.Docs.doc
doc(sin(x)) #
doc(sympy[:sin]) # explicit module lookup
doc(SymPy.mpmath[:hypercomb]) # explicit module lookup
doc(sympy.sin) # explicit module lookup
doc(SymPy.mpmath.hypercomb) # explicit module lookup
doc(Poly(x^2,x), :coeffs) # coeffs is an object method of the poly instance
doc([x 1;1 x], :LUsolve) # LUsolve is a matrix method
```
"""
Base.Docs.doc(x::SymbolicObject) = Base.Docs.doc(PyObject(x))
Base.Docs.doc(x::SymbolicObject, s::Symbol) = Base.Docs.doc(PyObject(x)[s])
Base.Docs.doc(x::Array{T,N}, s::Symbol) where {T <: SymbolicObject, N} = Base.Docs.doc(PyObject(x)[s])
Base.Docs.doc(x::Array{T,N}, s::Symbol) where {T <: SymbolicObject, N} = Base.Docs.doc(PyObject(x).s)

## Add some of SymPy's displays
## Some pretty printing
Expand All @@ -29,7 +29,7 @@ Base.Docs.doc(x::Array{T,N}, s::Symbol) where {T <: SymbolicObject, N} = Base.Do
#doc(x::SymbolicObject) = print(x[:__doc__])

"Map a symbolic object to a string"
_str(s::SymbolicObject) = s[:__str__]()
_str(s::SymbolicObject) = s.__str__()

"Map an array of symbolic objects to a string"
_str(a::AbstractArray{SymbolicObject}) = map(_str, a)
Expand All @@ -55,7 +55,7 @@ Base.show(io::IO, s::Sym) = print(io, jprint(s))
## We add show methods for the REPL (text/plain) and IJulia (text/latex)

## text/plain
show(io::IO, ::MIME"text/plain", s::SymbolicObject) = print(io, sympy["pretty"](s))
show(io::IO, ::MIME"text/plain", s::SymbolicObject) = print(io, sympy.pretty(s))
show(io::IO, ::MIME"text/latex", x::Sym) = print(io, latex(x, mode="equation*"))

function show(io::IO, ::MIME"text/latex", x::AbstractArray{Sym})
Expand Down
6 changes: 3 additions & 3 deletions src/dsolve.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ F,G,H = SymFunction("F, G, H")
function SymFunction(x::T) where {T<:AbstractString}
us = split(x, r",\s*")
if length(us) > 1
map(u -> SymFunction(sympy["Function"](u), 0), us)
map(u -> SymFunction(sympy."Function"(u), 0), us)
else
SymFunction(sympy["Function"](x), 0)
SymFunction(sympy."Function"(x), 0)
end
# u = sympy["Function"](x)
# u = sympy."Function"(x)
# SymFunction(u, 0)
end

Expand Down
Loading

0 comments on commit ff91a83

Please sign in to comment.