{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "#standard imports \n",
    "import numpy as np\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Superiorization\n",
    "\n",
    "The last two examples focused on feasibility seeking models. The goal of these is to only find a point meeting the defined constraints ($\\text{find } \\mathbf{x}\\in C = \\bigcap_i C_i$). Superiorization allows to expand on this by adding an additional objective function that is being reduced.\n",
    "\n",
    "In contrast to constrained optimization of this objective function over the feasible set $C$, the goal of superiorization is not to find a constrained minimum point but rather to reach a feasible point with reduced (not necessarily minimal) objective function value compared with the objective function value of a feasible point that would have been reached by the feasibility-seeking only algorithm.\n",
    "\n",
    "The resulting algorithm performs a perturbation of the feasibility seeking algorithm with respect to the given objective function (most commonly by performing a gradient step)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# import the superiorization package\n",
    "from suppy.superiorization import Superiorization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;31mInit signature:\u001b[0m\n",
      "\u001b[0mSuperiorization\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mbasic\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mperturbation_scheme\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0msuppy\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mperturbations\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_base\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mPerturbation\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mDocstring:\u001b[0m     \n",
      "Superiorization algorithm for constrained optimization problems.\n",
      "\n",
      "Parameters\n",
      "----------\n",
      "basic : Callable\n",
      "    The underlying feasibility seeking algorithm.\n",
      "perturbation_scheme : Perturbation\n",
      "    The perturbation scheme to be used for superiorization.\n",
      "\n",
      "Attributes\n",
      "----------\n",
      "basic : Callable\n",
      "    The underlying feasibility seeking algorithm.\n",
      "perturbation_scheme : Perturbation\n",
      "    The perturbation scheme to be used for superiorization.\n",
      "objective_tol : float\n",
      "    Tolerance for the objective function value change to determine stopping criteria.\n",
      "constr_tol : float\n",
      "    Tolerance for the constraint proximity value change to determine stopping criteria.\n",
      "f_k : float\n",
      "    The last value of the objective function.\n",
      "p_k : float\n",
      "    The last value of the constraint function.\n",
      "_k : int\n",
      "    The current iteration number.\n",
      "all_x : list | None\n",
      "    List of all points achieved during the optimization process, only stored if requested by the user.\n",
      "all_function_values : list | None\n",
      "    List of all objective function values achieved during the optimization process, only stored if requested by the user.\n",
      "all_x_function_reduction : list | None\n",
      "    List of all points achieved via the function reduction step, only stored if requested by the user.\n",
      "all_function_values_function_reduction : list | None\n",
      "    List of all objective function values achieved via the function reduction step, only stored if requested by the user.\n",
      "all_x_basic : list | None\n",
      "    List of all points achieved via the basic feasibility seeking algorithm, only stored if requested by the user.\n",
      "all_function_values_basic : list | None\n",
      "    List of all objective function values achieved via the basic feasibility seeking algorithm, only stored if requested by the user.\n",
      "\u001b[1;31mFile:\u001b[0m           c:\\users\\t590r\\documents\\github\\suppy\\suppy\\superiorization\\_standard_sup.py\n",
      "\u001b[1;31mType:\u001b[0m           ABCMeta\n",
      "\u001b[1;31mSubclasses:\u001b[0m     "
     ]
    }
   ],
   "source": [
    "Superiorization?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To set up a superiorization model an underlying feasibility seeking (basic) algorithm is needed, as well as a perturbation scheme.\n",
    "\n",
    "## Feasibility seeking/basic algorithm\n",
    "\n",
    "Projection based feasibility seeking algorithms can be imported from the *suppy.feasibility* or *suppy.projections package*. \n",
    "Let's set up a simple 2D example consisting of overlapping bands."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [],
   "source": [
    "from suppy.projections import BandProjection, SequentialProjection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "band_1 = BandProjection(np.array([1,2]), 3,4)\n",
    "band_2 = BandProjection(np.array([-1,1.5]), 1,2)\n",
    "seq_proj = SequentialProjection([band_1, band_2])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## For simple 2d objects a visualization is possible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 51420 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#visualize the system and the projections \n",
    "fig,ax = plt.subplots()\n",
    "seq_proj.visualize(ax)\n",
    "ax.set_xlim(-2,5)\n",
    "ax.set_ylim(-2,5)\n",
    "ax.grid()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### We can also solve this model directly using the feasibility seeking algorithm:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 52000 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "x_f = seq_proj.solve(np.array([3,2]), 1000,storage = True)\n",
    "fig,ax = plt.subplots()\n",
    "seq_proj.visualize(ax)\n",
    "ax.set_xlim(-2,5)\n",
    "ax.set_ylim(-2,5)\n",
    "plt.plot(x_f[0], x_f[1], 'ro')\n",
    "plt.plot(seq_proj.all_x[:,0], seq_proj.all_x[:,1], '-o',color = 'red')\n",
    "ax.grid()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Moving to superiorization\n",
    "To set up a superiorization model we require a perturbation scheme\n",
    "### Setting up a perturbation scheme\n",
    "\n",
    "We want to perform a perturbation with respect to an objective function by performing a gradient step. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[1;31mInit signature:\u001b[0m\n",
      "\u001b[0mPowerSeriesGradientPerturbation\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mfunc\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mCallable\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mgrad\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mCallable\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mfunc_args\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mList\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mgrad_args\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mList\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;33m[\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mn_red\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mstep_size\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m0.5\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m    \u001b[0mn_restart\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;33m-\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\n",
      "\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mDocstring:\u001b[0m     \n",
      "Objective function perturbation using gradient descent with step size\n",
      "reduction according to a power series.\n",
      "Has the option to \"restart\" the power series after a certain number of\n",
      "steps.\n",
      "\n",
      "func : Callable\n",
      "    The function to be optimized.\n",
      "grad : Callable\n",
      "    The gradient of the function to be optimized.\n",
      "func_args : List, optional\n",
      "    Additional arguments to be passed to the function, by default [].\n",
      "grad_args : List, optional\n",
      "    Additional arguments to be passed to the gradient function, by default [].\n",
      "n_red : int, optional\n",
      "    The number of reductions, by default 1.\n",
      "step_size : float, optional\n",
      "    The step size for the gradient descent, by default 0.5.\n",
      "n_restart : int, optional\n",
      "    The number of steps after which to restart the power series, by default -1 (no restart).\n",
      "\u001b[1;31mFile:\u001b[0m           c:\\users\\t590r\\documents\\github\\suppy\\suppy\\perturbations\\_base.py\n",
      "\u001b[1;31mType:\u001b[0m           ABCMeta\n",
      "\u001b[1;31mSubclasses:\u001b[0m     "
     ]
    }
   ],
   "source": [
    "from suppy.perturbations import PowerSeriesGradientPerturbation\n",
    "PowerSeriesGradientPerturbation?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As the underlying objective function for the above example we use the squared distance to the coordinate center."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func_1(x):\n",
    "    return 1/len(x)*(x**2).sum(axis = 0)\n",
    "\n",
    "def grad_1(x):\n",
    "    grad = 1/len(x)*2*x\n",
    "    return grad/np.sqrt(np.sum(grad**2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [],
   "source": [
    "pert = PowerSeriesGradientPerturbation(func_1,grad_1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 184948 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#visualize the system and the projections \n",
    "fig,ax = plt.subplots()\n",
    "seq_proj.visualize(ax)\n",
    "ax.set_xlim(-2,5)\n",
    "ax.set_ylim(-2,5)\n",
    "\n",
    "x_p = np.linspace(-5,5,100)\n",
    "X,Y = np.meshgrid(x_p,x_p)\n",
    "Z = func_1(np.array([X,Y]))\n",
    "ax.contour(X,Y,Z,30,alpha = 0.5,cmap = 'cividis')\n",
    "ax.grid()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "sup_model = Superiorization(seq_proj,pert)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Current iteration: 0\n",
      "Current iteration: 10\n",
      "Current iteration: 20\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "array([0.72391189, 1.1492746 ])"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sup_model.solve(np.array([3,2]), 1000,storage = True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x1ea935a0290>]"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 185928 characters]",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#visualize the system and the projections \n",
    "fig,ax = plt.subplots()\n",
    "seq_proj.visualize(ax)\n",
    "ax.set_xlim(-2,5)\n",
    "ax.set_ylim(-2,5)\n",
    "\n",
    "x_p = np.linspace(-5,5,100)\n",
    "X,Y = np.meshgrid(x_p,x_p)\n",
    "Z = func_1(np.array([X,Y]))\n",
    "ax.contour(X,Y,Z,30,alpha = 0.5,cmap = 'cividis')\n",
    "ax.grid()\n",
    "plt.plot(sup_model.all_x[:,0], sup_model.all_x[:,1], '-o',color = 'red')"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "suppy",
   "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.11.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
