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

Implemented query method for log class #2384

Merged
merged 2 commits into from
Oct 18, 2023

Conversation

anutosh491
Copy link
Collaborator

There's a TODO in this Pr which I need to address

So technically there is no SYMENGINE_EXP class which means to implement something like e.func == exp, we first need to check if e.func == Pow and e.base == E.

The first part here is easy but for the second part we need to use the basic_get_args which is defined as the following in sympy.

//! Returns a CVecBasic of vec_basic given by get_args
CWRAPPER_OUTPUT_TYPE basic_get_args(const basic self, CVecBasic *args);

It takes a pointer to an empty container I think and then populate it with the arguments. So basically we want the populated list and then the first element of that list would be our base.

Now the point is we shall pass a CPtr variable e as the first argument but I am not sure what would suffice for CVecBasic *args. I don't think a pointer to List[CPtr] works here so need to ideate on that.

@anutosh491
Copy link
Collaborator Author

My initial thought process here was that rather than extracting args and then extracting the first element from the list, we can expose a basic_get_base function (symengine/symengine#1980) but I don't think it was the best approach either.

@certik
Copy link
Contributor

certik commented Oct 18, 2023

I would not allow .func == Exp, because there is no such function. Rather, the user will have to do e.func == Pow and e.args == E in the Python code. In other words, this can be implemented on top, but LPython itself would not have "Exp" as a type.

@certik
Copy link
Contributor

certik commented Oct 18, 2023

Let's add a Python/SymPy test for "Log".

@anutosh491
Copy link
Collaborator Author

anutosh491 commented Oct 18, 2023

Ohh wait but can't you do something like the following is sympy

>>> from sympy import *
>>> x = Symbol("x")
>>> exp(x).func
exp
>>> exp(x).func == exp
True

Hence I thought I wouldn't be a bad idea to introduce ExpQ

@certik
Copy link
Contributor

certik commented Oct 18, 2023

We will always support just a subset. So you can also do exp(x).is_Log too in SymPy, but in LPython you can't. So we won't allow exp(x).func == exp, you have to do exp(x).func == Pow and exp(x).args[0] == E, or you can write a function like is_exp() that is implemented internally using exp(x).func == Pow and exp(x).args[0] == E. LPython doesn't care, it just doesn't support it.

@anutosh491
Copy link
Collaborator Author

Ohh ok yes that makes sense to me and we only try implementing the approach you are talking about.
I'll design that in a subsequent pr, for now I'll add tests for LogQ.

I have also fixed visit_Assert to handle something like assert(e.func == log).
I'll add tests for these too.

@anutosh491 anutosh491 changed the title Implemented query methods for log and exp class Implemented query methods for log class Oct 18, 2023
@anutosh491 anutosh491 changed the title Implemented query methods for log class Implemented query method for log class Oct 18, 2023
@anutosh491
Copy link
Collaborator Author

I think this would be ready now.

@certik certik merged commit f8fbd8d into lcompilers:main Oct 18, 2023
13 checks passed
@certik
Copy link
Contributor

certik commented Oct 18, 2023

Awesome, thanks!

@anutosh491
Copy link
Collaborator Author

anutosh491 commented Oct 21, 2023

So we won't allow exp(x).func == exp, you have to do exp(x).func == Pow and exp(x).args[0] == E, or you can write a function like is_exp() that is implemented internally using exp(x).func == Pow and exp(x).args[0] == E

I think we need to use exp(x).base == E rather than exp(x).args[0] == E. Because firstly if an expression is of type Pow, we can extract it's base

>>> (x*pi).base
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Mul' object has no attribute 'base'
>>> (x**pi).base
x

Secondly the args method returns the arguments being used in the expression. So we have the following rather than getting E out of here.

>>> exp(x).args
(x,)
>>> exp(x).args[0]
x

So what I see here is we need a baseQ and it only gives out the base if the argument (exp(x) or x**pi in this case) is an intrinsic function of type SymbolicPow else raturns an error like "base attribute not supported for this type/class"

@certik
Copy link
Contributor

certik commented Oct 21, 2023

You can introduce SymbolicPowBase to return the base for Pow, otherwise give an error. However I think it is redundant.

We need to support .args anyway, don't we? So let's implement args, which in turn will fix this issue as well automatically.

@anutosh491
Copy link
Collaborator Author

Actually I am somewhat confused. We still plan to maintain consistency with sympy right ? (Atleast we've done that for all tests till now which means the tests through sympy or lpython return the same result). But consider the following.
If we don't go for y.func == exp and go for something like this instead.

(lf) anutosh491@spbhat68:~/lpython/lpython$ cat examples/expr2.py 
from lpython import S
from sympy import pi, exp, Pow, Symbol

def main0():
    x: S = Symbol("x")
    y: S = exp(x)
    z: bool = y.func == Pow and y.base == E
    print(z)

We would be returning True for the following through LPython as discussed but we lose consistency with SymPy here

(lf) anutosh491@spbhat68:~/lpython/lpython$ PYTHONPATH="src/runtime/lpython" python examples/expr2.py 
False

Because through sympy we have the following

>>> from sympy import *
>>> x = Symbol("x")
>>> y = exp(x)
>>> y.base
E
>>> y.func
exp

What comes in my mind here is that users could still use y.func == exp in the front end and once we catch hold the attribute exp, we could transform that in ASR to use PowQ and BaseQ/ArgsQ. Somewhere here

} else if (symbolic_type == "Pow") {
tmp = attr_handler.eval_symbolic_is_Pow(se, al, x.base.base.loc, args, diag);
return;
} else if (symbolic_type == "log") {
tmp = attr_handler.eval_symbolic_is_log(se, al, x.base.base.loc, args, diag);
return;
} else {

But to begin with shouldn't we still start with y.func == exp to maintain consistency ?

@anutosh491
Copy link
Collaborator Author

anutosh491 commented Oct 21, 2023

But yeah, correct I think we might need indexing through args nonetheless as I can see at some places in def mrv. So yeah I'll try implementing args for now and then get back to this. I see a couple cases that we can tackle or atleast start with

CASE 1

    z: S = x + y
    ele1: S = z.args[0]

CASE 2

args: list[S] = (x**2).args
print(args[0], args[1])

Atleast in def mrv I see cases related to case1 where we aren't really generating the list or storing args in a list, we just use the subscript to extract the elements. I think we can start from case 1

@certik
Copy link
Contributor

certik commented Oct 22, 2023

The rule is that if LPython compiles it, then SymPy must as well. But not vice versa.

I didn't realize that exp(x) in sympy is indeed type "exp", not Pow. So we might need to do whatever it takes in the frontend to emulate this. In the backend (symengine) I would use the default, which his a Pow.

@anutosh491
Copy link
Collaborator Author

Yes correct. So to begin with we need to start with something like e.func == exp I suppose and then through the pass we might have to transform this such that we check the class to be Pow and the base to be E.

@certik
Copy link
Contributor

certik commented Oct 22, 2023

I would get y.func == Pow and y.args[0] == x working first for things like x**x. That should work in both SymPy and SymEngine/LPython.

Then, we can add special handling for y.func == exp and rewrite it to y.func == Pow and y.args[0] == E.

@anutosh491
Copy link
Collaborator Author

anutosh491 commented Oct 24, 2023

I would get y.func == Pow and y.args[0] == x

Actually I am not fully sure of how would can we represent this (y.args[0] == x) in ASR.
y.args[0] looks like a query method for getting the 1st argument but if that's the case there can be infinite of such query methods which we would need to introduce.

What does make sense to me is something like this

x : S = Symbol("x")
y : S = x**pi
args : list[S] = y.args
z: bool = y.func == Pow and args[0] == x

I think representing y.args makes more sense here as this can represent an IntrinsicFunction that holds the list of arguments overall.
But I am not sure how y.args[0] or y.args[1] would be represented. Can they be represented by the same intrinsic function (through the overload id I assume if we want to go forward with args[] as an intrinsic function as compared to args itself)

EDIT: Ohh wait, actually I forgot we had IntrinsicArrayFunctions too. Would we end up using that ?

@certik
Copy link
Contributor

certik commented Oct 25, 2023

I would implement it using the design at #2393.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants