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
Simplify Complex Arguments to Exp #1332
base: master
Are you sure you want to change the base?
Conversation
Ping @certik. |
@isuruf Can you review as well? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like the right fix. I'll wait for @isuruf to also review it.
symengine/pow.cpp
Outdated
for (const auto &p : dict) { | ||
if (eq(*p.first, *pi) and eq(*p.second, *one)) { | ||
return add(cos(mul(mul(I, minus_one), b)), | ||
mul(I, sin(mul(mul(I, minus_one), b)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems correct, as you can always do this transformation. The only question remaining is whether this transformation is only applied when it can simplify the exp(), and it seems it is.
It is probably possible to optimize this return statement further, using the information we know (i.e. not calling sin/cos at all), but that can be done later.
I don't understand how the code coverage could decrease with this PR. By clicking on it, it found some difference in |
Can you add some examples like ?
|
Ping @isuruf |
symengine/tests/basic/test_arit.cpp
Outdated
REQUIRE(eq(*r1, *r2)); | ||
|
||
r1 = exp(add(div(mul(I, pi), integer(10)), x)); | ||
r2 = mul(exp(x), exp(div(mul(I, pi), integer(10)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Above 2 check the same thing. Can you comment here with the string form of r1
?
symengine/tests/basic/test_arit.cpp
Outdated
REQUIRE(eq(*r1, *mul(I, minus_one))); | ||
|
||
r1 = div(exp(mul(mul(I, pi), x)), exp(x)); | ||
r2 = mul(exp(mul(minus_one, x)), exp(mul(mul(I, pi), x))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you comment the string form of r1
?
symengine/tests/basic/test_arit.cpp
Outdated
REQUIRE(eq(*r1, *r2)); | ||
|
||
r1 = exp(add(div(mul(I, pi), integer(10)), x)); | ||
REQUIRE(r1->__str__() == "exp(x)*(I*sin((1/10)*pi) + cos((1/10)*pi))"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is too complicated than it needs to be.
symengine/tests/basic/test_arit.cpp
Outdated
REQUIRE(eq(*r1, *mul(I, minus_one))); | ||
|
||
r1 = div(exp(mul(mul(I, pi), x)), exp(x)); | ||
REQUIRE(r1->__str__() == "exp(-x + I*x*pi)"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change this test to checking that r1
is a Pow
and that the base and exp are E
and -x + I * x * pi
respectively ?
symengine/pow.cpp
Outdated
if ((is_a<Integer>(*mul(coef, I)) | ||
or (is_a<Rational>(*mul(coef, I)) | ||
and (eq(*mul(coef, I), *rational(1, 2)) | ||
or eq(*mul(coef, I), *rational(-1, 2)))))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mul(coef, I)
is calculated repeatedly. How about exp(3*pi*I/2)
?
symengine/pow.cpp
Outdated
RCP<const Number> coef = down_cast<const Add &>(*b).get_coef(); | ||
RCP<const Basic> s = one; | ||
for (const auto &p : dict) { | ||
if (eq(*p.first, *pi) and is_a_Complex(*p.second)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use is_a<Complex>
to keep the consistency as above.
symengine/pow.cpp
Outdated
for (const auto &p : dict) { | ||
if (eq(*p.first, *pi) and eq(*p.second, *one)) { | ||
return add(cos(mul(mul(I, minus_one), b)), | ||
mul(I, sin(mul(mul(I, minus_one), b)))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we only care about multiples of I*pi/2
, how about checking for the multiple, and writing the formula for the 4 cases? Will be faster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, which four cases are we talking about?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the arg is n*I*pi/2
, whether n = 0,1,2,3 mod 4
symengine/pow.cpp
Outdated
RCP<const Basic> s = one; | ||
for (const auto &p : dict) { | ||
if (eq(*p.first, *pi) and is_a_Complex(*p.second)) { | ||
s = exp(mul(p.first, p.second)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you take out the logic of mul to a new function and then call it from here so that we know if s
was simplified that it is a number, else we can do Add::dict_add_term(new_dict, p.second, p.first);
@ShikharJ, optimizations I'm talking about maybe very small and won't matter for most of the cases, but when changing a very important function like |
Yes, we have to be very careful when modifying core routines like in this PR. |
Ping @isuruf. |
return mul(s, make_rcp<const Pow>( | ||
a, Add::from_dict(coef, std::move(new_dict)))); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to carefully benchmark these two if branches, before and after this PR. So the first if clause would run for something like E^(a*b)
. The second one for E^(a+b)
. Correct?
If so, then we need to benchmark these two things and similar things a lot.
@@ -87,6 +87,25 @@ int Pow::compare(const Basic &o) const | |||
return base_cmp; | |||
} | |||
|
|||
bool exp_mul_helper(const RCP<const Basic> &b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about this function returning -1, 0, 1,2,3 where -1 is when it is not what we want and the others n
in the function beloe
RCP<const Number> coef = down_cast<const Add &>(*b).get_coef(); | ||
RCP<const Basic> s = one; | ||
for (const auto &p : dict) { | ||
if (eq(*p.first, *pi) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To do this, you don't need to iterate the dictionary. Just use dict.find
to find the value pi
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way I can remove that particular entry of pi
as well? That way, there'd be no need of iteration at all.
for (const auto &p : dict) { | ||
if (eq(*p.first, *pi) | ||
and exp_mul_helper(mul(p.first, p.second))) { | ||
s = exp(mul(p.first, p.second)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can a call to exp
be avoided here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the expense of code duplication it can be. I can't seem to think of another way.
What needs to be done here to finish it? |
Relevant: #1330