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'), (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)