Skip to content

Commit ec6d218

Browse files
committed
Add PySymbol, a Symbol subclass that can be subclassed in Python
1 parent 83912b7 commit ec6d218

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

symengine/lib/symengine.pxd

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ cdef extern from "<symengine/symengine_rcp.h>" namespace "SymEngine":
130130
T& operator*() nogil except +
131131

132132
RCP[const Symbol] rcp_static_cast_Symbol "SymEngine::rcp_static_cast<const SymEngine::Symbol>"(RCP[const Basic] &b) nogil
133+
RCP[const PySymbol] rcp_static_cast_PySymbol "SymEngine::rcp_static_cast<const SymEngine::PySymbol>"(RCP[const Basic] &b) nogil
133134
RCP[const Integer] rcp_static_cast_Integer "SymEngine::rcp_static_cast<const SymEngine::Integer>"(RCP[const Basic] &b) nogil
134135
RCP[const Rational] rcp_static_cast_Rational "SymEngine::rcp_static_cast<const SymEngine::Rational>"(RCP[const Basic] &b) nogil
135136
RCP[const Complex] rcp_static_cast_Complex "SymEngine::rcp_static_cast<const SymEngine::Complex>"(RCP[const Basic] &b) nogil
@@ -257,6 +258,7 @@ cdef extern from "<symengine/basic.h>" namespace "SymEngine":
257258
bool is_a_Log "SymEngine::is_a<SymEngine::Log>"(const Basic &b) nogil
258259
bool is_a_PyNumber "SymEngine::is_a<SymEngine::PyNumber>"(const Basic &b) nogil
259260
bool is_a_ATan2 "SymEngine::is_a<SymEngine::ATan2>"(const Basic &b) nogil
261+
bool is_a_PySymbol "SymEngine::is_a_sub<SymEngine::PySymbol>"(const Basic &b) nogil
260262

261263
RCP[const Basic] expand(RCP[const Basic] &o) nogil except +
262264

@@ -292,6 +294,11 @@ cdef extern from "<symengine/pywrapper.h>" namespace "SymEngine":
292294
cdef cppclass PyFunction:
293295
PyObject* get_py_object()
294296

297+
cdef extern from "<symengine/pywrapper.h>" namespace "SymEngine":
298+
cdef cppclass PySymbol(Symbol):
299+
PySymbol(string name, PyObject* pyobj)
300+
PyObject* get_py_object()
301+
295302
cdef extern from "<symengine/integer.h>" namespace "SymEngine":
296303
cdef cppclass Integer(Number):
297304
Integer(int i) nogil
@@ -376,6 +383,7 @@ cdef extern from "<symengine/pow.h>" namespace "SymEngine":
376383
cdef extern from "<symengine/basic.h>" namespace "SymEngine":
377384
# We need to specialize these for our classes:
378385
RCP[const Basic] make_rcp_Symbol "SymEngine::make_rcp<const SymEngine::Symbol>"(string name) nogil
386+
RCP[const Basic] make_rcp_PySymbol "SymEngine::make_rcp<const SymEngine::PySymbol>"(string name, PyObject * pyobj) nogil
379387
RCP[const Basic] make_rcp_Constant "SymEngine::make_rcp<const SymEngine::Constant>"(string name) nogil
380388
RCP[const Basic] make_rcp_Integer "SymEngine::make_rcp<const SymEngine::Integer>"(int i) nogil
381389
RCP[const Basic] make_rcp_Integer "SymEngine::make_rcp<const SymEngine::Integer>"(integer_class i) nogil

symengine/lib/symengine/pywrapper.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,38 @@
88

99
namespace SymEngine {
1010

11+
/*
12+
* PySymbol is a subclass of Symbol that keeps a reference to a Python object.
13+
* When subclassing a Symbol from Python, the information stored in subclassed
14+
* object is lost because all the arithmetic and function evaluations happen on
15+
* the C++ side. The object returned by `(x + 1) - 1` is wrapped in the Python
16+
* class Symbol and therefore the fact that `x` is a subclass of Symbol is lost.
17+
*
18+
* By subclassing in the C++ side and keeping a python object reference, the
19+
* subclassed python object can be returned instead of wrapping in a Python
20+
* class Symbol.
21+
*
22+
* TODO: Python object and C++ object both keep a reference to each other as one
23+
* must be alive when the other is alive. This creates a cyclic reference and
24+
* should be fixed.
25+
*/
26+
27+
class PySymbol : public Symbol {
28+
private:
29+
PyObject* obj;
30+
public:
31+
PySymbol(const std::string& name, PyObject* obj) : Symbol(name), obj(obj) {
32+
Py_INCREF(obj);
33+
}
34+
PyObject* get_py_object() const {
35+
return obj;
36+
}
37+
virtual ~PySymbol() {
38+
// TODO: This is never called because of the cyclic reference.
39+
Py_DECREF(obj);
40+
}
41+
};
42+
1143
/*
1244
* This module provides classes to wrap Python objects defined in SymPy
1345
* or Sage into SymEngine.

symengine/lib/symengine_wrapper.pyx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ cdef c2py(RCP[const symengine.Basic] o):
3434
elif (symengine.is_a_Complex(deref(o))):
3535
r = Complex.__new__(Complex)
3636
elif (symengine.is_a_Symbol(deref(o))):
37+
if (symengine.is_a_PySymbol(deref(o))):
38+
return <object>(deref(symengine.rcp_static_cast_PySymbol(o)).get_py_object())
3739
r = Symbol.__new__(Symbol)
3840
elif (symengine.is_a_Constant(deref(o))):
3941
r = Constant.__new__(Constant)
@@ -650,11 +652,20 @@ def series(ex, x=None, x0=0, n=6, method='sympy', removeO=False):
650652

651653
cdef class Symbol(Basic):
652654

655+
"""
656+
Symbol is a class to store a symbolic variable with a given name.
657+
658+
Note: Subclassing `Symbol` will not work properly. Use `PySymbol`
659+
which is a subclass of `Symbol` for subclassing.
660+
"""
653661
def __cinit__(self, name = None):
654662
if name is None:
655663
return
656664
self.thisptr = symengine.make_rcp_Symbol(name.encode("utf-8"))
657665

666+
def __init__(self, name = None):
667+
return
668+
658669
def _sympy_(self):
659670
cdef RCP[const symengine.Symbol] X = symengine.rcp_static_cast_Symbol(self.thisptr)
660671
import sympy
@@ -677,6 +688,15 @@ cdef class Symbol(Basic):
677688
def is_Symbol(self):
678689
return True
679690

691+
692+
cdef class PySymbol(Symbol):
693+
def __init__(self, name, *args, **kwargs):
694+
super(PySymbol, self).__init__(name)
695+
if name is None:
696+
return
697+
self.thisptr = symengine.make_rcp_PySymbol(name.encode("utf-8"), <PyObject*>self)
698+
699+
680700
def symarray(prefix, shape, **kwargs):
681701
""" Creates an nd-array of symbols
682702

0 commit comments

Comments
 (0)