R/028_expressions_expression.R

Defines functions .pow_const_base log_log_curvature curvatures is_log_log_constant .expression_is_smooth .expression_is_dqcp expr_H .expression_is_zero .expression_is_dcp .expression_is_affine .expression_is_constant

Documented in curvatures expr_H log_log_curvature

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/expressions/expression.R
#####

## CVXPY SOURCE: expressions/expression.py
## Expression -- base class for mathematical expressions
##
## CVXPY 1.9 parity notes:
##  - DNLP predicates mirror expression.py:349-405: `is_smooth`,
##    `is_dnlp`, and the linearizable-convex / linearizable-concave hooks.
##  - CVXPY makes the linearizable hooks abstract on Expression; CVXR uses
##    FALSE defaults, matching the existing `is_convex` / `is_concave` default
##    style and relying on Leaf, Atom, and transforms for concrete behavior.


Expression <- new_class("Expression", parent = Canonical, package = "CVXR",
  properties = list(
    shape = new_property(class = class_integer, default = c(1L, 1L))
  )
)

## Register with S4 so that method([, Expression) and similar primitives work.
## Required before using S7::method() for S4 generics like `[`.
S4_register(Expression)

# -- Curvature methods (cached) ----------------------------------------
## CVXPY SOURCE: expression.py line 337-341

## is_constant(): 0 in shape OR all args constant
## Every cached member goes through compute_once (utilities/performance_utils.R),
## the one mirror of CVXPY's @perf.compute_once: keyed on the DPP scopes.
method(is_constant, Expression) <- function(x) {
  compute_once(x, "is_constant", .expression_is_constant)
}
.expression_is_constant <- function(x) {
  (0L %in% .shape(x)) || .all_args(x, is_constant)
}

## is_affine(): constant OR (convex AND concave)
## CVXPY SOURCE: expression.py line 344-347
method(is_affine, Expression) <- function(x) {
  compute_once(x, "is_affine", .expression_is_affine)
}
.expression_is_affine <- function(x) {
  is_constant(x) || (is_convex(x) && is_concave(x))
}

## is_convex() / is_concave() -- abstract on Expression, concrete on Leaf/Atom
## Default implementation: FALSE (subclasses override)
method(is_convex, Expression) <- function(x) FALSE
method(is_concave, Expression) <- function(x) FALSE

## is_dcp(): convex OR concave
## CVXPY SOURCE: expression.py line 382-399
method(is_dcp, Expression) <- function(x) compute_once(x, "is_dcp", .expression_is_dcp)
.expression_is_dcp <- function(x) is_convex(x) || is_concave(x)

## is_dpp(): one DCP (or DGP) pass inside the DPP scope, where a Parameter is
## affine rather than constant and the per-atom DPP restrictions live in the
## curvature hooks (is_atom_convex / is_atom_concave reading dpp_scope_active():
## kron.R, conv.R, quad_form.R, quad_over_lin.R, perspective.R, dotsort.R,
## binary_operators.R). Variable bounds are checked once, by the Problem
## (problem.py:400). Until 1.9.2.9163 this method also recursed over the
## arguments to reach per-class is_dpp methods; that loop was CVXR's own, cost
## about 30% of a compile-time DPP pass on a 405-node expression, and made
## `indicator(...) + x` and an expression over a Variable with a non-affine
## bound non-DPP where upstream says DPP (claims audit decision 29, C0428).
## CVXPY SOURCE: atom.py lines 300-308
## atom.py:307-308 `raise ValueError('Unsupported context ', context)`
## (ported 1.9.2.9162, decision 26).
method(is_dpp, Expression) <- function(x, context = "dcp") {
  ctx <- tolower(context)
  if (!ctx %in% c("dcp", "dgp")) {
    cli_abort("Unsupported context {.val {context}}; use {.val dcp} or {.val dgp}.")
  }
  if (identical(ctx, "dgp")) with_dpp_scope(is_dgp(x))
  else with_dpp_scope(is_dcp(x))
}

# -- Sign methods (cached) --------------------------------------------
## is_nonneg() / is_nonpos() -- abstract, defaults FALSE
method(is_nonneg, Expression) <- function(x) FALSE
method(is_nonpos, Expression) <- function(x) FALSE

## is_zero(): nonneg AND nonpos
## CVXPY SOURCE: expression.py line 548-551
method(is_zero, Expression) <- function(x) compute_once(x, "is_zero", .expression_is_zero)
.expression_is_zero <- function(x) is_nonneg(x) && is_nonpos(x)

## expr_sign(): returns sign string
## CVXPY SOURCE: expression.py::sign property
method(expr_sign, Expression) <- function(x) {
  expr_sign_str(x)
}

# -- Matrix property defaults -----------------------------------------

## is_symmetric: default -> scalar only
## CVXPY SOURCE: expression.py line 509-513
method(is_symmetric, Expression) <- function(x) expr_is_scalar(x)

## is_psd / is_nsd: default FALSE
## CVXPY SOURCE: expression.py lines 481-491
method(is_psd, Expression) <- function(x) FALSE
method(is_nsd, Expression) <- function(x) FALSE

## is_pos: default FALSE (overridden by Leaf and Constant)
method(is_pos, Expression) <- function(x) FALSE

## is_complex / is_imag: default FALSE
## CVXPY SOURCE: expression.py (abstract on Expression, concrete on Leaf)
method(is_complex, Expression) <- function(x) FALSE
method(is_imag, Expression) <- function(x) FALSE

## is_hermitian: default -> real AND symmetric
## CVXPY SOURCE: expression.py line 476-479
method(is_hermitian, Expression) <- function(x) is_real(x) && is_symmetric(x)

## is_skew_symmetric: default -> FALSE
## CVXPY SOURCE: expression.py line 515-518
method(is_skew_symmetric, Expression) <- function(x) FALSE

## is_real: default -> not complex
## CVXPY SOURCE: expression.py
method(is_real, Expression) <- function(x) !is_complex(x)

# -- Conjugate-transpose (.H in CVXPY) ------------------------------
## CVXPY SOURCE: expression.py lines 661-668
## In Python: expr.H property. In R: expr_H(x) function.
## For real expressions, this is just t(x).
## For complex expressions, this is Conj(t(x)).

#' Conjugate-Transpose of an Expression
#'
#' Equivalent to CVXPY's \code{.H} property. For real expressions, returns
#' \code{t(x)}. For complex expressions, returns \code{Conj(t(x))}.
#'
#' @param x A CVXR Expression.
#' @returns The conjugate-transpose expression.
#' @export
expr_H <- function(x) {
  if (is_real(x)) t(x) else Conj_(t(x))
}

## is_quasiconvex / is_quasiconcave / is_quasilinear / is_dqcp: defaults
## CVXPY SOURCE: expression.py lines 456-474
method(is_quasiconvex, Expression) <- function(x) is_convex(x)
method(is_quasiconcave, Expression) <- function(x) is_concave(x)
method(is_quasilinear, Expression) <- function(x) is_quasiconvex(x) && is_quasiconcave(x)
method(is_dqcp, Expression) <- function(x) compute_once(x, "is_dqcp", .expression_is_dqcp)
.expression_is_dqcp <- function(x) is_quasiconvex(x) || is_quasiconcave(x)

# -- DNLP curvature (CVXPY 1.9.0) ----------------------------------
## is_linearizable_convex / is_linearizable_concave -- abstract on Expression
## (CVXPY raises NotImplementedError); concrete on Leaf / Atom / Indicator /
## PartialProblem. Default FALSE mirrors the is_convex / is_concave defaults.
## CVXPY SOURCE: expression.py lines 369-379
method(is_linearizable_convex, Expression) <- function(x) FALSE
method(is_linearizable_concave, Expression) <- function(x) FALSE

## is_smooth(): constant OR (linearizable-convex AND linearizable-concave)
## CVXPY SOURCE: expression.py lines 349-355 (@perf.compute_once)
method(is_smooth, Expression) <- function(x) compute_once(x, "is_smooth", .expression_is_smooth)
.expression_is_smooth <- function(x) {
  is_constant(x) || (is_linearizable_convex(x) && is_linearizable_concave(x))
}

## is_dnlp(): linearizable-convex OR linearizable-concave
## CVXPY SOURCE: expression.py lines 401-405 (no compute_once)
method(is_dnlp, Expression) <- function(x) {
  is_linearizable_convex(x) || is_linearizable_concave(x)
}

## is_quadratic / has_quadratic_term / is_pwl / is_qpwa: defaults
## CVXPY SOURCE: expression.py lines 493-529
method(is_quadratic, Expression) <- function(x) is_constant(x)
method(has_quadratic_term, Expression) <- function(x) is_constant(x)
method(is_pwl, Expression) <- function(x) is_constant(x)
method(is_qpwa, Expression) <- function(x) is_quadratic(x) || is_pwl(x)

## is_log_log_constant: elementwise strictly positive constant
## CVXPY SOURCE: expression.py lines 407-416
## NOT a generic -- same logic for all Expression types.
is_log_log_constant <- function(x) {
  if (!is_constant(x)) return(FALSE)
  ## Constant and Parameter: use is_pos() (attribute-based)
  if (.s7_is(x, Constant) || .s7_is(x, Parameter)) {
    return(is_pos(x))
  }
  ## Other constant expressions: check actual value > 0
  v <- value(x)
  !is.null(v) && all(v > 0)
}

## is_log_log_*: default FALSE (overridden by atoms)
## CVXPY SOURCE: expression.py lines 418-435
method(is_log_log_convex, Expression) <- function(x) FALSE
method(is_log_log_concave, Expression) <- function(x) FALSE
method(is_log_log_affine, Expression) <- function(x) {
  is_log_log_constant(x) || (is_log_log_convex(x) && is_log_log_concave(x))
}

## is_dgp: checks whether the expression is log-log DCP
## CVXPY SOURCE: expression.py lines 437-448
method(is_dgp, Expression) <- function(x) {
  is_log_log_convex(x) || is_log_log_concave(x)
}

# -- curvatures / log_log_curvature ------------------------------------
## CVXPY SOURCE: expression.py lines 272-290 (curvatures) and 321-334
## (log_log_curvature); properties in CVXPY, functions here.

#' All Curvatures of an Expression
#'
#' Lists every curvature that holds for an expression, DCP, DGP and DQCP
#' alike, as CVXPY's \code{Expression.curvatures}. \code{\link{curvature}}
#' reports only the strongest DCP curvature.
#'
#' @param x A CVXR expression.
#' @returns A character vector, in the order \code{"CONSTANT"},
#'   \code{"AFFINE"}, \code{"CONVEX"}, \code{"CONCAVE"},
#'   \code{"LOG-LOG CONSTANT"}, \code{"LOG-LOG AFFINE"},
#'   \code{"LOG-LOG CONVEX"}, \code{"LOG-LOG CONCAVE"}, \code{"QUASILINEAR"},
#'   \code{"QUASICONVEX"}, \code{"QUASICONCAVE"}, holding those that apply;
#'   \code{"UNKNOWN"} alone if none does.
#' @seealso [curvature()], [log_log_curvature()]
#' @export
curvatures <- function(x) {
  checks <- list(
    list(is_constant, CONSTANT_CURV),
    list(is_affine, AFFINE),
    list(is_convex, CONVEX),
    list(is_concave, CONCAVE),
    list(is_log_log_constant, LOG_LOG_CONSTANT),
    list(is_log_log_affine, LOG_LOG_AFFINE),
    list(is_log_log_convex, LOG_LOG_CONVEX),
    list(is_log_log_concave, LOG_LOG_CONCAVE),
    list(is_quasilinear, QUASILINEAR),
    list(is_quasiconvex, QUASICONVEX),
    list(is_quasiconcave, QUASICONCAVE))
  out <- character(0)
  for (ck in checks) if (ck[[1L]](x)) out <- c(out, ck[[2L]])
  if (length(out) == 0L) UNKNOWN_CURVATURE else out
}

#' Log-Log Curvature of an Expression
#'
#' The DGP analogue of \code{\link{curvature}}, as CVXPY's
#' \code{Expression.log_log_curvature}.
#'
#' @param x A CVXR expression.
#' @returns Character: the first of \code{"LOG-LOG CONSTANT"},
#'   \code{"LOG-LOG AFFINE"}, \code{"LOG-LOG CONVEX"},
#'   \code{"LOG-LOG CONCAVE"} that holds, else \code{"UNKNOWN"}.
#' @seealso [curvatures()], [is_dgp()]
#' @export
log_log_curvature <- function(x) {
  if (is_log_log_constant(x)) LOG_LOG_CONSTANT
  else if (is_log_log_affine(x)) LOG_LOG_AFFINE
  else if (is_log_log_convex(x)) LOG_LOG_CONVEX
  else if (is_log_log_concave(x)) LOG_LOG_CONCAVE
  else UNKNOWN_CURVATURE
}

# -- value / grad / domain -- abstract defaults ------------------------

method(value, Expression) <- function(x) {
  cli_abort("value() not implemented for this expression type.")
}

method(grad, Expression) <- function(x) {
  cli_abort("grad() not implemented for this expression type.")
}

method(domain, Expression) <- function(x) list()

# -- expr_name default ------------------------------------------------

method(expr_name, Expression) <- function(x) {
  sprintf("Expression(%s, %s, (%s))",
          expr_curvature(x), expr_sign_str(x),
          paste(.shape(x), collapse = ", "))
}

# -- label / set_label / format_labeled --------------------------------
## CVXPY SOURCE: expression.py lines 203-263
##
## Storage: each Expression already carries a `.cache` env (mutable
## reference semantics).  CVXPY uses `self._label = None`; we use
## `x@.cache$.label` and treat absent / NULL as "no label set".

method(label, Expression) <- function(x) {
  if (exists(".label", envir = x@.cache, inherits = FALSE)) {
    get(".label", envir = x@.cache, inherits = FALSE)
  } else NULL
}

## CVXPY SOURCE: expression.py lines 208-224 (label setter + deleter)
method(`label<-`, Expression) <- function(x, value) {
  if (is.null(value)) {
    if (exists(".label", envir = x@.cache, inherits = FALSE)) {
      rm(list = ".label", envir = x@.cache)
    }
  } else {
    assign(".label", as.character(value)[[1L]], envir = x@.cache)
  }
  invisible(x)
}

## CVXPY SOURCE: expression.py lines 226-247 (set_label, returns self).
method(set_label, Expression) <- function(x, value) {
  label(x) <- value
  invisible(x)
}

## CVXPY SOURCE: expression.py lines 249-263 (format_labeled base).
## If a label is set, return it; otherwise default to expr_name().
## Atom subclasses with a custom expr_name override format_labeled to
## recurse via format_labeled() on children (see atom.R and per-atom files).
method(format_labeled, Expression) <- function(x) {
  lbl <- label(x)
  if (!is.null(lbl)) return(lbl)
  expr_name(x)
}

# -- print method -----------------------------------------------------

method(print, Expression) <- function(x, ...) {
  cat(sprintf("%s\n", expr_name(x)))
  invisible(x)
}

## CVXPY v1.9.0 fix: #3180 _pow_const_base (expression.py:57-73) + __rpow__
## (expression.py:688-705). Helper for b^x where b is a positive constant and
## x is a CVXPY Expression. Validates that base is constant and positive, then
## returns exp(x * log(b)) via the identity b^x = exp(x * log(b)).
## In CVXR, `^` (Ops group, both directions) and the `power()` factory route
## the const-base / variable-exponent case here.
.pow_const_base <- function(base, exponent) {
  base <- as_expr(base)
  if (!is_constant(base)) {
    cli_abort("The base of {.code b^x} must be a constant when the exponent is a variable.")
  }
  if (!is_pos(base)) {
    cli_abort("The base of {.code b^x} must be positive since we use {.code b^x = exp(x * log(b))}.")
  }
  Exp(Multiply(exponent, Log(base)))
}

Try the CVXR package in your browser

Any scripts or data that you put into this service are public.

CVXR documentation built on Oct. 19, 2026, 5:07 p.m.