diff --git a/PEPit/primitive_steps/exact_linesearch_step.py b/PEPit/primitive_steps/exact_linesearch_step.py index e97cff5b..b26de8e1 100644 --- a/PEPit/primitive_steps/exact_linesearch_step.py +++ b/PEPit/primitive_steps/exact_linesearch_step.py @@ -1,7 +1,7 @@ from PEPit.point import Point -def exact_linesearch_step(x0, f, directions): +def exact_linesearch_step(x0, f, directions, name='new_x'): """ This routine outputs some :math:`x` by *mimicking* an exact line/span search in specified directions. It is used for instance in ``PEPit.examples.unconstrained_convex_minimization.wc_gradient_exact_line_search`` @@ -63,6 +63,7 @@ def exact_linesearch_step(x0, f, directions): x0 (Point): the starting point. f (Function): the function on which the (sub)gradient will be evaluated. directions (List of Points): the list of all directions required to be orthogonal to the (sub)gradient of x. + name (Str): the name of the point we arrive to. Returns: x (Point): such that all vectors in directions are orthogonal to the (sub)gradient of f at x. @@ -72,18 +73,18 @@ def exact_linesearch_step(x0, f, directions): """ # Instantiate a Point - x = Point() + x = Point(name=name) # Define gradient and function value of f on x gx, fx = f.oracle(x) # Add constraints constraint = ((x - x0) * gx == 0) - constraint.set_name("exact_linesearch({})_on_{}".format(f.get_name(), x0.get_name())) + constraint.set_name("exact_linesearch({})_to_{}_from_{}".format(f.get_name(), x.get_name(), x0.get_name())) f.add_constraint(constraint) for d in directions: constraint = (d * gx == 0) - constraint.set_name("exact_linesearch({})_on_{}_in_direction_{}".format(f.get_name(), x0.get_name(), d.get_name())) + constraint.set_name("exact_linesearch({})_to_{}_from_{}_in_direction_{}".format(f.get_name(), x.get_name(), x0.get_name(), d.get_name())) f.add_constraint(constraint) # Return triplet of points diff --git a/README.md b/README.md index 0a9385c0..d2dc0bb0 100644 --- a/README.md +++ b/README.md @@ -274,16 +274,15 @@ PEPit supports the following [operator classes](https://pepit.readthedocs.io/en/ ## Contributors -### Creators +### Creators, internal contributors and maintainers -This toolbox has been created by - -- [**Baptiste Goujaud**](https://www.linkedin.com/in/baptiste-goujaud-b60060b3/) -- [**Céline Moucer**](https://cmoucer.github.io) -- [**Julien Hendrickx**](https://perso.uclouvain.be/julien.hendrickx/index.html) -- [**François Glineur**](https://perso.uclouvain.be/francois.glineur/) -- [**Adrien Taylor**](https://adrientaylor.github.io/) -- [**Aymeric Dieuleveut**](http://www.cmap.polytechnique.fr/~aymeric.dieuleveut/) +- [**Baptiste Goujaud**](https://bgoujaud.github.io/) (creator and maintainer) +- [**Céline Moucer**](https://cmoucer.github.io) (creator) +- [**Julien Hendrickx**](https://perso.uclouvain.be/julien.hendrickx/index.html) (creator) +- [**François Glineur**](https://perso.uclouvain.be/francois.glineur/) (creator) +- [**Adrien Taylor**](https://adrientaylor.github.io/) (creator and maintainer) +- [**Aymeric Dieuleveut**](http://www.cmap.polytechnique.fr/~aymeric.dieuleveut/) (creator and maintainer) +- [**Daniel Berg Thomsen**](https://bergthomsen.com/) (internal contributor)

@@ -297,6 +296,7 @@ This toolbox has been created by

+ ### External contributions All external contributions are welcome. diff --git a/docs/source/tutorials.rst b/docs/source/tutorials.rst index ddf1ae17..d5a4c20f 100644 --- a/docs/source/tutorials.rst +++ b/docs/source/tutorials.rst @@ -6,5 +6,6 @@ Tutorials :caption: Contents: Finding worst-case guarantees - Extracting a proof Extracting a worst-case example + Extracting a proof + Designing an algorithm diff --git a/docs/source/whatsnew.rst b/docs/source/whatsnew.rst index 4334ce05..5df7ca0e 100644 --- a/docs/source/whatsnew.rst +++ b/docs/source/whatsnew.rst @@ -13,3 +13,4 @@ What's new in PEPit whatsnew/0.3.3 whatsnew/0.4.0 whatsnew/0.5.0 + whatsnew/0.5.1 diff --git a/docs/source/whatsnew/0.5.1.rst b/docs/source/whatsnew/0.5.1.rst new file mode 100644 index 00000000..fa2ae8f9 --- /dev/null +++ b/docs/source/whatsnew/0.5.1.rst @@ -0,0 +1,12 @@ +What's new in PEPit 0.5.1 +========================= + +New demo: +--------- + +- The file `ressources/demo/PEPit_demo_algorithm_design.ipynb` contains examples for algorithm design with PEPit. + +Fixes: +------ + +- Improved constraint naming within the exact linesearch procedure. diff --git a/ressources/demo/PEPit_demo_algorithm_design.ipynb b/ressources/demo/PEPit_demo_algorithm_design.ipynb new file mode 100644 index 00000000..bd81d9c0 --- /dev/null +++ b/ressources/demo/PEPit_demo_algorithm_design.ipynb @@ -0,0 +1,2646 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Minicourse on performance estimation problems, part 3: algorithm design\n", + "\n", + "The goal of this notebook is to introduce a few techniques for algorithm design based on the performance estimation framework. More precisely, we treat performance estimation problems as black boxes capable of providing tight worst-case certificates, and then use them to design algorithms with optimized worst-case performance.\n", + "\n", + "For instance, consider the optimization problem\n", + "$$\\min_{x\\in\\mathbb{R}^d} f(x),$$\n", + "where $f$ is assumed to belong to some function class $\\mathcal{F}$. Assuming the gradients of $f$ are readily available, one may consider the following family of first-order algorithms parametrized by weights $\\{h_{i,j}\\}$:\n", + "$$\\begin{aligned}\n", + "y_1 &= y_0 - h_{1,0} \\nabla f(y_0),\\\\\n", + "y_2 &= y_1 - h_{2,0} \\nabla f(y_0) - h_{2,1} \\nabla f(y_1),\\\\\n", + "y_3 &= y_2 - h_{3,0} \\nabla f(y_0) - h_{3,1} \\nabla f(y_1) - h_{3,2} \\nabla f(y_2),\\\\\n", + "&\\vdots\\\\\n", + "y_N &= y_{N-1} - \\sum_{i=0}^{N-1} h_{N,i} \\nabla f(y_i),\n", + "\\end{aligned} \\tag{1}\\label{eq:ogm_start}$$\n", + "and aim to choose $\\{h_{i,j}\\}$ as a solution to the minimax problem:\n", + "$$\\min_{\\{h_{i,j}\\}}\\,\\, \\max_{f\\in\\mathcal{F}_{0,L}} \\left\\{ \\frac{f(y_N)-f_\\star}{\\|y_0-x_\\star\\|^2_2} \\, : \\, y_N \\text{ obtained from } \\eqref{eq:ogm_start} \\text{ and } y_0 \\right\\}. \\tag{2}\\label{eq:pep_opt_h}$$\n", + "One natural approach is to choose the step sizes by solving $\\eqref{eq:pep_opt_h}$. Of course, methods of the form $\\eqref{eq:ogm_start}$ are impractical in general, since they require keeping track of all previous gradients, and we will try to *factor* them into more convenient forms. This notebook illustrates the following approaches:\n", + "\n", + "- design by exploration and structural properties,\n", + "- design via the subspace-search elimination procedure [5, 6],\n", + "- design via nonlinear optimization (working example based on the approach in [8]; see also [9]).\n", + "\n", + "**Worked examples:** vanilla gradient descent, silver steps [4], an optimal method for nonsmooth convex minimization [6], and the optimized gradient method [1, 3]. The optimized gradient method is presented through the lens of conjugate gradient-like methods [5], but it was originally discovered via convex relaxations [1, 3]." + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "\n", + "## Table of Contents\n", + "\n", + "1. [An optimal multistep gradient descent, design via naive exploration and algebraic observations](#sec1)\n", + "2. [An optimal algorithm design for (possibly nonsmooth) convex minimization, design via a conjugate gradient method](#sec2)\n", + "3. [An optimal algorithm design for smooth convex minimization, design via a conjugate gradient method](#sec3)\n", + "4. [Going further](#sec4)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 1. Optimal algorithm design via naive exploration \n", + "\n", + "Start with a few imports used throughout this section." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# import PEPit and the required function class\n", + "from PEPit import PEP\n", + "from PEPit.functions import SmoothStronglyConvexFunction\n", + "\n", + "# import numpy and matplotlib\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.1. Base gradient descent\n", + "\n", + "We first consider a direct approach: fix the class parameters $L, \\mu$ (chosen below) and experiment with different step sizes $\\gamma$. Verify that the resulting convergence rate is smaller than one only when $\\gamma \\in \\left(0, \\frac{2}{L}\\right)$, and that it matches the well-known bound $\\max\\left\\{(1-\\gamma L)^2,\\, (1-\\gamma\\mu)^2\\right\\}$.\n", + "\n", + "The following code is standard and was already introduced in the previous exercise sessions." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# 1) write GD PEP\n", + "\n", + "def wc_gradient_descent(L, mu, gammas, verbose = 0):\n", + " \n", + " n = len(gammas)\n", + " \n", + " # Instantiate PEP\n", + " problem = PEP()\n", + "\n", + " # Declare a smooth convex function\n", + " func = problem.declare_function(SmoothStronglyConvexFunction, L=L, mu=mu)\n", + "\n", + " # Start by defining its unique optimal point xs = x_* and corresponding function value fs = f_*\n", + " xs = func.stationary_point(name='xs')\n", + "\n", + " # Then define the starting point x0 of the algorithm\n", + " x0 = problem.set_initial_point(name='x0')\n", + "\n", + " # Set the initial constraint that is the distance between x0 and x^*\n", + " problem.set_initial_condition((x0 - xs) ** 2 <= 1)\n", + "\n", + " # Run n steps of the GD method\n", + " x = x0\n", + " for i in range(n):\n", + " x = x - gammas[i] * func.gradient(x)\n", + " x.set_name('x{}'.format(i+1))\n", + "\n", + " # Set the performance metric to the function values accuracy\n", + " problem.set_performance_metric( (x - xs) ** 2 )\n", + "\n", + " # Solve the PEP\n", + " pepit_verbose = max(verbose, 0)\n", + " pepit_tau = problem.solve(verbose=pepit_verbose)\n", + " list_of_constraints = problem._list_of_prepared_constraints\n", + "\n", + "\n", + " # Return the worst-case guarantee of the evaluated method (and the reference theoretical value)\n", + " return pepit_tau, list_of_constraints, func.get_class_constraints_duals(), problem.residual" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now verify the rate for $(\\mu, L) = (0.1, 1)$ over a grid of step sizes." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "400 / 400 grid points computed\r" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "L = 1.\n", + "mu = .1\n", + "gamma_min, gamma_max = -.5, 2.5\n", + "nb_gammas = 400\n", + "gamma_list = np.linspace(gamma_min,gamma_max,nb_gammas)\n", + "\n", + "pepit_worst_case_value = list()\n", + "\n", + "for i, gamma in enumerate(gamma_list):\n", + " pepit_tau, _, _, _ = wc_gradient_descent(mu=mu,L=L,gammas=[gamma])\n", + " pepit_worst_case_value.append(pepit_tau)\n", + " print(f'{i + 1} / {nb_gammas} grid points computed', end='\\r', flush=True)\n", + " \n", + "plt.plot(gamma_list, pepit_worst_case_value, color='red', linestyle='-', linewidth=3, label='PEPit')\n", + "\n", + "plt.legend()\n", + "plt.xlabel(r'Stepsize $\\gamma$')\n", + "plt.ylabel(r'Worst-case $\\frac{\\|x_1-x_\\star\\|^2}{\\|x_0-x_\\star\\|^2}$')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Verify the output for a few step sizes:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.8100001805827125]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pepit_tau, _, _, _ = wc_gradient_descent(L=L,mu=mu,gammas=[1/L])\n", + "[pepit_tau]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Identify the pattern in the inequalities: does it make sense, and can you explain it in light of the PEP theory you know?" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IC_Function_0 xs x0\n", + "xs 0.0 1.8\n", + "x0 1.8 0.0\n" + ] + } + ], + "source": [ + "pepit_tau, list_of_constraints, tab, res = wc_gradient_descent(1, .1, [1/L])\n", + "\n", + "print(tab[\"smoothness_strong_convexity\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Are there other active constraints? (note: you can ignore the \"performance metric\" constraint for now)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Constraint \"Performance metric 1\" value: 0.9999999999866084\n", + "Constraint \"Initial condition\" value: 0.8100001805827125\n", + "Constraint \"IC_Function_0_smoothness_strong_convexity(xs, x0)\" value: 1.7999998617137993\n", + "Constraint \"IC_Function_0_smoothness_strong_convexity(x0, xs)\" value: 1.7999998617138078\n" + ] + } + ], + "source": [ + "for i, constraint in enumerate(list_of_constraints):\n", + " print('Constraint \\\"{}\\\" value: {}'.format(constraint.get_name(),\n", + " constraint._dual_variable_value))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, our goal is to compute the optimal point by observing a few properties of the corresponding PEP objects, assuming we do not already know that its parametric value is $2/(L+\\mu)$. In particular, observe the value of the residual matrix (at the best point of the grid) and compare it with its value for other step sizes. Can you explain this intuitively? Does it give you a hint on how to algebraically characterize this optimal step size?" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 5.30968577e-05 -5.30968577e-05 5.30953574e-04]\n", + " [-5.30968577e-05 5.30968577e-05 -5.30953574e-04]\n", + " [ 5.30953574e-04 -5.30953574e-04 5.30938571e-03]]\n" + ] + } + ], + "source": [ + "idx_best = np.argmin(pepit_worst_case_value)\n", + "gamma_best = gamma_list[idx_best]\n", + "tau_best = pepit_worst_case_value[idx_best]\n", + "\n", + "pepit_tau, list_of_constraints, tab, res = wc_gradient_descent(L=L, mu=mu, gammas=[gamma_best])\n", + "\n", + "print(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that you understand some algebraic properties of the optimal step size, let us try to compute it directly via symbolic computations (using SymPy)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Proof with SymPy :)\n", + "import sympy as sm\n", + "\n", + "# We start by writing the SDP in dual form:\n", + "\n", + "# problem parameters\n", + "L, mu, gamma = sm.symbols('L mu gamma')\n", + "# primal variables\n", + "x0, g0, f0 = sm.symbols('x0 g0 f0')\n", + "xs, gs, fs = 0, 0, 0 # wlog optimum at zero\n", + "# dual variables\n", + "rho, l1, l2 = sm.symbols('rho lambda_1 lambda_2')\n", + "# interpolation inequality factory\n", + "interp_ij = lambda xi, gi, fi, xj, gj, fj: (\n", + " fi - fj + gi*(xj-xi) + (gi-gj)**2/(2*L)\n", + " + mu/(2*(1-mu/L)) * (xi-xj-(gi-gj)/L)**2\n", + ")\n", + "# algorithm (GD)\n", + "x1 = x0 - gamma * g0\n", + "\n", + "# constraints (≤ 0)\n", + "constraint1 = interp_ij(x0, g0, f0, xs, gs, fs)\n", + "constraint2 = interp_ij(xs, gs, fs, x0, g0, f0)\n", + "\n", + "# objective and initial condition\n", + "primal_objective = (x1 - xs)**2\n", + "initial_condition = (x0 - xs)**2 - 1\n", + "\n", + "# Lagrangian\n", + "Lagrangian = (\n", + " - l1 * constraint1\n", + " - l2 * constraint2\n", + " - rho * initial_condition\n", + " + primal_objective\n", + ")\n", + "\n", + "# Linear matrix inequality (aka. residual on the dual side):\n", + "LMI = sm.simplify(sm.hessian(-Lagrangian, (x0, g0)) / 2)\n", + "# Linear constraint\n", + "Linear = sm.diff(-Lagrangian, f0)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now proceed step by step to cancel both terms, namely the LMI/residual and the linear constraint. We show below how to do everything at once, but the step-by-step approach is pedagogically useful and helps one get used to SymPy:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\left[\\begin{matrix}\\frac{\\frac{L \\lambda_{1} \\mu}{2} + \\frac{L \\lambda_{2} \\mu}{2} + \\left(L - \\mu\\right) \\left(\\rho - 1\\right)}{L - \\mu} & \\frac{- \\frac{L \\lambda_{1}}{2} + \\gamma \\left(L - \\mu\\right) - \\frac{\\lambda_{2} \\mu}{2}}{L - \\mu}\\\\\\frac{- \\frac{L \\lambda_{1}}{2} + \\gamma \\left(L - \\mu\\right) - \\frac{\\lambda_{2} \\mu}{2}}{L - \\mu} & \\frac{2 \\gamma^{2} \\left(- L + \\mu\\right) + \\lambda_{1} + \\lambda_{2}}{2 \\left(L - \\mu\\right)}\\end{matrix}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[(L*lambda_1*mu/2 + L*lambda_2*mu/2 + (L - mu)*(rho - 1))/(L - mu), (-L*lambda_1/2 + gamma*(L - mu) - lambda_2*mu/2)/(L - mu)],\n", + "[ (-L*lambda_1/2 + gamma*(L - mu) - lambda_2*mu/2)/(L - mu), (2*gamma**2*(-L + mu) + lambda_1 + lambda_2)/(2*(L - mu))]])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "LMI" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\lambda_{1} - \\lambda_{2}$" + ], + "text/plain": [ + "lambda_1 - lambda_2" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Linear" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\left[\\begin{matrix}\\frac{L \\lambda_{1} \\mu + \\left(L - \\mu\\right) \\left(\\rho - 1\\right)}{L - \\mu} & \\frac{- \\frac{L \\lambda_{1}}{2} + \\gamma \\left(L - \\mu\\right) - \\frac{\\lambda_{1} \\mu}{2}}{L - \\mu}\\\\\\frac{- \\frac{L \\lambda_{1}}{2} + \\gamma \\left(L - \\mu\\right) - \\frac{\\lambda_{1} \\mu}{2}}{L - \\mu} & \\frac{2 \\gamma^{2} \\left(- L + \\mu\\right) + 2 \\lambda_{1}}{2 \\left(L - \\mu\\right)}\\end{matrix}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[ (L*lambda_1*mu + (L - mu)*(rho - 1))/(L - mu), (-L*lambda_1/2 + gamma*(L - mu) - lambda_1*mu/2)/(L - mu)],\n", + "[(-L*lambda_1/2 + gamma*(L - mu) - lambda_1*mu/2)/(L - mu), (2*gamma**2*(-L + mu) + 2*lambda_1)/(2*(L - mu))]])" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# first: substitute one dual variable by the other (as the linear constraint forces them to be equal)\n", + "LMI = LMI.subs(l2,l1)\n", + "LMI" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\left[\\begin{matrix}0 & \\frac{- \\frac{L \\lambda_{1}}{2} + \\gamma \\left(L - \\mu\\right) - \\frac{\\lambda_{1} \\mu}{2}}{L - \\mu}\\\\\\frac{- \\frac{L \\lambda_{1}}{2} + \\gamma \\left(L - \\mu\\right) - \\frac{\\lambda_{1} \\mu}{2}}{L - \\mu} & \\frac{\\gamma^{2} \\left(- L + \\mu\\right) + \\lambda_{1}}{L - \\mu}\\end{matrix}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[ 0, (-L*lambda_1/2 + gamma*(L - mu) - lambda_1*mu/2)/(L - mu)],\n", + "[(-L*lambda_1/2 + gamma*(L - mu) - lambda_1*mu/2)/(L - mu), (gamma**2*(-L + mu) + lambda_1)/(L - mu)]])" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Then: pick rho so as to cancel out first entry of the LMI:\n", + "rho_val = sm.solve(LMI[0,0],rho)\n", + "LMI = sm.simplify(LMI.subs(rho,rho_val[0]))\n", + "LMI" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\left[\\begin{matrix}0 & 0\\\\0 & \\frac{\\gamma \\left(- \\gamma \\left(L + \\mu\\right) + 2\\right)}{L + \\mu}\\end{matrix}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[0, 0],\n", + "[0, gamma*(-gamma*(L + mu) + 2)/(L + mu)]])" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Then: pick \\lambda_1 to cancel out the off-diagonal term:\n", + "l1_val = sm.solve(LMI[1,0],l1)\n", + "LMI = sm.simplify(LMI.subs(l1,l1_val[0]))\n", + "LMI" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 2 possible solutions\n", + "Either gamma=0,\n", + "or gamma=2/(L + mu).\n" + ] + } + ], + "source": [ + "# Then: pick gamma to cancel out the last term:\n", + "gamma_val = sm.solve(LMI[1,1],gamma)\n", + "LMI = sm.simplify(LMI.subs(gamma,gamma_val[0]))\n", + "\n", + "# The optimal step size is thus one among the two solutions:\n", + "print('There are {} possible solutions'.format(len(gamma_val)))\n", + "print('Either gamma={},'.format(gamma_val[0]))\n", + "print('or gamma={}.'.format(gamma_val[1]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Nice: this is very much in line with what one would expect.\n", + "The next lines perform exactly the same operations, but all at once." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# problem parameters\n", + "L, mu, gamma = sm.symbols('L mu gamma')\n", + "# primal variables\n", + "x0, g0, f0 = sm.symbols('x0 g0 f0')\n", + "xs, gs, fs = 0, 0, 0 # wlog optimum at zero\n", + "# dual variables\n", + "rho, l1, l2 = sm.symbols('rho lambda_1 lambda_2')\n", + "# interpolation inequality factory\n", + "interp_ij = lambda xi, gi, fi, xj, gj, fj: (\n", + " fi - fj + gi*(xj-xi) + (gi-gj)**2/(2*L)\n", + " + mu/(2*(1-mu/L)) * (xi-xj-(gi-gj)/L)**2\n", + ")\n", + "# algorithm (GD)\n", + "x1 = x0 - gamma * g0\n", + "\n", + "# constraints (≤ 0)\n", + "constraint1 = interp_ij(x0, g0, f0, xs, gs, fs)\n", + "constraint2 = interp_ij(xs, gs, fs, x0, g0, f0)\n", + "\n", + "# objective and initial condition\n", + "primal_objective = (x1 - xs)**2\n", + "initial_condition = (x0 - xs)**2 - 1\n", + "\n", + "# Lagrangian\n", + "Lagrangian = (\n", + " - l1 * constraint1\n", + " - l2 * constraint2\n", + " - rho * initial_condition\n", + " + primal_objective\n", + ")\n", + "\n", + "# LMI\n", + "LMI = sm.simplify(sm.hessian(-Lagrangian, (x0, g0)) / 2)\n", + "Linear = sm.simplify(sm.diff(-Lagrangian,f0))\n", + "#LMI = LMI.subs(l2,l1)\n", + "\n", + "sols = sm.solve([LMI,Linear],rho,l1,l2,gamma)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((L - mu)**2/(L + mu)**2,\n", + " 4*(L - mu)/(L + mu)**2,\n", + " 4*(L - mu)/(L + mu)**2,\n", + " 2/(L + mu))" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sols[1]" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "A natural next question is how to turn these findings into a proof of the result:" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "Method: $x_1=x_0-\\gamma \\nabla f(x_0)$. The optimal step size is $\\tfrac{2}{L+\\mu}$, and the proof is as follows. Consider the weighted sum of\n", + "\n", + "- interpolation between $x_0$ and $x_\\star$ with weight $\\lambda_1=2\\gamma (1-\\gamma\\mu)$\n", + "- interpolation between $x_\\star$ and $x_0$ with weight $\\lambda_2=\\lambda_1$\n", + "\n", + "The weighted sum gives (by completing the squares; a.k.a., \"analytical Cholesky\"):\n", + "$$\\|x_1-x_\\star\\|^2\\leq (1-\\gamma \\mu)^2 \\|x_0-x_\\star\\|^2 - \\tfrac{\\gamma(2-\\gamma(L+\\mu))}{L-\\mu}\\|\\mu (x_0-x_\\star)-\\nabla f(x_0)\\|^2.$$\n", + "\n", + "Cancelling the last term, we obtain the value for $\\gamma$, and the rate $\\left(\\frac{L-\\mu}{L+\\mu}\\right)^2$." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 1.2. Multistep gradient descent (aka the Silver step-size schedule [4])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now consider the same optimization setup: we want to minimize a smooth strongly convex function\n", + "$$ \\min_{x\\in\\mathbb{R}^d} f(x),$$\n", + "but using a multistep gradient algorithm. That is, we will iterate:\n", + "\\begin{align*}\n", + "x_{1} &= x_0 - \\gamma_1 \\nabla f(x_0),\\\\\n", + "x_{2} &= x_1 - \\gamma_2 \\nabla f(x_1),\n", + "\\end{align*}\n", + "and our goal will be to design the pair $(\\gamma_1, \\gamma_2)$. We proceed with the following choice:\n", + "\n", + "$$\\min_{\\gamma_1,\\gamma_2}\\,\\, \\max_{f\\in\\mathcal{F}_{\\mu,L}} \\left\\{ \\frac{\\|x_2-x_\\star\\|^2_2}{\\|x_0-x_\\star\\|^2_2} \\, : \\, x_2 \\text{ obtained from } x_{1} = x_0 - \\gamma_1 \\nabla f(x_0),\\,x_{2} = x_1 - \\gamma_2 \\nabla f(x_1) \\right\\}.$$\n", + "\n", + "\n", + "To do this, we will follow steps similar to those used for vanilla GD.\n", + "\n", + "For simplicity, let us again carry out the analysis for specific parameter values." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "L, mu = 1, .1" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following lines provide helper functions for performing the grid search and plotting the results." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "from matplotlib.colors import LogNorm\n", + "\n", + "def pepit_grid_search(gamma1_grid, gamma2_grid, mu, L):\n", + " \"\"\"\n", + " Evaluate wc_gradient_descent on a grid of (gamma1, gamma2).\n", + "\n", + " Parameters\n", + " ----------\n", + " gamma1_grid : array-like\n", + " Grid values for gamma1.\n", + " gamma2_grid : array-like\n", + " Grid values for gamma2.\n", + " mu, L : float\n", + " Problem parameters.\n", + "\n", + " Returns\n", + " -------\n", + " G1, G2 : ndarray\n", + " Meshgrid of gamma1 and gamma2.\n", + " tau_grid : ndarray\n", + " Grid of pepit_tau values.\n", + " \"\"\"\n", + "\n", + " G1, G2 = np.meshgrid(gamma1_grid, gamma2_grid)\n", + " tau_grid = np.zeros_like(G1)\n", + "\n", + " total = tau_grid.size\n", + "\n", + " for k, (i, j) in enumerate(np.ndindex(G1.shape), start=1):\n", + " gamma1 = G1[i, j]\n", + " gamma2 = G2[i, j]\n", + "\n", + " pepit_tau, _, _, _ = wc_gradient_descent(mu=mu, L=L, gammas=[gamma1, gamma2])\n", + " tau_grid[i, j] = pepit_tau\n", + "\n", + " print(f'{k} / {total} grid points computed', end='\\r', flush=True)\n", + "\n", + " return G1, G2, tau_grid\n", + "\n", + "def plot_pepit_landscape(G1, G2, tau_grid):\n", + " \"\"\"\n", + " Plot the contour landscape of pepit_tau and highlight the minimum.\n", + "\n", + " Parameters\n", + " ----------\n", + " G1, G2 : ndarray\n", + " Meshgrid of gamma1 and gamma2.\n", + " tau_grid : ndarray\n", + " Grid of pepit_tau values.\n", + "\n", + " Returns\n", + " -------\n", + " i_best, j_best : int\n", + " Indices of the best grid point.\n", + " gamma1_best, gamma2_best : float\n", + " Best step sizes.\n", + " tau_best : float\n", + " Best value of pepit_tau.\n", + " \"\"\"\n", + "\n", + " plt.figure()\n", + "\n", + " levels = np.logspace(\n", + " np.log10(np.nanmin(tau_grid)),\n", + " np.log10(np.nanmax(tau_grid)),\n", + " 50\n", + " )\n", + "\n", + " cf = plt.contourf(\n", + " G1, G2, tau_grid,\n", + " levels=levels,\n", + " cmap='viridis_r',\n", + " norm=LogNorm()\n", + " )\n", + "\n", + " plt.colorbar(cf)\n", + "\n", + " # locate minimum\n", + " idx = np.nanargmin(tau_grid)\n", + " i_best, j_best = np.unravel_index(idx, tau_grid.shape)\n", + "\n", + " gamma1_best = G1[i_best, j_best]\n", + " gamma2_best = G2[i_best, j_best]\n", + " tau_best = tau_grid[i_best, j_best]\n", + "\n", + " # mark minimum\n", + " plt.scatter(gamma1_best, gamma2_best, s=120, color='red',\n", + " edgecolor='black', zorder=3)\n", + "\n", + " plt.xlabel(r'$\\gamma_1$')\n", + " plt.ylabel(r'$\\gamma_2$')\n", + " plt.title(r'Worst-case $\\frac{\\|x_2-x_\\star\\|^2}{\\|x_0-x_\\star\\|^2}$')\n", + "\n", + " plt.show()\n", + "\n", + " print(f\"Best grid index: (i,j) = ({i_best}, {j_best})\")\n", + " print(f\"gamma1 = {gamma1_best:.4f}, gamma2 = {gamma2_best:.4f}\")\n", + " print(f\"tau = {tau_best:.4e}\")\n", + "\n", + " return i_best, j_best, gamma1_best, gamma2_best, tau_best" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the previous pieces of code, perform a grid search over the step sizes and plot the corresponding convergence rate values." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "900 / 900 grid points computed\r" + ] + } + ], + "source": [ + "# 4) plot (2-step)\n", + "gamma_min, gamma_max = 0.1, 3.2\n", + "nb_gammas = 30\n", + "\n", + "gamma1_vals = np.linspace(gamma_min, gamma_max, nb_gammas)\n", + "gamma2_vals = gamma1_vals\n", + "\n", + "G1, G2, tau_grid = pepit_grid_search(gamma1_vals, gamma2_vals, mu=mu, L=L)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best grid index: (i,j) = (24, 12)\n", + "gamma1 = 1.3828, gamma2 = 2.6655\n", + "tau = 4.0889e-01\n" + ] + } + ], + "source": [ + "_, _, gamma1_best, gamma2_best, tau_best = plot_pepit_landscape(G1, G2, tau_grid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we want to find properties of the optimal point, let us refine the grid around the target point!" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "100 / 100 grid points computed\r" + ] + }, + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Best grid index: (i,j) = (1, 7)\n", + "gamma1 = 1.3838, gamma2 = 2.6501\n", + "tau = 4.0105e-01\n" + ] + } + ], + "source": [ + "gamma1_min, gamma1_max = 1.383, 1.384\n", + "gamma2_min, gamma2_max = 2.650, 2.651\n", + "nb_gammas = 10\n", + "\n", + "gamma1_vals = np.linspace(gamma1_min, gamma1_max, nb_gammas)\n", + "gamma2_vals = np.linspace(gamma2_min, gamma2_max, nb_gammas)\n", + "\n", + "G1, G2, tau_grid = pepit_grid_search(gamma1_vals, gamma2_vals, mu=mu, L=L)\n", + "_, _, gamma1_best, gamma2_best, tau_best = plot_pepit_landscape(G1, G2, tau_grid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What algebraic property do you empirically observe at (or around) this optimal point?" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[[ 5.83099974e-06 -5.83099974e-06 2.20037443e-05 4.21374901e-05]\n", + " [-5.83099974e-06 5.83099974e-06 -2.20037443e-05 -4.21374901e-05]\n", + " [ 2.20037443e-05 -2.20037443e-05 8.30328905e-05 1.59009192e-04]\n", + " [ 4.21374901e-05 -4.21374901e-05 1.59009192e-04 3.04504914e-04]]\n" + ] + } + ], + "source": [ + "pepit_tau, list_of_constraints, tab, res = wc_gradient_descent(L=L, mu=mu, gammas=[gamma1_best, gamma2_best])\n", + "\n", + "print(res)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In order to help SymPy, we also have to identify the sparsity pattern for the dual variables. What do you observe and how would you choose it?" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "IC_Function_0 xs x0 x1\n", + "xs 0.000000 1.159393 4.231237\n", + "x0 0.000206 0.000000 2.089818\n", + "x1 5.390423 0.930632 0.000000\n" + ] + } + ], + "source": [ + "print(tab[\"smoothness_strong_convexity\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use SymPy to try to compute the optimal step sizes." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# problem parameters\n", + "L, mu, gamma1, gamma2 = sm.symbols('L mu gamma1 gamma2', positive=True)\n", + "\n", + "# primal variables\n", + "x0, g0, g1, f0, f1 = sm.symbols('x0 g0 g1 f0 f1')\n", + "xs, gs, fs = 0, 0, 0 # wlog optimum at zero𝑖\n", + "\n", + "x1 = x0 - gamma1 * g0\n", + "x2 = x1 - gamma2 * g1\n", + "\n", + "# dual variables\n", + "rho, ls0, ls1, l01, l1s, l10 = sm.symbols('\\rho \\lambda_{*0} \\lambda_{*1} \\lambda_{01} \\lambda_{1*} \\lambda_{10}')\n", + "\n", + "\n", + "# interpolation inequality factory\n", + "interp_ij = lambda xi, gi, fi, xj, gj, fj: (\n", + " fj - fi + gj*(xi-xj) + (gi-gj)**2/(2*L)\n", + " + mu/(2*(1-mu/L)) * (xi-xj-(gi-gj)/L)**2\n", + ")\n", + "\n", + "# constraints (≤ 0)\n", + "constraints0 = interp_ij(xs, gs, fs, x0, g0, f0)\n", + "constraints1 = interp_ij(xs, gs, fs, x1, g1, f1)\n", + "constraint01 = interp_ij(x0, g0, f0, x1, g1, f1)\n", + "constraint1s = interp_ij(x1, g1, f1, xs, gs, fs)\n", + "constraint10 = interp_ij(x1, g1, f1, x0, g0, f0)\n", + "\n", + "# objective and initial condition\n", + "primal_objective = (x2 - xs)**2\n", + "initial_condition = (x0 - xs)**2 - 1\n", + "\n", + "# Lagrangian\n", + "Lagrangian = (\n", + " - ls0 * constraints0\n", + " - ls1 * constraints1\n", + " - l01 * constraint01\n", + " - l1s * constraint1s\n", + " - l10 * constraint10\n", + " - rho * initial_condition\n", + " + primal_objective\n", + ")\n", + "\n", + "# LMI\n", + "LMI = sm.simplify(sm.hessian(-Lagrangian, (x0, g0, g1)) / 2)\n", + "vars_vec = sm.Matrix([f0, f1])\n", + "Linear = sm.Matrix([-Lagrangian]).jacobian(vars_vec)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the (guessed) algebraic characterization, let us try to obtain closed-form expressions for all the parameters and dual variables. (This will take a few minutes to run.)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "sols = sm.solve([LMI,Linear],rho,ls0, ls1, l01, l1s, l10,gamma1,gamma2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How many solutions did SymPy find? Can you use the numerical experiments to identify which one is relevant here?" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "There are 5 solutions\n", + "Solution 3 has convergence rate 0.401032645553526 for the specific (L,mu) values above\n" + ] + } + ], + "source": [ + "print('There are {} solutions'.format(len(sols)))\n", + "\n", + "ind_sol = 3\n", + "sols_num = [expr.subs(mu, 0.1).subs(L,1).evalf() for expr in sols[ind_sol]]\n", + "print('Solution {} has convergence rate {} for the specific (L,mu) values above'.format(ind_sol, sols_num[0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.401032645553526,\n", + " 1.15927501788681,\n", + " 4.23143117213146,\n", + " 2.09014864431462,\n", + " 5.39070619001826,\n", + " 0.930873626427809,\n", + " 1.38373600523041,\n", + " 2.65027877285182]" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "sols_num" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What are the corresponding expressions for the step sizes?" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "First step size is (-mu + sqrt(2*L**2 - 2*L*mu + mu**2))/(L*(L - mu))\n", + "Second step size is (2*L + mu + sqrt(2*L**2 - 2*L*mu + mu**2))/(L*(L + 3*mu))\n" + ] + } + ], + "source": [ + "simplify_attempt = [sm.simplify(expr) for expr in sols[ind_sol]]\n", + "\n", + "print('First step size is {}'.format(simplify_attempt[-2]))\n", + "print('Second step size is {}'.format(simplify_attempt[-1]))" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\displaystyle \\frac{- \\mu + \\sqrt{2 L^{2} - 2 L \\mu + \\mu^{2}}}{L \\left(L - \\mu\\right)}$" + ], + "text/plain": [ + "(-mu + sqrt(2*L**2 - 2*L*mu + mu**2))/(L*(L - mu))" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "simplify_attempt[-2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "How would you summarize your findings? Do you observe an improvement compared with the classical optimal convergence rate of gradient descent?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Summary on Silver steps ($n=2$)\n", + "\n", + "Method: $x_1=x_0-\\gamma_1 \\nabla f(x_0)$, $x_2=x_1-\\gamma_2 \\nabla f(x_1)$.\n", + "Let us denote by $\\lambda_{i,j}$ the weight for using inequality\n", + "$$\n", + "0\\geq f(x_j) -f(x_i) +\\langle \\nabla f(x_j);x_i-x_j\\rangle + \\tfrac{1}{2L}\\|\\nabla f(x_i)-\\nabla f(x_j)\\|^2+\\tfrac{\\mu}{2(1-\\mu/L)} \\|x_i-x_j-\\tfrac1L (\\nabla f(x_i)-\\nabla f(x_j))\\|^2.\n", + "$$\n", + "\n", + "It seems that we must use the following nonzero values for these weights:\n", + "\n", + "$$\n", + "\\begin{array}{c|ccc}\n", + "(i,j) & \\star & 0 & 1 \\\\\n", + "\\hline\n", + "\\star & 0 & ✔ & ✔ \\\\\n", + "0 & 0 & 0 & ✔ \\\\\n", + "1 & ✔ & ✔ & 0\n", + "\\end{array}\n", + "$$\n", + "\n", + "The optimal step sizes are as follows:\n", + "\\begin{align*}\n", + " \\gamma_1 & = \\frac{1}{L}\\left(\\frac{\\sqrt{1 + (1-\\kappa)^2} - \\kappa}{1-\\kappa}\\right)\\\\\n", + " \\gamma_2 & = \\frac{1}{L}\\left(\\frac{\\sqrt{1 + (1-\\kappa)^2}+2+\\kappa}{1+3\\kappa}\\right),\n", + "\\end{align*}\n", + "with $\\kappa=\\tfrac{\\mu}{L}$.\n", + "\n", + "The proof consists of performing a weighted sum of inequalities:\n", + "\n", + "- interpolation inequality with $i\\leftarrow\\star $ and $j\\leftarrow0$ and multiplier $\\lambda_1=\\tfrac{2 \\gamma_1 (\\kappa -1) ((2 \\gamma_2-1) \\kappa -1)}{\\kappa (\\gamma_1 (-\\kappa )+\\gamma_1+1)+1}$\n", + "\n", + "- interpolation inequality with $i\\leftarrow \\star $ and $j\\leftarrow 1$ and multiplier $\\lambda_2=\\tfrac{2 (\\kappa -1) (\\gamma_1 (\\gamma_2-1) \\kappa +\\gamma_2)}{\\kappa (\\gamma_1 (\\kappa -1)-1)-1}$\n", + "\n", + "- interpolation inequality with $i\\leftarrow 0$ and $j\\leftarrow1$ and multiplier $\\lambda_3=\\tfrac{2 \\gamma_1 (\\kappa -1) (\\gamma_1 \\kappa -1) ((2 \\gamma_2-1) \\kappa -1)}{(\\gamma_1 \\kappa +\\gamma_1-2) (\\kappa (\\gamma_1 (-\\kappa )+\\gamma_1+1)+1)}$\n", + "\n", + "- interpolation inequality with $i\\leftarrow 1$ and $j\\leftarrow\\star$ and multiplier $\\lambda_4=\\tfrac{2 (\\kappa -1) (\\gamma_1 (\\gamma_2 \\kappa -1)-\\gamma_2)}{\\kappa (\\gamma_1 (-\\kappa )+\\gamma_1+1)+1}$\n", + "\n", + "- interpolation inequality with $i\\leftarrow 1$ and $j\\leftarrow0$ and multiplier $\\lambda_5=\\tfrac{2 (\\gamma_1-1) \\gamma_1 (\\kappa -1) ((2 \\gamma_2-1) \\kappa -1)}{(\\gamma_1 \\kappa +\\gamma_1-2) (\\kappa (\\gamma_1 (\\kappa -1)-1)-1)}$\n", + "\n", + "Summing all these inequalities can be reformulated as\n", + "\n", + "\\begin{equation*}\n", + "\\begin{aligned}\n", + "\\|x_2-x_\\star\\|^2\\leq& \\left(\\tfrac{2 \\kappa (\\gamma_1 (-\\gamma_2) \\kappa +\\gamma_1+\\gamma_2)}{\\kappa (\\gamma_1 (\\kappa -1)-1)-1}+1\\right)\\|x_0-x_\\star\\|^2\\\\&-\\tfrac{2 \\gamma_1 \\left(\\gamma_1^2 (\\kappa -1)-2 \\gamma_1 \\kappa +2\\right) (\\gamma_1 \\kappa -1) ((2 \\gamma_2-1) \\kappa -1)}{(\\gamma_1 \\kappa +\\gamma_1-2) (\\kappa (\\gamma_1 (\\kappa -1)-1)-1)}\\|\\nabla f(x_0)\\|^2\\\\&-\\tfrac{-2 \\gamma_1^2 \\gamma_2 (\\kappa -1) \\kappa (\\gamma_2 \\kappa +\\gamma_2-2)+2 \\gamma_1 \\left(\\gamma_2 \\left(3 \\gamma_2 \\kappa ^2+\\gamma_2-2 (\\kappa +1)\\right)-2 \\kappa +2\\right)-4 \\gamma_2 (\\gamma_2 \\kappa +\\gamma_2-2)}{(\\gamma_1 \\kappa +\\gamma_1-2) (\\kappa (\\gamma_1 (\\kappa -1)-1)-1)}\\|\\nabla f(x_1)\\|^2.\n", + "\\end{aligned}\n", + "\\end{equation*}\n", + "\n", + "Evaluating this expression at the previously chosen values of $\\gamma_1$ and $\\gamma_2$, we find that the two residual terms are equal to $0$.\n", + "\n", + "\n", + "\n", + "### **Bonus:** more iterations? (hint: using Schur complements might help the numerical exploration, and using other computer-algebra software allows going a bit further than with SymPy)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Try this pattern on a simple quadratic problem and compare it to vanilla gradient descent\n" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "\n", + "L, mu = 1., 0.1\n", + "kappa = mu / L\n", + "H = np.array([L, mu])\n", + "grad = lambda x: H * x\n", + "f = lambda x: 0.5 * (H * x**2).sum()\n", + "\n", + "def run_gd(x0, T):\n", + " x, traj = x0.copy(), [x0.copy()]\n", + " gamma = 2 / (L + mu)\n", + " for _ in range(T):\n", + " x -= gamma * grad(x)\n", + " traj.append(x.copy())\n", + " return np.array(traj)\n", + "\n", + "def run_silver(x0, T):\n", + " x, traj = x0.copy(), [x0.copy()]\n", + " gamma1 = (np.sqrt(1 + (1-kappa)**2) - kappa) / (L * (1 - kappa))\n", + " gamma2 = (np.sqrt(1 + (1-kappa)**2) + 2 + kappa) / (L * (1 + 3*kappa))\n", + " for _ in range(T):\n", + " x -= gamma1 * grad(x); traj.append(x.copy())\n", + " x -= gamma2 * grad(x); traj.append(x.copy())\n", + " return np.array(traj)\n", + "\n", + "x0 = np.array([-2.5, 1.5])\n", + "traj_gd = run_gd(x0, T=20)\n", + "traj_silver = run_silver(x0, T=10)\n", + "\n", + "fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(11, 4))\n", + "\n", + "X, Y = np.meshgrid(np.linspace(-3, 3, 200), np.linspace(-2, 2, 200))\n", + "ax1.contour(X, Y, 0.5*(H[0]*X**2 + H[1]*Y**2), levels=15, cmap=\"Blues\")\n", + "for traj, color, label in [(traj_gd, \"tomato\", \"GD\"), (traj_silver, \"mediumpurple\", \"Silver\")]:\n", + " ax1.plot(traj[:, 0], traj[:, 1], \"o-\", color=color, ms=3, label=label)\n", + "ax1.plot(*x0, \"go\", ms=8, label=\"start\")\n", + "ax1.plot(0, 0, \"*\", color=\"gold\", ms=12, label=\"x*\")\n", + "ax1.set(title=\"Trajectory\", xlabel=\"x1\", ylabel=\"x2\"); ax1.legend()\n", + "\n", + "ax2.semilogy([f(x) for x in traj_gd], \"o-\", color=\"tomato\", ms=3, label=\"GD\")\n", + "ax2.semilogy([f(x) for x in traj_silver], \"o-\", color=\"mediumpurple\", ms=3, label=\"Silver\")\n", + "ax2.set(title=\"Excess loss\", xlabel=\"iteration\", ylabel=\"f(xk) - f*\"); ax2.legend()\n", + "\n", + "plt.tight_layout(); plt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "## 2. Numerical design via ideal algorithms \n", + "\n", + "In this section, we perform algorithm design via [5]. That is, we study *ideal* algorithms that are allowed to perform exact line searches\n", + "(and even span searches), and deduce, from their analyses and worst-case bounds, algorithms that do not require any such operations.\n", + "\n", + "### 2.1. Back to gradient descent, exact line-search\n", + "\n", + "Start as follows:\n", + "$$ x_{k+1}\\in\\ \\underset{x\\in \\mathbb{R}^d}{\\mathrm{argmin}} \\left\\{f(x):\\; x\\in x_k+\\mathrm{span}\\{\\nabla f(x_k)\\}\\right\\}.$$\n", + "We will design an optimal step size for gradient descent that does not require line search.\n", + "\n", + "We proceed as follows. First, define the target convergence guarantee for gradient descent with exact line search:\n", + "$$\\rho \\triangleq \\max_{x_0,x_1, f\\in\\mathcal{F}_{\\mu,L}}\\frac{f(x_{1})-f_\\star}{f(x_0)-f_\\star} \\text{ s.t. } \\langle \\nabla f(x_1), \\nabla f(x_0)\\rangle = 0,\\ \\langle \\nabla f(x_1), x_1-x_0\\rangle = 0.$$\n", + "\n", + "Then, a natural upper bound from a Lagrangian relaxation with $\\lambda_1,\\lambda_2\\in\\mathbb{R}$ is\n", + "\n", + "$$\\rho \\leq \\bar{\\rho}(\\lambda_1,\\lambda_2) \\triangleq \\max_{x_0, x_1, f\\in\\mathcal{F}_{\\mu,L}} \\left\\{\\frac{f(x_{1})-f_\\star}{f(x_0)-f_\\star} +\\lambda_1 \\langle \\nabla f(x_1), \\nabla f(x_0)\\rangle+\\lambda_2 \\langle \\nabla f(x_1), x_1-x_0\\rangle\\right\\}.$$\n", + "\n", + "A similar upper bound can be obtained by combining the inequalities instead (which is a weaker relaxation than putting them directly in the objective):\n", + "\n", + "$$\\rho \\leq \\max_{x_0,x_1, f\\in\\mathcal{F}_{\\mu,L}}\\left\\{\\frac{f(x_{1})-f_\\star}{f(x_0)-f_\\star} \\text{ s.t. } \\lambda_1\\langle \\nabla f(x_1), \\nabla f(x_0)\\rangle +\\lambda_2 \\langle \\nabla f(x_1), x_1-x_0\\rangle = 0\\right\\} \\leq \\bar{\\rho}(\\lambda_1,\\lambda_2).$$\n", + "\n", + "So, for any pair $\\lambda_1,\\lambda_2\\in\\mathbb{R}$ we get:\n", + "- an upper bound $\\bar{\\rho}(\\lambda_1,\\lambda_2)$ (possibly $+\\infty$) on $\\rho$,\n", + "- all methods satisfying $\\langle \\nabla f(x_1), \\lambda_1 \\nabla f(x_0)+ \\lambda_2 (x_1-x_0)\\rangle = 0$ have convergence rate at most $\\bar{\\rho}(\\lambda_1,\\lambda_2)$.\n", + "- In particular, gradient descent with step size $\\frac{\\lambda_1}{\\lambda_2}$ also benefits from the rate $\\bar{\\rho}(\\lambda_1,\\lambda_2)$.\n", + "\n", + "**Bonus:** there exists a choice $\\lambda_1^\\star,\\lambda_2^\\star$ such that $\\rho=\\bar{\\rho}(\\lambda_1^\\star,\\lambda_2^\\star)$.\n", + "\n", + "#### 2.1.1. Designing an optimal gradient method\n", + "\n", + "Assuming again that we do not know the (worst-case) optimal step-size tuning, the goal here is to recover it from the perspective above.\n", + "Again, the motivation is that this perspective allows us to design methods beyond gradient descent, even though the technique can already be fully understood in the gradient-descent setting." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# func values + LS\n", + "from PEPit import PEP\n", + "from PEPit.functions import SmoothStronglyConvexFunction\n", + "from PEPit.primitive_steps import exact_linesearch_step\n", + "\n", + "def wc_gradient_ELS(L, mu, verbose=0):\n", + "\n", + " # Instantiate PEP\n", + " problem = PEP()\n", + "\n", + " # Declare a smooth convex function\n", + " func = problem.declare_function(SmoothStronglyConvexFunction, L=L, mu=mu, name=\"f\")\n", + "\n", + " # Start by defining its unique optimal point xs = x_* and corresponding function value fs = f_*\n", + " xs = func.stationary_point(name=\"xs\")\n", + " fs = func(xs)\n", + "\n", + " # Then define the starting point x0 of the algorithm\n", + " x0 = problem.set_initial_point(name=\"x0\")\n", + " g0, f0 = func.oracle(x0)\n", + "\n", + " # Set the initial constraint on function accuracy\n", + " problem.set_initial_condition(f0 - fs <= 1)\n", + "\n", + " # Run n steps of the Conjugate Gradient method\n", + " x_new = x0\n", + " g0.set_name(\"grad(x0)\")\n", + " x1, g1, f1 = exact_linesearch_step(x0, func, [g0], name='x1')\n", + " g1.set_name('grad(x1)')\n", + "\n", + " # Set the performance metric to the function value accuracy\n", + " problem.set_performance_metric(f1 - fs)\n", + "\n", + " # Solve the PEP\n", + " pepit_verbose = max(verbose, 0)\n", + " pepit_tau = problem.solve(verbose=pepit_verbose)\n", + " list_of_constraints = problem._list_of_prepared_constraints\n", + " \n", + " return pepit_tau, list_of_constraints, func.get_class_constraints_duals(), problem.residual" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Constraint \"Performance metric 1\" value: 1.0000000000005818\n", + "Constraint \"Initial condition\" value: 0.6694218823210809\n", + "Constraint \"IC_f_smoothness_strong_convexity(xs, x0)\" value: 0.14876101613030243\n", + "Constraint \"IC_f_smoothness_strong_convexity(xs, x1)\" value: 0.18181918706640812\n", + "Constraint \"IC_f_smoothness_strong_convexity(x0, xs)\" value: 1.094540561619177e-06\n", + "Constraint \"IC_f_smoothness_strong_convexity(x0, x1)\" value: 0.8181812237581545\n", + "Constraint \"IC_f_smoothness_strong_convexity(x1, xs)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_strong_convexity(x1, x0)\" value: 0.0\n", + "Constraint \"exact_linesearch(f)_to_x1_from_x0\" value: 0.9999991045753641\n", + "Constraint \"exact_linesearch(f)_to_x1_from_x0_in_direction_grad(x0)\" value: 1.8181806454530542\n" + ] + }, + { + "data": { + "text/plain": [ + "0.6694218823210809" + ] + }, + "execution_count": 32, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# verify that LS provides the appropriate step-size strategy!\n", + "L, mu = 1, .1\n", + "pepit_tau, list_of_constraints, dual_tab, dual_residual = wc_gradient_ELS(L=L,mu=mu)\n", + "\n", + "for i, constraint in enumerate(list_of_constraints):\n", + " print('Constraint \\\"{}\\\" value: {}'.format(constraint.get_name(),\n", + " constraint._dual_variable_value))\n", + " \n", + "pepit_tau" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1.8181822734982545" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "list_of_constraints[-1]._dual_variable_value/list_of_constraints[-2]._dual_variable_value\n", + "# simplify + resolve!\n" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# proof using SymPy!" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "### 2.3. Algorithm design for nonsmooth convex minimization\n", + "\n", + "The goal of this section is to apply the same technique to the class of (possibly nonsmooth) convex optimization problems\n", + "$$ \\min_{x\\in\\mathbb{R}^d} f(x),$$\n", + "where $f$ is convex and $M$-Lipschitz: $|f(x)-f(y)|\\leq M\\|x-y\\|_2$ for all $x,y\\in\\mathbb{R}^d$ (i.e., for all $x\\in\\mathbb{R}^d$ and $g_i\\in\\partial f(x)$ we have $\\|g_i\\|_2\\leq M$).\n", + "\n", + "**Differentiable $f$.** Assume for now that $f$ is differentiable. To design a (hopefully optimal) algorithm for this class, we start by analyzing the following \"conjugate gradient-like\" method:\n", + "\n", + "$$ x_{k+1} \\in \\mathrm{argmin}_x\\big\\{ f(x)\\,:\\, x\\in x_0+\\mathrm{span}\\{\\nabla f(x_0),\\nabla f(x_1),\\ldots,\\nabla f(x_k)\\}\\big\\}.$$\n", + "\n", + "To implement this algorithm within PEPit, we use the [line-search](https://pepit.readthedocs.io/en/latest/api/steps.html#exact-line-search-step) operation, which essentially enforces orthogonality between consecutive search directions and gradients.\n", + "That is, it imposes the following constraints (which we directly associate with dual variables):\n", + "$$\\begin{aligned}\n", + "&\\langle \\nabla f(x_i), \\nabla f(x_j)\\rangle=0, &&\\text{for all } 0\\leq j == 0\n", + " beta_table = np.zeros((n+1, n+1)) # beta[i,j]: dual of == 0\n", + "\n", + " for name, val in dual_vals.items():\n", + " # gamma[i,i]: \"exact_linesearch(f)_to_xi_from_x0\" <-> == 0\n", + " m = re.match(r'exact_linesearch\\(f\\)_to_x(\\d+)_from_x0$', name)\n", + " if m:\n", + " i = int(m.group(1))\n", + " gamma_table[i, i] = val\n", + " continue\n", + "\n", + " # gamma[i,j]: \"exact_linesearch(f)_to_xi_from_x0_in_direction_xj-x0\" <-> == 0\n", + " m = re.match(r'exact_linesearch\\(f\\)_to_x(\\d+)_from_x0_in_direction_x(\\d+)-x0', name)\n", + " if m:\n", + " i, j = int(m.group(1)), int(m.group(2))\n", + " gamma_table[i, j] = val\n", + " continue\n", + "\n", + " # beta[i,j]: \"exact_linesearch(f)_to_xi_from_x0_in_direction_grad(xj)\" <-> == 0\n", + " m = re.match(r'exact_linesearch\\(f\\)_to_x(\\d+)_from_x0_in_direction_grad\\(x(\\d+)\\)', name)\n", + " if m:\n", + " i, j = int(m.group(1)), int(m.group(2))\n", + " beta_table[i, j] = val\n", + "\n", + " print(\"gamma[i,j] table (dual of == 0):\")\n", + " print(np.array2string(gamma_table, precision=4, suppress_small=True))\n", + " print(\"\\nbeta[i,j] table (dual of == 0):\")\n", + " print(np.array2string(beta_table, precision=4, suppress_small=True))\n", + "\n", + " return gamma_table, beta_table " + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "#### 2.3.2. Experimenting with small time horizons ($n=1,2$)\n", + "\n", + "Experiment with $n=1,2$: what do you observe? Can you find algorithms (without line or span searches) that match the performance of the greedy conjugate gradient algorithm?" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.5773502841545819, 0.5773502691896258]" + ] + }, + "execution_count": 37, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Experiment here\n", + "M, R = 1, 1\n", + "n = 2\n", + "\n", + "pepit_tau, list_of_constraints, pep_prob = wc_conjugate_gradient_nonsmooth(M, R, n, verbose=0)\n", + "\n", + "[pepit_tau, 1/np.sqrt(n+1)]" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Experiment here, implement the corresponding algorithms: are the specific guarantees for those algorithms matching those of the conjugate gradient-like method?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### 2.3.3. General $n$\n", + "\n", + "The following lines deactivate the appropriate constraints and allow us to recover the corresponding tables of multipliers $\\beta_{i,j}$ and $\\gamma_{i,j}$." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gamma[i,j] table (dual of == 0):\n", + "[[ 0. 0. 0. 0. 0. ]\n", + " [ 0. 0.4 0. 0. 0. ]\n", + " [ 0. -0.4 0.6 0. 0. ]\n", + " [ 0. 0. -0.6 0.8 0. ]\n", + " [ 0. 0. 0. -0.8 1. ]]\n", + "\n", + "beta[i,j] table (dual of == 0):\n", + "[[0. 0. 0. 0. 0. ]\n", + " [0.0894 0. 0. 0. 0. ]\n", + " [0.0894 0.0894 0. 0. 0. ]\n", + " [0.0894 0.0894 0.0894 0. 0. ]\n", + " [0.0894 0.0894 0.0894 0.0894 0. ]]\n" + ] + } + ], + "source": [ + "M, R = 1, 1\n", + "n = 4\n", + "\n", + "pepit_tau, list_of_constraints, pep_prob = wc_conjugate_gradient_nonsmooth(M, R, n, verbose=0)\n", + "\n", + "helper_non_smooth_constraint_deactivation(list_of_constraints)\n", + "\n", + "verbose = 0\n", + "pep_prob.solve(verbose=verbose)\n", + "\n", + "if verbose:\n", + " for i, constraint in enumerate(list_of_constraints):\n", + " print('Constraint \\\"{}\\\" value: {}'.format(constraint.get_name(),\n", + " constraint._dual_variable_value))\n", + " \n", + "gamma_table, beta_table = helper_extract_betas_gammas(list_of_constraints)" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "tags": [] + }, + "source": [ + "Propose and implement an algorithm that performs as well as CG!" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "# Your answer here" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gamma[i,j] table (dual of == 0):\n", + "[[ 0. 0. 0. 0. 0.]\n", + " [ 0. 2. 0. 0. 0.]\n", + " [ 0. -2. 3. 0. 0.]\n", + " [ 0. 0. -3. 4. 0.]\n", + " [ 0. 0. 0. -4. 5.]]\n", + "\n", + "beta[i,j] table (dual of == 0):\n", + "[[0. 0. 0. 0. 0.]\n", + " [1. 0. 0. 0. 0.]\n", + " [1. 1. 0. 0. 0.]\n", + " [1. 1. 1. 0. 0.]\n", + " [1. 1. 1. 1. 0.]]\n" + ] + } + ], + "source": [ + "scaled_gamma_table = (n+1) * gamma_table\n", + "scaled_beta_table = (n+1)**(3/2) * beta_table\n", + "\n", + "print(\"gamma[i,j] table (dual of == 0):\")\n", + "print(np.array2string(scaled_gamma_table, precision=2, suppress_small=True))\n", + "print(\"\\nbeta[i,j] table (dual of == 0):\")\n", + "print(np.array2string(scaled_beta_table, precision=2, suppress_small=True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "It therefore looks like we can collect the coefficients and obtain clean constraints:\n", + "\n", + "Denote by $g_i\\in\\partial f(x_i)$; then all methods satisfying\n", + "$$ 0=\\left< g_i,x_i-\\left[\\frac{i}{i+1}x_{i-1}+\\frac{1}{i+1}x_0-\\frac{1}{i+1} \\frac{R}{M\\sqrt{n+1}}\\sum_{j=0}^{i-1}g_j \\right]\\right> $$ \n", + "benefit from the worst-case guarantee\n", + "$$ f(x_n)-f_\\star \\leq \\frac{M R}{\\sqrt{n+1}}.$$ \n", + "Below is a PEPit implementation of the resulting algorithm (sometimes referred to as a \"quasi-monotone subgradient method\" [7], since the worst-case guarantee is monotone)." + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "def wc_quasi_monotone(M, R, n, verbose=0):\n", + "\n", + " # Instantiate PEP\n", + " problem = PEP()\n", + "\n", + " # Declare a smooth convex function\n", + " func = problem.declare_function(ConvexLipschitzFunction, M=M, name=\"f\")\n", + "\n", + " # Start by defining its unique optimal point xs = x_* and corresponding function value fs = f_*\n", + " xs = func.stationary_point(name=\"xs\")\n", + " fs = func(xs)\n", + "\n", + " # Then define the starting point x0 of the algorithm\n", + " x0 = problem.set_initial_point(name=\"x0\")\n", + "\n", + " # Set the initial constraint that is the distance between x0 and x_*\n", + " problem.set_initial_condition((x0 - xs) ** 2 <= R**2)\n", + "\n", + " # Run n steps of the Conjugate Gradient method\n", + " x = x0\n", + " di = 0*x0\n", + " for i in range(n):\n", + " yi = (i+1)/(i+2)* x + 1/(i+2) * x0\n", + " di = ((i+1) * di + func.gradient(x))/(i+2)\n", + " x = yi - di / np.sqrt(n+1) / M * R\n", + "\n", + " # Set the performance metric to the function value accuracy\n", + " problem.set_performance_metric(func(x) - fs)\n", + "\n", + " # Solve the PEP\n", + " pepit_verbose = max(verbose, 0)\n", + " pepit_tau = problem.solve(verbose=pepit_verbose)\n", + " list_of_constraints = problem._list_of_prepared_constraints\n", + "\n", + " return pepit_tau, list_of_constraints, problem" + ] + }, + { + "cell_type": "code", + "execution_count": 43, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[0.30150888838186735, 0.30151276363482477]" + ] + }, + "execution_count": 43, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Verify numerical values\n", + "\n", + "M, R, n = 1, 1, 10\n", + "pepit_tau1, _, _ = wc_quasi_monotone(M,R,n)\n", + "pepit_tau2, _, _ = wc_conjugate_gradient_nonsmooth(M, R, n)\n", + "\n", + "[pepit_tau1, pepit_tau2]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. Back to smooth convex minimization: the optimized gradient method \n", + "\n", + "The goal of this section is to apply the same technique to the class of smooth convex optimization problems\n", + "$$ \\min_{x\\in\\mathbb{R}^d} f(x),$$\n", + "\n", + "and to verify that the resulting algorithm matches the optimized gradient method (OGM) [1, 3].\n", + "\n", + "### 3.1. Conjugate gradient method" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": { + "tags": [] + }, + "outputs": [], + "source": [ + "from math import sqrt\n", + "\n", + "from PEPit import PEP\n", + "from PEPit.functions import SmoothConvexFunction\n", + "from PEPit.primitive_steps import exact_linesearch_step\n", + "\n", + "def wc_conjugate_gradient(L, n, verbose=1):\n", + "\n", + " # Instantiate PEP\n", + " problem = PEP()\n", + "\n", + " # Declare a smooth convex function\n", + " func = problem.declare_function(SmoothConvexFunction, L=L, name=\"f\")\n", + "\n", + " # Start by defining its unique optimal point xs = x_* and corresponding function value fs = f_*\n", + " xs = func.stationary_point(name=\"xs\")\n", + " fs = func(xs)\n", + "\n", + " # Then define the starting point x0 of the algorithm\n", + " x0 = problem.set_initial_point(name=\"x0\")\n", + "\n", + " # Set the initial constraint that is the distance between x0 and x_*\n", + " problem.set_initial_condition((x0 - xs) ** 2 <= 1)\n", + "\n", + " # Run n steps of the Conjugate Gradient method\n", + " x_new = x0\n", + " g0, f0 = func.oracle(x0)\n", + " g0.set_name(\"grad(x0)\")\n", + " span = [g0] # list of search directions\n", + " for i in range(n):\n", + " x_old = x_new\n", + " x_new, gx, fx = exact_linesearch_step(x_new, func, span)\n", + " x_new.set_name('x{}'.format(i+1))\n", + " gx.set_name('grad(x{})'.format(i+1))\n", + " span.append(gx)\n", + " newdir = x_old - x_new\n", + " newdir.set_name('{}-{}'.format(x_old.get_name(), x_new.get_name()))\n", + " span.append(newdir)\n", + "\n", + " # Set the performance metric to the function value accuracy\n", + " problem.set_performance_metric(fx - fs)\n", + "\n", + " # Solve the PEP\n", + " pepit_verbose = max(verbose, 0)\n", + " pepit_tau = problem.solve(verbose=pepit_verbose)\n", + " list_of_constraints = problem._list_of_prepared_constraints\n", + "\n", + " return pepit_tau, list_of_constraints\n", + "\n", + "## HELPER" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Experiment with small values of the time horizon (e.g., $n=1,2$) and try to come up with the coefficients of the method." + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(PEPit) Setting up the problem: performance measure is the minimum of 1 element(s)\n", + "(PEPit) Setting up the problem: Adding initial conditions and general constraints ...\n", + "(PEPit) Setting up the problem: initial conditions and general constraints (1 constraint(s) added)\n", + "(PEPit) Setting up the problem: interpolation conditions for 1 function(s)\n", + "\t\t\tFunction 1 : Adding 12 scalar constraint(s) ...\n", + "\t\t\tFunction 1 : 12 scalar constraint(s) added\n", + "(PEPit) Setting up the problem: additional constraints for 1 function(s)\n", + "\t\t\tFunction 1 : Adding 6 scalar constraint(s) ...\n", + "\t\t\tFunction 1 : 6 scalar constraint(s) added\n", + "(PEPit) Setting up the problem: size of the Gram matrix: 7x7\n", + "(PEPit) Compiling SDP\n", + "(PEPit) Calling SDP solver\n", + "(PEPit) Solver status: optimal (wrapper:cvxpy, solver: SCS); optimal value: 0.06189419366127077\n", + "(PEPit) Primal feasibility check:\n", + "\t\tThe solver found a Gram matrix that is positive semi-definite\n", + "\t\tAll the primal scalar constraints are verified up to an error of 8.378963981675591e-08\n", + "(PEPit) Dual feasibility check:\n", + "\t\tThe solver found a residual matrix that is positive semi-definite up to an error of 9.336887945304327e-16\n", + "\t\tAll the dual scalar values associated with inequality constraints are nonnegative\n", + "(PEPit) The worst-case guarantee proof is perfectly reconstituted up to an error of 2.3806796613766964e-07\n", + "(PEPit) Final upper bound (dual): 0.061894195562268266 and lower bound (primal example): 0.06189419366127077 \n", + "(PEPit) Duality gap: absolute: 1.9009974983053013e-09 and relative: 3.0713664495072307e-08\n" + ] + } + ], + "source": [ + "L = 1\n", + "n = 2\n", + "\n", + "pepit_tau, list_of_constraints = wc_conjugate_gradient(L, n, verbose=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 46, + "metadata": { + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Constraint \"Performance metric 1\" value: 1.000000000000051\n", + "Constraint \"Initial condition\" value: 0.061894195562268266\n", + "Constraint \"IC_f_smoothness_convexity(xs, x0)\" value: 0.24757674917197692\n", + "Constraint \"IC_f_smoothness_convexity(xs, x1)\" value: 0.40058761240389895\n", + "Constraint \"IC_f_smoothness_convexity(xs, x2)\" value: 0.35183569826399763\n", + "Constraint \"IC_f_smoothness_convexity(x0, xs)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_convexity(x0, x1)\" value: 0.2475767204719302\n", + "Constraint \"IC_f_smoothness_convexity(x0, x2)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_convexity(x1, xs)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_convexity(x1, x0)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_convexity(x1, x2)\" value: 0.6481642905905665\n", + "Constraint \"IC_f_smoothness_convexity(x2, xs)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_convexity(x2, x0)\" value: 0.0\n", + "Constraint \"IC_f_smoothness_convexity(x2, x1)\" value: 0.0\n", + "Constraint \"exact_linesearch(f)_to_new_x_from_x0\" value: 0.6481643368849084\n", + "Constraint \"exact_linesearch(f)_to_new_x_from_x0_in_direction_grad(x0)\" value: 1.048751940770472\n", + "Constraint \"exact_linesearch(f)_to_new_x_from_x1\" value: 1.0000000014726265\n", + "Constraint \"exact_linesearch(f)_to_new_x_from_x1_in_direction_grad(x0)\" value: 0.7036714565770738\n", + "Constraint \"exact_linesearch(f)_to_new_x_from_x1_in_direction_grad(x1)\" value: 1.7867285711947292\n", + "Constraint \"exact_linesearch(f)_to_new_x_from_x1_in_direction_x0-x1\" value: -0.35183573177467986\n" + ] + } + ], + "source": [ + "for i, constraint in enumerate(list_of_constraints):\n", + " print('Constraint \\\"{}\\\" value: {}'.format(constraint.get_name(),\n", + " constraint._dual_variable_value))" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [], + "source": [ + "# Experiment and identify a relevent pattern of active inequalities (for small values of $n$ and $L=1$)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.2. The optimized gradient method (OGM)\n", + "\n", + "Verify that the same worst-case guarantees are valid for the following algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": 48, + "metadata": {}, + "outputs": [], + "source": [ + "from math import sqrt\n", + "\n", + "from PEPit import PEP\n", + "from PEPit.functions import SmoothConvexFunction\n", + "\n", + "def wc_optimized_gradient(L, n, wrapper=\"cvxpy\", solver=None, verbose=1):\n", + "\n", + " # Instantiate PEP\n", + " problem = PEP()\n", + "\n", + " # Declare a smooth convex function\n", + " func = problem.declare_function(SmoothConvexFunction, L=L)\n", + "\n", + " # Start by defining its unique optimal point xs = x_* and corresponding function value fs = f_*\n", + " xs = func.stationary_point()\n", + " fs = func(xs)\n", + "\n", + " # Then Define the starting point of the algorithm\n", + " x0 = problem.set_initial_point()\n", + "\n", + " # Set the initial constraint that is the distance between x0 and x^*\n", + " problem.set_initial_condition((x0 - xs) ** 2 <= 1)\n", + "\n", + " # Run n steps of the optimized gradient method (OGM) method\n", + " theta_new = 1\n", + " x_new = x0\n", + " y = x0\n", + " for i in range(n):\n", + " x_old = x_new\n", + " x_new = y - 1 / L * func.gradient(y)\n", + " theta_old = theta_new\n", + " if i < n - 1:\n", + " theta_new = (1 + sqrt(4 * theta_new ** 2 + 1)) / 2\n", + " else:\n", + " theta_new = (1 + sqrt(8 * theta_new ** 2 + 1)) / 2\n", + "\n", + " y = x_new + (theta_old - 1) / theta_new * (x_new - x_old) + theta_old / theta_new * (x_new - y)\n", + "\n", + " # Set the performance metric to the function value accuracy\n", + " problem.set_performance_metric(func(y) - fs)\n", + "\n", + " # Solve the PEP\n", + " pepit_verbose = max(verbose, 0)\n", + " pepit_tau = problem.solve(wrapper=wrapper, solver=solver, verbose=pepit_verbose)\n", + "\n", + " # Compute theoretical guarantee (for comparison)\n", + " theoretical_tau = L / (2 * theta_new ** 2)\n", + "\n", + " # Return the worst-case guarantee of the evaluated method (and the reference theoretical value)\n", + " return pepit_tau, theoretical_tau" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What we do next is to *unroll* the algorithms (the conjugate-gradient-based one and the optimized gradient method) to form their respective coefficient matrices $\\{h_{i,j}\\}$:\n", + "$$\\begin{aligned}\n", + "x_1 &= x_0 - h_{1,0} \\nabla f(x_0),\\\\\n", + "x_2 &= x_1 - h_{2,0} \\nabla f(x_0) - h_{2,1} \\nabla f(x_1),\\\\\n", + "x_3 &= x_2 - h_{3,0} \\nabla f(x_0) - h_{3,1} \\nabla f(x_1) - h_{3,2} \\nabla f(x_2),\\\\\n", + "&\\vdots\\\\\n", + "x_N &= x_{N-1} - \\sum_{i=0}^{N-1} h_{N,i} \\nabla f(x_i),\n", + "\\end{aligned}$$\n", + "and compare them to check that they match." + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "def plot_ogm_step_sizes(step_sizes):\n", + " \"\"\"\n", + " Compare a user-provided lower-triangular coefficient table against the\n", + " unrolled OGM coefficients from Kim--Fessler (equations (6.16) and (7.1)).\n", + "\n", + " Args:\n", + " step_sizes (array-like): either\n", + " - a list of rows, where row i contains [h_{i+1,0}, ..., h_{i+1,i}], or\n", + " - a square lower-triangular array whose upper triangle is ignored.\n", + "\n", + " Returns:\n", + " np.ndarray: the OGM coefficient table as an n x n lower-triangular array.\n", + " \"\"\"\n", + " if isinstance(step_sizes, np.ndarray):\n", + " user_steps = np.asarray(step_sizes, dtype=float)\n", + " else:\n", + " user_steps = np.asarray(step_sizes, dtype=object)\n", + "\n", + " if user_steps.ndim == 2 and user_steps.shape[0] == user_steps.shape[1]:\n", + " n = user_steps.shape[0]\n", + " user_table = np.full((n, n), np.nan)\n", + " for i in range(n):\n", + " user_table[i, :i + 1] = user_steps[i, :i + 1]\n", + " else:\n", + " n = len(step_sizes)\n", + " user_table = np.full((n, n), np.nan)\n", + " for i, row in enumerate(step_sizes):\n", + " row = np.asarray(row, dtype=float).ravel()\n", + " if row.size != i + 1:\n", + " raise ValueError(\n", + " f\"Row {i} should contain {i + 1} coefficients, got {row.size}.\"\n", + " )\n", + " user_table[i, :i + 1] = row\n", + "\n", + " theta = [1.0]\n", + " for i in range(n):\n", + " if i < n - 1:\n", + " theta.append((1 + np.sqrt(1 + 4 * theta[-1] ** 2)) / 2)\n", + " else:\n", + " theta.append((1 + np.sqrt(1 + 8 * theta[-1] ** 2)) / 2)\n", + "\n", + " ogm_table = np.full((n, n), np.nan)\n", + " previous_row = None\n", + " for i in range(n):\n", + " row = np.zeros(i + 1)\n", + " if i >= 2:\n", + " row[:i - 1] = (theta[i] - 1) / theta[i + 1] * previous_row[:i - 1]\n", + " if i >= 1:\n", + " row[i - 1] = (theta[i] - 1) / theta[i + 1] * (previous_row[i - 1] - 1)\n", + " row[i] = 1 + (2 * theta[i] - 1) / theta[i + 1]\n", + " ogm_table[i, :i + 1] = row\n", + " previous_row = row\n", + "\n", + " cmap = plt.cm.viridis.copy()\n", + " cmap.set_bad(color=\"white\")\n", + " values = np.concatenate((user_table[~np.isnan(user_table)], ogm_table[~np.isnan(ogm_table)]))\n", + " vmin, vmax = values.min(), values.max()\n", + "\n", + " fig, axes = plt.subplots(1, 2, figsize=(12, 4), constrained_layout=True)\n", + " for ax, table, title in zip(axes, [user_table, ogm_table], [\"Your coefficients\", \"OGM coefficients\"]):\n", + " im = ax.imshow(table, origin=\"upper\", aspect=\"auto\", cmap=cmap, vmin=vmin, vmax=vmax)\n", + " ax.set_title(title)\n", + " ax.set_xlabel(r\"Gradient index $j$\")\n", + " ax.set_ylabel(r\"Step $i+1$\")\n", + " ax.set_xticks(range(n))\n", + " ax.set_yticks(range(n))\n", + " ax.set_yticklabels(range(1, n + 1))\n", + "\n", + " fig.colorbar(im, ax=axes, shrink=0.85, label=r\"Coefficient $h_{i,j}$\")\n", + " return ogm_table" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [ + { + "ename": "AttributeError", + "evalue": "'ListedColormap' object has no attribute 'copy'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 8\u001b[0m ]\n\u001b[1;32m 9\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 10\u001b[0;31m \u001b[0mplot_ogm_step_sizes\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0myour_step_sizes\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m;\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mplot_ogm_step_sizes\u001b[0;34m(step_sizes)\u001b[0m\n\u001b[1;32m 54\u001b[0m \u001b[0mprevious_row\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mrow\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 55\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 56\u001b[0;31m \u001b[0mcmap\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcm\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mviridis\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcopy\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 57\u001b[0m \u001b[0mcmap\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mset_bad\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcolor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"white\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 58\u001b[0m \u001b[0mvalues\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mconcatenate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0muser_table\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m~\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misnan\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0muser_table\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mogm_table\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m~\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0misnan\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mogm_table\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mAttributeError\u001b[0m: 'ListedColormap' object has no attribute 'copy'" + ] + } + ], + "source": [ + "# Put your unrolled coefficients here as a lower-triangular list of rows,\n", + "# for example [[h_1,0], [h_2,0, h_2,1], [h_3,0, h_3,1, h_3,2]].\n", + "# Then run this cell to compare them with the OGM coefficients.\n", + "\n", + "your_step_sizes = [\n", + " [1.0],\n", + " [1.0, 1.0],\n", + "]\n", + "\n", + "plot_ogm_step_sizes(your_step_sizes);" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.3. Missing details.\n", + "\n", + "In order to *construct* these numerical algorithms, one often simply observes the coefficient matrices $\\{h_{i,j}\\}$ (think of what would happen if you were actually solving the minimax optimization problem over the coefficients of the algorithm).\n", + "In this case, one typically resorts to additional numerical experiments aimed at understanding the structural properties of the algorithm:\n", + "- what are the key inequalities to be used in the corresponding worst-case analysis?\n", + "- Can the method be *factorized* in a nice way (that does not require storing all past gradients)?\n", + "- Can we identify a compact proof (e.g., Lyapunov-based) that would make the analysis simpler?\n", + "\n", + "It turns out that the answers to these questions are, perhaps luckily, relatively nice in the case of the optimized gradient method.\n", + "We illustrate a potential/Lyapunov-based analysis of OGM below." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### 3.4. Potential/Lyapunov proof for the optimized gradient method\n", + "\n", + "Use PEPit to verify the following inequality for the iterates of the optimized gradient method (for simplicity, assume $k\n", + "\n", + "- Algorithm design based on nonlinear optimization; see, for instance, [8, 9]. Recent algorithms designed using such techniques include those in [9, 10, 12].\n", + "A few easy-to-interface solvers may also become available through CVXPY (e.g., [DNLP](https://github.com/cvxgrp/dnlp-examples/blob/main/nlp_examples/trimmed_log_reg.py)).\n", + "- Design based on convex relaxations, see, e.g., constructions in [1, 3, 11].\n", + "- Algebraic techniques for solving linear matrix inequalities (more to come!)\n", + "\n", + "---\n", + "\n", + "### References\n", + "\n", + "[1] Y. Drori and M. Teboulle, *Performance of first-order methods for smooth convex minimization: a novel approach*, Mathematical Programming, 2014. [arXiv:1206.3209](https://arxiv.org/abs/1206.3209)\n", + "\n", + "[2] A. B. Taylor, J. M. Hendrickx, and F. Glineur, *Smooth strongly convex interpolation and exact worst-case performance of first-order methods*, Mathematical Programming, 2017. [arXiv:1502.05666](https://arxiv.org/abs/1502.05666)\n", + "\n", + "[3] D. Kim and J. A. Fessler, *Optimized first-order methods for smooth convex minimization*, Mathematical Programming, 2016. [arXiv:1406.5468](https://arxiv.org/abs/1406.5468)\n", + "\n", + "[4] J. M. Altschuler and P. A. Parrilo, *Acceleration by Stepsize Hedging I: Multi-Step Descent and the Silver Stepsize Schedule*, Journal of the ACM, 2025. [arXiv:2309.07879](https://arxiv.org/abs/2309.07879)\n", + "\n", + "[5] Y. Drori and A. B. Taylor, *Efficient first-order methods for convex minimization: a constructive approach*, Mathematical Programming, 2020. [arXiv:1803.05676](https://arxiv.org/abs/1803.05676)\n", + "\n", + "[6] E. De Klerk, F. Glineur, A. B. Taylor, *On the worst-case complexity of the gradient method with exact line search for smooth strongly convex functions*, Optimization Letters, 2017. [arXiv:1606.09365](https://arxiv.org/abs/1606.09365)\n", + "\n", + "[7] Y. Nesterov and V. Shikhman, *Quasi-monotone subgradient methods for nonsmooth convex minimization*, Journal of Optimization Theory and Applications, 2015.\n", + "\n", + "[8] Y. Kamri, J. M. Hendrickx, and F. Glineur, *Numerical Design of Optimized First-Order Algorithms*, 2025. [arXiv:2507.20773](https://arxiv.org/abs/2507.20773)\n", + "\n", + "[9] S. Das Gupta, B. P. G. Van Parys, and E. K. Ryu, *Branch-and-bound performance estimation programming: a unified methodology for constructing optimal optimization methods*, Mathematical Programming, 2024. [arXiv:2203.07305](https://arxiv.org/abs/2203.07305)\n", + "\n", + "[10] D. Kim and J. A. Fessler, *Optimizing the efficiency of first-order methods for decreasing the gradient of smooth convex functions*, Journal of Optimization Theory and Applications, 2021. [arXiv:1803.06600](https://arxiv.org/abs/1803.06600)\n", + "\n", + "[11] Y. Drori and A. B. Taylor, *An optimal gradient method for smooth strongly convex minimization*, Mathematical Programming, 2022. [arXiv:2101.09741](https://arxiv.org/abs/2101.09741)\n", + "\n", + "[12] U. Jang, S. Das Gupta, and E. K. Ryu, *Computer-Assisted Design of Accelerated Composite Optimization Methods: OptISTA*, Mathematical Programming, 2025. [arXiv:2305.15704](https://arxiv.org/abs/2305.15704)" + ] + } + ], + "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.7.9" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}