Skip to content
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
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "SymPyPythonCall"
uuid = "bc8888f7-b21e-4b7c-a06a-5d9c9496438c"
authors = ["jverzani <jverzani@gmail.com> and contributors"]
version = "0.1.1"
version = "0.1.2"

[deps]
CommonEq = "3709ef60-1bee-4518-9f2f-acd86f176c50"
Expand Down
66 changes: 49 additions & 17 deletions docs/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@

!!! note "SymPyPythonCall"

This is a test of what works in `SymPyPythonCall`.
`SymPyPythonCall` is just a temporary package for now, with the expectation that it may become `SymPy`.
There would be a few deprecations:
* `@vars` would be deprecated; use `@syms` only
`SymPyPythonCall.jl` is like the `SymPy.jl` package in that it allows access to the underlying `SymPy`
library for `Python` from within `Julia`.

Whereas `SymPy.jl` uses the `PyCall` package, `SymPyPythonCall` uses the `PythonCall` package to provide
the bridge between `Julia` and `Python`.

Though the two packages work similarly, there are a few differences:

* `@vars` of `SymPy` is not provided; use the more powerful `@syms` macro only
* `Base.show` isn't *currently* using pretty printing
* `elements` for sets would be removed (convert to a `Set` by default)
* Would `Q` be exported? (only the slant italic Q is currently)
* What to do with matrices? E.g. `A\b` is failing now
* Sets are handled differently, with finite sets converted to `Julia` `Set`s.
* Currently `Q` is not exported. (only the slant italic Q is currently)
* Matrix support may not be complete.
* `sympy.poly` *not* `sympy.Poly`
* would we export `CommonEq`?
* `limit(ex, x, c)` deprecated; use `limit(ex, x=>c)` or `sympy.limit`
* The form `limit(ex, x, c)` is not provided; use `limit(ex, x=>c)` or `sympy.limit`
* no new special functions exported, just the ones in SpecialFunctions.jl

This document provides an introduction to using `SymPy` within `Julia`.
Expand Down Expand Up @@ -119,13 +123,13 @@ We jump ahead for a second to illustrate, but here we see that `solve` will resp

```jldoctest introduction
julia> solve(x^2 + 1) # ±i are not real
Any[]
Sym[]

```

```jldoctest introduction
julia> solve(y1 + 1) # -1 is not positive
Any[]
Sym[]

```

Expand All @@ -136,7 +140,7 @@ julia> @syms u1::positive u2::positive
(u1, u2)

julia> solve(u1 + u2) # empty, though solving u1 - u2 is not.
Any[]
Sym[]
```

Additionally you can rename arguments using pair notation:
Expand Down Expand Up @@ -169,8 +173,8 @@ function, the second to `SymPy`'s:
```jldoctest introduction
julia> [asin(1), asin(Sym(1))]
2-element Vector{Sym}:
1.57079632679490
pi/2
1.5707963267948966
pi/2

```

Expand Down Expand Up @@ -218,9 +222,12 @@ z + 1 + pi

!!! note

The calling pattern for `subs` is different from a typical `Julia` function call. The `subs` call is `object.method(arguments)` whereas a more "`Julia`n" function call is `method(objects, other objects....)`, as `Julia` offers multiple dispatch of methods. `SymPy` uses the Python calling method, adding in `Julia`n style when appropriate for generic usage within `Julia`. `SymPyPythonCall` imports many generic functions from the underlying `sympy` module and specializes them on a symbolic first argument.
The calling pattern for `subs` is different from a typical `Julia` function call. The `subs` call is `object.method(arguments...)` whereas a more "`Julia`n" function call is `method(objects, arguments...)`, as `Julia` offers multiple dispatch of methods. `SymPyPythonCall` uses the Python calling method, adding in `Julia`n style when appropriate for generic usage within `Julia`. `SymPyPythonCall` imports many generic functions from the underlying `sympy` module and specializes them on a symbolic first argument.

!!! note
The SymPy documentation for many functions can be read from the terminal using `Base.Docs.getdoc(ex)`, as in `Base.Docs.getdoc(sin(x))`.

For `subs`, the simple substitution `ex.object(x,a)` is similar to simple function evaluation, so `Julia`'s call notation will work. To specify the pairing off of `x` and `a`, the `=>` pairs notation is used.
For `subs`, the simple substitution `ex.object(x,a)` is similar to simple function evaluation, so `Julia`'s call notation will work. To specify the pairing off of `x` and `a`, the `=>` pairs notation is used.

This calling style will be equivalent to the last:

Expand Down Expand Up @@ -610,7 +617,7 @@ julia> q.coeffs()

!!! note

This is `sympy` not `SymPyPythonCall`. Using `sympy.Poly` is not supported. The `Poly` constructor from SymPy is *not* a function, so is not exported when `SymPyCa;;` is loaded.
This is `sympy` not `SymPyPythonCall` qualifying the method call. Using `sympy.Poly` is not supported. The `Poly` constructor from SymPy is *not* a function, so is not exported when `SymPyCall` is loaded.


## Polynomial roots: solve, real_roots, polyroots, nroots
Expand Down Expand Up @@ -841,6 +848,31 @@ julia> solve(p)
Dict(a => (-b*x - c)/x^2)
```

The output of `solveset` in Python is always a set, which may be finite or not. Finite sets are converted to `Set`s in `Julia`. Infinite sets have no natural counterpart and are not realized. Rather, they can be queried, as with `contains(haystack, needle)`. For example:

```jldoctest introduction
julia> u = solveset(sin(x) ≧ 0) # [\geqq] or with u = solveset(Ge(sin(x), 0))
ConditionSet(x, sin(x) >= 0, Complexes)

julia> contains(u, PI/2)
true

julia> contains(u, 3PI/2)
false
```

Infinite sets can have unions and intersections taken, but the SymPy methods must be called:

```jldoctest introduction
julia> v = solveset(cos(x) ≧ 0)
ConditionSet(x, cos(x) >= 0, Complexes)

julia> contains.((u, v, u.intersect(v), u.union(v)), 3PI/4)
(true, false, false, true)
```

---

Systems of equations can be solved as well. We specify them within a
vector of expressions, `[ex1, ex2, ..., exn]` where a found solution
is one where all the expressions are 0. For example, to solve this
Expand Down
13 changes: 6 additions & 7 deletions ext/SymPyPythonCallSymbolicsExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function __init__()
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(f, cls, T=Symbolics.Num) = PythonCall.pyconvert_add_rule(cls, T, f)

add_pyconvert_rule("sympy.core.numbers:Pi") do T::Type{Symbolics.Num}, x
PythonCall.pyconvert_return(Symbolics.Num(pi))
Expand All @@ -108,14 +108,13 @@ function __init__()
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))
# Complex{Num}
add_pyconvert_rule("sympy.core.numbers:ImaginaryUnit", Complex{Symbolics.Num}) do T::Type{Complex{Symbolics.Num}}, x
PythonCall.pyconvert_return(Complex(Symbolics.Num(0), Symbolics.Num{1}))
end
add_pyconvert_rule("sympy.core.numbers:ComplexInfinity") do T::Type{Symbolics.Num}, x
PythonCall.pyconvert_return(Symbolics.Num(Inf)) # errors: Complex(Inf,Inf)))
add_pyconvert_rule("sympy.core.numbers:ComplexInfinity", Complex{Symbolics.Num}) do T::Type{Complex{Symbolics.Num}}, x
PythonCall.pyconvert_return(Complex(Symbolics.Num(0), Symbolics.Num(Inf)) )
end
=#
end

end
4 changes: 0 additions & 4 deletions src/SymPyPythonCall.jl
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,6 @@ function __init__()
PythonCall.pycopy!(__FALSE__, pyconvert(Py, false))
PythonCall.pycopy!(__𝑄__, __sympy__.Q)


end




end
2 changes: 1 addition & 1 deletion src/assumptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ ask(x::Nothing, args...) = x
# ## for now, we can combine terms logically with And, Or, Not...
# ## these are in logic module


# XXX This is from SymPy. Do we want this in SymPyPythonCall?

# ## We make a module Q to hold the assumptions
# ## this follows this page http://docs.sympy.org/0.7.5/_modules/sympy/assumptions/ask.html
Expand Down
1 change: 1 addition & 0 deletions src/equations.jl
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Define ~ usage
Base.:~(lhs::Number, rhs::Sym) = ~(promote(lhs, rhs)...)
Base.:~(lhs::Sym, rhs::Number) = ~(promote(lhs, rhs)...)
Base.:~(lhs::Sym, rhs::Sym) = asSymbolic(sympy.py.Eq(lhs.py, rhs.py))
151 changes: 85 additions & 66 deletions src/gen_methods.jl
Original file line number Diff line number Diff line change
@@ -1,36 +1,48 @@
## XXX We should be hooking to the pyconvert methods XXX
## we need Py -> Sym (asSymbolic)
## we need Sym -> Py unSym
# we have this picture for calling SymPy methods
# Julia ⋯⋯⋯⋯ > Julia
# | ^
# | |
# | (unSym, Py, ↓) | (asSymbolic, ↑)
# | |
# v (SymPy) |
# Python ------> Python


"""
asSymbolic(x)

Convert `Python` object into symbolic `Julia` object. Aliased to `↑`.
"""
asSymbolic(x) = Sym(pyconvert(Py, x))
asSymbolic(x::Sym) = x
function asSymbolic(x::Py)

cls = x.__class__

if pyconvert(Bool, cls == pybuiltins.list)
return asSymbolic.(x)
elseif pyconvert(Bool, cls == pybuiltins.dict)
return Dict(asSymbolic(k) => asSymbolic(v) for (k,v) ∈ pyconvert(PyDict, x))
elseif pyconvert(Bool, cls == pybuiltins.tuple)
return Tuple(asSymbolic(xᵢ) for xᵢ ∈ x)
elseif pyconvert(Bool, cls == pybuiltins.set)
return Set(asSymbolic(xᵢ) for xᵢ ∈ x)
# elseif pyconvert(Bool, cls == pybuiltins.list)
end
asSymbolic(x::String) = Sym(x)

PyTypeName(x) = Val(Symbol(PythonCall.pyconvert_typename(pytype(x))))
asSymbolic(x::Py) = asSymbolic(PyTypeName(x), x) # special case based on typename

Bool(pygetattr(x, "is_FiniteSet", false)) && return Set(asSymbolic(xᵢ) for xᵢ ∈ x)
asSymbolic(::Val{T}, x::Py) where {T} = Sym(x) # fallback

if Bool(pygetattr(x, "is_Matrix", false))
sz = pyconvert(Tuple, x.shape)
!isa(sz[1], Real) && return Sym(x) # MatrixSymbol special case
return [asSymbolic(x.__getitem__((i-1, j-1))) for i ∈ 1:sz[1], j ∈ 1:sz[2]]
end
Sym(x)
asSymbolic(::Val{Symbol("builtins:list")}, x::Py) = isempty(x) ? Sym[] : [asSymbolic(xᵢ) for xᵢ ∈ x] # XXX are lists Tuples or vectors???
asSymbolic(::Val{Symbol("builtins:tuple")}, x::Py) = Tuple(asSymbolic(xᵢ) for xᵢ ∈ x)
asSymbolic(::Val{Symbol("builtins:dict")}, x::Py) = Dict(asSymbolic(k) => asSymbolic(v) for (k,v) ∈ pyconvert(PyDict, x))
asSymbolic(::Val{Symbol("builtins:set")}, x::Py) = Set(asSymbolic(xᵢ) for xᵢ ∈ x)
asSymbolic(::Val{Symbol("sympy.sets.sets:FiniteSet")}, x::Py) = Set(asSymbolic(xᵢ) for xᵢ ∈ x)
asSymbolic(::Val{Symbol("sympy.core.containers:Tuple")}, x::Py) = Tuple(asSymbolic(xᵢ) for xᵢ ∈ x)

function _as_symbolic_dense_matrix(x)
sz = pyconvert(Tuple, x.shape)
return [asSymbolic(x.__getitem__((i-1, j-1))) for i ∈ 1:sz[1], j ∈ 1:sz[2]]
end
asSymbolic(x::String) = x
asSymbolic(x) = Sym(pyconvert(Py, x))
↑(args...; kwargs...) = asSymbolic(args...; kwargs...)
asSymbolic(::Val{Symbol("sympy.matrices.dense:MutableDenseMatrix")}, x::Py) = _as_symbolic_dense_matrix(x)
asSymbolic(::Val{Symbol("sympy.matrices.dense:ImmutableDenseMatrix")}, x::Py) = _as_symbolic_dense_matrix(x)

function _as_symbolic_symbolic_matrix(x)
sz = pyconvert(Tuple, x.shape)
!isa(sz[1], Real) && return Sym(x) # MatrixSymbol special case
return [asSymbolic(x.__getitem__((i-1, j-1))) for i ∈ 1:sz[1], j ∈ 1:sz[2]]
end
asSymbolic(::Val{Symbol("sympy.matrices.expressions.matexpr:MatrixSymbol")}, x::Py) = _as_symbolic_symbolic_matrix(x)
asSymbolic(::Val{Symbol("sympy.matrices.expressions.inverse:Inverse")}, x::Py) = _as_symbolic_symbolic_matrix(x)

# used to pass arguments down into calls
unSym(x) = x
Expand All @@ -42,14 +54,17 @@ unSym(x::Dict) = convert(PyDict,Dict(unSym(k) => unSym(v) for (k,v) ∈ x)) # Ab
unSym(x::Set) = sympy.py.Set(unSym(collect(x)))
unSym(x::Irrational{:π}) = unSym(sympy.pi)
unSymkwargs(kw) = (k=>unSym(v) for (k,v) ∈ kw)
↓(args...; kwargs...) = unSym(args...; kwargs...)


#PythonCall.Py(x::Sym) = ↓(x)
PythonCall.Py(x::NTuple{N,T}) where {N, T <: SymbolicObject} = ↓(x)
PythonCall.Py(x::Vector{T}) where {T <: SymbolicObject} = unSym.(x)
PythonCall.Py(x::Matrix{T}) where {T <: SymbolicObject} = unSym.(x)
PythonCall.Py(x::Set{T}) where {T <: SymbolicObject} = unSym(x)

# use ↑, ↓ shortcuts
↑(args...; kwargs...) = asSymbolic(args...; kwargs...)
↓(args...; kwargs...) = unSym(args...; kwargs...)


## --------------------------------------------------
Expand Down Expand Up @@ -98,7 +113,7 @@ generic_methods = (
(:sympy, :Max, :Base, :max),
(:sympy, :Abs, :Base, :abs),
(:sympy, :Min, :Base, :min),

#
(:sympy, :re, :Base, :real),
(:sympy, :im, :Base, :imag),

Expand All @@ -116,28 +131,29 @@ generic_methods = (
# diff
(:sympy, :diff, :CommonEq, :diff),

# collect
(:sympy, :collect, :Base, :collect),

# SpecialFunctions
(:sympy, :airyai , :SpecialFunctions, :airyai),
(:sympy, :airyai , :SpecialFunctions, :airyai),
(:sympy, :airyaiprime , :SpecialFunctions, :airyaiprime),
(:sympy, :airybi , :SpecialFunctions, :airybi),
(:sympy, :besseli , :SpecialFunctions, :besseli),
(:sympy, :besselj , :SpecialFunctions, :besselj),
(:sympy, :besselk , :SpecialFunctions, :besselk),
(:sympy, :bessely , :SpecialFunctions, :bessely),
(:sympy, :beta , :SpecialFunctions, :beta),
(:sympy, :erf , :SpecialFunctions, :erf),
(:sympy, :erfc , :SpecialFunctions, :erfc),
(:sympy, :erfi , :SpecialFunctions, :erfi),
(:sympy, :erfinv , :SpecialFunctions, :erfinv),
(:sympy, :erfcinv , :SpecialFunctions, :erfcinv),
(:sympy, :gamma , :SpecialFunctions, :gamma),
(:sympy, :digamma , :SpecialFunctions, :digamma),
(:sympy, :polygamma , :SpecialFunctions, :polygamma),
(:sympy, :hankel1, :SpecialFunctions, :hankelh1),
(:sympy, :hankel2, :SpecialFunctions, :hankelh2),
(:sympy, :zeta , :SpecialFunctions, :zeta),
(:sympy, :airybi , :SpecialFunctions, :airybi),
(:sympy, :besseli , :SpecialFunctions, :besseli),
(:sympy, :besselj , :SpecialFunctions, :besselj),
(:sympy, :besselk , :SpecialFunctions, :besselk),
(:sympy, :bessely , :SpecialFunctions, :bessely),
(:sympy, :beta , :SpecialFunctions, :beta),
(:sympy, :erf , :SpecialFunctions, :erf),
(:sympy, :erfc , :SpecialFunctions, :erfc),
(:sympy, :erfi , :SpecialFunctions, :erfi),
(:sympy, :erfinv , :SpecialFunctions, :erfinv),
(:sympy, :erfcinv , :SpecialFunctions, :erfcinv),
(:sympy, :gamma , :SpecialFunctions, :gamma),
(:sympy, :digamma , :SpecialFunctions, :digamma),
(:sympy, :polygamma , :SpecialFunctions, :polygamma),
(:sympy, :hankel1, :SpecialFunctions, :hankelh1),
(:sympy, :hankel2, :SpecialFunctions, :hankelh2),
(:sympy, :zeta , :SpecialFunctions, :zeta),
)

# comment, Py(...) speeds things up a bit.
Expand All @@ -152,27 +168,30 @@ end
# pmod, pmeth, meth
#const
new_exported_methods = (
(:sympy, :simplify, :simplify),
(:sympy, :simplify, :simplify),
(:sympy, :expand_trig, :expand_trig),
(:sympy, :expand, :expand),
(:sympy, :together, :together),
(:sympy, :apart, :apart),
(:sympy, :factor, :factor),
(:sympy, :cancel, :cancel),
(:sympy, :degree, :degree),
(:sympy, :integrate, :integrate),
(:sympy, :real_roots, :real_roots),
(:sympy, :roots, :roots),
(:sympy, :nroots, :nroots),
(:sympy, :dsolve, :dsolve),
(:sympy, :nsolve, :nsolve),
(:sympy, :linsolve, :linsolve),
(:sympy, :expand, :expand),
(:sympy, :together, :together),
(:sympy, :apart, :apart),
(:sympy, :factor, :factor),
(:sympy, :cancel, :cancel),
#
(:sympy, :degree, :degree),
#
(:sympy, :integrate, :integrate),
#
(:sympy, :real_roots, :real_roots),
(:sympy, :roots, :roots),
(:sympy, :nroots, :nroots),
(:sympy, :dsolve, :dsolve),
(:sympy, :nsolve, :nsolve),
(:sympy, :linsolve, :linsolve),
(:sympy, :nonlinsolve, :nonlinsolve),
(:sympy, :solveset, :solveset),

(:sympy, :series, :series),
(:sympy, :summation, :summation),
(:sympy, :hessian, :hessian),
(:sympy, :solveset, :solveset),
#
(:sympy, :series, :series),
(:sympy, :summation, :summation),
(:sympy, :hessian, :hessian),
)

for (pmod, pmeth, jmeth) ∈ new_exported_methods
Expand Down
Loading