{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "541f3ecd",
   "metadata": {},
   "outputs": [],
   "source": [
    "# DAOUST Alexandre 242015"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c68d497b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "018d8af6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fct(x, nargout=2):\n",
    "    x1, x2 = x\n",
    "    \n",
    "    f = 2*x1**3 + 3*x2**3 + 2*x1**2 + 4*x2**2 + x1*x2 - 3*x1 - 2*x2\n",
    "    g = np.array([6*x1**2 + 4*x1 + x2 - 3,\n",
    "                  6*x2**2 + 8*x2 + x1 - 2])\n",
    "    if nargout==3:\n",
    "        H = np.array([[12*x1 + 4,     1    ],\n",
    "                      [    1,     12*x2 + 8]])\n",
    "        return f, g, H\n",
    "    return f, g"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "4ab88c2f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "f = 19.0000\n",
      "g = |28.0000|\n",
      "    |-2.0000|\n",
      "H = |28.0000  1.0000|\n",
      "    |1.0000  -4.0000|\n",
      "\n",
      "f = 0.0000\n",
      "g = |-3.0000|\n",
      "    |-2.0000|\n",
      "H = |4.0000  1.0000|\n",
      "    |1.0000  8.0000|\n"
     ]
    }
   ],
   "source": [
    "# Test de f en 2,-1\n",
    "x = np.array([2.0, -1.0])\n",
    "f, g, H = fct(x, nargout=3)\n",
    "print(f\"f = {f:.4f}\")\n",
    "print(f\"g = |{g[0]:.4f}|\\n    |{g[1]:.4f}|\")\n",
    "print(f\"H = |{H[0][0]:.4f}  {H[0][1]:.4f}|\\n    |{H[1][0]:.4f}  {H[1][1]:.4f}|\")\n",
    "print()\n",
    "\n",
    "# Test de f en 0,0\n",
    "x = np.array([0.0, 0.0])\n",
    "f, g, H = fct(x, nargout=3)\n",
    "print(f\"f = {f:.4f}\")\n",
    "print(f\"g = |{g[0]:.4f}|\\n    |{g[1]:.4f}|\")\n",
    "print(f\"H = |{H[0][0]:.4f}  {H[0][1]:.4f}|\\n    |{H[1][0]:.4f}  {H[1][1]:.4f}|\")\n",
    "# On a bien le bon résultat"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "958a11f2",
   "metadata": {},
   "outputs": [],
   "source": [
    "def CoordinateDescent(x0, maxiter=10):\n",
    "    # Formules calculées à la main selon x1 et x2 respectivement\n",
    "    # Elles correspondent à f(xi) = Somme(coeff*xi^n) + C (C constante reprennant le(s) xj!=xi actuel(s) et\n",
    "    # les autres constantes) qu'on dérive et qu'on optimise.\n",
    "    x = x0.copy()\n",
    "    n = len(x)\n",
    "    \n",
    "    for i in range(maxiter):\n",
    "        # Mise à jour de x1\n",
    "        delta = 4**2-4*6*(x[1]-3)\n",
    "        x[0] = (-4 + np.sqrt(delta))/12\n",
    "        \n",
    "        #Mise à jour de x2\n",
    "        delta = 8**2 - 4*6*(x[0]-2)\n",
    "        x[1] = (-8 + np.sqrt(delta))/12\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0b6c0173",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x* = |0.4297|\n",
      "     |0.1737|\n",
      "f = -0.8975\n",
      "g = |-0.0000|\n",
      "    |-0.0000|\n",
      "H = |9.1560  1.0000|\n",
      "    |1.0000  10.0840|\n",
      "Valeurs propres:\n",
      "    8.5176\n",
      "    10.7224\n"
     ]
    }
   ],
   "source": [
    "# Test de CoordinateDescent\n",
    "\n",
    "# On trouve bien un minimum car la gradient est nul (à une tolérance près) et la matrice hessienne est définie positive\n",
    "# (on augmente f si on se déplace du point)\n",
    "\n",
    "x = np.array([2.0, -1.0])\n",
    "x_opt = CoordinateDescent(x)\n",
    "print(f\"x* = |{x_opt[0]:.4f}|\\n     |{x_opt[1]:.4f}|\")\n",
    "f, g, H = fct(x_opt, nargout=3)\n",
    "print(f\"f = {f:.4f}\")\n",
    "print(f\"g = |{g[0]:.4f}|\\n    |{g[1]:.4f}|\")\n",
    "print(f\"H = |{H[0][0]:.4f}  {H[0][1]:.4f}|\\n    |{H[1][0]:.4f}  {H[1][1]:.4f}|\")\n",
    "eigval, eigvec = np.linalg.eig(H)\n",
    "print(f\"Valeurs propres:\\n    {eigval[0]:.4f}\\n    {eigval[1]:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "230d6b8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def w1(fct, x, d, alpha, beta1):\n",
    "    f0, g0 = fct(x)\n",
    "    f1, g1 = fct(x + alpha*d)\n",
    "    return f1 <= f0 + alpha*beta1*(g0@d)\n",
    "\n",
    "def w2(fct, x, d, alpha, beta2):\n",
    "    f0, g0 = fct(x)\n",
    "    f1, g1 = fct(x + alpha*d)\n",
    "    return g1@d >= beta2*(g0@d)\n",
    "\n",
    "def wolfebissection(fct, x, d, alpha=1, beta1=0.0001, beta2=0.9):\n",
    "    alphal = 0\n",
    "    alphar = np.inf\n",
    "    \n",
    "    wf1 = False\n",
    "    wf2 = False\n",
    "    \n",
    "    # k pour ne pas boucler à l'infini\n",
    "    k = 0\n",
    "    \n",
    "    while not (wf1 and wf2) and k < 1e4:\n",
    "        wf1 = w1(fct, x, d, alpha, beta1)\n",
    "        wf2 = w2(fct, x, d, alpha, beta2)\n",
    "        \n",
    "        if not wf1:\n",
    "            alphar = alpha\n",
    "            alpha = (alphal + alphar)/2\n",
    "            \n",
    "        elif not wf2:\n",
    "            alphal = alpha\n",
    "            if alphar < np.inf:\n",
    "                alpha = (alphal + alphar)/2\n",
    "            else:\n",
    "                alpha *= 2\n",
    "        \n",
    "        k += 1\n",
    "            \n",
    "    return alpha"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "14f1cafd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "alpha = 0.1250\n"
     ]
    }
   ],
   "source": [
    "# Test de wolfebissection\n",
    "x = np.array([0.0, 0.0])\n",
    "d = np.array([3.0, 2.0])\n",
    "alpha = wolfebissection(fct, x, d)\n",
    "print(f\"alpha = {alpha:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "2e390c95",
   "metadata": {},
   "outputs": [],
   "source": [
    "def MethGradient(x0, maxiter=10):\n",
    "    x = x0.copy()\n",
    "    \n",
    "    for k in range(maxiter):\n",
    "        f, g = fct(x)\n",
    "        d = -g\n",
    "        alpha = wolfebissection(fct, x, d)\n",
    "        x = x + alpha*d\n",
    "        \n",
    "        if np.linalg.norm(x) < 1e-9:\n",
    "            break\n",
    "            \n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "07f82a85",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x* = |0.4291|\n",
      "     |0.1674|\n",
      "f = -0.8978\n",
      "g = |-0.0116|\n",
      "    |-0.0640|\n",
      "H = |9.1490  1.0000|\n",
      "    |1.0000  10.0083|\n",
      "Valeurs propres:\n",
      "    8.4903\n",
      "    10.6671\n"
     ]
    }
   ],
   "source": [
    "# Test de MethGradient\n",
    "\n",
    "# La méthode du Gradient se raproche de la solution (x1 est correct, x2 est légèrement différent) mais n'y converge pas à cause\n",
    "# du calcul du pas alpha qui ne donne pas le pas exact pour arriver au minimum (autrement dit, on n'arrive pas à calculer\n",
    "# le pas alpha permettant de converger vers le minimum). Au plus on est proche, au plus le calcul d'un alpha admissible prend\n",
    "# du temps.\n",
    "\n",
    "x = np.array([0.0, 0.0])\n",
    "x_opt = MethGradient(x)\n",
    "print(f\"x* = |{x_opt[0]:.4f}|\\n     |{x_opt[1]:.4f}|\")\n",
    "f, g, H = fct(x_opt, nargout=3)\n",
    "print(f\"f = {f:.4f}\")\n",
    "print(f\"g = |{g[0]:.4f}|\\n    |{g[1]:.4f}|\")\n",
    "print(f\"H = |{H[0][0]:.4f}  {H[0][1]:.4f}|\\n    |{H[1][0]:.4f}  {H[1][1]:.4f}|\")\n",
    "eigval, eigvec = np.linalg.eig(H)\n",
    "print(f\"Valeurs propres:\\n    {eigval[0]:.4f}\\n    {eigval[1]:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "1efec1bb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def MethNewton(x0, maxiter=10):\n",
    "    x = x0.copy()\n",
    "    \n",
    "    for k in range(maxiter):\n",
    "        f, g, H = fct(x, nargout=3)\n",
    "        z = np.linalg.solve(H,g)\n",
    "        \n",
    "        x = x - z\n",
    "        \n",
    "        if np.linalg.norm(x) < 1e-9:\n",
    "            break\n",
    "            \n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "f151f070",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x* = |-1.2757|\n",
      "     |-1.6619|\n",
      "f = 5.6516\n",
      "g = |0.0000|\n",
      "    |-0.0000|\n",
      "H = |-11.3086  1.0000|\n",
      "    |1.0000  -11.9422|\n",
      "Valeurs propres:\n",
      "    -10.5764\n",
      "    -12.6744\n"
     ]
    }
   ],
   "source": [
    "# Test de MethNewton\n",
    "\n",
    "# La méthode de Newton est très sensible au point de départ, cela peut conduire (comme c'est le cas ici) à trouver un point\n",
    "# qui n'est pas un minimum mais un maximum.\n",
    "\n",
    "x = np.array([-1.0, -1.0])\n",
    "x_opt = MethNewton(x)\n",
    "print(f\"x* = |{x_opt[0]:.4f}|\\n     |{x_opt[1]:.4f}|\")\n",
    "f, g, H = fct(x_opt, nargout=3)\n",
    "print(f\"f = {f:.4f}\")\n",
    "print(f\"g = |{g[0]:.4f}|\\n    |{g[1]:.4f}|\")\n",
    "print(f\"H = |{H[0][0]:.4f}  {H[0][1]:.4f}|\\n    |{H[1][0]:.4f}  {H[1][1]:.4f}|\")\n",
    "eigval, eigvec = np.linalg.eig(H)\n",
    "print(f\"Valeurs propres:\\n    {eigval[0]:.4f}\\n    {eigval[1]:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b023fe44",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x* = |0.4297|\n",
      "     |0.1737|\n",
      "f = -0.8975\n",
      "g = |0.0000|\n",
      "    |0.0000|\n",
      "H = |9.1560  1.0000|\n",
      "    |1.0000  10.0840|\n",
      "Valeurs propres:\n",
      "    8.5176\n",
      "    10.7224\n"
     ]
    }
   ],
   "source": [
    "# Validation du point calculé par la CoordinateDescent (et par la MethGradient aussi)\n",
    "\n",
    "# On trouve bien un minimum car la gradient est nul (à une tolérance près) et la matrice hessienne est définie positive\n",
    "# (on augmente f si on se déplace du point)\n",
    "\n",
    "x = np.array([0.0, 0.0])\n",
    "x_opt = MethNewton(x)\n",
    "print(f\"x* = |{x_opt[0]:.4f}|\\n     |{x_opt[1]:.4f}|\")\n",
    "f, g, H = fct(x_opt, nargout=3)\n",
    "print(f\"f = {f:.4f}\")\n",
    "print(f\"g = |{g[0]:.4f}|\\n    |{g[1]:.4f}|\")\n",
    "print(f\"H = |{H[0][0]:.4f}  {H[0][1]:.4f}|\\n    |{H[1][0]:.4f}  {H[1][1]:.4f}|\")\n",
    "eigval, eigvec = np.linalg.eig(H)\n",
    "print(f\"Valeurs propres:\\n    {eigval[0]:.4f}\\n    {eigval[1]:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e3b81f36",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
