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 1 commit
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.18.5
PyCall 1.90.0
RecipesBase 0.5.0
SpecialFunctions 0.3.4
4 changes: 2 additions & 2 deletions src/utils.jl
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,12 @@ x = Sym("x")

"""
function getindex(x::SymbolicObject, i::Symbol)
if haskey(PyObject(x), i)
if PyCall.hasproperty(PyObject(x), i)
function __XXxxXX__(args...;kwargs...) # replace with generated name
object_meth(x, i, args...; kwargs...)
end
return __XXxxXX__
elseif haskey(sympy, i)
elseif PyCall.hasproperty(sympy, i)
function __XXyyXX__(args...;kwargs...)
sympy_meth(i, x, args...; kwargs...)
end
Expand Down