R/031_expressions_constants_constant.R

Defines functions .constant_nsd_test .constant_is_nsd .constant_psd_test .constant_is_psd .constant_is_symmetric .constant_is_pos .constant_sign .constant_is_skew_symmetric .constant_is_hermitian .constant_symm_herm .constant_is_imag .constant_is_complex

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

## CVXPY SOURCE: expressions/constants/constant.py
## Constant -- a constant value in the expression tree

#' Create a Constant Expression
#'
#' Wraps a numeric value as a CVXR constant for use in optimization
#' expressions. Constants are typically created implicitly when combining
#' numeric values with CVXR expressions via arithmetic operators.
#'
#' @param value A numeric scalar, vector, matrix, or sparse matrix.
#' @param name Optional character string name.
#' @returns A \code{Constant} object (inherits from \code{Leaf} and
#'   \code{Expression}).
#'
#' @examples
#' c1 <- Constant(5)
#' c2 <- Constant(matrix(1:6, 2, 3))
#'
#' @export
Constant <- new_class("Constant", parent = Leaf, package = "CVXR",
  properties = list(
    .name   = new_property(class = class_character, default = ""),
    .sparse = new_property(class = class_logical, default = FALSE)
  ),
  constructor = function(value, name = NULL) {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    ## Convert and normalize
    sparse <- intf_is_sparse(value)
    value <- intf_convert(value)
    shape <- as.integer(intf_shape(value))

    id <- next_expr_id()

    if (is.null(name)) name <- ""

    obj <- .fast_new(Constant, S7_object(),
      id = as.integer(id),
      .cache = new.env(parent = emptyenv()),
      shape = shape,
      .value = value,
      attributes = list(
        nonneg = FALSE, nonpos = FALSE,
        pos = FALSE, neg = FALSE,
        complex = FALSE, imag = FALSE,
        symmetric = FALSE, diag = FALSE,
        PSD = FALSE, NSD = FALSE,
        hermitian = FALSE, boolean = FALSE,
        integer = FALSE, sparsity = FALSE,
        bounds = NULL
      ),
      ## A Constant is never boolean/integer-constrained (its attributes are
      ## all FALSE above), but constraint 17 requires both properties be named:
      ## `.fast_new` gives an object nothing its constructor omits.
      .sparse_idx  = integer(0),
      .boolean_idx = integer(0),
      .integer_idx = integer(0),
      args = list(),
      .name = name,
      .sparse = sparse
    )
    obj
  }
)

# -- expr_name ---------------------------------------------------------
## CVXPY SOURCE: constant.py lines 96-108

method(expr_name, Constant) <- function(x) {
  if (nchar(x@.name) > 0L) return(x@.name)
  v <- x@.value
  ## is.matrix() is FALSE for a Matrix-package value; the abbreviation applies
  ## to every dimensioned constant (sweep item 4, pass H1).
  if ((is.matrix(v) || inherits(v, "Matrix")) && any(dim(v) > 1L)) {
    ## Abbreviated representation for matrices
    paste0("[", nrow(v), "x", ncol(v), " matrix]")
  } else {
    as.character(v[1L])
  }
}

# -- is_constant: always TRUE -----------------------------------------
## CVXPY SOURCE: constant.py line 115-116

method(is_constant, Constant) <- function(x) TRUE

# -- get_bounds: a constant's bounds are exactly (value, value) --------
## CVXPY SOURCE: constants/constant.py:292-304 (#3080). Needed so bounds
## propagation through e.g. matmul / scaling by a constant treats the constant
## as a point (lb == ub), enabling the exact split formula in matmul_bounds.
method(get_bounds, Constant) <- function(x) {
  v <- x@.value
  ## constant.py:303-304 returns a sparse value as-is, "to avoid memory
  ## blowup"; a Matrix value is already shaped (sweep item 4, pass B).
  if (!inherits(v, "Matrix")) {
    v <- matrix(as.numeric(v), nrow = .shape(x)[1L], ncol = .shape(x)[2L])
  }
  list(v, v)
}

# -- constants: returns self -------------------------------------------
## CVXPY SOURCE: constant.py lines 110-113

method(constants, Constant) <- function(x) list(x)

# -- value: read-only --------------------------------------------------
## CVXPY SOURCE: constant.py lines 118-122

method(value, Constant) <- function(x) x@.value

method(`value<-`, Constant) <- function(x, value) {
  cli_abort("Cannot set the value of a {.cls Constant}.")
}

# -- Complex queries: computed from value, cached ----------------------
## CVXPY SOURCE: constant.py lines 190-194, 183-188

## Every cached member goes through compute_once (utilities/performance_utils.R),
## the one mirror of CVXPY's @perf.compute_once. CVXPY decorates is_complex,
## is_symmetric, is_hermitian, is_psd and is_nsd (scope-keyed wrappers); the
## tests on the stored value themselves are plain instance attributes set
## once, whatever the scope: `_imag`/`_nonneg`/`_nonpos` (_compute_attr, lines
## 222-233), `_symm`/`_herm` (_compute_symm_attr, 235-241), `_skew_symm`
## (243-246), `_cached_is_pos` (124-134), `_psd_test`/`_nsd_test` (248-290).
## Those are `compute_once(..., scoped = FALSE)` here; the wrappers keep the
## scoped key. Each member's body is the `.constant_*` function below it.

method(is_complex, Constant) <- function(x) compute_once(x, "is_complex", .constant_is_complex)
.constant_is_complex <- function(x) is.complex(x@.value)

method(is_imag, Constant) <- function(x) {
  compute_once(x, "is_imag", .constant_is_imag, scoped = FALSE)
}
.constant_is_imag <- function(x) {
  v <- x@.value
  if (!is.complex(v)) return(FALSE)
  ## Imaginary: real part all ~zero, imaginary part has nonzero elements
  ## CVXPY SOURCE: interface/matrix_utilities.py is_complex() returns (is_real, is_imag)
  tol <- 1e-5
  real_max <- max(abs(Re(v)))
  imag_max <- max(abs(Im(v)))
  (real_max < tol) && (imag_max >= tol)
}

# -- Hermitian / skew-symmetric: computed from value, cached -----------
## CVXPY SOURCE: constant.py lines 209-246

## _compute_symm_attr: one intf_is_hermitian() call stores both `_symm` and
## `_herm`, consulted by the is_symmetric and is_hermitian wrappers below.
.constant_symm_herm <- function(x) intf_is_hermitian(x@.value)

method(is_hermitian, Constant) <- function(x) compute_once(x, "is_hermitian", .constant_is_hermitian)
.constant_is_hermitian <- function(x) {
  if (expr_is_scalar(x) && is_real(x)) return(TRUE)
  if (length(.shape(x)) != 2L || .shape(x)[1L] != .shape(x)[2L]) return(FALSE)
  compute_once(x, "symm_herm", .constant_symm_herm, scoped = FALSE)$is_hermitian
}

method(is_skew_symmetric, Constant) <- function(x) {
  compute_once(x, "is_skew_symmetric", .constant_is_skew_symmetric, scoped = FALSE)
}
## Delegates to intf_is_skew_symmetric which errors on non-square (matches CVXPY)
.constant_is_skew_symmetric <- function(x) intf_is_skew_symmetric(x@.value)

# -- Sign queries: computed from value, cached -------------------------
## CVXPY SOURCE: constant.py lines 169-181, 222-233
## One cached intf_sign() call answers both queries, as CVXPY's _compute_attr
## stores `self._nonneg, self._nonpos = intf.sign(self.value)` once.

.constant_sign <- function(x) intf_sign(x@.value)
method(is_nonneg, Constant) <- function(x) {
  compute_once(x, "sign", .constant_sign, scoped = FALSE)$is_nonneg
}
method(is_nonpos, Constant) <- function(x) {
  compute_once(x, "sign", .constant_sign, scoped = FALSE)$is_nonpos
}

# -- is_pos: strictly positive (all elements > 0), cached --------------
## CVXPY SOURCE: constant.py lines 124-134
method(is_pos, Constant) <- function(x) {
  compute_once(x, "is_pos", .constant_is_pos, scoped = FALSE)
}
.constant_is_pos <- function(x) {
  v <- x@.value
  if (is.complex(v)) {
    ## Complex values cannot be strictly positive
    FALSE
  } else if (inherits(v, "sparseMatrix")) {
    ## Sparse constants are NOT elementwise positive (zeros present)
    FALSE
  } else {
    all(v > 0)
  }
}

# -- Symmetry: computed from value, cached -----------------------------
## CVXPY SOURCE: constant.py lines 197-207

method(is_symmetric, Constant) <- function(x) compute_once(x, "is_symmetric", .constant_is_symmetric)
.constant_is_symmetric <- function(x) {
  if (expr_is_scalar(x)) return(TRUE)
  if (length(.shape(x)) != 2L || .shape(x)[1L] != .shape(x)[2L]) return(FALSE)
  compute_once(x, "symm_herm", .constant_symm_herm, scoped = FALSE)$is_symmetric
}

# -- PSD / NSD: computed from value, cached ----------------------------
## CVXPY SOURCE: constant.py lines 248-290

method(is_psd, Constant) <- function(x) compute_once(x, "is_psd", .constant_is_psd)
.constant_is_psd <- function(x) {
  ## Scalar cases
  if (expr_is_scalar(x)) return(is_nonneg(x))
  ## Must be square
  if (length(.shape(x)) != 2L || .shape(x)[1L] != .shape(x)[2L]) return(FALSE)
  ## Must be symmetric (real) or Hermitian (complex) -- matches CVXPY
  if (!is_hermitian(x)) return(FALSE)
  ## Eigenvalue test, stored once (`_psd_test`)
  compute_once(x, "psd_test", .constant_psd_test, scoped = FALSE)
}
.constant_psd_test <- function(x) intf_is_psd(x@.value, EIGVAL_TOL)

method(is_nsd, Constant) <- function(x) compute_once(x, "is_nsd", .constant_is_nsd)
.constant_is_nsd <- function(x) {
  if (expr_is_scalar(x)) return(is_nonpos(x))
  if (length(.shape(x)) != 2L || .shape(x)[1L] != .shape(x)[2L]) return(FALSE)
  if (!is_hermitian(x)) return(FALSE)
  ## Eigenvalue test on negation, stored once (`_nsd_test`)
  compute_once(x, "nsd_test", .constant_nsd_test, scoped = FALSE)
}
.constant_nsd_test <- function(x) intf_is_psd(-x@.value, EIGVAL_TOL)

# -- grad: empty (constants have no gradient w.r.t. variables) ---------
## CVXPY SOURCE: constant.py lines 137-145

method(grad, Constant) <- function(x) list()

# -- canonicalize ------------------------------------------------------
## CVXPY SOURCE: constant.py lines 153-160

method(canonicalize, Constant) <- function(x) {
  obj <- create_const(x@.value, .shape(x), x@.sparse)
  list(obj, list())
}

# -- print -------------------------------------------------------------

method(print, Constant) <- function(x, ...) {
  cat(sprintf("Constant(%s, %s, (%s))\n",
              expr_curvature(x), expr_sign_str(x),
              paste(.shape(x), collapse = ", ")))
  invisible(x)
}

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.