Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adjust to use getproperty over getindex with PyObject values #254

Merged
merged 2 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|> float or |> Float64 no longer works?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, surprisingly, I saw this when trying to run the README examples:

julia> using PyCall


julia> sympy = pyimport("sympy")
PyObject <module 'sympy' from '/Users/verzani/.julia/conda/3/lib/python3.7/site-packages/sympy/__init__.py'>

julia> x = sympy.Symbol("x")
PyObject x

julia> y = sympy.sin(x)
PyObject sin(x)

julia> z = y.subs(x, sympy.pi)
PyObject 0

julia> float(z)
ERROR: MethodError: no method matching AbstractFloat(::PyObject)
Closest candidates are:
  AbstractFloat(::Bool) at float.jl:252
  AbstractFloat(::Int8) at float.jl:253
  AbstractFloat(::Int16) at float.jl:254
  ...
Stacktrace:
 [1] float(::PyObject) at ./float.jl:271
 [2] top-level scope at none:0

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, that's because constructors no longer fall back to convert.

I tried adding the constructor methods in manually, but then I got a massive slowdown: JuliaLang/julia#27599

I should probably add a float method, though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t rely on it here, but it might be helpful. I’d like to see how much of SymPy can be trimmed out now that the getproperty feature is used. I think quite a bit.

```

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