{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Développements limités\n",
    "\n",
    "Marc Lorenzi\n",
    "\n",
    "7 avril 2020"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "L'objectif de ce qui va suivre est de nous amuser avec des développements limités. Nous allons nous cantonner à des DLs __en 0__ à coefficients __rationnels__ pour les deux raisons suivantes :\n",
    "\n",
    "- La manipulation de DLs à coefficients quelconque demande de mettre en place des techniques de calcul formel qui dépasseraient largement le cadre de ce notebook. En prenant des coefficients rationnels on dispose déjà d'une large classe de développements limités sur lesquels on peut faire des calculs exacts. Python dispose d'une classe `Fraction` qui permet de calculer sur les nombres rationnels.\n",
    "\n",
    "- Faire des développements limités en un réel $a$ quelconque ne surcharge pas énormément le code, mais notre restriction sur les coefficients rationnels fait que cela ne serait pas forcément très intéressant. En effet, la plupart des fonctions usuelles ont des DLs avec des coefficients irrationnels si l'on se place ailleurs qu'en 0. Par exemple, le DL de $\\ln(1+x)$ au point 1 a un coefficient constant égal à $\\ln 2$.  "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous allons définir tout au long du notebook des opérations sur l'ensemble $\\mathcal D$ : \n",
    "\n",
    "- Addition\n",
    "- Multiplication par un rationnel.\n",
    "- Multiplication de deux DLs\n",
    "- Intégration, dérivation.\n",
    "- Composition $F\\circ G$ (dans certains cas)\n",
    "\n",
    "Nous nous demanderons quels sont les DLs inversibles pour la multiplication et la composition, et nous calculerons leur inverse pour ces opérations lorsque c'est possible."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : Nous chercherons dans ce qui suit à écrire un code qui s'exécute en un temps raisonnable, mais pas forcément le plus efficace possible. Pour améliorer la lisibilité j'ai parfois choisi d'écrire un code plus simple, mais moins rapide. Nous nous dispenserons de toute façon de faire des DLs à l'ordre 1000 ..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1. Préliminaires"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Importons la classe `Fraction` qui permet de manipuler des rationnels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from fractions import Fraction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Pour avoir un affichage sympathique, les quelques lignes qui suivent importent un \"convertisseur LaTeX\" et définissent une fonction qui transforme une fraction en chaîne de caractères représentant le code LaTeX de cette fraction. Voir plus loin pour les résultats."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display, Math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def frac_to_latex(x):\n",
    "    p = x.numerator\n",
    "    q = x.denominator\n",
    "    if q == 1: return str(p)\n",
    "    else:\n",
    "        return r'\\frac{%d}{%d}' % (p, q)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "frac_to_latex(Fraction(13, 17))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2. Une classe pour les développements limités"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voici la classe `DL`. Un objet de cette classe représente un DL en 0 à l'ordre $m$.\n",
    "\n",
    "$$F(x)=\\sum_{k<m} F_k x^k+O(x^m)$$\n",
    "\n",
    "Remarquons que, contrairement à l'habitude, nos DLs finiront par des __grands__ $O$.\n",
    "\n",
    "Un tel objet $F$ possède trois champs :\n",
    "\n",
    "- Un champ `ord` qui contient l'ordre $m$ du DL.\n",
    "- Un champ data qui contient la liste $[F_0,F_1,\\ldots,F_{m-1}]$. `F.data[k]` est le coefficient $F_k$ de degré $k$ du DL $F$.\n",
    "- Un champ `val` qui contient la valuation du DL, c'est à dire le plus petit entier $k<m$ tel que $F_k\\ne 0$. La valuation est calculée lors de la construction de l'objet. Si tous les coefficients sont nuls, la valuation est égale à l'ordre.\n",
    "\n",
    "Le constructeur prend en paramètre une liste de fractions et un paramètre optionnel `ord`. Si `ord` n'est pas donné, on le prend égal à la longueur de la liste moins 1.\n",
    "\n",
    "Tout à la fin de la classe, on trouve la définition des méthodes `__add__`, ..., `__rtruediv__`. Ces méthodes font appel à des fonctions pas encore écrites, `add`, `mul`, etc. Le but du notebook est précisément d'écrire ces fonctions ! Ces méthodes nous permettront d'écrire des expressions du genre `F + G`, ..., `F / G` lorsque $F$ et $G$ sont des DLs. \n",
    "\n",
    "__Remarque__ : Le code de la classe `DL` a l'air bien long. Lisez éventuellement la méthode `__init__` et la méthode `Valuation`. Le reste est sans importance (si j'ose dire)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DL:\n",
    "    \n",
    "    def __init__(self, data, ord=None):\n",
    "        if ord == None: self.ord = len(data)\n",
    "        else: self.ord = ord\n",
    "        self.data = data[: self.ord] # Tronquer si data est trop longue\n",
    "        for k in range(len(data), self.ord):\n",
    "            self.data.append(0)         # Rajouter des zéros si data est trop courte \n",
    "        for k in range(self.ord): self.data[k] = Fraction(self.data[k]) # Tout convertir en Fractions\n",
    "        self.val = self.valuation()      # Calculer la valuation du DL\n",
    "            \n",
    "    def valuation(self):\n",
    "        k = 0\n",
    "        while k < self.ord and self.data[k] == 0: k += 1\n",
    "        return k\n",
    "\n",
    "    def __str__(self):\n",
    "        s = ''\n",
    "        for k in range(self.ord):\n",
    "            ak = self.data[k]\n",
    "            if ak < 0: s += '%s*x^%d' % (ak, k)\n",
    "            elif ak > 0: s += '+%s*x^%d' % (ak, k)\n",
    "        s += '+O(x^%d)' % self.ord\n",
    "        return s\n",
    "    \n",
    "    def to_latex(self):\n",
    "        s = ''\n",
    "        for k in range(self.ord):\n",
    "            ak = self.data[k]\n",
    "            if ak < 0: s += '-' + frac_to_latex(-ak) + (r'x^{%d}' % k)\n",
    "            elif ak > 0: s += '+' + frac_to_latex(ak) + (r'x^{%d}' %  k)\n",
    "        s += '+O(x^{%d})' % self.ord\n",
    "        return s\n",
    "    \n",
    "    def copie(self):\n",
    "        return DL(self.data.copy(), self.ord)\n",
    "    \n",
    "    def __add__(self, g): \n",
    "        if type(g) == Fraction or type(g) == int: return add_scal(g, self)\n",
    "        else: return add(self, g)\n",
    "    def __radd__(self, g): return self + g\n",
    "    def __neg__(self): return oppose(self)\n",
    "    def __sub__(self, g): return self + (-g)\n",
    "    def __rsub__(self, g): return -(self - g)\n",
    "    def __mul__(self, g): \n",
    "        if type(g) == Fraction or type(g) == int: return mul_scal(g, self)\n",
    "        else: return mul(self, g)\n",
    "    def __rmul__(self, g): return self * g\n",
    "    def __pow__(self, n):\n",
    "        if type(n) == int: return pow(self, n)\n",
    "        elif type(n) == Fraction and self.data[0] == 1:\n",
    "            g = self.copie()\n",
    "            g.data[0] = Fraction(0)\n",
    "            g.val = g.valuation()\n",
    "            return comp(puiss(n, g.ord), g)\n",
    "        else:\n",
    "            raise Exception('puissance incorrecte')\n",
    "    def __truediv__(self, g):\n",
    "        if type(g) == Fraction or type(g) == int: return Fraction(1, g) * self\n",
    "        else: return quotient(self, g)\n",
    "    def __rtruediv__(self, g):\n",
    "        return inverse(self / g)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction `pprint` prend un DL en paramètre et elle l'affiche presque joliment. Il y aurait encore quelques améliorations à apporter (le cas des coefficients égaux à $\\pm 1$, une signe $+$ parasite en début de DL), mais cela nous suffira pour ce que nous voulons en faire."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pprint(F): display(Math(F.to_latex()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Voici un exemple de DL, affiché de façon \"standard\" avec `print` puis affiché avec `pprint`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "F = DL([Fraction(1, 2), -Fraction(3,4), Fraction(5, 6), 0, 7], 10)\n",
    "print(F)\n",
    "pprint(F)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Le second affichage est clairement préférable. Voici maintenant la fonction \"grand $O$\" :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def O(n): return DL([], n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(O(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Définissons aussi $X_{k,n}=x^k+O(x^n)$. Remarquons que si $k \\ge n$, $x^k+O(x^n)=O(x^n)$. Inutile de s'en soucier, le constructeur de la classe `DL` fait ce qu'il faut pour simplifier les choses."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def X(k, n):\n",
    "    s = k * [0]\n",
    "    s.append(1)\n",
    "    return DL(s, n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(X(3, 4))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(X(4, 4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3. Développements limités usuels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nous allons passer en revue les DLs usuels."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1 Exponentielle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Rappelons qu'un DL de l'exeponentielle à l'ordre $n$ en 0 est \n",
    "$$e^x=\\sum_{k=0}^{n-1} \\frac {x^k}{k!}+O(x^n)$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def exp(n):\n",
    "    s = n * [Fraction(1)]\n",
    "    for k in range(1, n):\n",
    "        s[k] = s[k - 1] / k\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(exp(6))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2 Logarithme"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On a\n",
    "\n",
    "$$\\ln(1+x)=\\sum_{k=1}^{n-1}\\frac{(-1)^{k-1}}{k}x^k+O(x^n)$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ln(n):\n",
    "    return DL([0] + [Fraction((-1) ** (k - 1), k) for k in range(1, n)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(ln(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3 Inverse"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On a\n",
    "\n",
    "$$\\frac 1 {1+x}=\\sum_{k=0}^{n-1}(-1)^{k}x^k+O(x^n)$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def inv(n):\n",
    "    return DL([(-1) ** k for k in range(n)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(inv(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.4 Puissances"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $a\\in\\mathbb Q$. On a\n",
    "\n",
    "$$(1+x)^a=\\sum_{k=0}^{n-1}\\binom a k x^k+O(x^n)$$\n",
    "\n",
    "où \n",
    "\n",
    "$$\\binom a k =\\frac{a(a-1)\\ldots(a-k+1)}{k!}$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def puiss(a, n):\n",
    "    s = n * [Fraction(1)]\n",
    "    for k in range(1, n):\n",
    "        s[k] = s[k - 1] * (a - k + 1) / k\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss(6, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On peut aussi prendre un exposant fractionnaire. Voici $\\sqrt{1+x}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss(Fraction(1, 2), 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ou aussi un exposant négatif. Voici $\\frac 1{(1+x)^{1/3}}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss(Fraction(-1, 3), 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.5 sinus, cosinus, arc tangente"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On a \n",
    "\n",
    "$$\\sin x=\\sum_{k<m} \\frac {(-1)^k}{(2k+1)!}x^{2k+1}+o(x^n)$$\n",
    "\n",
    "où $m$ est le plus petit entier tel que $2m+1\\ge n$. On a donc\n",
    "\n",
    "$$2m-1 < n \\le 2m+1$$\n",
    "\n",
    "ou encore\n",
    "\n",
    "$$m-1<\\frac{n-1}2\\le m$$\n",
    "\n",
    "et ainsi,\n",
    "\n",
    "$$m=\\left\\lceil\\frac {n-1} 2\\right\\rceil=-\\left\\lfloor\\frac {1-n} 2\\right\\rfloor$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sin(n):\n",
    "    s = n * [Fraction(0)]\n",
    "    if n > 1: s[1] = Fraction(1)\n",
    "    m = -((1 - n) // 2)\n",
    "    for k in range(1, m):\n",
    "        s[2 * k + 1] = -s[2 * k - 1] / (2 * k * (2 * k + 1))\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin(9))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On a \n",
    "\n",
    "$$\\cos x=\\sum_{k<m} \\frac {(-1)^k}{(2k)!}x^{2k}+O(x^n)$$\n",
    "\n",
    "où $m$ est le plus petit entier tel que $2m\\ge n$. On a donc\n",
    "\n",
    "$$2m-2< n \\le 2m$$\n",
    "\n",
    "ou encore\n",
    "\n",
    "$$m-1<\\frac n 2\\le m$$\n",
    "\n",
    "et ainsi,\n",
    "\n",
    "$$m=\\left\\lceil\\frac {n} 2\\right\\rceil=-\\left\\lfloor-\\frac {n} 2\\right\\rfloor$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def cos(n):\n",
    "    s = n * [Fraction(0)]\n",
    "    if n > 0: s[0] = Fraction(1)\n",
    "    for k in range(1, -((-n) // 2)):\n",
    "        s[2 * k] = -s[2 * k - 2] / ((2 * k - 1) * 2 * k)\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(cos(9))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On a \n",
    "\n",
    "$$\\arctan x=\\sum_{k<m} \\frac {(-1)^k}{2k+1}x^{2k+1}+O(x^n)$$\n",
    "\n",
    "où $m$ est le plus petit entier tel que $2m+1>n$. On a donc\n",
    "\n",
    "$$2m\\le 2m+1 \\le n + 1 < 2m+2$$\n",
    "\n",
    "On est donc dans le même cadre que pour la fonction sinus :\n",
    "\n",
    "$$m=-\\left\\lfloor\\frac {1-n} 2\\right\\rfloor$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def arctan(n):\n",
    "    s = n * [Fraction(0)]\n",
    "    m = -((1 - n) // 2)\n",
    "    for k in range(m):\n",
    "        s[2 * k + 1] = Fraction((-1) ** k, 2 * k + 1)\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(arctan(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.6 sinus, cosinus et argument tangente hyperboliques"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Même chose en enlevant les puissances de $-1$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sh(n):\n",
    "    s = n * [Fraction(0)]\n",
    "    if n > 1: s[1] = Fraction(1)\n",
    "    m = -((1 - n) // 2)\n",
    "    for k in range(1, m):\n",
    "        s[2 * k + 1] = s[2 * k - 1] / (2 * k * (2 * k + 1))\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sh(10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ch(n):\n",
    "    s = n * [Fraction(0)]\n",
    "    if n > 0: s[0] = Fraction(1)\n",
    "    for k in range(1, -((-n) // 2)):\n",
    "        s[2 * k] = s[2 * k - 2] / ((2 * k - 1) * 2 * k)\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def ch(n):\n",
    "    s = (n + 1) * [Fraction(0)]\n",
    "    s[0] = Fraction(1)\n",
    "    for k in range(1, n // 2 + 1):\n",
    "        s[2 * k] = s[2 * k - 2] / ((2 * k - 1) * 2 * k)\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(ch(6))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def argth(n):\n",
    "    s = n * [Fraction(0)]\n",
    "    m = -((1 - n) // 2)\n",
    "    for k in range(m):\n",
    "        s[2 * k + 1] = Fraction(1, 2 * k + 1)\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(argth(15))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Après cette longue énumération, passons aux opérations sur les développements limités."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4. Opérations sur les développements limités"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1 Ordre et valuation d'un DL"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Définition__ : Soit $F(x)=P(x)+O(x^m)$. L'ordre de $F$ est $\\omega(F)=m$.\n",
    "\n",
    "Lorsque nous effectuerons des opérations sur des DLS (somme, produit, etc.), nous nous apercevrons que l'ordre du résultat dépend bien sur des ordres des deux DLs mais aussi, la plupart du temps, d'une autre quantité qui est leur __valuation__.\n",
    "\n",
    "__Définition__ Soit $F(x)=\\sum_{k<m}P_kx^k+o(X^m)$.\n",
    "- Si au moins l'un des $P_k$ est non nul, on appelle valuation de $F$, et on note $\\nu(F)$ le plus petit entier $k<m$ tel que $P_k\\ne 0$.\n",
    "- Si tous les $P_k$ sont nuls, on pose $\\nu(F)=m$.\n",
    "\n",
    "Remarquons que $\\nu(F)\\le \\omega(F)$, et que l'on a égalité si et seulement si $F(x)=O(x^{m})$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1 Somme"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Définition__ : Soient $F(x)=P(x)+O(x^m)$ et $G(x)=Q(x)+O(x^n)$ deux DLs. On a\n",
    "\n",
    "$$(F+G)(x)=P(x)+Q(x)+O(x^r)$$\n",
    "\n",
    "où \n",
    "\n",
    "$$r=\\min(m,n)$$\n",
    "\n",
    "__Proposition__ : Soient $F,G$ deux DLs. On a \n",
    "\n",
    "- $\\nu(F+G)\\ge\\min(\\nu(F),\\nu(G))$. Si $\\nu(F)\\ne\\nu(G)$ on a même égalité.\n",
    "- $\\omega(F+G)=\\min(\\omega(F),\\omega(G))$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add(F, G):\n",
    "    r = min(F.ord, G.ord)\n",
    "    s = r * [0]\n",
    "    for k in range(r):\n",
    "        s[k] = F.data[k] + G.data[k]\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La ligne magique ci-dessous fonctionne parce que nous avons inclus dans la classe `DL` une méthode `__add__` qui appelle la fonction `add` que nous venons d'écrire."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin(5) + cos(7))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La fonction bien utile `add_scal` ajoute le rationnel $t$ au DL $F$. Si $F$ est un DL à l'ordre $m$, elle renvoie la somme des deux DLs $(t+O(x^m))$ et $F(x)$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_scal(t, F):\n",
    "    return add(DL([t], F.ord), F)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La méthode `__add__` est suffisamment bien pensée pour nous permettre d'écrire :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(5 + cos(4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 Produit par un réel"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soient $F(x)=P(x)+O(x^m)$ et $t\\in\\mathbb Q$. Nous posons $(tF)(f)=tP(x)+O(x^m)$. Remarquons le résultat peu standard lorsque $t=0$ : $(0F)(x)=O(x^m)$, et pas 0. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mul_scal(t, F):\n",
    "    n = F.ord\n",
    "    s = n * [0]\n",
    "    for k in range(n):\n",
    "        s[k] = t * F.data[k]\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(3 * cos(7))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(0 * exp(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il est maintenant évident de faire des soustractions et, plus généralement, des combinaisons linéaires."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def oppose(F): return mul_scal(-1, F)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(-sin(5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sub(F, G):\n",
    "    return add(F, oppose(G))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(2 * sin(5) - 3 * cos(7))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(cos(5) - cos(5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Remarquons que maintenant nous pouvons presque écrire nos DLs de façon naturelle :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sum([Fraction(1, k ** 2) * X(k, 10) for k in range(1, 11)]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Question__ : L'ensemble $\\mathcal D$ des DLs est-il un espace vectoriel ? La réponse est non. En revanche, si nous appelons $\\mathcal D_n$ l'ensemble des DLs d'ordre $n$, alors $\\mathcal D_n$ est un espace vectoriel de dimension $n+1$. Remarquons que son vecteur nul n'est pas 0, mais $O(x^n)$. $\\mathcal D=\\bigcup_{n\\in\\mathbb N} \\mathcal D_n$ est donc une union d'espaces vectoriels disjoints ayant des neutres tous différents, et on a\n",
    "\n",
    "$$\\forall m\\le n, \\mathcal D_m+\\mathcal D_n=\\mathcal D_m$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3 Produit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Définition__ Soient $F$ et $G$ deux développements limités. On a\n",
    "\n",
    "$$(FG)(x)=P(x)Q(x)+O(x^r)$$\n",
    "\n",
    "où $r=\\min(\\omega(F)+\\nu(G),\\omega(G)+\\nu(F))$.\n",
    "\n",
    "__Proposition__ : Soient $F, G$ deux DLs. On a\n",
    "\n",
    "- $\\nu(FG)=\\nu(F)+\\nu(G)$.\n",
    "- $\\omega(FG)=\\min(\\omega(F)+\\nu(G),\\omega(G)+\\nu(F))$.\n",
    "\n",
    "La fonction `mul` effectuant le produit de deux DLs ne présente pas de difficulté particulière."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def mul(F, G):\n",
    "    m, n = F.ord, G.ord\n",
    "    mu, nu = F.val, G.val\n",
    "    r = min(m + nu, n + mu)\n",
    "    s = r * [0]\n",
    "    for i in range(m):\n",
    "        for j in range(min(n, r - i)):\n",
    "            s[i + j] += F.data[i] * G.data[j]\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(cos(6) * exp(8))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(O(4) * O(5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(exp(5) * 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Exercice__ : Quel est un équivalent en 0 de $(2+\\cos x)(2 +{\\rm ch\\ } x) - 9$ ? Facile !"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint((2 + cos(9))*(2 + ch(9)) - 9)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.3 Puissances"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Proposition__ : Soit $F\\in\\mathcal D$. Soit $n\\in\\mathbb N^*$. On a\n",
    "- $\\nu(F^n)=n\\nu(F)$.\n",
    "- $\\omega(F^n)=\\omega(F)+(n-1)\\nu(F)$.\n",
    "\n",
    "__Démonstration__ : Il suffit de faire une récurrence sur $n$. Pour $n=1$ c'est évident. Supposons les égalités vraie pour un certain $n\\ge 1$. On a alors\n",
    "\n",
    "- $\\nu(F^{n+1})=\\nu(F^nF)=\\nu(F^n)+\\nu(F)=n\\nu(F)+\\nu(F)=(n+1)\\nu(F)$.\n",
    "- $\\omega(F^{n+1})=\\omega(F^nF)=\\min(\\omega(F^n)+\\nu(F),\\omega(F)+\\nu(F^n))=\\min(\\omega(F)+n\\nu(F),\\omega(F)+n\\nu(F))=\\omega(F)+n\\nu(F)$.\n",
    "\n",
    "__Remarque__ : Que vaut $F^0$ ? 1 me direz-vous. Mais 1 n'est pas un DL. Alors $1+O(x^n)$ ? Mais pour quel $n$ ? Voilà une grande question. Décidons que nous voulons que la formule donnant l'ordre fonctionne pour $n=0$. On a donc $\\omega(F^0)=\\omega(F)-\\nu(F)$. Nous poserons donc\n",
    "\n",
    "$$F^0=1+O(x^{\\omega(F)-\\nu(F)})$$\n",
    "\n",
    "Ce choix est peut-être discutable, mais nous allons nous y tenir."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pow(F, n):\n",
    "    if n < 0: return inverse(pow(F, -n))\n",
    "    elif n == 0: return 1 + O(F.ord - F.val)\n",
    "    else:\n",
    "        P = X(0, F.ord)\n",
    "        for k in range(n): P = P * F\n",
    "        return P"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Testons avec $\\sin x$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin(6) ** 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin(6) ** 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin(6) ** 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dernier petit test, $\\sin^7x\\sin^4x-\\sin^{11}x$ devrait faire 0, à un $O$ près."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin(10) ** 4 * sin(10) ** 7 - sin(10) ** 11)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Remarque__ : Pour l'instant seules les puissances entières positives sont acceptées. Lorsque nous saurons inverser un DL nous pourrons également élever à une puissance négative. Lorsque nous saurons composer, nous pourrons sous certaines conditions) mettre une puissance fractionnaire."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.4 Composée"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soient $F,G\\in\\mathcal D$. Posons $F(x)=\\sum_{k<m}F_kx^k+O(x^m)$. Supposons de plus que $\\nu(G)\\ge 1$. On a alors\n",
    "\n",
    "$$(F\\circ G)(x)=\\sum_{k<m}F_kG(x)^k+O(G(x)^m)$$\n",
    "\n",
    "__Proposition__ :  Posons $\\mu=\\nu(F-F_0)$. On a $\\omega(F\\circ G)=\\min(\\omega(F)\\nu(G), \\omega(G)+(\\mu-1)\\nu(G))$.\n",
    "\n",
    "__Remarque__ : Dans le cas fréquent où $\\mu=1=\\nu(G)$, on obtient $\\omega(F\\circ G)=\\min(\\omega(F),\\omega(G))$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def comp(F, G):\n",
    "    if G.val == 0:\n",
    "        raise Exception('comp : composition impossible')\n",
    "    mu = (F - F.data[0]).val\n",
    "    r = min(F.ord * G.val, G.ord + (mu - 1) * G.val)\n",
    "    H = O(r)\n",
    "    Gk = X(0, G.ord)\n",
    "    for k in range(F.ord):\n",
    "        if F.data[k] != 0:\n",
    "            H = H + F.data[k] * Gk\n",
    "        if k < F.ord: Gk = Gk * G\n",
    "    return H"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Composons des DLs à l'ordre 20 de $e^x$ et $\\ln(1+x)$. On obtient un résultat rassurant."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(comp(exp(20), ln(20)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Maintenant nous pouvons élever un DL $F$ à une puissance $a\\in\\mathbb Q$, pourvu que $F_0=1$. En effet, dans ce cas, $F=1+G$ où $\\nu(G)\\ge 1$. On peut donc calculer $R\\circ G$ où $R=\\sqrt{1+X}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "F = cos(20) ** Fraction(1, 2)\n",
    "pprint(F)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vérifions. On devrait avoir $F(x)^2-\\cos x=0$ à $O(x^{20})$ près."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(F ** 2 - cos(20))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.5 Inverse, quotient"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__Proposition__ : Soit $F\\in\\mathcal D$ tel que $\\nu(F)=0$. Il existe $K\\in\\mathcal D$ tel que $FK=o(X^{\\omega(F)})$.\n",
    "\n",
    "__Démonstration__ : $\\nu(F)=0$ signifie que $F_0\\ne 0$. En posant $H=\\frac F{F_0}-1$, on obtient\n",
    "\n",
    "$$F = F_0(1+H)$$\n",
    "\n",
    "où $\\nu(H)\\ge 1$ et $\\omega(H)=n=\\omega(F)$. Soit $G(x)=1-x+x^2+\\ldots+(-1)^nx^n+O(x^n)$. On a alors \n",
    "\n",
    "$$(1 + H(x))(G\\circ H)(x)=1+O(x^n)$$\n",
    "\n",
    "et donc $F(x)K(x)=O(x^n)$ où\n",
    "\n",
    "$$K(x)=\\frac 1 {F_0}(G\\circ H)(x)$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def inverse(F):\n",
    "    G = inv(F.ord)\n",
    "    F0 = F.data[0]\n",
    "    if F0 == 0: raise Exception('inverse: calcul impossible')\n",
    "    H = (1 / F0) * F - 1\n",
    "    return (1 / F0) * comp(G, H)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(1 / cos(10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint((1 + sh(10)) * (1 / (1 + sh(10))))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On peut maintenant écrire des puissances négatives."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(cos(10) ** (-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Faire un quotient est bien entendu immédiat."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def quotient(f, g):\n",
    "    return mul(f, inverse(g))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quel est le DL à l'ordre 13 en 0 de la fonction tangente ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def tan(n):\n",
    "    return sin(n) / cos(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(tan(13))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vérification : calculons $\\tan x\\cos x-\\sin x$, à l'ordre 20. Cela devrait donner $O(x^{20})$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(tan(20) * cos(20) - sin(20))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quel est un équivalent en 0 de $\\sin(\\tan x) -\\tan(\\sin x)$ ?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(comp(sin(8), tan(8)) - comp(tan(8), sin(8)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.6 Primitives"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $F(x)=\\sum_{k<n}F_kx^k+O(x^n)$. On pose\n",
    "\n",
    "$$\\int_0^x F=\\sum_{k<n}\\frac {F_k}{k+1}x^{k+1}+O(x^{n+1})$$\n",
    "\n",
    "La primitive de $F$ qui vaut $c\\in\\mathbb Q$ en 0 est alors $c+\\int_0^x F$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def primitive(F, c):\n",
    "    n = F.ord\n",
    "    s = (n + 1) * [0]\n",
    "    for k in range(n):\n",
    "        s[k + 1] = F.data[k] / (k + 1)\n",
    "    s[0] = c\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Par exemple, si on intègre un DL de $\\frac 1 {x^2+1}$, on obtient un DL de la fonction arc tangente."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(primitive(1 / (1 + X(2, 10)), 0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "pour calculer un DL à l'ordre $n$ de la fonction $\\arcsin$, il suffit de calculer un DL de $\\frac 1 {\\sqrt{1-x^2}}$ à l'ordre $n-1$, puis d'intégrer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def arcsin(n):\n",
    "    if n == 0: return o(0)\n",
    "    else:\n",
    "        F = - X(2, n - 1)\n",
    "        G = comp(puiss(Fraction(-1, 2), n - 1), F)\n",
    "        return primitive(G, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(arcsin(20))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vérifions tout cela en calculant un DL de $\\sin\\circ\\arcsin$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(comp(sin(20), arcsin(20)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "pour calculer un DL à l'ordre $n$ de la fonction argsh, il suffit de calculer un DL de $\\frac 1 {\\sqrt{1+x^2}}$ à l'ordre $n-1$, puis d'intégrer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def argsh(n):\n",
    "    if n == 0: return o(0)\n",
    "    else:\n",
    "        F = -X(2, n - 1)\n",
    "        G = comp(puiss(Fraction(-1, 2), n - 1), F)\n",
    "        return primitive(G, 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(argsh(20))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5. retour aux DLs usuels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.1 L'exponentielle"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dans le paragraphe sur les développements usuels nous avons _parachuté_ le DL à l'ordre $n$ de $f(x)=\\exp x$. Serait-il possible, à ce stade du notebook, de le faire _calculer_ par Python ? La réponse est oui. Tout tient en deux faits :\n",
    "1. $f'=f$\n",
    "2. $f(0)=1$.\n",
    "\n",
    "Deux c'est encore trop. On peut compacter cela en une seule propriété :\n",
    "\n",
    "$$f(x) = 1 +\\int_0^x f(t)\\,dt$$\n",
    "\n",
    "__Exercice__ : Montrer que 1. et 2. équivalent à la ligne ci-dessus.\n",
    "\n",
    "Ainsi, en intégrant un DL de l'exponentielle et en ajoutant une constante convenable (1 en l'occurrence), on obtient à nouveau un DL de l'exponentielle, à un ordre 1 de plus. Notons $DL_n(\\exp)$ le DL de l'exponentielle à l'ordre $n$. Nous avons donc\n",
    "\n",
    "$$DL_n(\\exp)(x)=1+\\int_0^x DL_{n-1}(\\exp)(t)\\,dt$$\n",
    "\n",
    "Or nous savons que $DL_0(\\exp)(x)=1+o(1)$. En intgérant $n$ fois cette égalité on obtient le DL à l'ordre $n$. Voici notre nouvelle fonction $\\exp$ qui __calcule__ le DL de l'exponentielle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def exp2(n):\n",
    "    f = X(0, 0)\n",
    "    for k in range(n):\n",
    "        f = 1 + primitive(f, 0)\n",
    "    return f"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(exp2(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.2 Sinus et cosinus"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "On peut faire de même pour sinus et cosinus en remarquant que\n",
    "\n",
    "$$\\left\\lbrace\\begin{array}{ccc}\n",
    "\\sin x &=&0 + \\int_0^x\\cos t\\, dt\\\\\n",
    "\\cos x &=&1-\\int_0^x\\sin t\\, dt\\\\\n",
    "\\end{array}\\right.$$\n",
    "\n",
    "Sachant qu'un DL à l'ordre 0 de $\\sin x$ et $\\cos x$ est $\\sin x =o(1)$ et $\\cos x=1+o(1)$, $n$ intégrations successives nous fourniront les DLs à l'ordre $n$. Voici la fonction `sincos` qui fait ce travail."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sincos(n):\n",
    "    fsin = O(0)\n",
    "    fcos = X(0, 0)\n",
    "    for k in range(n):\n",
    "        fsin, fcos = primitive(fcos, 0), 1 - primitive(fsin, 0)\n",
    "    return fsin, fcos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Et voici donc nos DLs de $\\sin$ et $\\cos$ _calculés_ et pas _parachutés_."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def sin2(n): return sincos(n)[0]\n",
    "def cos2(n): return sincos(n)[1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(sin2(10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(cos2(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.3 Puissances"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $a\\in \\mathbb Q$. Notons $f_a(x)=(1+x)^a$. On a \n",
    "\n",
    "$$(1+x)^a=1+a\\int_0^x(1+x)^{a-1}\\,dx$$\n",
    "\n",
    "On peut donc ici obtenir un DL à l'ordre $n$ de $f_a$ en intégrant un DL à l'ordre $n-1$ de $f_{a-1}$. La situation est un peu plus compliquée que dans le cas de l'exponentielle, du sinus et du cosinus, et une fonction récursive se prête bien à la résolution du problème :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def puiss2(a, n):\n",
    "    if n == 0: return X(0, 0)\n",
    "    else:\n",
    "        return 1 + a * primitive(puiss2(a - 1, n - 1), 0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss2(-1, 10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss2(6, 10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss2(Fraction(1, 2), 10))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(puiss(Fraction(1, 2), 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5.4 Les autres DLs usuels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Que reste-t-il à examiner ? Les fonctions hyperboliques (exercice), l'arc sinus, le logarithme et l'arc tangente. Mais ces fonctions s'obtiennent en primitivant $\\frac 1 {\\sqrt{1-x^2}}$, $\\frac 1 {1+x}$ et $\\frac 1 {1+x^2}$. Il est donc immédiat de faire _calculer_ leur DL par Python.\n",
    "\n",
    "En conclusion, nous avons maintenant suffisamment de fonctions sur les DLs pour permettre à Python de _calculer_ tous les DLs usuels en utilisant des propriétés caractéristiques des fonctions usuelles."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6. Réciproque d'un développement limité"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.1 Dérivée d'un DL"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit $f$ une fonction ayant un DL à l'ordre $n\\ge 1$ en $0$, dérivable au voisinage de 0 et telle que $f'$ a un DL à l'ordre $n-1$ en 0. Alors on obtient un DL de $f'$ à l'ordre $n-1$ en dérivant formellement le DL de $f$. Formellement,\n",
    "\n",
    "$$\\left(\\sum_{k<n}f_k x^k+O(x^n)\\right)'=\\sum_{k<n-1}(k+1)f_{k+1}x^k+O(x^{n-1})$$\n",
    "\n",
    "Le cas $n=1$ est peu intéressant mais pas faux. Il nous dit que si $f$ est dérivable au voisinage de 0 et si $f'$ est bornée au voisinage de 0, alors $(f_0+O(x))'=O(1)$, ce qui est vrai."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def derivee(f):\n",
    "    n = f.ord\n",
    "    if n == 0:\n",
    "        raise Exception('derivee: calcul impossible')\n",
    "    s = (n - 1)  * [0]\n",
    "    for k in range(n - 1):\n",
    "        s[k] = (k + 1) * f.data[k + 1]\n",
    "    return DL(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Par exemple, en dérivant le DL de $\\sin x$ à l'ordre 8, on obtient le DL de $\\cos x$ à l'ordre 7."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(derivee(sin(8)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.2 Résolution d'une équation"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Étant donnés deux DLs $f$ et $h$, peut-on trouver un DL $g$ tel que $g\\circ f=h$ ?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Soit \n",
    "\n",
    "$$f(x)=\\sum_{k<n}f_kx^k+O(x^n)$$\n",
    "\n",
    "un DL à l'ordre $n\\ge 2$. On suppose que $f_0=0$ et $f_1\\ne 0$. Soit\n",
    "\n",
    "$$h(x)=\\sum_{k<m}h_kx^k+O(x^m)$$\n",
    "\n",
    "un DL à l'ordre $m\\ge 0$. Nous allons déterminer un DL $g$, à un ordre aussi grand que possible, tel que $g\\circ f = h$. Quel ordre peut-on espérer ? Eh bien nous allons voir par récurrence sur $m$ que c'est $\\min(m,n+\\nu(h'))$.\n",
    "\n",
    "- Commençons par le cas évident où $m=0$. On cherche $g(x)=O(1)$ tel que $O(1)=O(1)$. Il n'y a rien à faire.\n",
    "\n",
    "- Soit maintenant $m\\ge 1$ et supposons que nous ayons résolu le problème pour tous les ordres strictement inférieurs à $m$. Le DL $g$ convient si et seulement si $g_0=h_0$ et $(g\\circ f)'=h'$, ou encore $f'\\times g'\\circ f=h'$. Comme on a supposé $f_1=f'_0\\ne 0$, le DL $f'$ est inversible pour la multiplication. Ainsi, $g$ convient si et seulement si $g_0=h_0$ et\n",
    "$$g'\\circ f = \\frac{h'}{f'}$$\n",
    "\n",
    "Or $h_1=\\frac {h'}{f'}$ est un DL d'ordre $m_1=\\min(m-1,n-1+\\nu(h'))$, donc d'ordre strictement inférieur à $m$. Par l'hypothèse de récurrence il existe un DL $g_1$ d'ordre $m_1$ tel que $g_1\\circ f = h_1$. On en déduit par une primitivation un DL $g$ d'ordre $m_1+1=\\min(m,n+\\nu(h'))$ tel que $g\\circ f=h$."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Il y a donc existence et unicité de $g$. La fonction `solve_comp` qui calcule $g$ à partir de $f$ et $h$ est facile à écrire récursivement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def solve_comp(f, h):\n",
    "    if h.ord == 0: return O(0)\n",
    "    else:\n",
    "        h1 = quotient(derivee(h), derivee(f))\n",
    "        g1 = solve_comp(f, h1)\n",
    "        return primitive(g1, h.data[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Prenons un exemple. Cherchons $g$ tel que $g(\\sin x)=\\cos x$. L'avantage est que l'on connaît la réponse : $g(x)=\\sqrt{1-x^2}$.\n",
    "\n",
    "Résolvons ..."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(solve_comp(sin(10), cos(10)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Puis vérifions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint((1 - X(2, 10)) ** Fraction(1, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.3 Réciproque d'un DL"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Calculer la réciproque du DL $f$ à l'ordre $n$ consiste à prendre $h(x)=x+o(x^n)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def reciproque(f):\n",
    "    return solve_comp(f, X(1, f.ord))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Retrouvons un DL de $\\arcsin x$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(reciproque(sin(15)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vérifions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(arcsin(15))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Terminons par une fonction dont on ne connaît pas explicitement la réciproque. Prenons $f(x)=xe^x$. On a $f(0)=0$, $f'(0)=1\\ne 0$ et $f$ est donc une bijection d'un voisinage de 0 sur un voisinage de 0."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "f = X(1, 10) * exp(9)\n",
    "pprint(f)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "g = reciproque(f)\n",
    "pprint(g)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Vérification :"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(comp(f, g))\n",
    "pprint(comp(g, f))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6.4 Quelques remarques sur les réciproques des DLs usuels"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Que nous apporte le calcul de réciproques en ce qui concerne les fonctions usuelles ? En fait, rien. Passons-les en revue :\n",
    "\n",
    "- $\\sin$, $\\tan$, circulaires et hyperboliques : nous disposons déjà des DLs de leurs réciproques.\n",
    "- $\\cos$ : la fonction $\\arccos$ n'est pas dérivable en 0, elle n'y possède donc pas de DL (sauf à l'ordre 0).\n",
    "- $\\exp$, ou plus exactement $e^x-1$, pour que la fonction s'annule en 0. On a\n",
    "\n",
    "$$y=e^x-1\\Leftrightarrow x = \\ln(y + 1)$$\n",
    "\n",
    "et donc la réciproque de $e^x-1$ est $\\ln(1+x)$, dont on possède déjà un DL.\n",
    "\n",
    "- $(1+x)^a-1$. Cette fois,\n",
    "\n",
    "$$y = (1 + x) ^a - 1 \\Leftrightarrow x = (1 + y)^{\\frac 1 a} - 1$$\n",
    "\n",
    "et donc la réciproque de $(1+x)^a-1$ est $(1+x)^{\\frac 1 a}-1$."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
