Open certik opened 1 year ago
Well if we plan on using basic_get_args
for implemnting e.exp
and e.base
(instead of what I was trying to do in https://github.com/symengine/symengine/pull/1980), we would need a work around for the CVecBasic
type just like we had one for the basic
type.
If you remember, we introduced a workaround for declaring a basic variable as follows
_x: i64 = i64(0)
x: CPtr = empty_c_void_p()
p_c_pointer(pointer(_x, i64), x)
Now what basic_get_args
does is, it takes in a pointer to CVecBasic
and then it populates the value of args
with the arguments.
//! Returns a CVecBasic of vec_basic given by get_args
CWRAPPER_OUTPUT_TYPE basic_get_args(const basic self, CVecBasic *args);
Hence we might need to think about a workaround for implementing CVecBasic
in LPython.
Here it is a bit different that the general procedure for a basic
variable(which is to define it through CPtr
and then use the basic_new_stack
function )
Here we have something like
CVecBasic *args = vecbasic_new();
basic_get_args(e, args);
You can also write an auxiliary C function that does this, if it simplifies things.
Regarding the args
, let's first implement the following:
args: list[S] = (x**2).args
print(args[0], args[1])
Where we obtain the whole vector of arguments from SymEngine, and we convert it to a Python tuple. Then we just deal with a tuple. For that, we should first get tuples of type S
working in LPython, that is, get this working first:
args: list[S] = (x, S(2))
print(args[0], args[1])
And then once that works, let's get args: list[S] = (x**2).args
working. For that, I think we need some ASR function, let's say SymbolicGetArguments
that accepts an expression like x**2
and returns a tuple[S]
with the arguments.
I think after that, the following will also automatically start working:
print((x**2).args[0], (x**2).args[1])
However, for this to work, tuple would have to have a runtime size. Currently in LPython we have a compile time size, and in fact tuple[S]
is a tuple of just one element. tuple[S, S, S]
is a tuple of 3 elements. We have two ways forward:
args: list[S] = (x**2).args; print(args[0], args[1])
.Once symengine gives us the CVecBasic *args
, then you can construct the following ASR code to construct a list[S]
, as an implementation of SymbolicGetArguments
, in Python:
r: list[S] = []
args = (x**2).args
for i in range(len(args)):
r.append(args[i])
This has to be done in LPython using symengine's C API for handling the CVecBasic
structure. So there is a function get gets you the length (len(args)
) and there is another function to access an i
-th element of the vector (args[i]
).
I would add a new integration test test_gruntz.py
and in there I would start implementing the mrv
function, I would start small and then start adding things to it. Every time we discover something that does not work, I would isolate the bug, and add a dedicated separate test test_symbolics_??.py
for it. That way every single new feature will be tested. And then in test_gruntz.py
I would just keep improving the one mrv
implementation iteratively, whatever is there will be always working, and step by step we will get it to the point that it is equivalent (or almost equivalent) to SymPy's mrv: https://github.com/sympy/sympy/blob/b6e81e7c3f3860c7e826018e587531a8622fcc4e/sympy/series/gruntz.py#L248.
gruntz2
branch)e.has(x)
(ASR:SymbolicHasQ
)e.func == Mul
,e.func == Add
,e.func == Pow
e.func == log
(e.func == exp
will not be supported, since SymEngine doesn't have anexp
type, onlyPow
: https://github.com/symengine/symengine/issues/1984)e.func == sin
y.args[0]
andy.args[1]
(ASR:SymbolicGetArguments
, https://github.com/lcompilers/lpython/issues/2393), wherey
is any expressionexponent = int(e.args[1])
as_two_terms(e)
,as_independent(e, x)
(ASR:SymbolicAsTwoTerms
andSymbolicAsIndependent
){exp(x)}
), make sure printing and comparison worksmrv
using LPythonThe goal is to design a very small and clean ASR design. As few functions and ASR nodes as possible, the
S
type does not have methods, we have global ASR nodes (as few as possible) and IntrinsicFunctions (can be many) to operate on it. We want to keep this Symbolic API surface to be minimal and clean (no duplication).Let's write a pure Python implementation of this Symbolic API, initially we'll just ship it with LPython. It will implement exactly this API in pure CPython, so we will ensure 100% compatibility (thus satisfying the LPython contract that if it works in LPython, it gives exactly the same result in CPython). SymPy itself will be about 95% compatible, so most tests will work in both and we will continue testing both, but in some corner cases (such as
exp(x).func
, andsin(pi/8)
etc.) it will differ, so those tests will only test in our pure Python implementation (we will ensure that majority of tests work in SymPy). And then much later, we'll ensure that thesymengine.py
(Python wrappers to SymEngine) are a strict superset of our "lean Symbolic API", so that one can usesymengine.py
interchangeably with our pure Python implementation (and it will be faster, while still being in CPython). And we will carefully document all differences to SymPy. The contract will eventually be that if a symbolic code compiles and works with LPython, it will automatically work with our pure Python implementation as well as withsymengine.py
100% of the time, and with SymPy only about 95%. And we'll offer our pure Python implementation (which will be slower thansymengine.py
, but faster than current SymPy) to SymPy itself, and using LPython SymPy can gradually move to this new core, we can start with limits.