Skip to content

Commit

Permalink
Basic DQCP docs, & quasiconcave curvature.
Browse files Browse the repository at this point in the history
  • Loading branch information
akshayka committed May 26, 2019
1 parent f2e9b54 commit 3762a05
Show file tree
Hide file tree
Showing 14 changed files with 958 additions and 7 deletions.
9 changes: 9 additions & 0 deletions cvxpy/expressions/expression.py
Expand Up @@ -128,6 +128,12 @@ def curvature(self):
curvature_str = s.CONVEX
elif self.is_concave():
curvature_str = s.CONCAVE
elif self.is_quasilinear():
curvature_str = s.QUASILINEAR
elif self.is_quasiconvex():
curvature_str = s.QUASICONVEX
elif self.is_quasiconcave():
curvature_str = s.QUASICONCAVE
else:
curvature_str = s.UNKNOWN
return curvature_str
Expand Down Expand Up @@ -242,6 +248,9 @@ def is_quasiconvex(self):
def is_quasiconcave(self):
return self.is_concave()

def is_quasilinear(self):
return self.is_quasiconvex() and self.is_quasiconcave()

@perf.compute_once
def is_dqcp(self):
"""Checks whether the Expression is DQCP.
Expand Down
3 changes: 3 additions & 0 deletions cvxpy/settings.py
Expand Up @@ -125,6 +125,9 @@
AFFINE = "AFFINE"
CONVEX = "CONVEX"
CONCAVE = "CONCAVE"
QUASILINEAR = "QUASILINEAR"
QUASICONVEX = "QUASICONVEX"
QUASICONCAVE = "QUASICONCAVE"
LOG_LOG_CONSTANT = "LOG-LOG CONSTANT"
LOG_LOG_AFFINE = "LOG-LOG AFFINE"
LOG_LOG_CONVEX = "LOG-LOG CONVEX"
Expand Down
13 changes: 7 additions & 6 deletions cvxpy/tests/test_curvature.py
Expand Up @@ -16,6 +16,7 @@

from cvxpy import log
from cvxpy import Constant, Variable, Parameter
from cvxpy.settings import UNKNOWN, QUASILINEAR
from nose.tools import assert_equals


Expand All @@ -36,27 +37,27 @@ def setup_class(self):

def test_add(self):
assert_equals((self.const + self.cvx).curvature, self.cvx.curvature)
assert_equals((self.unknown_curv + self.ccv).curvature, self.unknown_curv.curvature)
assert_equals((self.cvx + self.ccv).curvature, self.unknown_curv.curvature)
assert_equals((self.unknown_curv + self.ccv).curvature, UNKNOWN)
assert_equals((self.cvx + self.ccv).curvature, UNKNOWN)
assert_equals((self.cvx + self.cvx).curvature, self.cvx.curvature)
assert_equals((self.aff + self.ccv).curvature, self.ccv.curvature)

def test_sub(self):
assert_equals((self.const - self.cvx).curvature, self.ccv.curvature)
assert_equals((self.unknown_curv - self.ccv).curvature, self.unknown_curv.curvature)
assert_equals((self.unknown_curv - self.ccv).curvature, UNKNOWN)
assert_equals((self.cvx - self.ccv).curvature, self.cvx.curvature)
assert_equals((self.cvx - self.cvx).curvature, self.unknown_curv.curvature)
assert_equals((self.cvx - self.cvx).curvature, UNKNOWN)
assert_equals((self.aff - self.ccv).curvature, self.cvx.curvature)

def test_sign_mult(self):
assert_equals((self.zero * self.cvx).curvature, self.aff.curvature)
assert_equals((self.neg*self.cvx).curvature, self.ccv.curvature)
assert_equals((self.neg*self.ccv).curvature, self.cvx.curvature)
assert_equals((self.neg*self.unknown_curv).curvature, self.unknown_curv.curvature)
assert_equals((self.neg*self.unknown_curv).curvature, QUASILINEAR)
assert_equals((self.pos*self.aff).curvature, self.aff.curvature)
assert_equals((self.pos*self.ccv).curvature, self.ccv.curvature)
assert_equals((self.unknown_sign*self.const).curvature, self.const.curvature)
assert_equals((self.unknown_sign*self.ccv).curvature, self.unknown_curv.curvature)
assert_equals((self.unknown_sign*self.ccv).curvature, UNKNOWN)

def test_neg(self):
assert_equals((-self.cvx).curvature, self.ccv.curvature)
Expand Down
34 changes: 34 additions & 0 deletions cvxpy/tests/test_dqcp.py
Expand Up @@ -523,3 +523,37 @@ def test_div_const(self):
problem.solve(qcp=True)
self.assertAlmostEqual(x.value, 10, places=1)
self.assertAlmostEqual(problem.value, -20, places=1)

def test_tutorial_example(self):
x = cp.Variable()
y = cp.Variable(pos=True)
objective_fn = -cp.sqrt(x) / y
problem = cp.Problem(cp.Minimize(objective_fn), [cp.exp(x) <= y])
# smoke test
problem.solve(qcp=True)

def test_curvature(self):
x = cp.Variable(3)
expr = cp.length(x)
self.assertEqual(expr.curvature, s.QUASICONVEX)
expr = -cp.length(x)
self.assertEqual(expr.curvature, s.QUASICONCAVE)
expr = cp.ceil(x)
self.assertEqual(expr.curvature, s.QUASILINEAR)
self.assertTrue(expr.is_quasilinear())

def test_tutorial_dqcp(self):
# The sign of variables affects curvature analysis.
x = cp.Variable(nonneg=True)
concave_frac = x * cp.sqrt(x)
constraint = [cp.ceil(x) <= 10]
problem = cp.Problem(cp.Maximize(concave_frac), constraint)
self.assertTrue(concave_frac.is_quasiconcave())
self.assertTrue(constraint[0].is_dqcp())
self.assertTrue(problem.is_dqcp())

w = cp.Variable()
fn = w * cp.sqrt(w)
problem = cp.Problem(cp.Maximize(fn))
self.assertFalse(fn.is_dqcp())
self.assertFalse(problem.is_dqcp())
17 changes: 17 additions & 0 deletions doc/source/citing/index.rst
Expand Up @@ -47,3 +47,20 @@ and use the following BibTeX citation:
primaryClass = {math.OC},
year = {2018},
}


If you use CVXPY's support for disciplined quasiconvex programming, please
cite the `accompanying paper <https://web.stanford.edu/~boyd/papers/dqcp.html>`_
and use the following BibTeX citation:

::

@article{dqcp,
author = {Akshay Agrawal and Stephen Boyd},
title = {Disciplined Quasiconvex Programming},
journal = {arXiv},
archivePrefix = {arXiv},
eprint = {1905.00562},
primaryClass = {math.OC},
year = {2019},
}
75 changes: 75 additions & 0 deletions doc/source/examples/dqcp/concave_fractional_function.rst
@@ -0,0 +1,75 @@

Fractional optimization
=======================

This notebook shows how to solve a simple *concave fractional problem*,
in which the objective is to maximize the ratio of a nonnegative concave
function and a positive convex function. Concave fractional problems are
quasiconvex programs (QCPs). They can be specified using disciplined
quasiconvex programming
(`DQCP <https://www.cvxpy.org/tutorial/dqcp/index.html>`__), and hence
can be solved using CVXPY.

.. code::
!pip install --upgrade cvxpy
.. code::
import cvxpy as cp
import numpy as np
import matplotlib.pyplot as plt
Our goal is to minimize the function

.. math:: \frac{\sqrt{x}}{\exp(x)}.

This function is not concave, but it is quasiconcave, as can be seen by
inspecting its graph.

.. code::
plt.plot([np.sqrt(y) / np.exp(y) for y in np.linspace(0, 10)])
plt.show()
.. image:: concave_fractional_function_files/concave_fractional_function_4_0.png


The below code specifies and solves the QCP, using DQCP. The concave
fraction function is DQCP-compliant, because the ratio atom is
quasiconcave (actually, quasilinear), increasing in the numerator when
the denominator is positive, and decreasing in the denominator when the
numerator is nonnegative.

.. code::
x = cp.Variable()
concave_fractional_fn = cp.sqrt(x) / cp.exp(x)
problem = cp.Problem(cp.Maximize(concave_fractional_fn))
assert problem.is_dqcp()
problem.solve(qcp=True)
.. parsed-literal::
0.4288821220397949
.. code::
x.value
.. parsed-literal::
array(0.50000165)
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
95 changes: 95 additions & 0 deletions doc/source/examples/dqcp/minimum_length_least_squares.rst
@@ -0,0 +1,95 @@

Minimum-length least squares
============================

This notebook shows how to solve a *minimum-length least squares*
problem, which finds a minimum-length vector :math:`x \in \mathbf{R}^n`
achieving small mean-square error (MSE) for a particular least squares
problem:

.. math::
\begin{array}{ll}
\mbox{minimize} & \mathrm{len}(x) \\
\mbox{subject to} & \frac{1}{n}\|Ax - b\|_2^2 \leq \epsilon,
\end{array}
where the variable is :math:`x` and the problem data are :math:`n`,
:math:`A`, :math:`b`, and :math:`\epsilon`.

This is a quasiconvex program (QCP). It can be specified using
disciplined quasiconvex programming
(`DQCP <https://www.cvxpy.org/tutorial/dqcp/index.html>`__), and it can
therefore be solved using CVXPY.

.. code::
!pip install --upgrade cvxpy
.. code::
import cvxpy as cp
import numpy as np
The below cell constructs the problem data.

.. code::
n = 10
np.random.seed(1)
A = np.random.randn(n, n)
x_star = np.random.randn(n)
b = A @ x_star
epsilon = 1e-2
And the next cell constructs and solves the QCP.

.. code::
x = cp.Variable(n)
mse = cp.sum_squares(A @ x - b)/n
problem = cp.Problem(cp.Minimize(cp.length(x)), [mse <= epsilon])
print("Is problem DQCP?: ", problem.is_dqcp())
problem.solve(qcp=True)
print("Found a solution, with length: ", problem.value)
.. parsed-literal::
Is problem DQCP?: True
Found a solution, with length: 8.0
.. code::
print("MSE: ", mse.value)
.. parsed-literal::
MSE: 0.00926009328813662
.. code::
print("x: ", x.value)
.. parsed-literal::
x: [-2.58366030e-01 1.38434327e+00 2.10714108e-01 9.44811159e-01
-1.14622208e+00 1.51283929e-01 6.62931941e-01 -1.16358584e+00
2.78132907e-13 -1.76314786e-13]
.. code::
print("x_star: ", x_star)
.. parsed-literal::
x_star: [-0.44712856 1.2245077 0.40349164 0.59357852 -1.09491185 0.16938243
0.74055645 -0.9537006 -0.26621851 0.03261455]
8 changes: 8 additions & 0 deletions doc/source/examples/index.rst
Expand Up @@ -103,3 +103,11 @@ Disciplined Geometric Programming
- :doc:`Power control <dgp/power_control>` `[.ipynb] <https://colab.research.google.com/github/cvxgrp/cvxpy/blob/master/examples/notebooks/dgp/power_control.ipynb>`_
- :doc:`Perron-Frobenius matrix completion <dgp/pf_matrix_completion>` `[.ipynb] <https://colab.research.google.com/github/cvxgrp/cvxpy/blob/master/examples/notebooks/dgp/pf_matrix_completion.ipynb>`_
- :doc:`Rank-one nonnegative matrix factorization <dgp/rank_one_nmf>` `[.ipynb] <https://colab.research.google.com/github/cvxgrp/cvxpy/blob/master/examples/notebooks/dgp/rank_one_nmf.ipynb>`_


.. _dqcp-examples:

Disciplined Quasiconvex Programming
-----------------------------------
- :doc:`Concave fractional function <dqcp/concave_fractional_function>` `[.ipynb] <https://colab.research.google.com/github/cvxgrp/cvxpy/blob/master/examples/notebooks/dqcp/concave_fractional_function.ipynb>`_
- :doc:`Minimum-length least squares <dqcp/minimum_length_least_squares>` `[.ipynb] <https://colab.research.google.com/github/cvxgrp/cvxpy/blob/master/examples/notebooks/dqcp/minimum_length_least_squares.ipynb>`_
3 changes: 2 additions & 1 deletion doc/source/index.rst
Expand Up @@ -59,9 +59,10 @@ more.
including more efficient use of parameters, code generation,
and differentiation through problems.

* CVXPY will soon support
* CVXPY v1.0.24 supports
`disciplined quasiconvex programming <https://web.stanford.edu/~boyd/papers/dqcp.html>`_,
which lets you formulate and solve quasiconvex programs.
See the :doc:`tutorial </tutorial/dqcp/index>` for more information.

* CVXPY v1.0.11 supports
`disciplined geometric programming <https://web.stanford.edu/~boyd/papers/dgp.html>`_,
Expand Down

0 comments on commit 3762a05

Please sign in to comment.