K ixNdZddlmZddlmZddlmZddlmZm Z ddl m Z ddl m Z ddlmZmZmZdd lmZmZdd lmZdd lmZdd lmZdd lmZddlmZddlm Z dZ!d(dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)dZ*dZ+dZ,dZ-dZ.dZ/d Z0d!Z1d"Z2d#Z3d$Z4d%Z5d&Z6d)d'Z7y)*a This module contains :py:meth:`~sympy.solvers.ode.riccati.solve_riccati`, a function which gives all rational particular solutions to first order Riccati ODEs. A general first order Riccati ODE is given by - .. math:: y' = b_0(x) + b_1(x)w + b_2(x)w^2 where `b_0, b_1` and `b_2` can be arbitrary rational functions of `x` with `b_2 \ne 0`. When `b_2 = 0`, the equation is not a Riccati ODE anymore and becomes a Linear ODE. Similarly, when `b_0 = 0`, the equation is a Bernoulli ODE. The algorithm presented below can find rational solution(s) to all ODEs with `b_2 \ne 0` that have a rational solution, or prove that no rational solution exists for the equation. Background ========== A Riccati equation can be transformed to its normal form .. math:: y' + y^2 = a(x) using the transformation .. math:: y = -b_2(x) - \frac{b'_2(x)}{2 b_2(x)} - \frac{b_1(x)}{2} where `a(x)` is given by .. math:: a(x) = \frac{1}{4}\left(\frac{b_2'}{b_2} + b_1\right)^2 - \frac{1}{2}\left(\frac{b_2'}{b_2} + b_1\right)' - b_0 b_2 Thus, we can develop an algorithm to solve for the Riccati equation in its normal form, which would in turn give us the solution for the original Riccati equation. Algorithm ========= The algorithm implemented here is presented in the Ph.D thesis "Rational and Algebraic Solutions of First-Order Algebraic ODEs" by N. Thieu Vo. The entire thesis can be found here - https://www3.risc.jku.at/publications/download/risc_5387/PhDThesisThieu.pdf We have only implemented the Rational Riccati solver (Algorithm 11, Pg 78-82 in Thesis). Before we proceed towards the implementation of the algorithm, a few definitions to understand are - 1. Valuation of a Rational Function at `\infty`: The valuation of a rational function `p(x)` at `\infty` is equal to the difference between the degree of the denominator and the numerator of `p(x)`. NOTE: A general definition of valuation of a rational function at any value of `x` can be found in Pg 63 of the thesis, but is not of any interest for this algorithm. 2. Zeros and Poles of a Rational Function: Let `a(x) = \frac{S(x)}{T(x)}, T \ne 0` be a rational function of `x`. Then - a. The Zeros of `a(x)` are the roots of `S(x)`. b. The Poles of `a(x)` are the roots of `T(x)`. However, `\infty` can also be a pole of a(x). We say that `a(x)` has a pole at `\infty` if `a(\frac{1}{x})` has a pole at 0. Every pole is associated with an order that is equal to the multiplicity of its appearance as a root of `T(x)`. A pole is called a simple pole if it has an order 1. Similarly, a pole is called a multiple pole if it has an order `\ge` 2. Necessary Conditions ==================== For a Riccati equation in its normal form, .. math:: y' + y^2 = a(x) we can define a. A pole is called a movable pole if it is a pole of `y(x)` and is not a pole of `a(x)`. b. Similarly, a pole is called a non-movable pole if it is a pole of both `y(x)` and `a(x)`. Then, the algorithm states that a rational solution exists only if - a. Every pole of `a(x)` must be either a simple pole or a multiple pole of even order. b. The valuation of `a(x)` at `\infty` must be even or be `\ge` 2. This algorithm finds all possible rational solutions for the Riccati ODE. If no rational solutions are found, it means that no rational solutions exist. The algorithm works for Riccati ODEs where the coefficients are rational functions in the independent variable `x` with rational number coefficients i.e. in `Q(x)`. The coefficients in the rational function cannot be floats, irrational numbers, symbols or any other kind of expression. The reasons for this are - 1. When using symbols, different symbols could take the same value and this would affect the multiplicity of poles if symbols are present here. 2. An integer degree bound is required to calculate a polynomial solution to an auxiliary differential equation, which in turn gives the particular solution for the original ODE. If symbols/floats/irrational numbers are present, we cannot determine if the expression for the degree bound is an integer or not. Solution ======== With these definitions, we can state a general form for the solution of the equation. `y(x)` must have the form - .. math:: y(x) = \sum_{i=1}^{n} \sum_{j=1}^{r_i} \frac{c_{ij}}{(x - x_i)^j} + \sum_{i=1}^{m} \frac{1}{x - \chi_i} + \sum_{i=0}^{N} d_i x^i where `x_1, x_2, \dots, x_n` are non-movable poles of `a(x)`, `\chi_1, \chi_2, \dots, \chi_m` are movable poles of `a(x)`, and the values of `N, n, r_1, r_2, \dots, r_n` can be determined from `a(x)`. The coefficient vectors `(d_0, d_1, \dots, d_N)` and `(c_{i1}, c_{i2}, \dots, c_{i r_i})` can be determined from `a(x)`. We will have 2 choices each of these vectors and part of the procedure is figuring out which of the 2 should be used to get the solution correctly. Implementation ============== In this implementation, we use ``Poly`` to represent a rational function rather than using ``Expr`` since ``Poly`` is much faster. Since we cannot represent rational functions directly using ``Poly``, we instead represent a rational function with 2 ``Poly`` objects - one for its numerator and the other for its denominator. The code is written to match the steps given in the thesis (Pg 82) Step 0 : Match the equation - Find `b_0, b_1` and `b_2`. If `b_2 = 0` or no such functions exist, raise an error Step 1 : Transform the equation to its normal form as explained in the theory section. Step 2 : Initialize an empty set of solutions, ``sol``. Step 3 : If `a(x) = 0`, append `\frac{1}/{(x - C1)}` to ``sol``. Step 4 : If `a(x)` is a rational non-zero number, append `\pm \sqrt{a}` to ``sol``. Step 5 : Find the poles and their multiplicities of `a(x)`. Let the number of poles be `n`. Also find the valuation of `a(x)` at `\infty` using ``val_at_inf``. NOTE: Although the algorithm considers `\infty` as a pole, it is not mentioned if it a part of the set of finite poles. `\infty` is NOT a part of the set of finite poles. If a pole exists at `\infty`, we use its multiplicity to find the laurent series of `a(x)` about `\infty`. Step 6 : Find `n` c-vectors (one for each pole) and 1 d-vector using ``construct_c`` and ``construct_d``. Now, determine all the ``2**(n + 1)`` combinations of choosing between 2 choices for each of the `n` c-vectors and 1 d-vector. NOTE: The equation for `d_{-1}` in Case 4 (Pg 80) has a printinig mistake. The term `- d_N` must be replaced with `-N d_N`. The same has been explained in the code as well. For each of these above combinations, do Step 8 : Compute `m` in ``compute_m_ybar``. `m` is the degree bound of the polynomial solution we must find for the auxiliary equation. Step 9 : In ``compute_m_ybar``, compute ybar as well where ``ybar`` is one part of y(x) - .. math:: \overline{y}(x) = \sum_{i=1}^{n} \sum_{j=1}^{r_i} \frac{c_{ij}}{(x - x_i)^j} + \sum_{i=0}^{N} d_i x^i Step 10 : If `m` is a non-negative integer - Step 11: Find a polynomial solution of degree `m` for the auxiliary equation. There are 2 cases possible - a. `m` is a non-negative integer: We can solve for the coefficients in `p(x)` using Undetermined Coefficients. b. `m` is not a non-negative integer: In this case, we cannot find a polynomial solution to the auxiliary equation, and hence, we ignore this value of `m`. Step 12 : For each `p(x)` that exists, append `ybar + \frac{p'(x)}{p(x)}` to ``sol``. Step 13 : For each solution in ``sol``, apply an inverse transformation, so that the solutions of the original equation are found using the solutions of the equation in its normal form. )product)S)Add)ooFloat) count_ops)Eq)symbolsSymbolDummy)sqrtexp)sign)Integral)ZZ)Poly)roots)linsolvecJ| |z|j|d|zz z |dz z S)a Given a solution `w(x)` to the equation .. math:: w'(x) = b_0(x) + b_1(x)*w(x) + b_2(x)*w(x)^2 and rational function coefficients `b_1(x)` and `b_2(x)`, this function transforms the solution to give a solution `y(x)` for its corresponding normal Riccati ODE .. math:: y'(x) + y(x)^2 = a(x) using the transformation .. math:: y(x) = -b_2(x)*w(x) - b'_2(x)/(2*b_2(x)) - b_1(x)/2 diff)wxb1b2s _/mnt/ssd/data/python-lab/Trading/venv/lib/python3.12/site-packages/sympy/solvers/ode/riccati.pyriccati_normalrs/" 3q52771:qt$ $r!t ++Nc`|$|j| d|dzzz |d|zz z }| |z |zS)zq Inverse transforming the solution to the normal Riccati ODE to get the solution to the Riccati ODE. rr)yrrrbps rriccati_inverse_normalr#sB zggaj[!BE' "R2Y . 2b52:rcrt|||\}}|sy|\}}}| |z|dzdz z|j|dz z d|j|dzzd|dzzz z||j|zd|zz z|j|dd|zz z }||j|||dzz|z S)zN Convert a Riccati ODE into its corresponding normal Riccati ODE. Fr) match_riccatir) eqfrmatchfuncsb0rras rriccati_reducedr.s !Q*LE5 JBB BQq2771:a<'!BGGAJM/1RU7*CCbQRmUVWYUYFZZ 1 qt A Q499Qz match_riccati..5s4qAbD==?4s rc3K|]E}t|jtdkDxst|jtGyw)N)lenatomsr r)r:rs rr<z match_riccati..>s7N1s1776?#a'>3qwwu~+>>NsA A FT) isinstancer lhsrhsexpandcollectcoeffrrargsanyr?r@allis_rational_function)r(r)rrrr,r+r;s @rr'r's("b VVbff_   QqT "B !A$))A, B  Qw:b#&4BGG4 5 = =ad Chhqtn_hhqtQw diilR!W$r!A$'z1B6 > > @R  NN N"9  rxx{ 3a1H1H1K  # #A &(?(?(B(D$E"9 U{ "9rcH|j||j|z Sr8)degree)numdenrs r val_at_infrOIs ::a=3::a= ((rcV|dk\xs|dkxr|dzdk(xrtd|DS)a The necessary conditions for a rational solution to exist are as follows - i) Every pole of a(x) must be either a simple pole or a multiple pole of even order. ii) The valuation of a(x) at infinity must be even or be greater than or equal to 2. Here, a simple pole is a pole with multiplicity 1 and a multiple pole is a pole with multiplicity greater than 1. rrc3HK|]}|dk(xs|dzdk(xr|dk\yw)r>rrN)r:muls rr<z(check_necessary_conds..^s- BcC1H 1Q!0q 1 Bs ")rI)val_infmulss rcheck_necessary_condsrVNs; qL =W\ 1/x in a rational function that is represented using Poly objects for numerator and denominator. r>rTinclude)rrOexpr transformr9)rMrNronexpolypwrs rinverse_transform_polyr_as q!*C AJE S#q !C ax 88q=--U+af4C--U+CmmC'mmC'!sd)3 ::c4: ((rct||| }|dkDr1tt|j|jz zS|dk(r!|j|jz Sy)z9 Find the limit of a rational function at oo r)rOrrLC)rMrNrr^s r limit_at_infrb|sd c3 " "C Qw$svvx())) vvx  rcJ|t||z dz|dzj|d\}}|j|||j||z }|td dz k7r0dt dd|zzzdz gdt dd|zzz dz ggStj ggS)NrT extensionrXr>r%)rr9subsrr Half)rMrNrpolenum1den1rs rconstruct_c_case_1rlsdAHq=!t<<DDSRVDWJD$ 1d dii401A QqTE!G|d1qs7m#Q&'1tA!G}+)rational_laurent_seriesranger ) rMrNrrhrSrisericplusssmjcminuss rconstruct_c_case_2rzs aB "#sAtS! rRrRrrconstruct_c_case_3r|s C5Lrc &g}t||D]\}}|jg|dk(r|djt9|dk(r!|djt ||||_|djt ||||||S)zZ Helper function to calculate the coefficients in the c-vector for each pole. r>ror)r1appendextendr|rlrz)rMrNrpolesrUcrhrSs r construct_crs A%E c   !8 bELL+- .AX bELL+Ca> ? bELL+CasC D!E$ Hrct|dzDcgc]}d}}t|d|z||<t|dz ddD]I}d}t|dz|D]}||||||z|z zz }|dk7s3|||z|z d||zz ||<K|Dcgc]}| }}||z|||zz z d||zz |d<|||z|||zz |z d||zz |d<||k7r||gS|Scc}wcc}w)Nrrr>ro)rqr ) rsNrtdplusrvrwrxrdminuss rconstruct_d_case_4rsOac #1Q #E #C!H~E!H1Q3B 4 qsA (A %(51Q<' 'B ( 7AaC2 %( 3E!H 4 Qqb F QqSAeAhJ&+aaj9E"Iac(Qvay[(2-&) r%)rbrrr rg)rMrNrs_infs rconstruct_d_case_6rs adAs*C 3E 1ad1qw;''*+q4AeG 3D/Da.G-HII VVH:rc| dz}|dkr| nd}t|||t|d}|dkrt||}|S|dk(r t|}|St |||}|S)z} Helper function to calculate the coefficients in the d-vector based on the valuation of the function at oo. rrr>)rprrrr)rMrNrrTrrSrsds r construct_dr"s} ! Ak7(qC !#sAr3 :C{ sA & H A s # H sC + Hrcftd|d}|tk(rt|||\}}td}|rB|j t||z|d|}|j t||z|d|}|||zzj |d\}}dt |j|jz}td|t}||t|ddd |zz } | jddd d|} t| |\} |jddd } | d| dd}} t| t|kr>tfd t|D| z }j!| t|kr>tDcic] \}}||z | c}}Scc}}w) a` The function computes the Laurent series coefficients of a rational function. Parameters ========== num: A Poly object that is the numerator of `f(x)`. den: A Poly object that is the denominator of `f(x)`. x: The variable of expansion of the series. r: The point of expansion of the series. m: Multiplicity of r if r is a pole of `f(x)`. Should be zero otherwise. n: Order of the term upto which the series is expanded. Returns ======= series: A dictionary that has power of the term as key and coefficient of that term as value. Below is a basic outline of how the Laurent series of a rational function `f(x)` about `x_0` is being calculated - 1. Substitute `x + x_0` in place of `x`. If `x_0` is a pole of `f(x)`, multiply the expression by `x^m` where `m` is the multiplicity of `x_0`. Denote the the resulting expression as g(x). We do this substitution so that we can now find the Laurent series of g(x) about `x = 0`. 2. We can then assume that the Laurent series of `g(x)` takes the following form - .. math:: g(x) = \frac{num(x)}{den(x)} = \sum_{m = 0}^{\infty} a_m x^m where `a_m` denotes the Laurent series coefficients. 3. Multiply the denominator to the RHS of the equation and form a recurrence relation for the coefficients `a_m`. r>TrdrrXza:clsNroc3:K|]\}}|d|z zyw)roNrR)r:nrseriess rr<z*rational_laurent_series..s!Ida1VBqD\>Is)rrr_rr[r9maxrLr r all_coeffsrr2r?r enumerater~)rMrNrrkmrr\ maxdegreer3r coeff_diffscoeffs recursiondivrec_rhs next_coeffrtvalrs @rrprp<sT q!t $CBw*#sA6S aDmmDQT:C@mmDQT:C@AqD  d 3HCC cjjl33I R {# /D tD2J** *D//#DbD)*95K+t,JV 2&IQ<12C &\F f+/Ii6HIJSP  zk" f+/(1'8 9VQa!eSj 9F M:sF-cfd}t|dd|d}g}t|D]O\}}t||D]"\} } |j| ||z | dzzz $|t||d|dz}Q|t|z }t |dzD]}||d|||zzz }|j |fS)a Helper function to calculate - 1. m - The degree bound for the polynomial solution that must be found for the auxiliary differential equation. 2. ybar - Part of the solution which can be computed using the poles, c and d vectors. rroTrdr>)rrr~rrqrZ) rrchoicerybarrdybarrtpoleirxcijs rcompute_m_ybarrs D VBZ^Q$/A Ee$25q * 3FAs LLa%i1q511 2 3 D1qD 112 CKD1Q3Z# r 1 ad""# FFD>rctd|t}t|}t|j||t||z||z}||j ||z||j |zz |dzzz||dzzz |z} |dk\r"|j |} | | d|z|z|zzz } |dk\r|  j ||dz|zzz } |dk7r|t | j|dfStj| | dk(fS)zt Helper function to find a polynomial solution of degree m for the auxiliary differential equation. zC0:r)domainrr>rT) r r rrgensrr5rrOne) numadenanumydenyrrpsymsKpsolauxeqpxs r solve_aux_eqrs c!I5 )E 5 A ! $tAqD!A'> >D499Q<$tDIIaL'8847B Cd4QR7l RTX XEAv YYq\ QtVD[%&&Av T1WT\**Av]5#3#3#5u=tCCuueUaZ''rc|jtt}|jtt}|jj Dcgc]}t ||dc}\}}|jj Dcgc]}t ||dc}\}} || z||zz }t |jtt} t| rst|j| } t| rMt|t|kDr|St|t|k(rt|t|k\r|S|S|Syycc}wcc}w)zY Helper function to remove redundant solutions to the differential equation. TrdN) r@r r togetheras_numer_denomrr2r?rrr) sol1sol2rsyms1syms2erirjnum2den2r3redns rremove_redundant_solsrs JJvu %E JJvu %E6:mmo6T6T6VW$q!t,WJD$6:mmo6T6T6VW$q!t,WJD$ T DIA & 'D 4y - t95zCJ& Us5z)(/9U3CCtMM   XWs EEct|dk(rgSt|dk(rS|d}ttd|z|}|t||z |z}|j}|dk(s|dk(r|S|d|z zSt|dk(r|\}}t|j t t|j t zdkDr'tt||z |j}n4t d}|tt||z |jz}|dk(r|S||z|z |dz z S|dd\}}} t d}|dz|z|| z z||z|z|dz| zz z S)a" Helper function which computes the general solution for a Riccati ODE from its particular solutions. There are 3 cases to find the general solution from the particular solutions for a Riccati ODE depending on the number of particular solution(s) we have - 1, 2 or 3. For more information, see Section 6 of "Methods of Solution of the Riccati Differential Equation" by D. R. Haaheim and F. M. Stein rr>rC1Nr&)r?rrdoitr@r ) part_solsr-ry1rtzy2ury3s rget_gen_sol_from_part_solrsz$ 9~ Y1  q\ 2q! " 1a  FFH 6Q!VIAaCx Y1 B rxx #bhhuo"6 6 :HR"Wa()..0AtB3xR+,1133A 6I1r AE"" r] B 4[Q{BG$bebjBFB;&>??rc | |z|dzdz z|j|dz z d|j|dzzd|dzzz z||j|zd|zz z|j|dd|zz z }|j}|jDcgc]}t||dc}\} } | j | d\} } g} | dk(r!| j d|t d zz nM|| jj| jvr&| jt|t| gt| |} t| jt| j} } t| | |}t!| r:t#|| s t%d t'| | || | }t)| | ||}|j |t+|}|D]}t-|| || dz\}}|jjDcgc]}t||dc}\}}|j.dk(sc|j0dk(sst3| | ||||\}}}|s|dk(r|dk(r| j |t!|s|j5|}| j ||j||z zt7}t9t!| D]F}t9|dzt!| D])}t;| || ||}||j=|+H| Dcgc] }||vs| }}|j d|dzzz |d|zz z }|rt?|||g}|D cgc]+} tA|tC| ||||j d-} } | Scc}wcc}wcc}wcc} w) z The main function that gives particular/general solutions to Riccati ODEs that have atleast 1 rational particular solution. rr%r&TrdrXrr>rzRational Solution doesn't exist)"rrrrr9r~r free_symbolsunionrr rr2keysvaluesrOr?rV ValueErrorrrrris_nonnegative is_integerrxreplacesetrqraddrr r#)!fxrr,rrgensolr-a_trrMrNpresolrrUrTrrchoicesrrrrrrrexistsremovertrxremsolsr"r!s! r solve_riccatirs BQq2771:a<'!BGGAJM/1RU7*CCbQRmUVWYUYFZZ 1 qt A **,C474F4F4HIqQT*IHCzz#tz,HCF ax aU4[)* #""(()9)9: : tAwa)* #qMEuzz|$d5<<>&:4Ec1%G 5z$Wd3>? ? S!UD 1 S!W -  1+ @F$Qvx{CGAt>Bmmo>\>\>^_$q!t4_JD$4'ALLD,@(4CdD!Q'O$ffqyVq[ d+V#}}V4 dTYYq\$->&>?+ @0UF 3v;  qsCK( A'q 6!9a@C 3   1!&A 1D 1 ''!*aAg QrT *B)$156`d dZ[b+Aq"b"=DDtDTU dF d MqJb`8 2esN: N?> OO0O r8)F)8__doc__ itertoolsr sympy.corersympy.core.addrsympy.core.numbersrrsympy.core.functionrsympy.core.relationalr sympy.core.symbolr r r sympy.functionsr r$sympy.functions.elementary.complexesrsympy.integrals.integralsrsympy.polys.domainsrsympy.polys.polytoolsrsympy.polys.polyrootsrsympy.solvers.solvesetrrr#r.r5r'rOrVr_rbrlrzr|rrrrrrprrrrrrRrrrsDN()$44%5."&'+,( & )1h) C&)6, %P  68& 4L\:(6>7@tbr