Repository: sympy/sympy
Pull request: Added hyperbolic function in undetermined coefficients
<!-- 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. -->
Fixes #18408 

#### Brief description of what is fixed or changed
Earlier the ode involving hyperbolic function were not classified by `nth_linear_constant_coeff_undetermined_coefficients` not it is fixed

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. 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 -->
* solvers
  *  Now `dsolve` can solve linear non-homogeneous ODEs involving hyperbolic functions when using the method of undetermined coefficients.
<!-- END RELEASE NOTES -->

Problem statement:
classify_ode doesn't recognise nth_linear_constant_coeff_undetermined_coefficients
> ```julia
> In [58]: eq = f(x).diff(x, 2) - 49*f(x) - sinh(3*x)                                                                                     
> 
> In [59]: classify_ode(eq)                                                                                                         
> Out[59]: 
> ('nth_linear_constant_coeff_variation_of_parameters', 
>  'nth_linear_constant_coeff_variation_of_parameters_Integral')
> 
> ```
Although `dsolve(eq)` gives the correct answer but it should also classify this equation as `nth_linear_constant_coeff_undetermined_coefficients`
If it could have identified then solution time will also reduce as 'nth_linear_constant_coeff_variation_of_parameters' is slower than `nth_linear_constant_coeff_undetermined_coefficients`

Hints from issue discussion:
It doesn't look like `sinh` or `cosh` are checked for at all.
`sinh(x)` can be written as `(exp(x) - exp(-x))/2` so shouldn't the classifiers identify this?
Yes. We can do that now:
```julia
In [1]: eq = f(x).diff(x, 2) - 49*f(x) - sinh(3*x)

In [2]: classify_ode(eq)
Out[2]: 
('nth_linear_constant_coeff_variation_of_parameters',
 'nth_linear_constant_coeff_variation_of_parameters_Integral')

In [3]: classify_ode(eq.rewrite(exp))
Out[3]: 
('nth_linear_constant_coeff_undetermined_coefficients',
 'nth_linear_constant_coeff_variation_of_parameters',
 'nth_linear_constant_coeff_variation_of_parameters_Integral')
```
However I think it would be better to handle `sinh` and `cosh` as well as `sin` and `cos` internally. If the user uses `sinh` then we should try to give them an answer in that form.
so `eq.rewrite(exp)`  should be used in `classify_ode`?
if this is the case then how we will again convert back into `sinh`
I think it is better to not use rewrite. Rather the solver should handle sinh and cosh explicitly (like it does for sin and cos even though they can be written with exp).

Commit message: synching

Patch to review:
diff --git a/sympy/solvers/ode.py b/sympy/solvers/ode.py
index 2b6bca431c44..a41ba1851236 100644
--- a/sympy/solvers/ode.py
+++ b/sympy/solvers/ode.py
@@ -253,7 +253,7 @@
 
 from sympy.logic.boolalg import (BooleanAtom, And, Not, BooleanTrue,
                                 BooleanFalse)
-from sympy.functions import cos, exp, im, log, re, sin, tan, sqrt, \
+from sympy.functions import cos, cosh, exp, im, log, re, sin, sinh, tan, sqrt, \
     atan2, conjugate, Piecewise, cbrt, besselj, bessely, airyai, airybi
 from sympy.functions.combinatorial.factorials import factorial
 from sympy.integrals.integrals import Integral, integrate
@@ -5822,7 +5822,7 @@ def _test_term(expr, x):
                             foundtrig = True
             return all(_test_term(i, x) for i in expr.args)
         elif expr.is_Function:
-            if expr.func in (sin, cos, exp):
+            if expr.func in (sin, cos, exp, sinh, cosh):
                 if expr.args[0].match(a*x + b):
                     return True
                 else:
diff --git a/sympy/solvers/tests/test_ode.py b/sympy/solvers/tests/test_ode.py
index a4dc6160720f..c8cf9424f11b 100644
--- a/sympy/solvers/tests/test_ode.py
+++ b/sympy/solvers/tests/test_ode.py
@@ -3830,3 +3830,15 @@ def test_issue_15996():
     sol = Eq(f(x), C1 + (C2 + C3*x - x**2/8 + 5*exp(2*I*x)/16)*sin(x) + (C4 + C5*x + I*x**2/8 + 5*I*exp(2*I*x)/16)*cos(x) - I*exp(I*x))
     assert sol == dsolve(eq, hint='nth_linear_constant_coeff_variation_of_parameters')
     assert checkodesol(eq, sol) == (True, 0)
+
+
+def test_issue_18408():
+    eq = f(x).diff(x, 3) - f(x).diff(x) - sinh(x)
+    sol = Eq(f(x), C1 + C2*exp(-x) + C3*exp(x) + x*sinh(x)/2)
+    assert sol == dsolve(eq)
+    assert checkodesol(eq, sol) == (True, 0)
+
+    eq = f(x).diff(x, 2) - 49*f(x) - sinh(3*x)
+    sol = Eq(f(x), C1*exp(-7*x) + C2*exp(7*x) - sinh(3*x)/40)
+    assert sol == dsolve(eq)
+    assert checkodesol(eq, sol) == (True, 0)


Code review comments:
diff_hunk: @@ -3830,3 +3830,15 @@ def test_issue_15996():
     sol = Eq(f(x), C1 + (C2 + C3*x - x**2/8 + 5*exp(2*I*x)/16)*sin(x) + (C4 + C5*x + I*x**2/8 + 5*I*exp(2*I*x)/16)*cos(x) - I*exp(I*x))
     assert sol == dsolve(eq, hint='nth_linear_constant_coeff_variation_of_parameters')
     assert checkodesol(eq, sol) == (True, 0)
+
+
+def test_issue_18408():
+    eq = f(x).diff(x, 3) - f(x).diff(x) - sinh(x)
+    sol = Eq(f(x), C1 + C2*exp(-x) + C3*exp(x) + x*sinh(x)/2)
+    assert sol == dsolve(eq)
+    assert checkodesol(eq, sol) == (True, 0)
+
+    eq = f(x).diff(x, 2) - 49*f(x) - sinh(3*x)
+    sol = Eq(f(x), C1*exp(-7*x) + C2*exp(7*x) - sinh(3*x)/40)
+    assert sol == dsolve(eq)
path: sympy/solvers/tests/test_ode.py
text: @user1:
Since these ODEs can already be solved on master these tests should specify the hint as in `hint='nth_..._undetermined_coefficients'`.

@author:
> What happens if the rhs includes both a hyperbolic function and exp(x)? Does it get confused? We should add a test for this.

It treats them seperately and i have added test for this .