Skip to content

Conversation

oscargus
Copy link
Contributor

@oscargus oscargus commented Feb 2, 2022

References to other Issues or PRs

Closes #22992

Brief description of what is fixed or changed

cot is automatically rewritten to tan to support lambdifying.

Other comments

Release Notes

  • printing
    • cot, sec, csc and related inverse and hyperbolic functions are automatically rewritten if they cannot be printed. Enables lambdify for these expressions.
  • functions
    • hyperbolic functions can be rewritten to more other hyperbolic functions.

@sympy-bot
Copy link

sympy-bot commented Feb 2, 2022

Hi, I am the SymPy bot (v167). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • functions

    • hyperbolic functions can be rewritten to more other hyperbolic functions. (#22993 by @oscargus)
  • printing

    • cot, sec, csc and related inverse and hyperbolic functions are automatically rewritten if they cannot be printed. Enables lambdify for these expressions. (#22993 by @oscargus)

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.11.

Click here to see the pull request description that was parsed.
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->

Closes #22992 

#### Brief description of what is fixed or changed

`cot` is automatically rewritten to `tan` to support lambdifying.

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below between the BEGIN and END
statements. The basic format is a bulleted list with the name of the subpackage
and the release note for this PR. For example:

* solvers
  * Added a new solver for logarithmic equations.

* functions
  * Fixed a bug with log of integers.

or if no release note(s) should be included use:

NO ENTRY

See https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more
information on how to write release notes. The bot will check your release
notes automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
* printing
   * `cot`, `sec`, `csc` and related inverse and hyperbolic functions are automatically rewritten if they cannot be printed. Enables `lambdify` for these expressions. 
* functions
    * hyperbolic functions can be rewritten to more other hyperbolic functions.
<!-- END RELEASE NOTES -->

Update

The release notes on the wiki have been updated.

@@ -69,6 +69,7 @@ class CodePrinter(StrPrinter):
# may be supported
# function_to_rewrite : (function_to_rewrite_to, iterable_with_other_functions_required)
_rewriteable_functions = {
'cot': ('tan', []),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably this should also be done for sec, csc, coth, sech, csch as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And all the inverses asec, acsc etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good observation! I'll add those as well (but maybe not tests for all combinations etc...).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth testing all combinations. Some of them won't work and some of the rewrite rules seem to be wrong or broken e.g.:

In [2]: sech(x).rewrite(cosh)
Out[2]: sech(x)

In [3]: sech(x).rewrite(sinh)
Out[3]: 
   1   
───────
cosh(x)

In [4]: csch(x).rewrite(cosh)
Out[4]: 
   1   
───────
sinh(x)

In [5]: csch(x).rewrite(sinh)
Out[5]: csch(x)

Also if any rewrite rule is missing then lambdify goes into infinite recursion so that would happen for asech here:

In [9]: asec(x).rewrite(acos)
Out[9]: 
    ⎛1acos⎜─⎟
    ⎝xIn [10]: asech(x).rewrite(acosh)
Out[10]: asech(x)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK! I'll update the current one then. I found that the a*h could not be rewritten, but not the others.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some are simply not supported, while others are getting rewritten as part of automatic evaluation. For example:

In [3]: sech(x).rewrite(sinh)
Out[3]: 
   1   
───────
cosh(x)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a sech._eval_rewrite_as_cosh method.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see you've added it now

@github-actions
Copy link

github-actions bot commented Feb 2, 2022

Benchmark results from GitHub Actions

Lower numbers are good, higher numbers are bad. A ratio less than 1
means a speed up and greater than 1 means a slowdown. Green lines
beginning with + are slowdowns (the PR is slower then master or
master is slower than the previous release). Red lines beginning
with - are speedups.

Significantly changed benchmark results (PR vs master)

       before           after         ratio
     [bb7275c1]       [8ac511a8]
+     30.2±0.09μs      49.2±0.04μs     1.63  core.expand.TimeExpand.time_expand_log

Significantly changed benchmark results (master vs previous release)

       before           after         ratio
     [907895ac]       [bb7275c1]
-         260±1ms        157±0.9ms     0.60  large_exprs.TimeLargeExpressionOperations.time_subs
-     17.7±0.04ms      11.8±0.03ms     0.67  matrices.TimeMatrixExpression.time_MatAdd_doit
-       270±0.7μs        130±0.1μs     0.48  matrices.TimeMatrixExpression.time_MatMul
-     17.0±0.02ms      9.05±0.04ms     0.53  matrices.TimeMatrixExpression.time_MatMul_doit
-         4.99±0s          371±2ms     0.07  polygon.PolygonArbitraryPoint.time_bench01
+     4.03±0.02ms      6.75±0.02ms     1.67  solve.TimeMatrixOperations.time_det(4, 2)
+     4.03±0.02ms      6.78±0.01ms     1.68  solve.TimeMatrixOperations.time_det_bareiss(4, 2)
+      44.9±0.2ms       79.9±0.5ms     1.78  solve.TimeMatrixSolvePyDySlow.time_linsolve(1)
+      45.4±0.5ms       79.3±0.4ms     1.75  solve.TimeMatrixSolvePyDySlow.time_solve(1)

Full benchmark results can be found as artifacts in GitHub Actions
(click on checks at the top of the PR).

# Standard math
F = lambdify([a, t], expr)

assert abs(expr.subs(point) - F(*point.values())) <= 1e-10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any choices of point that would deviate substantially between cot and tan**-1 due to numerical implementation? I'd argue that this test would imply to users that this accuracy holds for any point, but that might not actually be the case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't have thought that there would be a substantial deviation. Rounding error from division is generally minimal.

For inverse functions there could be issues with branch cuts and signed zeros (#21751)

Copy link
Contributor Author

@oscargus oscargus Feb 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess one can probably find cases where it is better to evaluate the original function as opposed to a rewrite, but to some extent it is choice where to get the errors... And in most cases, I assume that the rewrites are OK enough and not worse that just evaluating a large symbolic argument to cot like sqrt(sqrt(2) + sin(pi/7)) or so, which we do not really promise are accurate enough.

@oscargus oscargus changed the title Fix issue with lambdifying cot Enable automatic rewrite for cot, sec, csc, and related inverse and hyperbolic functions Feb 2, 2022
@oscargus oscargus marked this pull request as draft February 2, 2022 13:50
@oscargus
Copy link
Contributor Author

oscargus commented Feb 2, 2022

This currently is enough (and a bit more) to support code generation for all the (a)cot/sec/csc(h) variants, but for each, it is in general possible to rewrite it to almost any other form (including the sin/cos/tan ones), so typically 12 variants (plus log and meijerg etc) for each (> 144 in total). Hence, this is quite a bit of work.

I am currently happy for now. While it is of course possible to add all, I am not sure if it is worth it. Some of them are automatically evaluated back and others have quite massive expressions for the general case. I cannot even imagine adding tests for all the cases. Maybe the way forward is to add an issue for adding the remaining one. It is pretty straightforward, although tedious, and it is easy to show by an example what code and test to add (although, evaluation can make it a bit challenging to figure out a good test case).

@oscargus oscargus marked this pull request as ready for review February 2, 2022 15:15
assert asinh(x).rewrite(atanh) == atanh(x/sqrt(1 + x**2))
assert asinh(x).rewrite(asin) == asinh(x)
assert asinh(x*(1 + I)).rewrite(asin) == -I*asin(I*x*(1+I))
assert asinh(x).rewrite(acos) == I*(-I*asinh(x) + pi/2) - I*pi/2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this odd: no acos, just a rewriting in terms of original function?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And when expanded, it is just the original.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is automatic evaluation:

In [2]: acos(I * x)
Out[2]: 
              π
-asinh(x) +2

@@ -637,6 +641,8 @@ def test_acosh():
def test_acosh_rewrite():
x = Symbol('x')
assert acosh(x).rewrite(log) == log(x + sqrt(x - 1)*sqrt(x + 1))
assert acosh(x).rewrite(asinh) == Abs(asinh(sqrt(x**2 - 1)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At x=0 these are not the same.

@@ -637,6 +641,8 @@ def test_acosh():
def test_acosh_rewrite():
x = Symbol('x')
assert acosh(x).rewrite(log) == log(x + sqrt(x - 1)*sqrt(x + 1))
assert acosh(x).rewrite(asinh) == Abs(asinh(sqrt(x**2 - 1)))
assert acosh(x).rewrite(atanh) == Abs(atanh(sqrt(x**2 - 1)/x))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not the same at 1.

@@ -721,6 +727,9 @@ def test_asech_series():
def test_asech_rewrite():
x = Symbol('x')
assert asech(x).rewrite(log) == log(1/x + sqrt(1/x - 1) * sqrt(1/x + 1))
assert asech(x).rewrite(acosh) == acosh(1/x)
assert asech(x).rewrite(asinh) == Abs(asinh(sqrt(1/x**2 - 1)))
assert asech(x).rewrite(atanh) == Abs(atanh(x*sqrt(1/x**2 - 1)))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issues with branch, again, when x > 1 -- do these need to be Piecewise?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe some of these rewrites that aren't needed for lambdify should be removed so that the main issue can be fixed.

@oscarbenjamin
Copy link
Collaborator

If a few of the rewrite methods are removed then I think this is good to go.

@oscargus
Copy link
Contributor Author

I've updated the rewrites. In some cases I used a Piecewise, for others, I went for the most general expressions available.

I'm a bit confused about

    def _eval_rewrite_as_asinh(self, x, **kwargs):
        return sqrt(x - 1)/sqrt(1 - x)*(pi/2 + S.ImaginaryUnit*asinh(S.ImaginaryUnit*x))

as Sympy will evaluate asinh(S.ImaginaryUnit*x) as S.ImaginaryUnit*asinh(x). Primarily, I wonder if the evaluation is really correct? (If so, why did Wolfram not include that in the general rewrite, would be much easier to use sqrt(x - 1)/sqrt(1 - x)*(pi/2 - asinh(x)), but that is not given...)

@oscarbenjamin
Copy link
Collaborator

I tested all combinations like this:

from sympy import *

x = symbols('x')

funcs = [cot , csc , sec , acot , acsc , asec,
         coth, csch, sech, acoth, acsch, asech]

points = [
    0, 1, -1, I, -I, 1+I, 1-I, -1+I, -1-I,
]

for f1 in funcs:
    for f2 in funcs:
        expr1 = f1(x)
        expr2 = expr1.rewrite(f2)
        for p in points:
            if expr1.subs(x, p).n() != expr2.subs(x, p).n():
                print()
                print(f1, f2, p)
                print(expr1, ' --> ', expr2)
                print(expr1.subs(x, p), expr2.subs(x, p))
                print(expr1.subs(x, p).n(5), expr2.subs(x, p).n(5))

There are a bunch of cases that don't work out e.g.:

In [32]: sec(0)
Out[32]: 1

In [33]: sec(x).rewrite(cot).subs(x, 0)
Out[33]: nan

The full output is

csc cot 0
csc(x)  -->  (cot(x/2)**2 + 1)/(2*cot(x/2))
zoo nan
zoo nan

sec cot 0
sec(x)  -->  (cot(x/2)**2 + 1)/(cot(x/2)**2 - 1)
1 nan
1.0000 nan

acot acsc 0
acot(x)  -->  x*(-acsc(sqrt((x**2 + 1)/x**2)) + pi/2)*sqrt(x**(-2))
pi/2 nan
1.5708 nan

acot acsc I
acot(x)  -->  x*(-acsc(sqrt((x**2 + 1)/x**2)) + pi/2)*sqrt(x**(-2))
-oo*I zoo
-oo*I zoo

acot acsc -I
acot(x)  -->  x*(-acsc(sqrt((x**2 + 1)/x**2)) + pi/2)*sqrt(x**(-2))
oo*I zoo
oo*I zoo

acot acsc -1 + I
acot(x)  -->  x*(-acsc(sqrt((x**2 + 1)/x**2)) + pi/2)*sqrt(x**(-2))
-acot(1 - I) (-1 - I)*(-1 + I)*(pi/2 - acsc(sqrt((1 + (-1 + I)**2)/(-1 + I)**2)))/2
-0.55357 - 0.40236*I 0.55357 + 0.40236*I

acot acsc -1 - I
acot(x)  -->  x*(-acsc(sqrt((x**2 + 1)/x**2)) + pi/2)*sqrt(x**(-2))
-acot(1 + I) (-1 - I)*(-1 + I)*(pi/2 - acsc(sqrt((1 + (-1 - I)**2)/(-1 - I)**2)))/2
-0.55357 + 0.40236*I 0.55357 - 0.40236*I

acot asec 0
acot(x)  -->  x*sqrt(x**(-2))*asec(sqrt((x**2 + 1)/x**2))
pi/2 nan
1.5708 nan

acot asec I
acot(x)  -->  x*sqrt(x**(-2))*asec(sqrt((x**2 + 1)/x**2))
-oo*I zoo
-oo*I zoo

acot asec -I
acot(x)  -->  x*sqrt(x**(-2))*asec(sqrt((x**2 + 1)/x**2))
oo*I zoo
oo*I zoo

acot asec -1 + I
acot(x)  -->  x*sqrt(x**(-2))*asec(sqrt((x**2 + 1)/x**2))
-acot(1 - I) (-1 - I)*(-1 + I)*asec(sqrt((1 + (-1 + I)**2)/(-1 + I)**2))/2
-0.55357 - 0.40236*I 0.55357 + 0.40236*I

acot asec -1 - I
acot(x)  -->  x*sqrt(x**(-2))*asec(sqrt((x**2 + 1)/x**2))
-acot(1 + I) (-1 - I)*(-1 + I)*asec(sqrt((1 + (-1 - I)**2)/(-1 - I)**2))/2
-0.55357 + 0.40236*I 0.55357 - 0.40236*I

acsc acot 0
acsc(x)  -->  (-acot(1/sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x
zoo nan
zoo nan

acsc acot I
acsc(x)  -->  (-acot(1/sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x
acsc(I) pi/2 - I*acoth(sqrt(2)/2)
-0.88137*I 0.e-10 - 0.88137*I

acsc acot -I
acsc(x)  -->  (-acot(1/sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x
-acsc(I) -pi/2 + I*acoth(sqrt(2)/2)
0.88137*I -0.e-10 + 0.88137*I

acsc acot -1 + I
acsc(x)  -->  (-acot(1/sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x
-acsc(1 - I) (-1 - I)*(-1 + I)*(pi/2 - acot(1/sqrt(-1 + (-1 + I)**2)))/2
-0.45228 - 0.53064*I 0.45228 + 0.53064*I

acsc acot -1 - I
acsc(x)  -->  (-acot(1/sqrt(x**2 - 1)) + pi/2)*sqrt(x**2)/x
-acsc(1 + I) (-1 - I)*(-1 + I)*(pi/2 - acot(1/sqrt(-1 + (-1 - I)**2)))/2
-0.45228 + 0.53064*I 0.45228 - 0.53064*I

acsc asec I
acsc(x)  -->  -asec(x) + pi/2
acsc(I) pi/2 - asec(I)
-0.88137*I 0.e-10 - 0.88137*I

acsc asec -I
acsc(x)  -->  -asec(x) + pi/2
-acsc(I) pi/2 - asec(-I)
0.88137*I 0.e-10 + 0.88137*I

asec acot 0
asec(x)  -->  (2*acot(x - sqrt(x**2 - 1)) - pi/2)*sqrt(x**2)/x
zoo nan
zoo nan

asec acot -1 + I
asec(x)  -->  (2*acot(x - sqrt(x**2 - 1)) - pi/2)*sqrt(x**2)/x
asec(-1 + I) (-1 - I)*(-1 + I)*(-pi/2 - 2*acot(1 + sqrt(-1 + (-1 + I)**2) - I))/2
2.0231 + 0.53064*I -2.0231 - 0.53064*I

asec acot -1 - I
asec(x)  -->  (2*acot(x - sqrt(x**2 - 1)) - pi/2)*sqrt(x**2)/x
asec(-1 - I) (-1 - I)*(-1 + I)*(-pi/2 - 2*acot(1 + I + sqrt(-1 + (-1 - I)**2)))/2
2.0231 - 0.53064*I -2.0231 + 0.53064*I

csch coth 0
csch(x)  -->  (coth(x/2)**2 - 1)/(2*coth(x/2))
zoo nan
zoo nan

sech coth 0
sech(x)  -->  (coth(x/2)**2 - 1)/(coth(x/2)**2 + 1)
1 nan
1.0000 nan

asech acsch 0
asech(x)  -->  sqrt(-1 + 1/x)*(-I*acsch(I*x) + pi/2)/sqrt(1 - 1/x)
oo nan
oo nan

asech acsch 1
asech(x)  -->  sqrt(-1 + 1/x)*(-I*acsch(I*x) + pi/2)/sqrt(1 - 1/x)
0 nan
0 nan

I don't think any of these are rewrites that are needed for lambdify though. Many of the examples fail on master as well.

@oscargus
Copy link
Contributor Author

Well, I have only added rewrite equations for the hyperbolic functions, so cannot really say anything about the trig-rewrites. (Some of those seems to be fundamentally incorrect.)

For the hyperbolic functions, I assume that the equations (given at Wolframs function site) do assume certain things, such as 1/0 = oo (rather than NaN), so the limit will be correct but as Sympy has made the choice to do that, the results will be a bit messed up. But it is of course possible to return a Piecewise for those cases.

@oscargus
Copy link
Contributor Author

There are so many rewrites that are incorrect in the existing code that it is not really possible to fix it in this PR. I mean, not even this holds: tan(x).rewrite('sincos')

I'll push the latest version that fixes the introduced issues pointed out in your test and also introduces some support for +/-I*oo.

@oscargus
Copy link
Contributor Author

Here is an extended test:

from sympy import *

x = symbols('x')

funcs = [cos, sin, tan, cot , csc , sec , acos, asin, atan, acot , acsc , asec,
         cosh, sinh, tanh, coth, csch, sech, acosh, asinh, atanh, acoth, acsch, asech]

points = [
    0, 1, -1, I, -I, 1+I, 1-I, -1+I, -1-I, oo, -oo, I*oo, -I*oo, zoo
]

for f1 in funcs:
    for f2 in funcs:
        expr1 = f1(x)
        expr2 = expr1.rewrite(f2)
        for p in points:
            if expr1.subs(x, p).n() != expr2.subs(x, p).n():
                print()
                print(f1, f2, p)
                print(expr1, ' --> ', expr2)
                print(expr1.subs(x, p), expr2.subs(x, p))
                print(expr1.subs(x, p).n(5), expr2.subs(x, p).n(5))

@oscarbenjamin
Copy link
Collaborator

There are so many rewrites that are incorrect in the existing code that it is not really possible to fix it in this PR

I wasn't suggesting that they all needed to be fixed here.

@oscarbenjamin
Copy link
Collaborator

Here is an extended test:

Okay, there's a lot of fails (on master)...

@oscargus
Copy link
Contributor Author

oscargus commented Mar 2, 2022

I think all failing tests of your original test are not caused by my addition, even the ones for hyperbolic functions. Some of the basic hyperbolic rewrites are just using the reciprocal of the argument or function, which then of course fails for 0 inputs.

@oscargus
Copy link
Contributor Author

Yes, most of the hyperbolic I would say. Also, it would reduce the risk of actually not typing the correct Piecewise, I think (at least) one of them is not fully correct yet.

I'm slowly trying to get rid of the last bugs and converting the previous rewrites to correct ones (evaluating with 0 or pi kills quite a few of the basic rewrites, like sin(x).rewrite(tan).subs(x, pi)). These should probably stay for correctness.

@oscarbenjamin
Copy link
Collaborator

Ideally we should avoid using Piecewise wherever possible (it is very awkward both symbolically and numerically e.g. in lambdify). So if we are using a Piecewise in place of a potential non-Piecewise expression just to work around some bug then it would be good to have a comment clearly showing what the non-Piecewise version of the formula would be and why it isn't being used.

I want to have a go at fixing the sqrt but but I'm not sure how easy it is to do or how disruptive it might potentially be (maybe this is too late in the release cycle). If we had more time then it would make sense to fix that bug first and then get all the formulae here perfected with minimial use of Piecewise. If we want to get this PR in for 1.11 though then it's probably best not to wait for that. We should make sure though that we can easily fix all the formulae later if/when the bug does get fixed.

@oscarbenjamin
Copy link
Collaborator

I want to have a go at fixing the sqrt but but I'm not sure how easy it is to do

Actually it seems to be quite easy to do (#23653)

@oscarbenjamin
Copy link
Collaborator

Now that #23653 is merged it shouldn't be necessary to work around the sqrt bug using unnecessary Piecewise.

@oscarbenjamin
Copy link
Collaborator

It would be good to get this in for 1.11

def _eval_rewrite_as_asinh(self, x, **kwargs):
return Piecewise((S.Zero, Eq(x, 1)),
(sqrt(x - 1)/sqrt(1 - x)*(pi/2 +
S.ImaginaryUnit*asinh(S.ImaginaryUnit*x)), True))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is acosh(x).rewrite(asinh). I think it can be done without Piecewise:

In [25]: 2*asinh(sqrt((x - 1)/2))
Out[25]: 
       ⎛    _______⎞
       ⎜   ╱ x   12asinh⎜  ╱  ─ - ─ ⎟
       ⎝╲╱   2   2

Comment on lines 1609 to 1614
def _eval_rewrite_as_asinh(self, x, **kwargs):
from sympy.functions.elementary.complexes import arg, Abs
return Piecewise((S.Half*asinh(2*x/(1 - x**2)) - pi*x/2*sqrt(-1/x**2),
Abs(x) < 1),
(-S.Half*asinh(2*x/(1 - x**2)), Abs(x) > 1),
(S.Infinity*arg(x), True))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is acoth(x).rewrite(asinh). The final case in the Piecewise seems incorrect for x=I:

In [1]: acoth(x).subs(x, I)
Out[1]: 
-π 
─────
  4  

In [2]: acoth(x).rewrite(asinh).subs(x, I)
Out[2]: ∞

@oscarbenjamin
Copy link
Collaborator

This is the failing test:

assert tan(x).rewrite(sin) == 2*sin(x)**2/sin(2*x)

It has been changed to

In [39]: tan(x).rewrite(sin)
Out[39]: 
⎧    0      for im(x) = 0 ∧ x mod π = 0
⎪                                      
⎪     2                                
⎨2⋅sin (x)                             
⎪─────────           otherwise         
⎪ sin(2⋅x)                             
⎩           

which is correct for x = pi. The second case still gives zoo for pi/2 etc.

@oscargus
Copy link
Contributor Author

oscargus commented Jul 7, 2022

I'll try to get some time to retype the general expressions.

@oscarbenjamin oscarbenjamin mentioned this pull request Jul 8, 2022
44 tasks
@oscargus oscargus force-pushed the cot_py_generation branch 2 times, most recently from c9ca06c to cbb767c Compare July 9, 2022 14:47
@oscargus
Copy link
Contributor Author

oscargus commented Jul 9, 2022

This is now updated. (But will be updated again in a few minutes as I realized that some Piecewises that should not be there was left.)

The possible controversial thing is that e.g sin(x).rewrite(cot) is now a Piecewise. The problem is that the earlier expression, 2*cot(x/2)/(cot(x/2)**2 + 1)`, did not evaluate correctly for e.g. 0.

@oscargus oscargus force-pushed the cot_py_generation branch 2 times, most recently from 2032945 to 94ab406 Compare July 9, 2022 15:17
@smichr
Copy link
Member

smichr commented Jul 10, 2022

Now that #23653 is merged

sorry I missed this; glad to see it was fixed

@oscargus oscargus force-pushed the cot_py_generation branch from 94ab406 to 74c43ce Compare July 10, 2022 13:08
@oscarbenjamin
Copy link
Collaborator

Okay, let's get this in.

There are some followups to be done from the discussion above but we can defer those to a future issue.

@maxkway
Copy link

maxkway commented Nov 16, 2023

There is some mistake due lambdifing expressions with cot(x)**2 (and another functions with power auto-rewrite):
lambdify(x, cot(x)**2) has
Source code: def _lambdifygenerated(x): return tan(x)**(-1.0)**2
and represents actually tan(x) instead of (tan(x)**(-1.0))**2 (=tan(x)**(-2.0))

@oscargus oscargus deleted the cot_py_generation branch November 16, 2023 12:50
@oscargus
Copy link
Contributor Author

I am not sure it is caused by this. Entering tan(x)**(-1.0)**2 gives tan(x)**(1.0)

@oscargus
Copy link
Contributor Author

Ahh, I see the problem now. However, it is not clear how to get around this (at least from the changes made in this PR). Maybe one can return an unevaluated result in this case though.

@oscarbenjamin
Copy link
Collaborator

It looks correct to me:

In [2]: f = lambdify(x, cot(x)**2)

In [3]: import inspect

In [4]: inspect.getsource(f)
Out[4]: 'def _lambdifygenerated(x):\n    return (tan(x)**(-1.0))**2\n'

In [5]: f(2)
Out[5]: 0.20945043706303793

In [6]: cot(2.0)**2
Out[6]: 0.209450437063038

@oscarbenjamin
Copy link
Collaborator

I think that the issue was already fixed in 83f8c53 from gh-25514

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

Successfully merging this pull request may close these issues.

Lambdify Error: 'NameError: name 'cot' is not defined'
7 participants