ai-content-maker/.venv/Lib/site-packages/sympy/solvers/ode/tests/test_riccati.py

878 lines
29 KiB
Python

from sympy.core.random import randint
from sympy.core.function import Function
from sympy.core.mul import Mul
from sympy.core.numbers import (I, Rational, oo)
from sympy.core.relational import Eq
from sympy.core.singleton import S
from sympy.core.symbol import (Dummy, symbols)
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.hyperbolic import tanh
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.trigonometric import sin
from sympy.polys.polytools import Poly
from sympy.simplify.ratsimp import ratsimp
from sympy.solvers.ode.subscheck import checkodesol
from sympy.testing.pytest import slow
from sympy.solvers.ode.riccati import (riccati_normal, riccati_inverse_normal,
riccati_reduced, match_riccati, inverse_transform_poly, limit_at_inf,
check_necessary_conds, val_at_inf, construct_c_case_1,
construct_c_case_2, construct_c_case_3, construct_d_case_4,
construct_d_case_5, construct_d_case_6, rational_laurent_series,
solve_riccati)
f = Function('f')
x = symbols('x')
# These are the functions used to generate the tests
# SHOULD NOT BE USED DIRECTLY IN TESTS
def rand_rational(maxint):
return Rational(randint(-maxint, maxint), randint(1, maxint))
def rand_poly(x, degree, maxint):
return Poly([rand_rational(maxint) for _ in range(degree+1)], x)
def rand_rational_function(x, degree, maxint):
degnum = randint(1, degree)
degden = randint(1, degree)
num = rand_poly(x, degnum, maxint)
den = rand_poly(x, degden, maxint)
while den == Poly(0, x):
den = rand_poly(x, degden, maxint)
return num / den
def find_riccati_ode(ratfunc, x, yf):
y = ratfunc
yp = y.diff(x)
q1 = rand_rational_function(x, 1, 3)
q2 = rand_rational_function(x, 1, 3)
while q2 == 0:
q2 = rand_rational_function(x, 1, 3)
q0 = ratsimp(yp - q1*y - q2*y**2)
eq = Eq(yf.diff(), q0 + q1*yf + q2*yf**2)
sol = Eq(yf, y)
assert checkodesol(eq, sol) == (True, 0)
return eq, q0, q1, q2
# Testing functions start
def test_riccati_transformation():
"""
This function tests the transformation of the
solution of a Riccati ODE to the solution of
its corresponding normal Riccati ODE.
Each test case 4 values -
1. w - The solution to be transformed
2. b1 - The coefficient of f(x) in the ODE.
3. b2 - The coefficient of f(x)**2 in the ODE.
4. y - The solution to the normal Riccati ODE.
"""
tests = [
(
x/(x - 1),
(x**2 + 7)/3*x,
x,
-x**2/(x - 1) - x*(x**2/3 + S(7)/3)/2 - 1/(2*x)
),
(
(2*x + 3)/(2*x + 2),
(3 - 3*x)/(x + 1),
5*x,
-5*x*(2*x + 3)/(2*x + 2) - (3 - 3*x)/(Mul(2, x + 1, evaluate=False)) - 1/(2*x)
),
(
-1/(2*x**2 - 1),
0,
(2 - x)/(4*x - 2),
(2 - x)/((4*x - 2)*(2*x**2 - 1)) - (4*x - 2)*(Mul(-4, 2 - x, evaluate=False)/(4*x - \
2)**2 - 1/(4*x - 2))/(Mul(2, 2 - x, evaluate=False))
),
(
x,
(8*x - 12)/(12*x + 9),
x**3/(6*x - 9),
-x**4/(6*x - 9) - (8*x - 12)/(Mul(2, 12*x + 9, evaluate=False)) - (6*x - 9)*(-6*x**3/(6*x \
- 9)**2 + 3*x**2/(6*x - 9))/(2*x**3)
)]
for w, b1, b2, y in tests:
assert y == riccati_normal(w, x, b1, b2)
assert w == riccati_inverse_normal(y, x, b1, b2).cancel()
# Test bp parameter in riccati_inverse_normal
tests = [
(
(-2*x - 1)/(2*x**2 + 2*x - 2),
-2/x,
(-x - 1)/(4*x),
8*x**2*(1/(4*x) + (-x - 1)/(4*x**2))/(-x - 1)**2 + 4/(-x - 1),
-2*x*(-1/(4*x) - (-x - 1)/(4*x**2))/(-x - 1) - (-2*x - 1)*(-x - 1)/(4*x*(2*x**2 + 2*x \
- 2)) + 1/x
),
(
3/(2*x**2),
-2/x,
(-x - 1)/(4*x),
8*x**2*(1/(4*x) + (-x - 1)/(4*x**2))/(-x - 1)**2 + 4/(-x - 1),
-2*x*(-1/(4*x) - (-x - 1)/(4*x**2))/(-x - 1) + 1/x - Mul(3, -x - 1, evaluate=False)/(8*x**3)
)]
for w, b1, b2, bp, y in tests:
assert y == riccati_normal(w, x, b1, b2)
assert w == riccati_inverse_normal(y, x, b1, b2, bp).cancel()
def test_riccati_reduced():
"""
This function tests the transformation of a
Riccati ODE to its normal Riccati ODE.
Each test case 2 values -
1. eq - A Riccati ODE.
2. normal_eq - The normal Riccati ODE of eq.
"""
tests = [
(
f(x).diff(x) - x**2 - x*f(x) - x*f(x)**2,
f(x).diff(x) + f(x)**2 + x**3 - x**2/4 - 3/(4*x**2)
),
(
6*x/(2*x + 9) + f(x).diff(x) - (x + 1)*f(x)**2/x,
-3*x**2*(1/x + (-x - 1)/x**2)**2/(4*(-x - 1)**2) + Mul(6, \
-x - 1, evaluate=False)/(2*x + 9) + f(x)**2 + f(x).diff(x) \
- (-1 + (x + 1)/x)/(x*(-x - 1))
),
(
f(x)**2 + f(x).diff(x) - (x - 1)*f(x)/(-x - S(1)/2),
-(2*x - 2)**2/(4*(2*x + 1)**2) + (2*x - 2)/(2*x + 1)**2 + \
f(x)**2 + f(x).diff(x) - 1/(2*x + 1)
),
(
f(x).diff(x) - f(x)**2/x,
f(x)**2 + f(x).diff(x) + 1/(4*x**2)
),
(
-3*(-x**2 - x + 1)/(x**2 + 6*x + 1) + f(x).diff(x) + f(x)**2/x,
f(x)**2 + f(x).diff(x) + (3*x**2/(x**2 + 6*x + 1) + 3*x/(x**2 \
+ 6*x + 1) - 3/(x**2 + 6*x + 1))/x + 1/(4*x**2)
),
(
6*x/(2*x + 9) + f(x).diff(x) - (x + 1)*f(x)/x,
False
),
(
f(x)*f(x).diff(x) - 1/x + f(x)/3 + f(x)**2/(x**2 - 2),
False
)]
for eq, normal_eq in tests:
assert normal_eq == riccati_reduced(eq, f, x)
def test_match_riccati():
"""
This function tests if an ODE is Riccati or not.
Each test case has 5 values -
1. eq - The Riccati ODE.
2. match - Boolean indicating if eq is a Riccati ODE.
3. b0 -
4. b1 - Coefficient of f(x) in eq.
5. b2 - Coefficient of f(x)**2 in eq.
"""
tests = [
# Test Rational Riccati ODEs
(
f(x).diff(x) - (405*x**3 - 882*x**2 - 78*x + 92)/(243*x**4 \
- 945*x**3 + 846*x**2 + 180*x - 72) - 2 - f(x)**2/(3*x + 1) \
- (S(1)/3 - x)*f(x)/(S(1)/3 - 3*x/2),
True,
45*x**3/(27*x**4 - 105*x**3 + 94*x**2 + 20*x - 8) - 98*x**2/ \
(27*x**4 - 105*x**3 + 94*x**2 + 20*x - 8) - 26*x/(81*x**4 - \
315*x**3 + 282*x**2 + 60*x - 24) + 2 + 92/(243*x**4 - 945*x**3 \
+ 846*x**2 + 180*x - 72),
Mul(-1, 2 - 6*x, evaluate=False)/(9*x - 2),
1/(3*x + 1)
),
(
f(x).diff(x) + 4*x/27 - (x/3 - 1)*f(x)**2 - (2*x/3 + \
1)*f(x)/(3*x + 2) - S(10)/27 - (265*x**2 + 423*x + 162) \
/(324*x**3 + 216*x**2),
True,
-4*x/27 + S(10)/27 + 3/(6*x**3 + 4*x**2) + 47/(36*x**2 \
+ 24*x) + 265/(324*x + 216),
Mul(-1, -2*x - 3, evaluate=False)/(9*x + 6),
x/3 - 1
),
(
f(x).diff(x) - (304*x**5 - 745*x**4 + 631*x**3 - 876*x**2 \
+ 198*x - 108)/(36*x**6 - 216*x**5 + 477*x**4 - 567*x**3 + \
360*x**2 - 108*x) - S(17)/9 - (x - S(3)/2)*f(x)/(x/2 - \
S(3)/2) - (x/3 - 3)*f(x)**2/(3*x),
True,
304*x**4/(36*x**5 - 216*x**4 + 477*x**3 - 567*x**2 + 360*x - \
108) - 745*x**3/(36*x**5 - 216*x**4 + 477*x**3 - 567*x**2 + \
360*x - 108) + 631*x**2/(36*x**5 - 216*x**4 + 477*x**3 - 567* \
x**2 + 360*x - 108) - 292*x/(12*x**5 - 72*x**4 + 159*x**3 - \
189*x**2 + 120*x - 36) + S(17)/9 - 12/(4*x**6 - 24*x**5 + \
53*x**4 - 63*x**3 + 40*x**2 - 12*x) + 22/(4*x**5 - 24*x**4 \
+ 53*x**3 - 63*x**2 + 40*x - 12),
Mul(-1, 3 - 2*x, evaluate=False)/(x - 3),
Mul(-1, 9 - x, evaluate=False)/(9*x)
),
# Test Non-Rational Riccati ODEs
(
f(x).diff(x) - x**(S(3)/2)/(x**(S(1)/2) - 2) + x**2*f(x) + \
x*f(x)**2/(x**(S(3)/4)),
False, 0, 0, 0
),
(
f(x).diff(x) - sin(x**2) + exp(x)*f(x) + log(x)*f(x)**2,
False, 0, 0, 0
),
(
f(x).diff(x) - tanh(x + sqrt(x)) + f(x) + x**4*f(x)**2,
False, 0, 0, 0
),
# Test Non-Riccati ODEs
(
(1 - x**2)*f(x).diff(x, 2) - 2*x*f(x).diff(x) + 20*f(x),
False, 0, 0, 0
),
(
f(x).diff(x) - x**2 + x**3*f(x) + (x**2/(x + 1))*f(x)**3,
False, 0, 0, 0
),
(
f(x).diff(x)*f(x)**2 + (x**2 - 1)/(x**3 + 1)*f(x) + 1/(2*x \
+ 3) + f(x)**2,
False, 0, 0, 0
)]
for eq, res, b0, b1, b2 in tests:
match, funcs = match_riccati(eq, f, x)
assert match == res
if res:
assert [b0, b1, b2] == funcs
def test_val_at_inf():
"""
This function tests the valuation of rational
function at oo.
Each test case has 3 values -
1. num - Numerator of rational function.
2. den - Denominator of rational function.
3. val_inf - Valuation of rational function at oo
"""
tests = [
# degree(denom) > degree(numer)
(
Poly(10*x**3 + 8*x**2 - 13*x + 6, x),
Poly(-13*x**10 - x**9 + 5*x**8 + 7*x**7 + 10*x**6 + 6*x**5 - 7*x**4 + 11*x**3 - 8*x**2 + 5*x + 13, x),
7
),
(
Poly(1, x),
Poly(-9*x**4 + 3*x**3 + 15*x**2 - 6*x - 14, x),
4
),
# degree(denom) == degree(numer)
(
Poly(-6*x**3 - 8*x**2 + 8*x - 6, x),
Poly(-5*x**3 + 12*x**2 - 6*x - 9, x),
0
),
# degree(denom) < degree(numer)
(
Poly(12*x**8 - 12*x**7 - 11*x**6 + 8*x**5 + 3*x**4 - x**3 + x**2 - 11*x, x),
Poly(-14*x**2 + x, x),
-6
),
(
Poly(5*x**6 + 9*x**5 - 11*x**4 - 9*x**3 + x**2 - 4*x + 4, x),
Poly(15*x**4 + 3*x**3 - 8*x**2 + 15*x + 12, x),
-2
)]
for num, den, val in tests:
assert val_at_inf(num, den, x) == val
def test_necessary_conds():
"""
This function tests the necessary conditions for
a Riccati ODE to have a rational particular solution.
"""
# Valuation at Infinity is an odd negative integer
assert check_necessary_conds(-3, [1, 2, 4]) == False
# Valuation at Infinity is a positive integer lesser than 2
assert check_necessary_conds(1, [1, 2, 4]) == False
# Multiplicity of a pole is an odd integer greater than 1
assert check_necessary_conds(2, [3, 1, 6]) == False
# All values are correct
assert check_necessary_conds(-10, [1, 2, 8, 12]) == True
def test_inverse_transform_poly():
"""
This function tests the substitution x -> 1/x
in rational functions represented using Poly.
"""
fns = [
(15*x**3 - 8*x**2 - 2*x - 6)/(18*x + 6),
(180*x**5 + 40*x**4 + 80*x**3 + 30*x**2 - 60*x - 80)/(180*x**3 - 150*x**2 + 75*x + 12),
(-15*x**5 - 36*x**4 + 75*x**3 - 60*x**2 - 80*x - 60)/(80*x**4 + 60*x**3 + 60*x**2 + 60*x - 80),
(60*x**7 + 24*x**6 - 15*x**5 - 20*x**4 + 30*x**2 + 100*x - 60)/(240*x**2 - 20*x - 30),
(30*x**6 - 12*x**5 + 15*x**4 - 15*x**2 + 10*x + 60)/(3*x**10 - 45*x**9 + 15*x**5 + 15*x**4 - 5*x**3 \
+ 15*x**2 + 45*x - 15)
]
for f in fns:
num, den = [Poly(e, x) for e in f.as_numer_denom()]
num, den = inverse_transform_poly(num, den, x)
assert f.subs(x, 1/x).cancel() == num/den
def test_limit_at_inf():
"""
This function tests the limit at oo of a
rational function.
Each test case has 3 values -
1. num - Numerator of rational function.
2. den - Denominator of rational function.
3. limit_at_inf - Limit of rational function at oo
"""
tests = [
# deg(denom) > deg(numer)
(
Poly(-12*x**2 + 20*x + 32, x),
Poly(32*x**3 + 72*x**2 + 3*x - 32, x),
0
),
# deg(denom) < deg(numer)
(
Poly(1260*x**4 - 1260*x**3 - 700*x**2 - 1260*x + 1400, x),
Poly(6300*x**3 - 1575*x**2 + 756*x - 540, x),
oo
),
# deg(denom) < deg(numer), one of the leading coefficients is negative
(
Poly(-735*x**8 - 1400*x**7 + 1680*x**6 - 315*x**5 - 600*x**4 + 840*x**3 - 525*x**2 \
+ 630*x + 3780, x),
Poly(1008*x**7 - 2940*x**6 - 84*x**5 + 2940*x**4 - 420*x**3 + 1512*x**2 + 105*x + 168, x),
-oo
),
# deg(denom) == deg(numer)
(
Poly(105*x**7 - 960*x**6 + 60*x**5 + 60*x**4 - 80*x**3 + 45*x**2 + 120*x + 15, x),
Poly(735*x**7 + 525*x**6 + 720*x**5 + 720*x**4 - 8400*x**3 - 2520*x**2 + 2800*x + 280, x),
S(1)/7
),
(
Poly(288*x**4 - 450*x**3 + 280*x**2 - 900*x - 90, x),
Poly(607*x**4 + 840*x**3 - 1050*x**2 + 420*x + 420, x),
S(288)/607
)]
for num, den, lim in tests:
assert limit_at_inf(num, den, x) == lim
def test_construct_c_case_1():
"""
This function tests the Case 1 in the step
to calculate coefficients of c-vectors.
Each test case has 4 values -
1. num - Numerator of the rational function a(x).
2. den - Denominator of the rational function a(x).
3. pole - Pole of a(x) for which c-vector is being
calculated.
4. c - The c-vector for the pole.
"""
tests = [
(
Poly(-3*x**3 + 3*x**2 + 4*x - 5, x, extension=True),
Poly(4*x**8 + 16*x**7 + 9*x**5 + 12*x**4 + 6*x**3 + 12*x**2, x, extension=True),
S(0),
[[S(1)/2 + sqrt(6)*I/6], [S(1)/2 - sqrt(6)*I/6]]
),
(
Poly(1200*x**3 + 1440*x**2 + 816*x + 560, x, extension=True),
Poly(128*x**5 - 656*x**4 + 1264*x**3 - 1125*x**2 + 385*x + 49, x, extension=True),
S(7)/4,
[[S(1)/2 + sqrt(16367978)/634], [S(1)/2 - sqrt(16367978)/634]]
),
(
Poly(4*x + 2, x, extension=True),
Poly(18*x**4 + (2 - 18*sqrt(3))*x**3 + (14 - 11*sqrt(3))*x**2 + (4 - 6*sqrt(3))*x \
+ 8*sqrt(3) + 16, x, domain='QQ<sqrt(3)>'),
(S(1) + sqrt(3))/2,
[[S(1)/2 + sqrt(Mul(4, 2*sqrt(3) + 4, evaluate=False)/(19*sqrt(3) + 44) + 1)/2], \
[S(1)/2 - sqrt(Mul(4, 2*sqrt(3) + 4, evaluate=False)/(19*sqrt(3) + 44) + 1)/2]]
)]
for num, den, pole, c in tests:
assert construct_c_case_1(num, den, x, pole) == c
def test_construct_c_case_2():
"""
This function tests the Case 2 in the step
to calculate coefficients of c-vectors.
Each test case has 5 values -
1. num - Numerator of the rational function a(x).
2. den - Denominator of the rational function a(x).
3. pole - Pole of a(x) for which c-vector is being
calculated.
4. mul - The multiplicity of the pole.
5. c - The c-vector for the pole.
"""
tests = [
# Testing poles with multiplicity 2
(
Poly(1, x, extension=True),
Poly((x - 1)**2*(x - 2), x, extension=True),
1, 2,
[[-I*(-1 - I)/2], [I*(-1 + I)/2]]
),
(
Poly(3*x**5 - 12*x**4 - 7*x**3 + 1, x, extension=True),
Poly((3*x - 1)**2*(x + 2)**2, x, extension=True),
S(1)/3, 2,
[[-S(89)/98], [-S(9)/98]]
),
# Testing poles with multiplicity 4
(
Poly(x**3 - x**2 + 4*x, x, extension=True),
Poly((x - 2)**4*(x + 5)**2, x, extension=True),
2, 4,
[[7*sqrt(3)*(S(60)/343 - 4*sqrt(3)/7)/12, 2*sqrt(3)/7], \
[-7*sqrt(3)*(S(60)/343 + 4*sqrt(3)/7)/12, -2*sqrt(3)/7]]
),
(
Poly(3*x**5 + x**4 + 3, x, extension=True),
Poly((4*x + 1)**4*(x + 2), x, extension=True),
-S(1)/4, 4,
[[128*sqrt(439)*(-sqrt(439)/128 - S(55)/14336)/439, sqrt(439)/256], \
[-128*sqrt(439)*(sqrt(439)/128 - S(55)/14336)/439, -sqrt(439)/256]]
),
# Testing poles with multiplicity 6
(
Poly(x**3 + 2, x, extension=True),
Poly((3*x - 1)**6*(x**2 + 1), x, extension=True),
S(1)/3, 6,
[[27*sqrt(66)*(-sqrt(66)/54 - S(131)/267300)/22, -2*sqrt(66)/1485, sqrt(66)/162], \
[-27*sqrt(66)*(sqrt(66)/54 - S(131)/267300)/22, 2*sqrt(66)/1485, -sqrt(66)/162]]
),
(
Poly(x**2 + 12, x, extension=True),
Poly((x - sqrt(2))**6, x, extension=True),
sqrt(2), 6,
[[sqrt(14)*(S(6)/7 - 3*sqrt(14))/28, sqrt(7)/7, sqrt(14)], \
[-sqrt(14)*(S(6)/7 + 3*sqrt(14))/28, -sqrt(7)/7, -sqrt(14)]]
)]
for num, den, pole, mul, c in tests:
assert construct_c_case_2(num, den, x, pole, mul) == c
def test_construct_c_case_3():
"""
This function tests the Case 3 in the step
to calculate coefficients of c-vectors.
"""
assert construct_c_case_3() == [[1]]
def test_construct_d_case_4():
"""
This function tests the Case 4 in the step
to calculate coefficients of the d-vector.
Each test case has 4 values -
1. num - Numerator of the rational function a(x).
2. den - Denominator of the rational function a(x).
3. mul - Multiplicity of oo as a pole.
4. d - The d-vector.
"""
tests = [
# Tests with multiplicity at oo = 2
(
Poly(-x**5 - 2*x**4 + 4*x**3 + 2*x + 5, x, extension=True),
Poly(9*x**3 - 2*x**2 + 10*x - 2, x, extension=True),
2,
[[10*I/27, I/3, -3*I*(S(158)/243 - I/3)/2], \
[-10*I/27, -I/3, 3*I*(S(158)/243 + I/3)/2]]
),
(
Poly(-x**6 + 9*x**5 + 5*x**4 + 6*x**3 + 5*x**2 + 6*x + 7, x, extension=True),
Poly(x**4 + 3*x**3 + 12*x**2 - x + 7, x, extension=True),
2,
[[-6*I, I, -I*(17 - I)/2], [6*I, -I, I*(17 + I)/2]]
),
# Tests with multiplicity at oo = 4
(
Poly(-2*x**6 - x**5 - x**4 - 2*x**3 - x**2 - 3*x - 3, x, extension=True),
Poly(3*x**2 + 10*x + 7, x, extension=True),
4,
[[269*sqrt(6)*I/288, -17*sqrt(6)*I/36, sqrt(6)*I/3, -sqrt(6)*I*(S(16969)/2592 \
- 2*sqrt(6)*I/3)/4], [-269*sqrt(6)*I/288, 17*sqrt(6)*I/36, -sqrt(6)*I/3, \
sqrt(6)*I*(S(16969)/2592 + 2*sqrt(6)*I/3)/4]]
),
(
Poly(-3*x**5 - 3*x**4 - 3*x**3 - x**2 - 1, x, extension=True),
Poly(12*x - 2, x, extension=True),
4,
[[41*I/192, 7*I/24, I/2, -I*(-S(59)/6912 - I)], \
[-41*I/192, -7*I/24, -I/2, I*(-S(59)/6912 + I)]]
),
# Tests with multiplicity at oo = 4
(
Poly(-x**7 - x**5 - x**4 - x**2 - x, x, extension=True),
Poly(x + 2, x, extension=True),
6,
[[-5*I/2, 2*I, -I, I, -I*(-9 - 3*I)/2], [5*I/2, -2*I, I, -I, I*(-9 + 3*I)/2]]
),
(
Poly(-x**7 - x**6 - 2*x**5 - 2*x**4 - x**3 - x**2 + 2*x - 2, x, extension=True),
Poly(2*x - 2, x, extension=True),
6,
[[3*sqrt(2)*I/4, 3*sqrt(2)*I/4, sqrt(2)*I/2, sqrt(2)*I/2, -sqrt(2)*I*(-S(7)/8 - \
3*sqrt(2)*I/2)/2], [-3*sqrt(2)*I/4, -3*sqrt(2)*I/4, -sqrt(2)*I/2, -sqrt(2)*I/2, \
sqrt(2)*I*(-S(7)/8 + 3*sqrt(2)*I/2)/2]]
)]
for num, den, mul, d in tests:
ser = rational_laurent_series(num, den, x, oo, mul, 1)
assert construct_d_case_4(ser, mul//2) == d
def test_construct_d_case_5():
"""
This function tests the Case 5 in the step
to calculate coefficients of the d-vector.
Each test case has 3 values -
1. num - Numerator of the rational function a(x).
2. den - Denominator of the rational function a(x).
3. d - The d-vector.
"""
tests = [
(
Poly(2*x**3 + x**2 + x - 2, x, extension=True),
Poly(9*x**3 + 5*x**2 + 2*x - 1, x, extension=True),
[[sqrt(2)/3, -sqrt(2)/108], [-sqrt(2)/3, sqrt(2)/108]]
),
(
Poly(3*x**5 + x**4 - x**3 + x**2 - 2*x - 2, x, domain='ZZ'),
Poly(9*x**5 + 7*x**4 + 3*x**3 + 2*x**2 + 5*x + 7, x, domain='ZZ'),
[[sqrt(3)/3, -2*sqrt(3)/27], [-sqrt(3)/3, 2*sqrt(3)/27]]
),
(
Poly(x**2 - x + 1, x, domain='ZZ'),
Poly(3*x**2 + 7*x + 3, x, domain='ZZ'),
[[sqrt(3)/3, -5*sqrt(3)/9], [-sqrt(3)/3, 5*sqrt(3)/9]]
)]
for num, den, d in tests:
# Multiplicity of oo is 0
ser = rational_laurent_series(num, den, x, oo, 0, 1)
assert construct_d_case_5(ser) == d
def test_construct_d_case_6():
"""
This function tests the Case 6 in the step
to calculate coefficients of the d-vector.
Each test case has 3 values -
1. num - Numerator of the rational function a(x).
2. den - Denominator of the rational function a(x).
3. d - The d-vector.
"""
tests = [
(
Poly(-2*x**2 - 5, x, domain='ZZ'),
Poly(4*x**4 + 2*x**2 + 10*x + 2, x, domain='ZZ'),
[[S(1)/2 + I/2], [S(1)/2 - I/2]]
),
(
Poly(-2*x**3 - 4*x**2 - 2*x - 5, x, domain='ZZ'),
Poly(x**6 - x**5 + 2*x**4 - 4*x**3 - 5*x**2 - 5*x + 9, x, domain='ZZ'),
[[1], [0]]
),
(
Poly(-5*x**3 + x**2 + 11*x + 12, x, domain='ZZ'),
Poly(6*x**8 - 26*x**7 - 27*x**6 - 10*x**5 - 44*x**4 - 46*x**3 - 34*x**2 \
- 27*x - 42, x, domain='ZZ'),
[[1], [0]]
)]
for num, den, d in tests:
assert construct_d_case_6(num, den, x) == d
def test_rational_laurent_series():
"""
This function tests the computation of coefficients
of Laurent series of a rational function.
Each test case has 5 values -
1. num - Numerator of the rational function.
2. den - Denominator of the rational function.
3. x0 - Point about which Laurent series is to
be calculated.
4. mul - Multiplicity of x0 if x0 is a pole of
the rational function (0 otherwise).
5. n - Number of terms upto which the series
is to be calculated.
"""
tests = [
# Laurent series about simple pole (Multiplicity = 1)
(
Poly(x**2 - 3*x + 9, x, extension=True),
Poly(x**2 - x, x, extension=True),
S(1), 1, 6,
{1: 7, 0: -8, -1: 9, -2: -9, -3: 9, -4: -9}
),
# Laurent series about multiple pole (Multiplicity > 1)
(
Poly(64*x**3 - 1728*x + 1216, x, extension=True),
Poly(64*x**4 - 80*x**3 - 831*x**2 + 1809*x - 972, x, extension=True),
S(9)/8, 2, 3,
{0: S(32177152)/46521675, 2: S(1019)/984, -1: S(11947565056)/28610830125, \
1: S(209149)/75645}
),
(
Poly(1, x, extension=True),
Poly(x**5 + (-4*sqrt(2) - 1)*x**4 + (4*sqrt(2) + 12)*x**3 + (-12 - 8*sqrt(2))*x**2 \
+ (4 + 8*sqrt(2))*x - 4, x, extension=True),
sqrt(2), 4, 6,
{4: 1 + sqrt(2), 3: -3 - 2*sqrt(2), 2: Mul(-1, -3 - 2*sqrt(2), evaluate=False)/(-1 \
+ sqrt(2)), 1: (-3 - 2*sqrt(2))/(-1 + sqrt(2))**2, 0: Mul(-1, -3 - 2*sqrt(2), evaluate=False \
)/(-1 + sqrt(2))**3, -1: (-3 - 2*sqrt(2))/(-1 + sqrt(2))**4}
),
# Laurent series about oo
(
Poly(x**5 - 4*x**3 + 6*x**2 + 10*x - 13, x, extension=True),
Poly(x**2 - 5, x, extension=True),
oo, 3, 6,
{3: 1, 2: 0, 1: 1, 0: 6, -1: 15, -2: 17}
),
# Laurent series at x0 where x0 is not a pole of the function
# Using multiplicity as 0 (as x0 will not be a pole)
(
Poly(3*x**3 + 6*x**2 - 2*x + 5, x, extension=True),
Poly(9*x**4 - x**3 - 3*x**2 + 4*x + 4, x, extension=True),
S(2)/5, 0, 1,
{0: S(3345)/3304, -1: S(399325)/2729104, -2: S(3926413375)/4508479808, \
-3: S(-5000852751875)/1862002160704, -4: S(-6683640101653125)/6152055138966016}
),
(
Poly(-7*x**2 + 2*x - 4, x, extension=True),
Poly(7*x**5 + 9*x**4 + 8*x**3 + 3*x**2 + 6*x + 9, x, extension=True),
oo, 0, 6,
{0: 0, -2: 0, -5: -S(71)/49, -1: 0, -3: -1, -4: S(11)/7}
)]
for num, den, x0, mul, n, ser in tests:
assert ser == rational_laurent_series(num, den, x, x0, mul, n)
def check_dummy_sol(eq, solse, dummy_sym):
"""
Helper function to check if actual solution
matches expected solution if actual solution
contains dummy symbols.
"""
if isinstance(eq, Eq):
eq = eq.lhs - eq.rhs
_, funcs = match_riccati(eq, f, x)
sols = solve_riccati(f(x), x, *funcs)
C1 = Dummy('C1')
sols = [sol.subs(C1, dummy_sym) for sol in sols]
assert all([x[0] for x in checkodesol(eq, sols)])
assert all([s1.dummy_eq(s2, dummy_sym) for s1, s2 in zip(sols, solse)])
def test_solve_riccati():
"""
This function tests the computation of rational
particular solutions for a Riccati ODE.
Each test case has 2 values -
1. eq - Riccati ODE to be solved.
2. sol - Expected solution to the equation.
Some examples have been taken from the paper - "Statistical Investigation of
First-Order Algebraic ODEs and their Rational General Solutions" by
Georg Grasegger, N. Thieu Vo, Franz Winkler
https://www3.risc.jku.at/publications/download/risc_5197/RISCReport15-19.pdf
"""
C0 = Dummy('C0')
# Type: 1st Order Rational Riccati, dy/dx = a + b*y + c*y**2,
# a, b, c are rational functions of x
tests = [
# a(x) is a constant
(
Eq(f(x).diff(x) + f(x)**2 - 2, 0),
[Eq(f(x), sqrt(2)), Eq(f(x), -sqrt(2))]
),
# a(x) is a constant
(
f(x)**2 + f(x).diff(x) + 4*f(x)/x + 2/x**2,
[Eq(f(x), (-2*C0 - x)/(C0*x + x**2))]
),
# a(x) is a constant
(
2*x**2*f(x).diff(x) - x*(4*f(x) + f(x).diff(x) - 4) + (f(x) - 1)*f(x),
[Eq(f(x), (C0 + 2*x**2)/(C0 + x))]
),
# Pole with multiplicity 1
(
Eq(f(x).diff(x), -f(x)**2 - 2/(x**3 - x**2)),
[Eq(f(x), 1/(x**2 - x))]
),
# One pole of multiplicity 2
(
x**2 - (2*x + 1/x)*f(x) + f(x)**2 + f(x).diff(x),
[Eq(f(x), (C0*x + x**3 + 2*x)/(C0 + x**2)), Eq(f(x), x)]
),
(
x**4*f(x).diff(x) + x**2 - x*(2*f(x)**2 + f(x).diff(x)) + f(x),
[Eq(f(x), (C0*x**2 + x)/(C0 + x**2)), Eq(f(x), x**2)]
),
# Multiple poles of multiplicity 2
(
-f(x)**2 + f(x).diff(x) + (15*x**2 - 20*x + 7)/((x - 1)**2*(2*x \
- 1)**2),
[Eq(f(x), (9*C0*x - 6*C0 - 15*x**5 + 60*x**4 - 94*x**3 + 72*x**2 \
- 30*x + 6)/(6*C0*x**2 - 9*C0*x + 3*C0 + 6*x**6 - 29*x**5 + \
57*x**4 - 58*x**3 + 30*x**2 - 6*x)), Eq(f(x), (3*x - 2)/(2*x**2 \
- 3*x + 1))]
),
# Regression: Poles with even multiplicity > 2 fixed
(
f(x)**2 + f(x).diff(x) - (4*x**6 - 8*x**5 + 12*x**4 + 4*x**3 + \
7*x**2 - 20*x + 4)/(4*x**4),
[Eq(f(x), (2*x**5 - 2*x**4 - x**3 + 4*x**2 + 3*x - 2)/(2*x**4 \
- 2*x**2))]
),
# Regression: Poles with even multiplicity > 2 fixed
(
Eq(f(x).diff(x), (-x**6 + 15*x**4 - 40*x**3 + 45*x**2 - 24*x + 4)/\
(x**12 - 12*x**11 + 66*x**10 - 220*x**9 + 495*x**8 - 792*x**7 + 924*x**6 - \
792*x**5 + 495*x**4 - 220*x**3 + 66*x**2 - 12*x + 1) + f(x)**2 + f(x)),
[Eq(f(x), 1/(x**6 - 6*x**5 + 15*x**4 - 20*x**3 + 15*x**2 - 6*x + 1))]
),
# More than 2 poles with multiplicity 2
# Regression: Fixed mistake in necessary conditions
(
Eq(f(x).diff(x), x*f(x) + 2*x + (3*x - 2)*f(x)**2/(4*x + 2) + \
(8*x**2 - 7*x + 26)/(16*x**3 - 24*x**2 + 8) - S(3)/2),
[Eq(f(x), (1 - 4*x)/(2*x - 2))]
),
# Regression: Fixed mistake in necessary conditions
(
Eq(f(x).diff(x), (-12*x**2 - 48*x - 15)/(24*x**3 - 40*x**2 + 8*x + 8) \
+ 3*f(x)**2/(6*x + 2)),
[Eq(f(x), (2*x + 1)/(2*x - 2))]
),
# Imaginary poles
(
f(x).diff(x) + (3*x**2 + 1)*f(x)**2/x + (6*x**2 - x + 3)*f(x)/(x*(x \
- 1)) + (3*x**2 - 2*x + 2)/(x*(x - 1)**2),
[Eq(f(x), (-C0 - x**3 + x**2 - 2*x)/(C0*x - C0 + x**4 - x**3 + x**2 \
- x)), Eq(f(x), -1/(x - 1))],
),
# Imaginary coefficients in equation
(
f(x).diff(x) - 2*I*(f(x)**2 + 1)/x,
[Eq(f(x), (-I*C0 + I*x**4)/(C0 + x**4)), Eq(f(x), -I)]
),
# Regression: linsolve returning empty solution
# Large value of m (> 10)
(
Eq(f(x).diff(x), x*f(x)/(S(3)/2 - 2*x) + (x/2 - S(1)/3)*f(x)**2/\
(2*x/3 - S(1)/2) - S(5)/4 + (281*x**2 - 1260*x + 756)/(16*x**3 - 12*x**2)),
[Eq(f(x), (9 - x)/x), Eq(f(x), (40*x**14 + 28*x**13 + 420*x**12 + 2940*x**11 + \
18480*x**10 + 103950*x**9 + 519750*x**8 + 2286900*x**7 + 8731800*x**6 + 28378350*\
x**5 + 76403250*x**4 + 163721250*x**3 + 261954000*x**2 + 278326125*x + 147349125)/\
((24*x**14 + 140*x**13 + 840*x**12 + 4620*x**11 + 23100*x**10 + 103950*x**9 + \
415800*x**8 + 1455300*x**7 + 4365900*x**6 + 10914750*x**5 + 21829500*x**4 + 32744250\
*x**3 + 32744250*x**2 + 16372125*x)))]
),
# Regression: Fixed bug due to a typo in paper
(
Eq(f(x).diff(x), 18*x**3 + 18*x**2 + (-x/2 - S(1)/2)*f(x)**2 + 6),
[Eq(f(x), 6*x)]
),
# Regression: Fixed bug due to a typo in paper
(
Eq(f(x).diff(x), -3*x**3/4 + 15*x/2 + (x/3 - S(4)/3)*f(x)**2 \
+ 9 + (1 - x)*f(x)/x + 3/x),
[Eq(f(x), -3*x/2 - 3)]
)]
for eq, sol in tests:
check_dummy_sol(eq, sol, C0)
@slow
def test_solve_riccati_slow():
"""
This function tests the computation of rational
particular solutions for a Riccati ODE.
Each test case has 2 values -
1. eq - Riccati ODE to be solved.
2. sol - Expected solution to the equation.
"""
C0 = Dummy('C0')
tests = [
# Very large values of m (989 and 991)
(
Eq(f(x).diff(x), (1 - x)*f(x)/(x - 3) + (2 - 12*x)*f(x)**2/(2*x - 9) + \
(54924*x**3 - 405264*x**2 + 1084347*x - 1087533)/(8*x**4 - 132*x**3 + 810*x**2 - \
2187*x + 2187) + 495),
[Eq(f(x), (18*x + 6)/(2*x - 9))]
)]
for eq, sol in tests:
check_dummy_sol(eq, sol, C0)