o
    8Vaw                     @   s|  d Z ddlmZ ddlmZ ddlmZ ddlmZm	Z	 ddl
mZ ddlmZ ddlmZmZmZ dd	lmZmZ dd
lmZ ddlmZ ddlmZ ddlmZ ddlmZ ddlm Z  dd Z!d@ddZ"dd Z#dd Z$dd Z%dd Z&dd Z'dd  Z(d!d" Z)d#d$ Z*d%d& Z+d'd( Z,d)d* Z-d+d, Z.d-d. Z/d/d0 Z0d1d2 Z1d3d4 Z2d5d6 Z3d7d8 Z4d9d: Z5d;d< Z6dAd>d?Z7dS )Ba  
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 appearence 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, ..., x_n` are non-movable poles of `a(x)`,
`\chi_1, \chi_2, ..., \chi_m` are movable poles of `a(x)`, and the values
of `N, n, r_1, r_2, ..., r_n` can be determined from `a(x)`. The
coefficient vectors `(d_0, d_1, ..., d_N)` and `(c_{i1}, c_{i2}, ..., 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 implementatin, 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 multiplicty 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)ooFloat)	count_ops)Eq)symbolsSymbolDummy)sqrtexp)sign)Integral)ZZPoly)roots)linsolvec                 C   s$   | |  | |d|   |d  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)wxb1b2 r   ;/usr/lib/python3/dist-packages/sympy/solvers/ode/riccati.pyriccati_normal   s   $r   Nc                 C   s:   |du r| | d|d   |d|   }|  | | S )zq
    Inverse transforming the solution to the normal
    Riccati ODE to get the solution to the Riccati ODE.
    Nr   r   )yr   r   r   bpr   r   r   riccati_inverse_normal   s   $r!   c           	      C   s   t | ||\}}|sdS |\}}}| | |d d  ||d  d||d  d|d    ||| d|   ||dd|   }|||||d  | S )zN
    Convert a Riccati ODE into its corresponding
    normal Riccati ODE.
    Fr         )match_riccatir   )	eqfr   matchfuncsb0r   r   ar   r   r   riccati_reduced   s   
Vr+   c                 C   s.   t | |}|s	i S dd t|t|d D S )z.
    Get the output of linsolve as a dict
    c                 S   s   i | ]\}}||qS r   r   ).0kvr   r   r   
<dictcomp>  s    z!linsolve_dict.<locals>.<dictcomp>r   )r   ziplist)r%   symsZsolr   r   r   linsolve_dict	  s   
r3   c                    s<  t | tr| j| j } |  ||} | |||  dkrt | trt fdd| j	D  ||} | || }| ||d  }||||||  |||d   |   }|||g}t
dd |||fD rxdg fS t||st|dk||||||gsdg fS d|fS dg fS )	a  
    A function that matches and returns the coefficients
    if an equation is a Riccati ODE

    Parameters
    ==========

    eq: Equation to be matched
    f: Dependent variable
    x: Independent variable

    Returns
    =======

    match: True if equation is a Riccati ODE, False otherwise
    funcs: [b0, b1, b2] if match is True, [] otherwise. Here,
    b0, b1 and b2 are rational functions which match the equation.
    r   c                 3   s    | ]	}|    V  qd S N)cancelr,   r   Zcfr   r   	<genexpr>5  s    z match_riccati.<locals>.<genexpr>r   c                 S   s,   g | ]}t |td kpt |tqS )   )lenatomsr
   r   r6   r   r   r   
<listcomp>>  s   , z!match_riccati.<locals>.<listcomp>FT)
isinstancer   ZlhsZrhsexpandZcollectZcoeffr   r   argsanyr:   r;   allZis_rational_function)r%   r&   r   r   r   r)   r(   r   r7   r   r$     s$   
"2
r$   c                 C   s   | ||  | S r4   )degree)numdenr   r   r   r   
val_at_infI  s   rE   c                 C   s.   | dks| dko| d dkot dd |D S )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.
    r   r   c                 S   s(   g | ]}|d kp|d dko|dkqS )r9   r   r   r   )r,   mulr   r   r   r<   ^  s   ( z)check_necessary_conds.<locals>.<listcomp>)rA   )val_infmulsr   r   r   check_necessary_condsN  s   rI   c                 C   s   t d|}t ||}t| ||}|dkr*| jdkr)| ||||  } |||}n| ||} |||||   }| j|ddS )z
    A function to make the substitution
    x -> 1/x in a rational function that
    is represented using Poly objects for
    numerator and denominator.
    r9   r   TZinclude)r   rE   expr	transformr5   )rC   rD   r   oneZxpolypwrr   r   r   inverse_transform_polya  s   


rO   c                 C   sJ   t | || }|dkrtt|  |   S |dkr#|  |  S dS )z9
    Find the limit of a rational function
    at oo
    r   )rE   r   r   ZLC)rC   rD   r   rN   r   r   r   limit_at_inf|  s   rP   c                 C   s   | t || d |dd j|dd\}}|||||| }|td d krCdtdd|   d gdtdd|   d ggS tdd ggS )Nr   T	extensionrJ   r9   r"   )r   r5   Zsubsr   r   )rC   rD   r   polenum1den1rr   r   r   construct_c_case_1  s
   (4rW   c                 C   s\  |d }t | ||||d}dd t|D }t|d|  ||d < |d }d}	t|d ddD ]8}d}	t|d |D ]}
|	||
d  ||| |
 d   7 }	q;|dkrh|||  |	 d||d    ||d < q0dd |D }|||  |	 |||d    d||d    |d< |||  |	 |||d    d||d    |d< ||kr||gS |S )	Nr      c                 S      g | ]}d qS r   r   r,   ir   r   r   r<         z&construct_c_case_2.<locals>.<listcomp>r9   r   c                 S      g | ]}| qS r   r   r6   r   r   r   r<         )rational_laurent_seriesranger   )rC   rD   r   rS   rF   ZriserZcplusssmjZcminusr   r   r   construct_c_case_2  s&   &(44rg   c                   C   s   dggS )Nr9   r   r   r   r   r   construct_c_case_3  s   rh   c              	   C   s|   g }t ||D ]4\}}|g  |dkr|d t  q|dkr.|d t| ||| q|d t| |||| q|S )zZ
    Helper function to calculate the coefficients
    in the c-vector for each pole.
    r9   r^   r   )r0   appendextendrh   rW   rg   )rC   rD   r   polesrH   crS   rF   r   r   r   construct_c  s   
rm   c                 C   s  dd t |d D }t| d|  ||< t |d ddD ]0}d}t |d |D ]}||| ||| |   7 }q(|dkrM| ||  | d||   ||< qdd |D }| ||  |||   | d||   |d< | ||  |||   | d||   |d< ||kr||gS |S )	Nc                 S   rY   rZ   r   r[   r   r   r   r<     r]   z&construct_d_case_4.<locals>.<listcomp>r   r9   r^   r   c                 S   r_   r   r   r6   r   r   r   r<     r`   )rb   r   )rc   Ndplusrd   re   rf   dminusr   r   r   construct_d_case_4  s    ,,rr   c                 C   sR   ddg}t | d |d< | d d|d   |d< dd |D }||kr'||gS |S )Nr   r^   r   c                 S   r_   r   r   r6   r   r   r   r<     r`   z&construct_d_case_5.<locals>.<listcomp>)r   )rc   rp   rq   r   r   r   construct_d_case_5  s   rs   c                 C   sp   t t|d ||  ||}|td d kr0dtdd|   d gdtdd|   d ggS tdd ggS )Nr   r9   r"   )rP   r   r   r   )rC   rD   r   Zs_infr   r   r   construct_d_case_6  s   4rt   c                 C   sh   | d }|dk r| nd}t | ||t|d}|dk r"t||}|S |dkr,t|}|S t| ||}|S )z}
    Helper function to calculate the coefficients
    in the d-vector based on the valuation of the
    function at oo.
    r   r   r9   )ra   r   rr   rs   rt   )rC   rD   r   rG   ro   rF   rc   dr   r   r   construct_d"  s   


rv   c                    sv  t d|dd}|tkrt| ||\} }td}|r3| t || |dd|} |t || |dd|}| |   j|dd\} }dt|  |  }td| t	d}| |t |ddd	 |  }	|	
 ddd	 d| }
t|
|\}|
 ddd	 }|d |dd }}t|t|k rtfd
dt|D  | }|  t|k s fddtD S )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`.
    r9   TrQ   r   rJ   za:clsNr^   c                 3   s$    | ]\}}| d |   V  qdS )r^   Nr   )r,   nrl   )seriesr   r   r8     s   " z*rational_laurent_series.<locals>.<genexpr>c                    s   i | ]	\}} | |qS r   r   )r,   r\   val)mr   r   r/         z+rational_laurent_series.<locals>.<dictcomp>)r   r   rO   r   rL   r5   maxrB   r	   r   
all_coeffsr   r1   r:   r   	enumerateri   )rC   rD   r   rV   r|   ry   rM   Z	maxdegreer2   r   Zcoeff_diffscoeffsZ	recursionZdivZrec_rhsZ
next_coeffr   )r|   rz   r   ra   <  s,   *ra   c                 C   s   d}t |d d | dd}tt|D ],}tt|| D ]}||| | | ||  |d   7 }q|t || d | dd8 }qt|d D ]}||d | | |  7 }qF|j|fS )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.
    r   r^   TrQ   r9   )r   rb   r:   rK   )r   rk   choicero   ybarr|   r\   rf   r   r   r   compute_m_ybar  s   &
r   c                 C   s   t d| td}t| }t|j||dt|| ||d }|||| |||  |d   | |d   | }	|dkrO||}
|	|
d| | |  7 }	|dkr`|	|
||d |  7 }	|dkrn|t|	 |dfS td|	|	dkfS )zt
    Helper function to find a polynomial solution
    of degree m for the auxiliary differential
    equation.
    zC0:rw   )domainr   r9   r   T)	r	   r   r   r   Zgensr   r3   r   r   )ZnumaZdenanumydenyr   r|   ZpsymsKpsolZauxeqZpxr   r   r   solve_aux_eq  s   "8
r   c                    s   |  tt}| tt} fdd|   D \}} fdd|  D \}}|| ||  }	t|	 tt}
t|
rit|	 |
}t|rkt|t|krS|S t|t|krgt	|t	|kre| S |S | S dS dS )zY
    Helper function to remove redundant
    solutions to the differential equation.
    c                       g | ]	}t | d dqS TrQ   r   r,   er   r   r   r<     r}   z)remove_redundant_sols.<locals>.<listcomp>c                    r   r   r   r   r   r   r   r<     r}   N)
r;   r
   r   togetheras_numer_denomr1   r:   r   r   r   )Zsol1Zsol2r   Zsyms1Zsyms2rT   rU   Znum2Zden2r   r2   Zrednr   r   r   remove_redundant_sols  s    	r   c           
      C   sD  t | dkrg S t | dkr8| d }ttd| |}|t|| | }| }|dks0|dkr2|S |d|  S t | dkr| \}}t |tt |t dkr^tt|| | }ntd}|tt|| |  }|dkru|S || | |d  S | dd \}}}	td}|d | ||	  || | |d |	   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
    r   r9   r   C1Nr#   )r:   r   r   Zdoitr;   r   )
Z	part_solsr*   r   Zy1r\   zZy2ur   Zy3r   r   r   get_gen_sol_from_part_sol  s,    ,r   Fc                    s  |   d d    d  d d  dd       d    dd   }| }fdd| D \}}	|j|	dd\}}	g }
|dkrb|
d	td
   n|j|	jvrw|
t	|t	| g t
|	}t| t| }}t||	}t|rt||stdt||	||}t||	|}|| t| }|D ]\}t||| d \}}fdd|  D \}}|jdkr|jdkrt||	|||\}}}|r|d	kr|dkr|
| qt|r||}|
|| |   qt tt|
D ]$}t|d	 t|
D ]}t|
| |
| }|dur>| q(qfdd|
D }  dd    d   |rgt||g} fdd|D }
|
S )z
    The main function that gives particular/general
    solutions to Riccati ODEs that have atleast 1
    rational particular solution.
    r   r"   r#   c                    r   r   r   r   r   r   r   r<   #  r}   z!solve_riccati.<locals>.<listcomp>TrJ   r   r9   r   zRational Solution doesn't existc                    r   r   r   r   r   r   r   r<   T  r}   Nc                    s   g | ]}| vr|qS r   r   r6   )remover   r   r<   p  s    c              
      s*   g | ]}t t| jd dqS r   )r   r!   r5   )r,   r   )r   r   r    fxr   r   r   r<   z  s   * )r   r   r   r5   ri   r   Zfree_symbolsunionrj   r   r   r1   keysvaluesrE   r:   rI   
ValueErrorrm   rv   r   r   Zis_nonnegative
is_integerr   Zxreplacesetrb   r   addr   )r   r   r)   r   r   Zgensolr*   Za_trC   rD   Zpresolrk   rH   rG   rl   ru   choicesr   r|   r   r   r   r   r   existsr\   rf   ZremZsolsr   )r   r   r    r   r   r   r   solve_riccati  s^   V







$r   r4   )F)8__doc__	itertoolsr   Z
sympy.corer   Zsympy.core.addr   Zsympy.core.numbersr   r   Zsympy.core.functionr   Zsympy.core.relationalr   Zsympy.core.symbolr	   r
   r   Zsympy.functionsr   r   Z$sympy.functions.elementary.complexesr   Zsympy.integrals.integralsr   Zsympy.polys.domainsr   Zsympy.polys.polytoolsr   Zsympy.polys.polyrootsr   Zsympy.solvers.solvesetr   r   r!   r+   r3   r$   rE   rI   rO   rP   rW   rg   rh   rm   rr   rs   rt   rv   ra   r   r   r   r   r   r   r   r   r   <module>   sN     H
4(N: