Skip to content

Commit ff91a83

Browse files
authored
adjust to use getproperty over getindex with PyObject values (#254)
* adjust to use getproperty over getindex with PyObject values * adjust to getproperty interface
1 parent 3da7674 commit ff91a83

25 files changed

+252
-257
lines changed

README.md

Lines changed: 33 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![SymPy](http://pkg.julialang.org/badges/SymPy_0.7.svg)](http://pkg.julialang.org/?pkg=SymPy&ver=0.7)
1+
[![SymPy](http://pkg.julialang.org/badges/SymPy_0.7.svg)](http://pkg.julialang.org/?pkg=SymPy&ver=0.7)
22

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

1111

1212

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

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

4646
```
47-
using PyCall
48-
@pyimport sympy
47+
using PyCall
48+
sympy = pyimport("sympy")
4949
x = sympy.Symbol("x")
5050
y = sympy.sin(x)
51-
y[:subs](x, sympy.pi) |> float
51+
z = y.subs(x, sympy.pi)
52+
convert(Float64, z)
5253
```
5354

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

62-
The above isn't so awkward, but even more cumbersome is the similarly
63-
simple task of finding `sin(pi*x)`. As this multiplication is done at
64-
the python level and is not a method of `sympy` or the `x` object, we
65-
need to evaluate python code. Here is one solution:
64+
65+
Alternatively, `PyCall` now has a `*` method, so this could be done with
6666

6767
```
6868
x = sympy.Symbol("x")
69-
y = pycall(sympy.Mul, PyAny, sympy.pi, x)
70-
z = sympy.sin(y)
71-
z[:subs](x, 1) |> float
69+
y = sympy.pi * x
70+
z = sympy.sin(y)
71+
convert(Float64, z.subs(x, 1))
7272
```
7373

74-
This gets replaced by a more `julia`n syntax:
74+
With `SymPy` this gets replaced by a more `julia`n syntax:
7575

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

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

9898
However, for some tasks the `PyCall` interface is still needed, as
9999
only a portion of the `SymPy` interface is exposed. To call an
100-
underlying SymPy method, the `getindex` method is overloaded for
101-
`symbol` indices so that `ex[:meth_name](...)` dispatches to either to
102-
SymPy's `ex.meth_name(...)` or `meth_name(ex, ...)`, as possible.
103-
100+
underlying SymPy method, the `getproperty` method is overloaded so
101+
that `ex.meth_name(...)` dispatches the method of the object and
102+
`sympy.meth_name(...)` calls a function in the SymPy module.
104103

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

110106
```
111-
@vars x
112-
y = sympy"sin"(pi * x)
113-
y(1)
107+
y = sympy.sin(pi * x)
108+
y.subs(x, 1)
114109
```
115110

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

138133
```
139-
sympy"det"(a)
134+
sympy.det(a)
140135
```
141136

142137
Similarly for `trace`, `eigenvects`, ... . Note these are `sympy`
143138
methods, not `Julia` methods that have been ported. (Hence,
144139
`eigenvects` and not `eigvecs`.)
145-
146-
147-
148-
149-

REQUIRE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
julia 0.7.0
2-
PyCall 1.7.1
2+
PyCall 1.90.0
33
RecipesBase 0.5.0
44
SpecialFunctions 0.3.4

src/SymPy.jl

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ for prop in union(core_object_properties,
241241
polynomial_predicates)
242242

243243
prop_name = string(prop)
244-
@eval ($prop)(ex::SymbolicObject) = PyObject(ex)[Symbol($prop_name)]
244+
@eval ($prop)(ex::SymbolicObject) = getproperty(PyObject(ex), Symbol($prop_name))
245245
eval(Expr(:export, prop))
246246
end
247247

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

@@ -333,7 +333,7 @@ end
333333

334334

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

355355
copy!(combinatorics, PyCall.pyimport_conda("sympy.combinatorics", "sympy"))
356-
pytype_mapping(combinatorics["permutations"]["Permutation"], SymPermutation)
357-
pytype_mapping(combinatorics["perm_groups"]["PermutationGroup"], SymPermutationGroup)
358-
polytype = sympy["polys"]["polytools"]["Poly"]
356+
pytype_mapping(combinatorics."permutations"."Permutation", SymPermutation)
357+
pytype_mapping(combinatorics."perm_groups"."PermutationGroup", SymPermutationGroup)
358+
polytype = sympy.polys.polytools.Poly
359359
pytype_mapping(polytype, Sym)
360360

361361
try
362-
pytype_mapping(sympy["Matrix"], Array{Sym})
363-
pytype_mapping(sympy["matrices"]["MatrixBase"], Array{Sym})
362+
pytype_mapping(sympy."Matrix", Array{Sym})
363+
pytype_mapping(sympy."matrices"."MatrixBase", Array{Sym})
364364
catch e
365365
end
366366

367367

368-
basictype = sympy["basic"]["Basic"]
368+
## need "" here, as basictype = sympy.basic.Basic will not convert
369+
basictype = sympy."basic"."Basic"
369370
pytype_mapping(basictype, Sym)
370371

371372

src/assumptions.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ ask(Q.positive(1 + x^2) # true -- must be postive now.
7070
7171
The ask function uses tri-state logic, returning one of 3 values:
7272
`true`; `false`; or `nothing`, when the query is indeterminate.
73-
73+
7474
The construction of predicates is done through `Q` methods. These can
7575
be combined logically. For example, this will be `true`:
7676
@@ -85,7 +85,7 @@ The above use `&` as an infix operation for the binary operator
8585
Note: As `SymPy.jl` converts symbolic matrices into Julia's `Array`
8686
type and not as matrices within Python, the predicate functions from SymPy for
8787
matrices are not used, though a replacement is given.
88-
"""
88+
"""
8989
module Q
9090
import SymPy
9191
import PyCall
@@ -143,7 +143,7 @@ for meth in Q_predicates
143143
# `$($nm)`: a SymPy function.
144144
# The SymPy documentation can be found through: http://docs.sympy.org/latest/search.html?q=$($nm)
145145
# """ ->
146-
($meth)(x) = PyCall.pycall(SymPy.sympy["Q"][$nm], SymPy.Sym, x)::SymPy.Sym
146+
($meth)(x) = PyCall.pycall(SymPy.sympy.Q.$nm, SymPy.Sym, x)::SymPy.Sym
147147
end
148148
end
149149

@@ -190,7 +190,7 @@ function orthogonal(M::Array{T,2}) where {T <: SymPy.Sym}
190190
no_nothing > 0 && return nothing
191191
return true
192192
end
193-
193+
194194

195195
function unitary(M::Array{T,2}) where {T <: SymPy.Sym}
196196
vals = SymPy.simplify.(SymPy.simplify.(M*ctranspose(M)) .== one(T))
@@ -216,7 +216,7 @@ function normal(M::Array{T,2}) where {T <: SymPy.Sym}
216216
for val in vals
217217
a = SymPy.ask(val)
218218
if a == nothing
219-
no_nothing += 1
219+
no_nothing += 1
220220
elseif a == false
221221
return false
222222
end
@@ -250,7 +250,7 @@ end
250250

251251

252252
upper_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istriu(M)
253-
lower_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istril(M)
253+
lower_triangular(M::Array{T,2}) where {T <: SymPy.Sym} = SymPy.istril(M)
254254
diagonal(M::Array{T,2}) where {T <: SymPy.Sym} = upper_triangular(M) && lower_triangular(M)
255255
triangular(M::Array{T,2}) where {T <: SymPy.Sym} = upper_triangular(M) || lower_triangular(M)
256256

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

263-
263+
264264
rr, p = SymPy.rref(M)
265265
lr = rr[end, :] # is this zero?
266266
no_nothing = 0
@@ -281,7 +281,7 @@ function full_rank(M::Array{T,2}) where {T <: SymPy.Sym}
281281
else
282282
return true
283283
end
284-
284+
285285
end
286286

287287

@@ -304,7 +304,7 @@ end
304304
function complex_elements(M::Array{T,2}) where {T <: SymPy.Sym}
305305
vals = real.(M)
306306
for val in vals
307-
a = SymPy.ask(SymPy.sympy["Q"][:complex](val))
307+
a = SymPy.ask(SymPy.sympy."Q".complex(val))
308308
(a == nothing || a == false) && return false
309309
end
310310
return true

src/core.jl

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
core_object_methods = (:as_poly, :atoms,
33
:compare, :compare_pretty,
44
:doit, :dummy_eq, # :count,
5-
:has,
5+
:has,
66
:sort_key,
77
:args_cnc,
88
:as_coeff_Add, :as_coeff_Mul, :as_coeff_add,
@@ -55,8 +55,8 @@ x = Sym("x")
5555
Eq(x, sin(x)) |> rhs ## sin(x)
5656
```
5757
"""
58-
rhs(ex::Sym, args...; kwargs...) = PyObject(ex)[:rhs]
59-
lhs(ex::Sym, args...; kwargs...) = PyObject(ex)[:lhs]
58+
rhs(ex::Sym, args...; kwargs...) = PyObject(ex).rhs
59+
lhs(ex::Sym, args...; kwargs...) = PyObject(ex).lhs
6060

6161

6262

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

7979
export free_symbols
80-

src/display.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@ Examples
1212
@vars x
1313
import Base.Docs.doc
1414
doc(sin(x)) #
15-
doc(sympy[:sin]) # explicit module lookup
16-
doc(SymPy.mpmath[:hypercomb]) # explicit module lookup
15+
doc(sympy.sin) # explicit module lookup
16+
doc(SymPy.mpmath.hypercomb) # explicit module lookup
1717
doc(Poly(x^2,x), :coeffs) # coeffs is an object method of the poly instance
1818
doc([x 1;1 x], :LUsolve) # LUsolve is a matrix method
1919
```
2020
"""
2121
Base.Docs.doc(x::SymbolicObject) = Base.Docs.doc(PyObject(x))
2222
Base.Docs.doc(x::SymbolicObject, s::Symbol) = Base.Docs.doc(PyObject(x)[s])
23-
Base.Docs.doc(x::Array{T,N}, s::Symbol) where {T <: SymbolicObject, N} = Base.Docs.doc(PyObject(x)[s])
23+
Base.Docs.doc(x::Array{T,N}, s::Symbol) where {T <: SymbolicObject, N} = Base.Docs.doc(PyObject(x).s)
2424

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

3131
"Map a symbolic object to a string"
32-
_str(s::SymbolicObject) = s[:__str__]()
32+
_str(s::SymbolicObject) = s.__str__()
3333

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

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

6161
function show(io::IO, ::MIME"text/latex", x::AbstractArray{Sym})

src/dsolve.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,11 @@ F,G,H = SymFunction("F, G, H")
3636
function SymFunction(x::T) where {T<:AbstractString}
3737
us = split(x, r",\s*")
3838
if length(us) > 1
39-
map(u -> SymFunction(sympy["Function"](u), 0), us)
39+
map(u -> SymFunction(sympy."Function"(u), 0), us)
4040
else
41-
SymFunction(sympy["Function"](x), 0)
41+
SymFunction(sympy."Function"(x), 0)
4242
end
43-
# u = sympy["Function"](x)
43+
# u = sympy."Function"(x)
4444
# SymFunction(u, 0)
4545
end
4646

0 commit comments

Comments
 (0)