Disciplined Quasiconvex Programming

Author

CVXPY Developers and Balasubramanian Narasimhan

Introduction

Disciplined quasiconvex programming (DQCP) is a generalization of DCP for quasiconvex functions. Quasiconvexity generalizes convexity: a function f is quasiconvex if and only if its domain is a convex set and its sublevel sets {x:f(x)t} are convex, for all t.

While DCP is a ruleset for constructing convex programs, DQCP is a ruleset for quasiconvex programs (QCPs), which are optimization problems in which the objective is to minimize a quasiconvex function over a convex set. The convex set can be specified using equalities of affine functions and inequalities of convex and concave functions, just as in DCP; additionally, DQCP permits inequalities of the form f(x)t, where f(x) is a quasiconvex expression and t is constant, and f(x)t, where f(x) is quasiconcave and t is constant. Every disciplined convex program is a disciplined quasiconvex program, but the converse is not true.

CVXR lets you form and solve DQCP problems, just as it does for DCP problems. For example, the following code solves a simple QCP:

x <- Variable()
y <- Variable(pos = TRUE)
objective_fn <- -sqrt(x) / y
problem <- Problem(Minimize(objective_fn), list(exp(x) <= y))
result <- psolve(problem, qcp = TRUE)
check_solver_status(problem)
cat("Is DQCP?", is_dqcp(problem), "\n")
cat("Optimal value:", result, "\n")
cat("x:", value(x), "\n")
cat("y:", value(y), "\n")
Is DQCP? TRUE 
Optimal value: -0.4288818 
x: 0.5000292 
y: 1.64877 

Note that to solve DQCP problems, you must pass the option qcp = TRUE to psolve().

Curvature

DQCP adds two new types of curvature to CVXR: quasiconvex and quasiconcave. A function f is quasiconvex if and only if its domain is a convex set and its sublevel sets {x:f(x)t} are convex, for all t; f is quasiconcave if f is quasiconvex. Every convex function is also quasiconvex, and every concave function is also quasiconcave; the converses of these statements are not true. An expression that is both quasiconvex and quasiconcave is called quasilinear.

CVXR’s curvature analysis can flag Expressions as unknown even when they are quasiconvex or quasiconcave, but it will never mistakenly flag an expression as quasiconvex or quasiconcave.

The curvature of an Expression is stored in its curvature() attribute. For example:

x <- Variable(3)
w <- ceil_expr(x)
cat("Curvature of ceil_expr(x):", curvature(w), "\n")

y <- Variable(nonneg = TRUE)
z <- Variable(nonneg = TRUE)
product <- multiply(y, z)
Warning: `multiply()` is deprecated. Use `x * y` instead.
This warning is displayed once per session.
cat("Curvature of multiply(y, z) (nonneg):", curvature(product), "\n")
Curvature of ceil_expr(x): UNKNOWN 
Curvature of multiply(y, z) (nonneg): UNKNOWN 

You can also check the curvature of an Expression by calling is_quasiconvex() and is_quasiconcave(). For example, is_quasiconvex(y) and is_quasiconcave(z) would evaluate to TRUE. You can check if an expression is quasilinear by calling is_quasilinear().

Composition Rules

DQCP analysis is based on applying a general composition theorem from convex analysis to each expression. An expression is verifiably quasiconvex under DQCP if it is one of the following:

  • convex (under DCP);
  • a quasiconvex atom, applied to a variable or constant;
  • the maximum of quasiconvex expressions;
  • an increasing function of a quasiconvex expression, or a decreasing function of a quasiconcave expression;
  • an expression of the form f(e1,e2,,en) such that
    1. f is a quasiconvex atom, and (2) for each i, f is increasing in argument i and ei is convex, f is decreasing in argument i and ei is concave, or ei is affine.

An expression is quasiconcave under DQCP if it is one of the following:

  • concave (under DCP);
  • a quasiconcave atom, applied to a variable or constant;
  • the minimum of quasiconcave expressions;
  • an increasing function of a quasiconcave expression, or a decreasing function of a quasiconvex expression;
  • an expression of the form f(e1,e2,,en) such that
    1. f is a quasiconcave atom, and (2) for each i, f is increasing in argument i and ei is concave, f is decreasing in argument i and ei is convex, or ei is affine.

Whether an atom is quasiconvex or quasiconcave may depend on the signs of its arguments. For example, the scalar product xy is quasiconcave when x and y are either both nonnegative or both nonpositive, and quasiconvex when one of the arguments is nonnegative and the other is nonpositive.

If an Expression satisfies the above rules, we say that the Expression “is DQCP.” You can check whether an Expression is DQCP by calling is_dqcp(). For example:

x <- Variable(pos = TRUE)
y <- Variable(pos = TRUE)

product <- multiply(x, y)

cat("Is quasiconcave?", is_quasiconcave(product), "\n")
cat("Is DQCP?", is_dqcp(product), "\n")
Is quasiconcave? TRUE 
Is DQCP? TRUE 

An Expression is DQCP precisely when it has known curvature, which means at least one of is_constant(), is_affine(), is_convex(), is_concave(), is_quasiconvex(), or is_quasiconcave() returns TRUE.

DQCP Problems

A Problem is constructed from an objective and a list of constraints. If a problem follows the DQCP rules, it is guaranteed to be a QCP and solvable by CVXR (if a solution to the problem exists). The DQCP rules require that the problem objective have one of two forms:

  • Minimize(quasiconvex)
  • Maximize(quasiconcave)

The only valid constraints under the DQCP rules are:

  • affine == affine
  • convex <= concave
  • concave >= convex
  • quasiconvex <= constant
  • quasiconcave >= constant

You can check that a problem, constraint, or objective satisfies the DQCP rules by calling is_dqcp(). Here are some examples of DQCP and non-DQCP problems:

## The sign of variables affects curvature analysis.
x <- Variable(nonneg = TRUE)
concave_fractional_fn <- x * sqrt(x)
constraint <- list(ceil_expr(x) <= 10)
problem <- Problem(Maximize(concave_fractional_fn), constraint)
cat("Is concave_fractional_fn quasiconcave?",
    is_quasiconcave(concave_fractional_fn), "\n")
cat("Is constraint DQCP?", is_dqcp(constraint[[1]]), "\n")
cat("Is problem DQCP?", is_dqcp(problem), "\n")
Is concave_fractional_fn quasiconcave? TRUE 
Is constraint DQCP? TRUE 
Is problem DQCP? TRUE 
w <- Variable()
fn <- w * sqrt(w)
problem2 <- Problem(Maximize(fn))
cat("Is fn DQCP?", is_dqcp(fn), "\n")
cat("Is problem DQCP?", is_dqcp(problem2), "\n")
Is fn DQCP? FALSE 
Is problem DQCP? FALSE 

CVXR will raise an error if you call psolve(problem, qcp = TRUE) on a non-DQCP problem.

DQCP Atoms

Quasiconvex and quasiconcave expressions can be constructed using convex and concave atoms, using the curvature rules given above. This section describes new semantics for some existing atoms under DQCP and introduces new atoms that are quasiconvex or quasiconcave (but not convex or concave). Many of these new atoms are integer-valued.

Ratio

The infix operator / is an atom, denoting ratio. This atom is both quasiconvex and quasiconcave when the denominator is known to be either nonnegative or nonpositive. The ratio x/y is increasing in x when y is nonnegative, increasing in y when x is nonpositive, decreasing in x when y is nonpositive, and decreasing in y when x is nonnegative.

The ratio atom can be used with the composition rule to construct interesting quasiconvex and quasiconcave expressions. For example, the ratio of a nonnegative concave function and a positive convex function is quasiconcave, and the ratio of a nonnegative convex function and a positive concave function is quasiconvex. Similarly, the ratio of two affine functions is quasilinear when the denominator is positive.

Scalar Product

The scalar product * is quasiconvex when one of its arguments is nonnegative and the other is nonpositive, and it is quasiconcave when its arguments are both nonnegative or both nonpositive. Hence, by the composition rule, the product of two nonnegative concave functions is quasiconcave, and the product of a nonnegative concave function and a nonpositive convex function is quasiconvex.

Ceiling and Floor

The atoms ceil_expr(x) and floor_expr(x) are quasilinear and increasing in their arguments.

Solving DQCP Problems

A DQCP problem can be solved by calling psolve(problem, qcp = TRUE). CVXR uses a bisection method on the optimal value of the problem to solve QCPs, and it will automatically find an upper and lower bound for the bisection. You can optionally provide your own upper and lower bound when solving a QCP, which can sometimes be helpful. You can provide these bounds via the keyword arguments low and high; for example, psolve(problem, qcp = TRUE, low = 12, high = 17) would limit the bisection to objective values between 12 and 17.

Bisection involves solving a sequence of optimization problems. If your problem is ill-conditioned, or if you are unlucky, a solver might fail to solve one of these subproblems, which will result in an error. If this happens, you can try using a different solver via the solver keyword argument. For example, psolve(problem, qcp = TRUE, solver = "SCS"). To obtain verbose output describing the bisection, supply the keyword argument verbose = TRUE to the solve method: psolve(problem, qcp = TRUE, verbose = TRUE).

Session Info

R version 4.5.2 (2025-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Tahoe 26.3

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

time zone: America/Los_Angeles
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] CVXR_1.8.0.9214

loaded via a namespace (and not attached):
 [1] vctrs_0.7.1       slam_0.1-55       cli_3.6.5         knitr_1.51       
 [5] ECOSolveR_0.6.1   rlang_1.1.7       xfun_0.56         clarabel_0.11.2  
 [9] otel_0.2.0        gurobi_13.0-1     Rglpk_0.6-5.1     highs_1.12.0-3   
[13] cccp_0.3-3        scs_3.2.7         S7_0.2.1          jsonlite_2.0.0   
[17] glue_1.8.0        Rcplex_0.3-8      backports_1.5.0   rprojroot_2.1.1  
[21] htmltools_0.5.9   Rmosek_11.1.1     gmp_0.7-5.1       piqp_0.6.2       
[25] rmarkdown_2.30    grid_4.5.2        evaluate_1.0.5    fastmap_1.2.0    
[29] lifecycle_1.0.5   yaml_2.3.12       compiler_4.5.2    codetools_0.2-20 
[33] htmlwidgets_1.6.4 Rcpp_1.1.1        here_1.0.2        osqp_1.0.0       
[37] lattice_0.22-9    digest_0.6.39     pillar_1.11.1     checkmate_2.3.4  
[41] Matrix_1.7-4      tools_4.5.2      

References

  • Agrawal, A., Boyd, S. (2020). Disciplined Quasiconvex Programming. Optimization Letters, 14, 1643–1657.