{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# DGP fundamentals\n",
    "\n",
    "This notebook will introduce you to the fundamentals of **disciplined geometric programming** (**DGP**), which lets you formulate and solve *log-log convex programs* (LLCPs) in CVXPY.\n",
    "\n",
    "LLCPs are problems that become convex after the variables, objective functions, and constraint functions are replaced with their logs, an operation that we refer to as a log-log transformation. LLCPs generalize geometric programming."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cvxpy as cp"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. Log-log curvature\n",
    "\n",
    "Just as every Expression in CVXPY has a curvature (constant, affine, convex, concave, or unknown), every Expression also has a log-log curvature.\n",
    "\n",
    "A function $f : D \\subseteq \\mathbf{R}^{n}_{++} \\to \\mathbf{R}$ is said to be **log-log convex** if the function $F(u)=\\log f(e^u)$, with domain $\\{u \\in \\mathbf{R}^n : e^u \\in D\\}$, is convex (where $\\mathbf{R}^{n}_{++}$ denotes the set of positive reals and the logarithm and exponential are meant elementwise); the function $F$ is called the log-log transformation of $f$. The function $f$ is **log-log concave** if $F$ is concave, and it is **log-log affine** if $F$ is affine.\n",
    "\n",
    "Notice that if a function has log-log curvature, then its domain and range can only include positive numbers.\n",
    "\n",
    "The log-log curvature of an `Expression` can be accessed via its `.log_log_curvature` attribute. For an `Expression` to have known log-log curvature, all of the `Constant`s, `Variable`s, and `Parameter`s it refers to must be elementwise positive."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1.0 LOG-LOG CONSTANT\n",
      "[1. 2.] LOG-LOG CONSTANT\n",
      "[1. 0.] UNKNOWN\n",
      "-2.0 UNKNOWN\n"
     ]
    }
   ],
   "source": [
    "# Only elementwise positive constants are allowed in DGP.\n",
    "c = cp.Constant(1.0)\n",
    "print(c, c.log_log_curvature)\n",
    "\n",
    "c = cp.Constant([1.0, 2.0])\n",
    "print(c, c.log_log_curvature)\n",
    "\n",
    "c = cp.Constant([1.0, 0.0])\n",
    "print(c, c.log_log_curvature)\n",
    "\n",
    "c = cp.Constant(-2.0)\n",
    "print(c, c.log_log_curvature)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "var0 LOG-LOG AFFINE\n",
      "var1 UNKNOWN\n",
      "param2 LOG-LOG CONSTANT\n",
      "param3 UNKNOWN\n"
     ]
    }
   ],
   "source": [
    "# Variables and parameters must be positive, ie, they must be constructed with the option `pos=True`\n",
    "v = cp.Variable(pos=True)\n",
    "print(v, v.log_log_curvature)\n",
    "\n",
    "v = cp.Variable()\n",
    "print(v, v.log_log_curvature)\n",
    "\n",
    "p = cp.Parameter(pos=True)\n",
    "print(p, p.log_log_curvature)\n",
    "\n",
    "p = cp.Parameter()\n",
    "print(p, p.log_log_curvature)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Functions from geometric programming\n",
    "\n",
    "A function $f(x)$ is log-log affine if and only if it is given by\n",
    "\n",
    "$$\n",
    "f(x) = cx_1^{a_1}x_2^{a_2} \\ldots x_n^{a_n},\n",
    "$$\n",
    "\n",
    "where $c > 0$ and the $a_i$ are real numbers. In the context of geometric programming, such a function is called\n",
    "a monomial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0 * power(x[0], 1/2) * power(x[1], 2) * power(x[2], 9/5) : LOG-LOG AFFINE\n"
     ]
    }
   ],
   "source": [
    "x = cp.Variable(shape=(3,), pos=True, name=\"x\")\n",
    "c = 2.0\n",
    "a = [0.5, 2.0, 1.8]\n",
    "\n",
    "monomial = c * x[0] ** a[0] * x[1] ** a[1] * x[2] ** a[2]\n",
    "# Monomials are not convex.\n",
    "assert not monomial.is_convex()\n",
    "\n",
    "# They are, however, log-log affine.\n",
    "print(monomial, \":\", monomial.log_log_curvature)\n",
    "assert monomial.is_log_log_affine()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A sum of monomial functions is log-log convex; in the context of geometric programming, such a function is called a posynomial. There are functions that are not posynomials that are still log-log convex."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0 : LOG-LOG CONSTANT\n",
      "2.0 * x * y : LOG-LOG AFFINE\n",
      "2.0 * x * y + power(x, 3/2) * power(y, -1) : LOG-LOG CONVEX\n",
      "power(2.0 * x * y + power(x, 3/2) * power(y, -1), -1) : LOG-LOG CONCAVE\n",
      "power(2.0 * x * y + power(x, 3/2) * power(y, -1), -1) + 2.0 * x * y + power(x, 3/2) * power(y, -1) : UNKNOWN\n"
     ]
    }
   ],
   "source": [
    "x = cp.Variable(pos=True, name=\"x\")\n",
    "y = cp.Variable(pos=True, name=\"y\")\n",
    "\n",
    "constant = cp.Constant(2.0)\n",
    "monomial = constant * x * y\n",
    "posynomial = monomial + (x ** 1.5) * (y ** -1)\n",
    "reciprocal = posynomial ** -1\n",
    "unknown = reciprocal + posynomial\n",
    "\n",
    "print(constant, \":\", constant.log_log_curvature)\n",
    "print(monomial, \":\", monomial.log_log_curvature)\n",
    "print(posynomial, \":\", posynomial.log_log_curvature)\n",
    "print(reciprocal, \":\", reciprocal.log_log_curvature)\n",
    "print(unknown, \":\", unknown.log_log_curvature)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 2. Log-log curvature ruleset\n",
    "\n",
    "CVXPY has a library of atomic functions with known log-log curvature and monotonicity. It uses this information to\n",
    "tag every `Expression`, i.e., every composition of atomic functions, with a log-log curvature. In particular, \n",
    "\n",
    "A function $f(expr_1,expr_2,...,expr_n)$ is log-log convex if  $f$ is a log-log convex function and for each expri one of the following conditions holds:\n",
    "\n",
    "$f$ is increasing in argument i and $expr_i$ is log-log convex.\n",
    "$f$ is decreasing in argument $i$ and $expr_i$ is log-log concave.\n",
    "$expr_i$ is log-log affine.\n",
    "A function $f(expr_1,expr_2,...,expr_n)$ is log-log concave if  $f$ is a log-log concave function and for each $expr_i$ one of the following conditions holds:\n",
    "\n",
    "$f$ is increasing in argument $i$ and $expr_i$ is log-log concave.\n",
    "$f$ is decreasing in argument $i$ and $expr_i$ is log-log convex.\n",
    "$expr_i$ is log-log affine.\n",
    "A function $f(expr_1,expr_2,...,expr_n)$ is log-log affine if $f$ is an log-log affine function and each $expr_i$ is log-log affine.\n",
    "\n",
    "If none of the three rules apply, the expression $f(expr_1,expr_2,...,expr_n)$ is marked as having unknown curvature.\n",
    "\n",
    "If an Expression satisfies the composition rule, we colloquially say that the `Expression` “is DGP.” You can check whether an `Expression` is DGP by calling the method `is_dgp()`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2.0 * x * y is dgp? True\n",
      "2.0 * x * y + power(x, 3/2) * power(y, -1) is dgp? True\n"
     ]
    }
   ],
   "source": [
    "x = cp.Variable(pos=True, name=\"x\")\n",
    "y = cp.Variable(pos=True, name=\"y\")\n",
    "\n",
    "monomial = 2.0 * x * y\n",
    "posynomial = monomial + (x ** 1.5) * (y ** -1)\n",
    "\n",
    "print(monomial, \"is dgp?\", monomial.is_dgp())\n",
    "print(posynomial, \"is dgp?\", posynomial.is_dgp())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3. DGP problems\n",
    "\n",
    "An LLCP is an optimization problem of the form\n",
    "\n",
    "$$\n",
    "\\begin{equation}\n",
    "\\begin{array}{ll}\n",
    "\\mbox{minimize} & f_0(x) \\\\\n",
    "\\mbox{subject to} & f_i(x) \\leq \\tilde{f_i}, \\quad i=1, \\ldots, m\\\\\n",
    "& g_i(x) = \\tilde{g_i}, \\quad i=1, \\ldots, p,\n",
    "\\end{array}\n",
    "\\end{equation}\n",
    "$$\n",
    "\n",
    "where the functions $f_i$ are log-log convex, $\\tilde{f_i}$ are log-log concave, and the functions $g_i$ and $\\tilde{g_i}$ are log-log affine. An optimization problem with constraints of the above form in which the goal is to maximize a log-log concave function is also an LLCP.\n",
    "\n",
    "A problem is DGP if additionally all the functions are DGP. You can check whether a CVXPY `Problem` is DGP by calling\n",
    "its `.is_dgp()` method.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "maximize x * y * z\n",
      "subject to 4.0 * x * y * z + 2.0 * x * z <= 10.0\n",
      "           x <= 2.0 * y\n",
      "           y <= 2.0 * x\n",
      "           1.0 <= z\n",
      "Is this problem DGP? True\n"
     ]
    }
   ],
   "source": [
    "x = cp.Variable(pos=True, name=\"x\")\n",
    "y = cp.Variable(pos=True, name=\"y\")\n",
    "z = cp.Variable(pos=True, name=\"z\")\n",
    "\n",
    "objective_fn = x * y * z\n",
    "constraints = [\n",
    "  4 * x * y * z + 2 * x * z <= 10, x <= 2*y, y <= 2*x, z >= 1]\n",
    "assert objective_fn.is_log_log_concave()\n",
    "assert all(constraint.is_dgp() for constraint in constraints)\n",
    "problem = cp.Problem(cp.Maximize(objective_fn), constraints)\n",
    "\n",
    "print(problem)\n",
    "print(\"Is this problem DGP?\", problem.is_dgp())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Solving DGP problems\n",
    "\n",
    "You can solve a DGP `Problem` by calling its `solve` method with `gp=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Optimal value: 1.9999999926890524\n",
      "x : 0.9999999989968756\n",
      "y : 1.9999999529045318\n",
      "z : 1.000000020895385\n",
      "Dual values:  [1.1111111199586956, 1.94877846244994e-09, 0.1111111217156332, 0.11111112214962586]\n"
     ]
    }
   ],
   "source": [
    "problem.solve(gp=True)\n",
    "print(\"Optimal value:\", problem.value)\n",
    "print(x, \":\", x.value)\n",
    "print(y, \":\", y.value)\n",
    "print(z, \":\", z.value)\n",
    "print(\"Dual values: \", list(c.dual_value for c in constraints))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you forget to supply `gp=True`, an error will be raised."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Problem does not follow DCP rules. However, the problem does follow DGP rules. Consider calling this function with `gp=True`.\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    problem.solve()\n",
    "except cp.DCPError as e:\n",
    "    print(e)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 4. Next steps\n",
    "\n",
    "## Atoms\n",
    "CVXPY has a large library of log-log convex functions, including common functions like $\\exp$, $\\log$, and the difference between two numbers. Check out the tutorial on our website for the full list of atoms: https://www.cvxpy.org/tutorial/dgp/index.html\n",
    "\n",
    "## References\n",
    "For a reference on DGP, consult the following paper: https://web.stanford.edu/~boyd/papers/dgp.html"
   ]
  }
 ],
 "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.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
