12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331 |
- """
- Algorithms for solving Parametric Risch Differential Equations.
- The methods used for solving Parametric Risch Differential Equations parallel
- those for solving Risch Differential Equations. See the outline in the
- docstring of rde.py for more information.
- The Parametric Risch Differential Equation problem is, given f, g1, ..., gm in
- K(t), to determine if there exist y in K(t) and c1, ..., cm in Const(K) such
- that Dy + f*y == Sum(ci*gi, (i, 1, m)), and to find such y and ci if they exist.
- For the algorithms here G is a list of tuples of factions of the terms on the
- right hand side of the equation (i.e., gi in k(t)), and Q is a list of terms on
- the right hand side of the equation (i.e., qi in k[t]). See the docstring of
- each function for more information.
- """
- from functools import reduce
- from sympy.core import Dummy, ilcm, Add, Mul, Pow, S
- from sympy.integrals.rde import (order_at, order_at_oo, weak_normalizer,
- bound_degree)
- from sympy.integrals.risch import (gcdex_diophantine, frac_in, derivation,
- residue_reduce, splitfactor, residue_reduce_derivation, DecrementLevel,
- recognize_log_derivative)
- from sympy.polys import Poly, lcm, cancel, sqf_list
- from sympy.polys.polymatrix import PolyMatrix as Matrix
- from sympy.solvers import solve
- zeros = Matrix.zeros
- eye = Matrix.eye
- def prde_normal_denom(fa, fd, G, DE):
- """
- Parametric Risch Differential Equation - Normal part of the denominator.
- Explanation
- ===========
- Given a derivation D on k[t] and f, g1, ..., gm in k(t) with f weakly
- normalized with respect to t, return the tuple (a, b, G, h) such that
- a, h in k[t], b in k<t>, G = [g1, ..., gm] in k(t)^m, and for any solution
- c1, ..., cm in Const(k) and y in k(t) of Dy + f*y == Sum(ci*gi, (i, 1, m)),
- q == y*h in k<t> satisfies a*Dq + b*q == Sum(ci*Gi, (i, 1, m)).
- """
- dn, ds = splitfactor(fd, DE)
- Gas, Gds = list(zip(*G))
- gd = reduce(lambda i, j: i.lcm(j), Gds, Poly(1, DE.t))
- en, es = splitfactor(gd, DE)
- p = dn.gcd(en)
- h = en.gcd(en.diff(DE.t)).quo(p.gcd(p.diff(DE.t)))
- a = dn*h
- c = a*h
- ba = a*fa - dn*derivation(h, DE)*fd
- ba, bd = ba.cancel(fd, include=True)
- G = [(c*A).cancel(D, include=True) for A, D in G]
- return (a, (ba, bd), G, h)
- def real_imag(ba, bd, gen):
- """
- Helper function, to get the real and imaginary part of a rational function
- evaluated at sqrt(-1) without actually evaluating it at sqrt(-1).
- Explanation
- ===========
- Separates the even and odd power terms by checking the degree of terms wrt
- mod 4. Returns a tuple (ba[0], ba[1], bd) where ba[0] is real part
- of the numerator ba[1] is the imaginary part and bd is the denominator
- of the rational function.
- """
- bd = bd.as_poly(gen).as_dict()
- ba = ba.as_poly(gen).as_dict()
- denom_real = [value if key[0] % 4 == 0 else -value if key[0] % 4 == 2 else 0 for key, value in bd.items()]
- denom_imag = [value if key[0] % 4 == 1 else -value if key[0] % 4 == 3 else 0 for key, value in bd.items()]
- bd_real = sum(r for r in denom_real)
- bd_imag = sum(r for r in denom_imag)
- num_real = [value if key[0] % 4 == 0 else -value if key[0] % 4 == 2 else 0 for key, value in ba.items()]
- num_imag = [value if key[0] % 4 == 1 else -value if key[0] % 4 == 3 else 0 for key, value in ba.items()]
- ba_real = sum(r for r in num_real)
- ba_imag = sum(r for r in num_imag)
- ba = ((ba_real*bd_real + ba_imag*bd_imag).as_poly(gen), (ba_imag*bd_real - ba_real*bd_imag).as_poly(gen))
- bd = (bd_real*bd_real + bd_imag*bd_imag).as_poly(gen)
- return (ba[0], ba[1], bd)
- def prde_special_denom(a, ba, bd, G, DE, case='auto'):
- """
- Parametric Risch Differential Equation - Special part of the denominator.
- Explanation
- ===========
- Case is one of {'exp', 'tan', 'primitive'} for the hyperexponential,
- hypertangent, and primitive cases, respectively. For the hyperexponential
- (resp. hypertangent) case, given a derivation D on k[t] and a in k[t],
- b in k<t>, and g1, ..., gm in k(t) with Dt/t in k (resp. Dt/(t**2 + 1) in
- k, sqrt(-1) not in k), a != 0, and gcd(a, t) == 1 (resp.
- gcd(a, t**2 + 1) == 1), return the tuple (A, B, GG, h) such that A, B, h in
- k[t], GG = [gg1, ..., ggm] in k(t)^m, and for any solution c1, ..., cm in
- Const(k) and q in k<t> of a*Dq + b*q == Sum(ci*gi, (i, 1, m)), r == q*h in
- k[t] satisfies A*Dr + B*r == Sum(ci*ggi, (i, 1, m)).
- For case == 'primitive', k<t> == k[t], so it returns (a, b, G, 1) in this
- case.
- """
- # TODO: Merge this with the very similar special_denom() in rde.py
- if case == 'auto':
- case = DE.case
- if case == 'exp':
- p = Poly(DE.t, DE.t)
- elif case == 'tan':
- p = Poly(DE.t**2 + 1, DE.t)
- elif case in ('primitive', 'base'):
- B = ba.quo(bd)
- return (a, B, G, Poly(1, DE.t))
- else:
- raise ValueError("case must be one of {'exp', 'tan', 'primitive', "
- "'base'}, not %s." % case)
- nb = order_at(ba, p, DE.t) - order_at(bd, p, DE.t)
- nc = min([order_at(Ga, p, DE.t) - order_at(Gd, p, DE.t) for Ga, Gd in G])
- n = min(0, nc - min(0, nb))
- if not nb:
- # Possible cancellation.
- if case == 'exp':
- dcoeff = DE.d.quo(Poly(DE.t, DE.t))
- with DecrementLevel(DE): # We are guaranteed to not have problems,
- # because case != 'base'.
- alphaa, alphad = frac_in(-ba.eval(0)/bd.eval(0)/a.eval(0), DE.t)
- etaa, etad = frac_in(dcoeff, DE.t)
- A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE)
- if A is not None:
- Q, m, z = A
- if Q == 1:
- n = min(n, m)
- elif case == 'tan':
- dcoeff = DE.d.quo(Poly(DE.t**2 + 1, DE.t))
- with DecrementLevel(DE): # We are guaranteed to not have problems,
- # because case != 'base'.
- betaa, alphaa, alphad = real_imag(ba, bd*a, DE.t)
- betad = alphad
- etaa, etad = frac_in(dcoeff, DE.t)
- if recognize_log_derivative(Poly(2, DE.t)*betaa, betad, DE):
- A = parametric_log_deriv(alphaa, alphad, etaa, etad, DE)
- B = parametric_log_deriv(betaa, betad, etaa, etad, DE)
- if A is not None and B is not None:
- Q, s, z = A
- # TODO: Add test
- if Q == 1:
- n = min(n, s/2)
- N = max(0, -nb)
- pN = p**N
- pn = p**-n # This is 1/h
- A = a*pN
- B = ba*pN.quo(bd) + Poly(n, DE.t)*a*derivation(p, DE).quo(p)*pN
- G = [(Ga*pN*pn).cancel(Gd, include=True) for Ga, Gd in G]
- h = pn
- # (a*p**N, (b + n*a*Dp/p)*p**N, g1*p**(N - n), ..., gm*p**(N - n), p**-n)
- return (A, B, G, h)
- def prde_linear_constraints(a, b, G, DE):
- """
- Parametric Risch Differential Equation - Generate linear constraints on the constants.
- Explanation
- ===========
- Given a derivation D on k[t], a, b, in k[t] with gcd(a, b) == 1, and
- G = [g1, ..., gm] in k(t)^m, return Q = [q1, ..., qm] in k[t]^m and a
- matrix M with entries in k(t) such that for any solution c1, ..., cm in
- Const(k) and p in k[t] of a*Dp + b*p == Sum(ci*gi, (i, 1, m)),
- (c1, ..., cm) is a solution of Mx == 0, and p and the ci satisfy
- a*Dp + b*p == Sum(ci*qi, (i, 1, m)).
- Because M has entries in k(t), and because Matrix doesn't play well with
- Poly, M will be a Matrix of Basic expressions.
- """
- m = len(G)
- Gns, Gds = list(zip(*G))
- d = reduce(lambda i, j: i.lcm(j), Gds)
- d = Poly(d, field=True)
- Q = [(ga*(d).quo(gd)).div(d) for ga, gd in G]
- if not all(ri.is_zero for _, ri in Q):
- N = max(ri.degree(DE.t) for _, ri in Q)
- M = Matrix(N + 1, m, lambda i, j: Q[j][1].nth(i), DE.t)
- else:
- M = Matrix(0, m, [], DE.t) # No constraints, return the empty matrix.
- qs, _ = list(zip(*Q))
- return (qs, M)
- def poly_linear_constraints(p, d):
- """
- Given p = [p1, ..., pm] in k[t]^m and d in k[t], return
- q = [q1, ..., qm] in k[t]^m and a matrix M with entries in k such
- that Sum(ci*pi, (i, 1, m)), for c1, ..., cm in k, is divisible
- by d if and only if (c1, ..., cm) is a solution of Mx = 0, in
- which case the quotient is Sum(ci*qi, (i, 1, m)).
- """
- m = len(p)
- q, r = zip(*[pi.div(d) for pi in p])
- if not all(ri.is_zero for ri in r):
- n = max(ri.degree() for ri in r)
- M = Matrix(n + 1, m, lambda i, j: r[j].nth(i), d.gens)
- else:
- M = Matrix(0, m, [], d.gens) # No constraints.
- return q, M
- def constant_system(A, u, DE):
- """
- Generate a system for the constant solutions.
- Explanation
- ===========
- Given a differential field (K, D) with constant field C = Const(K), a Matrix
- A, and a vector (Matrix) u with coefficients in K, returns the tuple
- (B, v, s), where B is a Matrix with coefficients in C and v is a vector
- (Matrix) such that either v has coefficients in C, in which case s is True
- and the solutions in C of Ax == u are exactly all the solutions of Bx == v,
- or v has a non-constant coefficient, in which case s is False Ax == u has no
- constant solution.
- This algorithm is used both in solving parametric problems and in
- determining if an element a of K is a derivative of an element of K or the
- logarithmic derivative of a K-radical using the structure theorem approach.
- Because Poly does not play well with Matrix yet, this algorithm assumes that
- all matrix entries are Basic expressions.
- """
- if not A:
- return A, u
- Au = A.row_join(u)
- Au, _ = Au.rref()
- # Warning: This will NOT return correct results if cancel() cannot reduce
- # an identically zero expression to 0. The danger is that we might
- # incorrectly prove that an integral is nonelementary (such as
- # risch_integrate(exp((sin(x)**2 + cos(x)**2 - 1)*x**2), x).
- # But this is a limitation in computer algebra in general, and implicit
- # in the correctness of the Risch Algorithm is the computability of the
- # constant field (actually, this same correctness problem exists in any
- # algorithm that uses rref()).
- #
- # We therefore limit ourselves to constant fields that are computable
- # via the cancel() function, in order to prevent a speed bottleneck from
- # calling some more complex simplification function (rational function
- # coefficients will fall into this class). Furthermore, (I believe) this
- # problem will only crop up if the integral explicitly contains an
- # expression in the constant field that is identically zero, but cannot
- # be reduced to such by cancel(). Therefore, a careful user can avoid this
- # problem entirely by being careful with the sorts of expressions that
- # appear in his integrand in the variables other than the integration
- # variable (the structure theorems should be able to completely decide these
- # problems in the integration variable).
- A, u = Au[:, :-1], Au[:, -1]
- D = lambda x: derivation(x, DE, basic=True)
- for j in range(A.cols):
- for i in range(A.rows):
- if A[i, j].expr.has(*DE.T):
- # This assumes that const(F(t0, ..., tn) == const(K) == F
- Ri = A[i, :]
- # Rm+1; m = A.rows
- DAij = D(A[i, j])
- Rm1 = Ri.applyfunc(lambda x: D(x) / DAij)
- um1 = D(u[i]) / DAij
- Aj = A[:, j]
- A = A - Aj * Rm1
- u = u - Aj * um1
- A = A.col_join(Rm1)
- u = u.col_join(Matrix([um1], u.gens))
- return (A, u)
- def prde_spde(a, b, Q, n, DE):
- """
- Special Polynomial Differential Equation algorithm: Parametric Version.
- Explanation
- ===========
- Given a derivation D on k[t], an integer n, and a, b, q1, ..., qm in k[t]
- with deg(a) > 0 and gcd(a, b) == 1, return (A, B, Q, R, n1), with
- Qq = [q1, ..., qm] and R = [r1, ..., rm], such that for any solution
- c1, ..., cm in Const(k) and q in k[t] of degree at most n of
- a*Dq + b*q == Sum(ci*gi, (i, 1, m)), p = (q - Sum(ci*ri, (i, 1, m)))/a has
- degree at most n1 and satisfies A*Dp + B*p == Sum(ci*qi, (i, 1, m))
- """
- R, Z = list(zip(*[gcdex_diophantine(b, a, qi) for qi in Q]))
- A = a
- B = b + derivation(a, DE)
- Qq = [zi - derivation(ri, DE) for ri, zi in zip(R, Z)]
- R = list(R)
- n1 = n - a.degree(DE.t)
- return (A, B, Qq, R, n1)
- def prde_no_cancel_b_large(b, Q, n, DE):
- """
- Parametric Poly Risch Differential Equation - No cancellation: deg(b) large enough.
- Explanation
- ===========
- Given a derivation D on k[t], n in ZZ, and b, q1, ..., qm in k[t] with
- b != 0 and either D == d/dt or deg(b) > max(0, deg(D) - 1), returns
- h1, ..., hr in k[t] and a matrix A with coefficients in Const(k) such that
- if c1, ..., cm in Const(k) and q in k[t] satisfy deg(q) <= n and
- Dq + b*q == Sum(ci*qi, (i, 1, m)), then q = Sum(dj*hj, (j, 1, r)), where
- d1, ..., dr in Const(k) and A*Matrix([[c1, ..., cm, d1, ..., dr]]).T == 0.
- """
- db = b.degree(DE.t)
- m = len(Q)
- H = [Poly(0, DE.t)]*m
- for N in range(n, -1, -1): # [n, ..., 0]
- for i in range(m):
- si = Q[i].nth(N + db)/b.LC()
- sitn = Poly(si*DE.t**N, DE.t)
- H[i] = H[i] + sitn
- Q[i] = Q[i] - derivation(sitn, DE) - b*sitn
- if all(qi.is_zero for qi in Q):
- dc = -1
- M = zeros(0, 2, DE.t)
- else:
- dc = max([qi.degree(DE.t) for qi in Q])
- M = Matrix(dc + 1, m, lambda i, j: Q[j].nth(i), DE.t)
- A, u = constant_system(M, zeros(dc + 1, 1, DE.t), DE)
- c = eye(m, DE.t)
- A = A.row_join(zeros(A.rows, m, DE.t)).col_join(c.row_join(-c))
- return (H, A)
- def prde_no_cancel_b_small(b, Q, n, DE):
- """
- Parametric Poly Risch Differential Equation - No cancellation: deg(b) small enough.
- Explanation
- ===========
- Given a derivation D on k[t], n in ZZ, and b, q1, ..., qm in k[t] with
- deg(b) < deg(D) - 1 and either D == d/dt or deg(D) >= 2, returns
- h1, ..., hr in k[t] and a matrix A with coefficients in Const(k) such that
- if c1, ..., cm in Const(k) and q in k[t] satisfy deg(q) <= n and
- Dq + b*q == Sum(ci*qi, (i, 1, m)) then q = Sum(dj*hj, (j, 1, r)) where
- d1, ..., dr in Const(k) and A*Matrix([[c1, ..., cm, d1, ..., dr]]).T == 0.
- """
- m = len(Q)
- H = [Poly(0, DE.t)]*m
- for N in range(n, 0, -1): # [n, ..., 1]
- for i in range(m):
- si = Q[i].nth(N + DE.d.degree(DE.t) - 1)/(N*DE.d.LC())
- sitn = Poly(si*DE.t**N, DE.t)
- H[i] = H[i] + sitn
- Q[i] = Q[i] - derivation(sitn, DE) - b*sitn
- if b.degree(DE.t) > 0:
- for i in range(m):
- si = Poly(Q[i].nth(b.degree(DE.t))/b.LC(), DE.t)
- H[i] = H[i] + si
- Q[i] = Q[i] - derivation(si, DE) - b*si
- if all(qi.is_zero for qi in Q):
- dc = -1
- M = Matrix()
- else:
- dc = max([qi.degree(DE.t) for qi in Q])
- M = Matrix(dc + 1, m, lambda i, j: Q[j].nth(i), DE.t)
- A, u = constant_system(M, zeros(dc + 1, 1, DE.t), DE)
- c = eye(m, DE.t)
- A = A.row_join(zeros(A.rows, m, DE.t)).col_join(c.row_join(-c))
- return (H, A)
- # else: b is in k, deg(qi) < deg(Dt)
- t = DE.t
- if DE.case != 'base':
- with DecrementLevel(DE):
- t0 = DE.t # k = k0(t0)
- ba, bd = frac_in(b, t0, field=True)
- Q0 = [frac_in(qi.TC(), t0, field=True) for qi in Q]
- f, B = param_rischDE(ba, bd, Q0, DE)
- # f = [f1, ..., fr] in k^r and B is a matrix with
- # m + r columns and entries in Const(k) = Const(k0)
- # such that Dy0 + b*y0 = Sum(ci*qi, (i, 1, m)) has
- # a solution y0 in k with c1, ..., cm in Const(k)
- # if and only y0 = Sum(dj*fj, (j, 1, r)) where
- # d1, ..., dr ar in Const(k) and
- # B*Matrix([c1, ..., cm, d1, ..., dr]) == 0.
- # Transform fractions (fa, fd) in f into constant
- # polynomials fa/fd in k[t].
- # (Is there a better way?)
- f = [Poly(fa.as_expr()/fd.as_expr(), t, field=True)
- for fa, fd in f]
- B = Matrix.from_Matrix(B.to_Matrix(), t)
- else:
- # Base case. Dy == 0 for all y in k and b == 0.
- # Dy + b*y = Sum(ci*qi) is solvable if and only if
- # Sum(ci*qi) == 0 in which case the solutions are
- # y = d1*f1 for f1 = 1 and any d1 in Const(k) = k.
- f = [Poly(1, t, field=True)] # r = 1
- B = Matrix([[qi.TC() for qi in Q] + [S.Zero]], DE.t)
- # The condition for solvability is
- # B*Matrix([c1, ..., cm, d1]) == 0
- # There are no constraints on d1.
- # Coefficients of t^j (j > 0) in Sum(ci*qi) must be zero.
- d = max([qi.degree(DE.t) for qi in Q])
- if d > 0:
- M = Matrix(d, m, lambda i, j: Q[j].nth(i + 1), DE.t)
- A, _ = constant_system(M, zeros(d, 1, DE.t), DE)
- else:
- # No constraints on the hj.
- A = Matrix(0, m, [], DE.t)
- # Solutions of the original equation are
- # y = Sum(dj*fj, (j, 1, r) + Sum(ei*hi, (i, 1, m)),
- # where ei == ci (i = 1, ..., m), when
- # A*Matrix([c1, ..., cm]) == 0 and
- # B*Matrix([c1, ..., cm, d1, ..., dr]) == 0
- # Build combined constraint matrix with m + r + m columns.
- r = len(f)
- I = eye(m, DE.t)
- A = A.row_join(zeros(A.rows, r + m, DE.t))
- B = B.row_join(zeros(B.rows, m, DE.t))
- C = I.row_join(zeros(m, r, DE.t)).row_join(-I)
- return f + H, A.col_join(B).col_join(C)
- def prde_cancel_liouvillian(b, Q, n, DE):
- """
- Pg, 237.
- """
- H = []
- # Why use DecrementLevel? Below line answers that:
- # Assuming that we can solve such problems over 'k' (not k[t])
- if DE.case == 'primitive':
- with DecrementLevel(DE):
- ba, bd = frac_in(b, DE.t, field=True)
- for i in range(n, -1, -1):
- if DE.case == 'exp': # this re-checking can be avoided
- with DecrementLevel(DE):
- ba, bd = frac_in(b + (i*(derivation(DE.t, DE)/DE.t)).as_poly(b.gens),
- DE.t, field=True)
- with DecrementLevel(DE):
- Qy = [frac_in(q.nth(i), DE.t, field=True) for q in Q]
- fi, Ai = param_rischDE(ba, bd, Qy, DE)
- fi = [Poly(fa.as_expr()/fd.as_expr(), DE.t, field=True)
- for fa, fd in fi]
- Ai = Ai.set_gens(DE.t)
- ri = len(fi)
- if i == n:
- M = Ai
- else:
- M = Ai.col_join(M.row_join(zeros(M.rows, ri, DE.t)))
- Fi, hi = [None]*ri, [None]*ri
- # from eq. on top of p.238 (unnumbered)
- for j in range(ri):
- hji = fi[j] * (DE.t**i).as_poly(fi[j].gens)
- hi[j] = hji
- # building up Sum(djn*(D(fjn*t^n) - b*fjnt^n))
- Fi[j] = -(derivation(hji, DE) - b*hji)
- H += hi
- # in the next loop instead of Q it has
- # to be Q + Fi taking its place
- Q = Q + Fi
- return (H, M)
- def param_poly_rischDE(a, b, q, n, DE):
- """Polynomial solutions of a parametric Risch differential equation.
- Explanation
- ===========
- Given a derivation D in k[t], a, b in k[t] relatively prime, and q
- = [q1, ..., qm] in k[t]^m, return h = [h1, ..., hr] in k[t]^r and
- a matrix A with m + r columns and entries in Const(k) such that
- a*Dp + b*p = Sum(ci*qi, (i, 1, m)) has a solution p of degree <= n
- in k[t] with c1, ..., cm in Const(k) if and only if p = Sum(dj*hj,
- (j, 1, r)) where d1, ..., dr are in Const(k) and (c1, ..., cm,
- d1, ..., dr) is a solution of Ax == 0.
- """
- m = len(q)
- if n < 0:
- # Only the trivial zero solution is possible.
- # Find relations between the qi.
- if all(qi.is_zero for qi in q):
- return [], zeros(1, m, DE.t) # No constraints.
- N = max([qi.degree(DE.t) for qi in q])
- M = Matrix(N + 1, m, lambda i, j: q[j].nth(i), DE.t)
- A, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE)
- return [], A
- if a.is_ground:
- # Normalization: a = 1.
- a = a.LC()
- b, q = b.quo_ground(a), [qi.quo_ground(a) for qi in q]
- if not b.is_zero and (DE.case == 'base' or
- b.degree() > max(0, DE.d.degree() - 1)):
- return prde_no_cancel_b_large(b, q, n, DE)
- elif ((b.is_zero or b.degree() < DE.d.degree() - 1)
- and (DE.case == 'base' or DE.d.degree() >= 2)):
- return prde_no_cancel_b_small(b, q, n, DE)
- elif (DE.d.degree() >= 2 and
- b.degree() == DE.d.degree() - 1 and
- n > -b.as_poly().LC()/DE.d.as_poly().LC()):
- raise NotImplementedError("prde_no_cancel_b_equal() is "
- "not yet implemented.")
- else:
- # Liouvillian cases
- if DE.case in ('primitive', 'exp'):
- return prde_cancel_liouvillian(b, q, n, DE)
- else:
- raise NotImplementedError("non-linear and hypertangent "
- "cases have not yet been implemented")
- # else: deg(a) > 0
- # Iterate SPDE as long as possible cumulating coefficient
- # and terms for the recovery of original solutions.
- alpha, beta = a.one, [a.zero]*m
- while n >= 0: # and a, b relatively prime
- a, b, q, r, n = prde_spde(a, b, q, n, DE)
- beta = [betai + alpha*ri for betai, ri in zip(beta, r)]
- alpha *= a
- # Solutions p of a*Dp + b*p = Sum(ci*qi) correspond to
- # solutions alpha*p + Sum(ci*betai) of the initial equation.
- d = a.gcd(b)
- if not d.is_ground:
- break
- # a*Dp + b*p = Sum(ci*qi) may have a polynomial solution
- # only if the sum is divisible by d.
- qq, M = poly_linear_constraints(q, d)
- # qq = [qq1, ..., qqm] where qqi = qi.quo(d).
- # M is a matrix with m columns an entries in k.
- # Sum(fi*qi, (i, 1, m)), where f1, ..., fm are elements of k, is
- # divisible by d if and only if M*Matrix([f1, ..., fm]) == 0,
- # in which case the quotient is Sum(fi*qqi).
- A, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE)
- # A is a matrix with m columns and entries in Const(k).
- # Sum(ci*qqi) is Sum(ci*qi).quo(d), and the remainder is zero
- # for c1, ..., cm in Const(k) if and only if
- # A*Matrix([c1, ...,cm]) == 0.
- V = A.nullspace()
- # V = [v1, ..., vu] where each vj is a column matrix with
- # entries aj1, ..., ajm in Const(k).
- # Sum(aji*qi) is divisible by d with exact quotient Sum(aji*qqi).
- # Sum(ci*qi) is divisible by d if and only if ci = Sum(dj*aji)
- # (i = 1, ..., m) for some d1, ..., du in Const(k).
- # In that case, solutions of
- # a*Dp + b*p = Sum(ci*qi) = Sum(dj*Sum(aji*qi))
- # are the same as those of
- # (a/d)*Dp + (b/d)*p = Sum(dj*rj)
- # where rj = Sum(aji*qqi).
- if not V: # No non-trivial solution.
- return [], eye(m, DE.t) # Could return A, but this has
- # the minimum number of rows.
- Mqq = Matrix([qq]) # A single row.
- r = [(Mqq*vj)[0] for vj in V] # [r1, ..., ru]
- # Solutions of (a/d)*Dp + (b/d)*p = Sum(dj*rj) correspond to
- # solutions alpha*p + Sum(Sum(dj*aji)*betai) of the initial
- # equation. These are equal to alpha*p + Sum(dj*fj) where
- # fj = Sum(aji*betai).
- Mbeta = Matrix([beta])
- f = [(Mbeta*vj)[0] for vj in V] # [f1, ..., fu]
- #
- # Solve the reduced equation recursively.
- #
- g, B = param_poly_rischDE(a.quo(d), b.quo(d), r, n, DE)
- # g = [g1, ..., gv] in k[t]^v and and B is a matrix with u + v
- # columns and entries in Const(k) such that
- # (a/d)*Dp + (b/d)*p = Sum(dj*rj) has a solution p of degree <= n
- # in k[t] if and only if p = Sum(ek*gk) where e1, ..., ev are in
- # Const(k) and B*Matrix([d1, ..., du, e1, ..., ev]) == 0.
- # The solutions of the original equation are then
- # Sum(dj*fj, (j, 1, u)) + alpha*Sum(ek*gk, (k, 1, v)).
- # Collect solution components.
- h = f + [alpha*gk for gk in g]
- # Build combined relation matrix.
- A = -eye(m, DE.t)
- for vj in V:
- A = A.row_join(vj)
- A = A.row_join(zeros(m, len(g), DE.t))
- A = A.col_join(zeros(B.rows, m, DE.t).row_join(B))
- return h, A
- def param_rischDE(fa, fd, G, DE):
- """
- Solve a Parametric Risch Differential Equation: Dy + f*y == Sum(ci*Gi, (i, 1, m)).
- Explanation
- ===========
- Given a derivation D in k(t), f in k(t), and G
- = [G1, ..., Gm] in k(t)^m, return h = [h1, ..., hr] in k(t)^r and
- a matrix A with m + r columns and entries in Const(k) such that
- Dy + f*y = Sum(ci*Gi, (i, 1, m)) has a solution y
- in k(t) with c1, ..., cm in Const(k) if and only if y = Sum(dj*hj,
- (j, 1, r)) where d1, ..., dr are in Const(k) and (c1, ..., cm,
- d1, ..., dr) is a solution of Ax == 0.
- Elements of k(t) are tuples (a, d) with a and d in k[t].
- """
- m = len(G)
- q, (fa, fd) = weak_normalizer(fa, fd, DE)
- # Solutions of the weakly normalized equation Dz + f*z = q*Sum(ci*Gi)
- # correspond to solutions y = z/q of the original equation.
- gamma = q
- G = [(q*ga).cancel(gd, include=True) for ga, gd in G]
- a, (ba, bd), G, hn = prde_normal_denom(fa, fd, G, DE)
- # Solutions q in k<t> of a*Dq + b*q = Sum(ci*Gi) correspond
- # to solutions z = q/hn of the weakly normalized equation.
- gamma *= hn
- A, B, G, hs = prde_special_denom(a, ba, bd, G, DE)
- # Solutions p in k[t] of A*Dp + B*p = Sum(ci*Gi) correspond
- # to solutions q = p/hs of the previous equation.
- gamma *= hs
- g = A.gcd(B)
- a, b, g = A.quo(g), B.quo(g), [gia.cancel(gid*g, include=True) for
- gia, gid in G]
- # a*Dp + b*p = Sum(ci*gi) may have a polynomial solution
- # only if the sum is in k[t].
- q, M = prde_linear_constraints(a, b, g, DE)
- # q = [q1, ..., qm] where qi in k[t] is the polynomial component
- # of the partial fraction expansion of gi.
- # M is a matrix with m columns and entries in k.
- # Sum(fi*gi, (i, 1, m)), where f1, ..., fm are elements of k,
- # is a polynomial if and only if M*Matrix([f1, ..., fm]) == 0,
- # in which case the sum is equal to Sum(fi*qi).
- M, _ = constant_system(M, zeros(M.rows, 1, DE.t), DE)
- # M is a matrix with m columns and entries in Const(k).
- # Sum(ci*gi) is in k[t] for c1, ..., cm in Const(k)
- # if and only if M*Matrix([c1, ..., cm]) == 0,
- # in which case the sum is Sum(ci*qi).
- ## Reduce number of constants at this point
- V = M.nullspace()
- # V = [v1, ..., vu] where each vj is a column matrix with
- # entries aj1, ..., ajm in Const(k).
- # Sum(aji*gi) is in k[t] and equal to Sum(aji*qi) (j = 1, ..., u).
- # Sum(ci*gi) is in k[t] if and only is ci = Sum(dj*aji)
- # (i = 1, ..., m) for some d1, ..., du in Const(k).
- # In that case,
- # Sum(ci*gi) = Sum(ci*qi) = Sum(dj*Sum(aji*qi)) = Sum(dj*rj)
- # where rj = Sum(aji*qi) (j = 1, ..., u) in k[t].
- if not V: # No non-trivial solution
- return [], eye(m, DE.t)
- Mq = Matrix([q]) # A single row.
- r = [(Mq*vj)[0] for vj in V] # [r1, ..., ru]
- # Solutions of a*Dp + b*p = Sum(dj*rj) correspond to solutions
- # y = p/gamma of the initial equation with ci = Sum(dj*aji).
- try:
- # We try n=5. At least for prde_spde, it will always
- # terminate no matter what n is.
- n = bound_degree(a, b, r, DE, parametric=True)
- except NotImplementedError:
- # A temporary bound is set. Eventually, it will be removed.
- # the currently added test case takes large time
- # even with n=5, and much longer with large n's.
- n = 5
- h, B = param_poly_rischDE(a, b, r, n, DE)
- # h = [h1, ..., hv] in k[t]^v and and B is a matrix with u + v
- # columns and entries in Const(k) such that
- # a*Dp + b*p = Sum(dj*rj) has a solution p of degree <= n
- # in k[t] if and only if p = Sum(ek*hk) where e1, ..., ev are in
- # Const(k) and B*Matrix([d1, ..., du, e1, ..., ev]) == 0.
- # The solutions of the original equation for ci = Sum(dj*aji)
- # (i = 1, ..., m) are then y = Sum(ek*hk, (k, 1, v))/gamma.
- ## Build combined relation matrix with m + u + v columns.
- A = -eye(m, DE.t)
- for vj in V:
- A = A.row_join(vj)
- A = A.row_join(zeros(m, len(h), DE.t))
- A = A.col_join(zeros(B.rows, m, DE.t).row_join(B))
- ## Eliminate d1, ..., du.
- W = A.nullspace()
- # W = [w1, ..., wt] where each wl is a column matrix with
- # entries blk (k = 1, ..., m + u + v) in Const(k).
- # The vectors (bl1, ..., blm) generate the space of those
- # constant families (c1, ..., cm) for which a solution of
- # the equation Dy + f*y == Sum(ci*Gi) exists. They generate
- # the space and form a basis except possibly when Dy + f*y == 0
- # is solvable in k(t}. The corresponding solutions are
- # y = Sum(blk'*hk, (k, 1, v))/gamma, where k' = k + m + u.
- v = len(h)
- M = Matrix([wl[:m] + wl[-v:] for wl in W]) # excise dj's.
- N = M.nullspace()
- # N = [n1, ..., ns] where the ni in Const(k)^(m + v) are column
- # vectors generating the space of linear relations between
- # c1, ..., cm, e1, ..., ev.
- C = Matrix([ni[:] for ni in N], DE.t) # rows n1, ..., ns.
- return [hk.cancel(gamma, include=True) for hk in h], C
- def limited_integrate_reduce(fa, fd, G, DE):
- """
- Simpler version of step 1 & 2 for the limited integration problem.
- Explanation
- ===========
- Given a derivation D on k(t) and f, g1, ..., gn in k(t), return
- (a, b, h, N, g, V) such that a, b, h in k[t], N is a non-negative integer,
- g in k(t), V == [v1, ..., vm] in k(t)^m, and for any solution v in k(t),
- c1, ..., cm in C of f == Dv + Sum(ci*wi, (i, 1, m)), p = v*h is in k<t>, and
- p and the ci satisfy a*Dp + b*p == g + Sum(ci*vi, (i, 1, m)). Furthermore,
- if S1irr == Sirr, then p is in k[t], and if t is nonlinear or Liouvillian
- over k, then deg(p) <= N.
- So that the special part is always computed, this function calls the more
- general prde_special_denom() automatically if it cannot determine that
- S1irr == Sirr. Furthermore, it will automatically call bound_degree() when
- t is linear and non-Liouvillian, which for the transcendental case, implies
- that Dt == a*t + b with for some a, b in k*.
- """
- dn, ds = splitfactor(fd, DE)
- E = [splitfactor(gd, DE) for _, gd in G]
- En, Es = list(zip(*E))
- c = reduce(lambda i, j: i.lcm(j), (dn,) + En) # lcm(dn, en1, ..., enm)
- hn = c.gcd(c.diff(DE.t))
- a = hn
- b = -derivation(hn, DE)
- N = 0
- # These are the cases where we know that S1irr = Sirr, but there could be
- # others, and this algorithm will need to be extended to handle them.
- if DE.case in ('base', 'primitive', 'exp', 'tan'):
- hs = reduce(lambda i, j: i.lcm(j), (ds,) + Es) # lcm(ds, es1, ..., esm)
- a = hn*hs
- b -= (hn*derivation(hs, DE)).quo(hs)
- mu = min(order_at_oo(fa, fd, DE.t), min([order_at_oo(ga, gd, DE.t) for
- ga, gd in G]))
- # So far, all the above are also nonlinear or Liouvillian, but if this
- # changes, then this will need to be updated to call bound_degree()
- # as per the docstring of this function (DE.case == 'other_linear').
- N = hn.degree(DE.t) + hs.degree(DE.t) + max(0, 1 - DE.d.degree(DE.t) - mu)
- else:
- # TODO: implement this
- raise NotImplementedError
- V = [(-a*hn*ga).cancel(gd, include=True) for ga, gd in G]
- return (a, b, a, N, (a*hn*fa).cancel(fd, include=True), V)
- def limited_integrate(fa, fd, G, DE):
- """
- Solves the limited integration problem: f = Dv + Sum(ci*wi, (i, 1, n))
- """
- fa, fd = fa*Poly(1/fd.LC(), DE.t), fd.monic()
- # interpreting limited integration problem as a
- # parametric Risch DE problem
- Fa = Poly(0, DE.t)
- Fd = Poly(1, DE.t)
- G = [(fa, fd)] + G
- h, A = param_rischDE(Fa, Fd, G, DE)
- V = A.nullspace()
- V = [v for v in V if v[0] != 0]
- if not V:
- return None
- else:
- # we can take any vector from V, we take V[0]
- c0 = V[0][0]
- # v = [-1, c1, ..., cm, d1, ..., dr]
- v = V[0]/(-c0)
- r = len(h)
- m = len(v) - r - 1
- C = list(v[1: m + 1])
- y = -sum([v[m + 1 + i]*h[i][0].as_expr()/h[i][1].as_expr() \
- for i in range(r)])
- y_num, y_den = y.as_numer_denom()
- Ya, Yd = Poly(y_num, DE.t), Poly(y_den, DE.t)
- Y = Ya*Poly(1/Yd.LC(), DE.t), Yd.monic()
- return Y, C
- def parametric_log_deriv_heu(fa, fd, wa, wd, DE, c1=None):
- """
- Parametric logarithmic derivative heuristic.
- Explanation
- ===========
- Given a derivation D on k[t], f in k(t), and a hyperexponential monomial
- theta over k(t), raises either NotImplementedError, in which case the
- heuristic failed, or returns None, in which case it has proven that no
- solution exists, or returns a solution (n, m, v) of the equation
- n*f == Dv/v + m*Dtheta/theta, with v in k(t)* and n, m in ZZ with n != 0.
- If this heuristic fails, the structure theorem approach will need to be
- used.
- The argument w == Dtheta/theta
- """
- # TODO: finish writing this and write tests
- c1 = c1 or Dummy('c1')
- p, a = fa.div(fd)
- q, b = wa.div(wd)
- B = max(0, derivation(DE.t, DE).degree(DE.t) - 1)
- C = max(p.degree(DE.t), q.degree(DE.t))
- if q.degree(DE.t) > B:
- eqs = [p.nth(i) - c1*q.nth(i) for i in range(B + 1, C + 1)]
- s = solve(eqs, c1)
- if not s or not s[c1].is_Rational:
- # deg(q) > B, no solution for c.
- return None
- M, N = s[c1].as_numer_denom()
- M_poly = M.as_poly(q.gens)
- N_poly = N.as_poly(q.gens)
- nfmwa = N_poly*fa*wd - M_poly*wa*fd
- nfmwd = fd*wd
- Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE, 'auto')
- if Qv is None:
- # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical.
- return None
- Q, v = Qv
- if Q.is_zero or v.is_zero:
- return None
- return (Q*N, Q*M, v)
- if p.degree(DE.t) > B:
- return None
- c = lcm(fd.as_poly(DE.t).LC(), wd.as_poly(DE.t).LC())
- l = fd.monic().lcm(wd.monic())*Poly(c, DE.t)
- ln, ls = splitfactor(l, DE)
- z = ls*ln.gcd(ln.diff(DE.t))
- if not z.has(DE.t):
- # TODO: We treat this as 'no solution', until the structure
- # theorem version of parametric_log_deriv is implemented.
- return None
- u1, r1 = (fa*l.quo(fd)).div(z) # (l*f).div(z)
- u2, r2 = (wa*l.quo(wd)).div(z) # (l*w).div(z)
- eqs = [r1.nth(i) - c1*r2.nth(i) for i in range(z.degree(DE.t))]
- s = solve(eqs, c1)
- if not s or not s[c1].is_Rational:
- # deg(q) <= B, no solution for c.
- return None
- M, N = s[c1].as_numer_denom()
- nfmwa = N.as_poly(DE.t)*fa*wd - M.as_poly(DE.t)*wa*fd
- nfmwd = fd*wd
- Qv = is_log_deriv_k_t_radical_in_field(nfmwa, nfmwd, DE)
- if Qv is None:
- # (N*f - M*w) is not the logarithmic derivative of a k(t)-radical.
- return None
- Q, v = Qv
- if Q.is_zero or v.is_zero:
- return None
- return (Q*N, Q*M, v)
- def parametric_log_deriv(fa, fd, wa, wd, DE):
- # TODO: Write the full algorithm using the structure theorems.
- # try:
- A = parametric_log_deriv_heu(fa, fd, wa, wd, DE)
- # except NotImplementedError:
- # Heuristic failed, we have to use the full method.
- # TODO: This could be implemented more efficiently.
- # It isn't too worrisome, because the heuristic handles most difficult
- # cases.
- return A
- def is_deriv_k(fa, fd, DE):
- r"""
- Checks if Df/f is the derivative of an element of k(t).
- Explanation
- ===========
- a in k(t) is the derivative of an element of k(t) if there exists b in k(t)
- such that a = Db. Either returns (ans, u), such that Df/f == Du, or None,
- which means that Df/f is not the derivative of an element of k(t). ans is
- a list of tuples such that Add(*[i*j for i, j in ans]) == u. This is useful
- for seeing exactly which elements of k(t) produce u.
- This function uses the structure theorem approach, which says that for any
- f in K, Df/f is the derivative of a element of K if and only if there are ri
- in QQ such that::
- --- --- Dt
- \ r * Dt + \ r * i Df
- / i i / i --- = --.
- --- --- t f
- i in L i in E i
- K/C(x) K/C(x)
- Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is
- transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i
- in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic
- monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i
- is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some
- a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of
- hyperexponential monomials of K over C(x)). If K is an elementary extension
- over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the
- transcendence degree of K over C(x). Furthermore, because Const_D(K) ==
- Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and
- deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x)
- and L_K/C(x) are disjoint.
- The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed
- recursively using this same function. Therefore, it is required to pass
- them as indices to D (or T). E_args are the arguments of the
- hyperexponentials indexed by E_K (i.e., if i is in E_K, then T[i] ==
- exp(E_args[i])). This is needed to compute the final answer u such that
- Df/f == Du.
- log(f) will be the same as u up to a additive constant. This is because
- they will both behave the same as monomials. For example, both log(x) and
- log(2*x) == log(x) + log(2) satisfy Dt == 1/x, because log(2) is constant.
- Therefore, the term const is returned. const is such that
- log(const) + f == u. This is calculated by dividing the arguments of one
- logarithm from the other. Therefore, it is necessary to pass the arguments
- of the logarithmic terms in L_args.
- To handle the case where we are given Df/f, not f, use is_deriv_k_in_field().
- See also
- ========
- is_log_deriv_k_t_radical_in_field, is_log_deriv_k_t_radical
- """
- # Compute Df/f
- dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)), fd*fa
- dfa, dfd = dfa.cancel(dfd, include=True)
- # Our assumption here is that each monomial is recursively transcendental
- if len(DE.exts) != len(DE.D):
- if [i for i in DE.cases if i == 'tan'] or \
- ({i for i in DE.cases if i == 'primitive'} -
- set(DE.indices('log'))):
- raise NotImplementedError("Real version of the structure "
- "theorems with hypertangent support is not yet implemented.")
- # TODO: What should really be done in this case?
- raise NotImplementedError("Nonelementary extensions not supported "
- "in the structure theorems.")
- E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.indices('exp')]
- L_part = [DE.D[i].as_expr() for i in DE.indices('log')]
- # The expression dfa/dfd might not be polynomial in any of its symbols so we
- # use a Dummy as the generator for PolyMatrix.
- dum = Dummy()
- lhs = Matrix([E_part + L_part], dum)
- rhs = Matrix([dfa.as_expr()/dfd.as_expr()], dum)
- A, u = constant_system(lhs, rhs, DE)
- u = u.to_Matrix() # Poly to Expr
- if not A or not all(derivation(i, DE, basic=True).is_zero for i in u):
- # If the elements of u are not all constant
- # Note: See comment in constant_system
- # Also note: derivation(basic=True) calls cancel()
- return None
- else:
- if not all(i.is_Rational for i in u):
- raise NotImplementedError("Cannot work with non-rational "
- "coefficients in this case.")
- else:
- terms = ([DE.extargs[i] for i in DE.indices('exp')] +
- [DE.T[i] for i in DE.indices('log')])
- ans = list(zip(terms, u))
- result = Add(*[Mul(i, j) for i, j in ans])
- argterms = ([DE.T[i] for i in DE.indices('exp')] +
- [DE.extargs[i] for i in DE.indices('log')])
- l = []
- ld = []
- for i, j in zip(argterms, u):
- # We need to get around things like sqrt(x**2) != x
- # and also sqrt(x**2 + 2*x + 1) != x + 1
- # Issue 10798: i need not be a polynomial
- i, d = i.as_numer_denom()
- icoeff, iterms = sqf_list(i)
- l.append(Mul(*([Pow(icoeff, j)] + [Pow(b, e*j) for b, e in iterms])))
- dcoeff, dterms = sqf_list(d)
- ld.append(Mul(*([Pow(dcoeff, j)] + [Pow(b, e*j) for b, e in dterms])))
- const = cancel(fa.as_expr()/fd.as_expr()/Mul(*l)*Mul(*ld))
- return (ans, result, const)
- def is_log_deriv_k_t_radical(fa, fd, DE, Df=True):
- r"""
- Checks if Df is the logarithmic derivative of a k(t)-radical.
- Explanation
- ===========
- b in k(t) can be written as the logarithmic derivative of a k(t) radical if
- there exist n in ZZ and u in k(t) with n, u != 0 such that n*b == Du/u.
- Either returns (ans, u, n, const) or None, which means that Df cannot be
- written as the logarithmic derivative of a k(t)-radical. ans is a list of
- tuples such that Mul(*[i**j for i, j in ans]) == u. This is useful for
- seeing exactly what elements of k(t) produce u.
- This function uses the structure theorem approach, which says that for any
- f in K, Df is the logarithmic derivative of a K-radical if and only if there
- are ri in QQ such that::
- --- --- Dt
- \ r * Dt + \ r * i
- / i i / i --- = Df.
- --- --- t
- i in L i in E i
- K/C(x) K/C(x)
- Where C = Const(K), L_K/C(x) = { i in {1, ..., n} such that t_i is
- transcendental over C(x)(t_1, ..., t_i-1) and Dt_i = Da_i/a_i, for some a_i
- in C(x)(t_1, ..., t_i-1)* } (i.e., the set of all indices of logarithmic
- monomials of K over C(x)), and E_K/C(x) = { i in {1, ..., n} such that t_i
- is transcendental over C(x)(t_1, ..., t_i-1) and Dt_i/t_i = Da_i, for some
- a_i in C(x)(t_1, ..., t_i-1) } (i.e., the set of all indices of
- hyperexponential monomials of K over C(x)). If K is an elementary extension
- over C(x), then the cardinality of L_K/C(x) U E_K/C(x) is exactly the
- transcendence degree of K over C(x). Furthermore, because Const_D(K) ==
- Const_D(C(x)) == C, deg(Dt_i) == 1 when t_i is in E_K/C(x) and
- deg(Dt_i) == 0 when t_i is in L_K/C(x), implying in particular that E_K/C(x)
- and L_K/C(x) are disjoint.
- The sets L_K/C(x) and E_K/C(x) must, by their nature, be computed
- recursively using this same function. Therefore, it is required to pass
- them as indices to D (or T). L_args are the arguments of the logarithms
- indexed by L_K (i.e., if i is in L_K, then T[i] == log(L_args[i])). This is
- needed to compute the final answer u such that n*f == Du/u.
- exp(f) will be the same as u up to a multiplicative constant. This is
- because they will both behave the same as monomials. For example, both
- exp(x) and exp(x + 1) == E*exp(x) satisfy Dt == t. Therefore, the term const
- is returned. const is such that exp(const)*f == u. This is calculated by
- subtracting the arguments of one exponential from the other. Therefore, it
- is necessary to pass the arguments of the exponential terms in E_args.
- To handle the case where we are given Df, not f, use
- is_log_deriv_k_t_radical_in_field().
- See also
- ========
- is_log_deriv_k_t_radical_in_field, is_deriv_k
- """
- if Df:
- dfa, dfd = (fd*derivation(fa, DE) - fa*derivation(fd, DE)).cancel(fd**2,
- include=True)
- else:
- dfa, dfd = fa, fd
- # Our assumption here is that each monomial is recursively transcendental
- if len(DE.exts) != len(DE.D):
- if [i for i in DE.cases if i == 'tan'] or \
- ({i for i in DE.cases if i == 'primitive'} -
- set(DE.indices('log'))):
- raise NotImplementedError("Real version of the structure "
- "theorems with hypertangent support is not yet implemented.")
- # TODO: What should really be done in this case?
- raise NotImplementedError("Nonelementary extensions not supported "
- "in the structure theorems.")
- E_part = [DE.D[i].quo(Poly(DE.T[i], DE.T[i])).as_expr() for i in DE.indices('exp')]
- L_part = [DE.D[i].as_expr() for i in DE.indices('log')]
- # The expression dfa/dfd might not be polynomial in any of its symbols so we
- # use a Dummy as the generator for PolyMatrix.
- dum = Dummy()
- lhs = Matrix([E_part + L_part], dum)
- rhs = Matrix([dfa.as_expr()/dfd.as_expr()], dum)
- A, u = constant_system(lhs, rhs, DE)
- u = u.to_Matrix() # Poly to Expr
- if not A or not all(derivation(i, DE, basic=True).is_zero for i in u):
- # If the elements of u are not all constant
- # Note: See comment in constant_system
- # Also note: derivation(basic=True) calls cancel()
- return None
- else:
- if not all(i.is_Rational for i in u):
- # TODO: But maybe we can tell if they're not rational, like
- # log(2)/log(3). Also, there should be an option to continue
- # anyway, even if the result might potentially be wrong.
- raise NotImplementedError("Cannot work with non-rational "
- "coefficients in this case.")
- else:
- n = reduce(ilcm, [i.as_numer_denom()[1] for i in u])
- u *= n
- terms = ([DE.T[i] for i in DE.indices('exp')] +
- [DE.extargs[i] for i in DE.indices('log')])
- ans = list(zip(terms, u))
- result = Mul(*[Pow(i, j) for i, j in ans])
- # exp(f) will be the same as result up to a multiplicative
- # constant. We now find the log of that constant.
- argterms = ([DE.extargs[i] for i in DE.indices('exp')] +
- [DE.T[i] for i in DE.indices('log')])
- const = cancel(fa.as_expr()/fd.as_expr() -
- Add(*[Mul(i, j/n) for i, j in zip(argterms, u)]))
- return (ans, result, n, const)
- def is_log_deriv_k_t_radical_in_field(fa, fd, DE, case='auto', z=None):
- """
- Checks if f can be written as the logarithmic derivative of a k(t)-radical.
- Explanation
- ===========
- It differs from is_log_deriv_k_t_radical(fa, fd, DE, Df=False)
- for any given fa, fd, DE in that it finds the solution in the
- given field not in some (possibly unspecified extension) and
- "in_field" with the function name is used to indicate that.
- f in k(t) can be written as the logarithmic derivative of a k(t) radical if
- there exist n in ZZ and u in k(t) with n, u != 0 such that n*f == Du/u.
- Either returns (n, u) or None, which means that f cannot be written as the
- logarithmic derivative of a k(t)-radical.
- case is one of {'primitive', 'exp', 'tan', 'auto'} for the primitive,
- hyperexponential, and hypertangent cases, respectively. If case is 'auto',
- it will attempt to determine the type of the derivation automatically.
- See also
- ========
- is_log_deriv_k_t_radical, is_deriv_k
- """
- fa, fd = fa.cancel(fd, include=True)
- # f must be simple
- n, s = splitfactor(fd, DE)
- if not s.is_one:
- pass
- z = z or Dummy('z')
- H, b = residue_reduce(fa, fd, DE, z=z)
- if not b:
- # I will have to verify, but I believe that the answer should be
- # None in this case. This should never happen for the
- # functions given when solving the parametric logarithmic
- # derivative problem when integration elementary functions (see
- # Bronstein's book, page 255), so most likely this indicates a bug.
- return None
- roots = [(i, i.real_roots()) for i, _ in H]
- if not all(len(j) == i.degree() and all(k.is_Rational for k in j) for
- i, j in roots):
- # If f is the logarithmic derivative of a k(t)-radical, then all the
- # roots of the resultant must be rational numbers.
- return None
- # [(a, i), ...], where i*log(a) is a term in the log-part of the integral
- # of f
- respolys, residues = list(zip(*roots)) or [[], []]
- # Note: this might be empty, but everything below should work find in that
- # case (it should be the same as if it were [[1, 1]])
- residueterms = [(H[j][1].subs(z, i), i) for j in range(len(H)) for
- i in residues[j]]
- # TODO: finish writing this and write tests
- p = cancel(fa.as_expr()/fd.as_expr() - residue_reduce_derivation(H, DE, z))
- p = p.as_poly(DE.t)
- if p is None:
- # f - Dg will be in k[t] if f is the logarithmic derivative of a k(t)-radical
- return None
- if p.degree(DE.t) >= max(1, DE.d.degree(DE.t)):
- return None
- if case == 'auto':
- case = DE.case
- if case == 'exp':
- wa, wd = derivation(DE.t, DE).cancel(Poly(DE.t, DE.t), include=True)
- with DecrementLevel(DE):
- pa, pd = frac_in(p, DE.t, cancel=True)
- wa, wd = frac_in((wa, wd), DE.t)
- A = parametric_log_deriv(pa, pd, wa, wd, DE)
- if A is None:
- return None
- n, e, u = A
- u *= DE.t**e
- elif case == 'primitive':
- with DecrementLevel(DE):
- pa, pd = frac_in(p, DE.t)
- A = is_log_deriv_k_t_radical_in_field(pa, pd, DE, case='auto')
- if A is None:
- return None
- n, u = A
- elif case == 'base':
- # TODO: we can use more efficient residue reduction from ratint()
- if not fd.is_sqf or fa.degree() >= fd.degree():
- # f is the logarithmic derivative in the base case if and only if
- # f = fa/fd, fd is square-free, deg(fa) < deg(fd), and
- # gcd(fa, fd) == 1. The last condition is handled by cancel() above.
- return None
- # Note: if residueterms = [], returns (1, 1)
- # f had better be 0 in that case.
- n = reduce(ilcm, [i.as_numer_denom()[1] for _, i in residueterms], S.One)
- u = Mul(*[Pow(i, j*n) for i, j in residueterms])
- return (n, u)
- elif case == 'tan':
- raise NotImplementedError("The hypertangent case is "
- "not yet implemented for is_log_deriv_k_t_radical_in_field()")
- elif case in ('other_linear', 'other_nonlinear'):
- # XXX: If these are supported by the structure theorems, change to NotImplementedError.
- raise ValueError("The %s case is not supported in this function." % case)
- else:
- raise ValueError("case must be one of {'primitive', 'exp', 'tan', "
- "'base', 'auto'}, not %s" % case)
- common_denom = reduce(ilcm, [i.as_numer_denom()[1] for i in [j for _, j in
- residueterms]] + [n], S.One)
- residueterms = [(i, j*common_denom) for i, j in residueterms]
- m = common_denom//n
- if common_denom != n*m: # Verify exact division
- raise ValueError("Inexact division")
- u = cancel(u**m*Mul(*[Pow(i, j) for i, j in residueterms]))
- return (common_denom, u)
|