R/029_expressions_variable.R

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

## CVXPY SOURCE: expressions/variable.py
## Variable -- an optimization variable
##
## CVXPY 1.9 parity notes:
##  - `sample_bounds` is stored as mutable per-variable state for NLP
##    random restarts (variable.py:39-44,64).
##  - Variables report parameters embedded in expression bounds and validate
##    those bounds in DPP/DGP checks (variable.py:78-108).

#' Create an Optimization Variable
#'
#' Constructs a variable to be used in a CVXR optimization problem. Variables
#' are decision variables that the solver optimizes over.
#'
#' @param shape Integer vector of length 1 or 2 giving the variable dimensions.
#'   A scalar \code{n} is interpreted as \code{c(n, 1)}.
#'   Defaults to \code{c(1, 1)} (scalar).
#' @param name Optional character string name for the variable. If \code{NULL},
#'   an automatic name \code{"var<id>"} is generated.
#' @param value Optional numeric initial value (scalar, vector, or matrix
#'   matching \code{shape}).  Validated and projected onto the attribute
#'   domain via the same path as \code{value(var) <- val}.
#' @param var_id Optional integer ID. If \code{NULL}, a unique ID is generated.
#' @param latex_name Optional character string giving a custom LaTeX name for
#'   use in visualizations. For example, \code{"\\\\mathbf{x}"}.
#'   If \code{NULL} (default), visualizations auto-generate a LaTeX name.
#' @param ... Additional attributes: \code{nonneg}, \code{nonpos}, \code{PSD},
#'   \code{NSD}, \code{symmetric}, \code{boolean}, \code{integer}, etc.
#' @returns A \code{Variable} object (inherits from \code{Leaf} and
#'   \code{Expression}).
#'
#' @examples
#' x <- Variable(3)        # 3x1 column vector
#' X <- Variable(c(2, 3))  # 2x3 matrix
#' y <- Variable(2, nonneg = TRUE)  # non-negative variable
#' z <- Variable(3, name = "z", latex_name = "\\mathbf{z}")  # custom LaTeX
#'
#' @export
Variable <- new_class("Variable", parent = Leaf, package = "CVXR",
  properties = list(
    .name = new_property(class = class_character),
    .latex_name = new_property(class = class_character)
  ),
  constructor = function(shape = c(1L, 1L), name = NULL, value = NULL,
                         var_id = NULL, latex_name = NULL, ...) {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    ## Normalize scalar shape: Variable(3) -> c(3, 1)
    if (is.numeric(shape) && length(shape) == 1L) {
      shape <- c(as.integer(shape), 1L)
    }
    shape <- validate_shape(shape)

    id <- if (!is.null(var_id)) as.integer(var_id) else next_expr_id()

    ## Auto-name deferred: compute lazily in expr_name() to avoid paste0 overhead
    ## for intermediate variables created during canonicalization.
    ## CVXPY SOURCE: variable.py lines 40-45
    if (is.null(name)) {
      nm <- ""
    } else if (!is.character(name)) {
      cli_abort("Variable name {.val {as.character(name)}} must be a string.")
    } else {
      nm <- name
    }

    ## LaTeX name for visualizations (visualization-only, never touches solver)
    lnm <- if (is.null(latex_name)) "" else as.character(latex_name)

    ## Build leaf attributes from ...
    attrs <- do.call(.build_leaf_attrs, c(list(shape = shape), list(...)))

    obj <- .fast_new(Variable, S7_object(),
      id = as.integer(id),
      .cache = new.env(parent = emptyenv()),
      shape = shape,
      .value = NULL,
      attributes = attrs,
      ## Derived from `attrs`, not from a formal: this constructor takes its
      ## leaf attributes through `...` (constraint 17 -- both named explicitly).
      .sparse_idx  = .mip_idx(attrs$sparsity, shape, "sparsity"),
      .boolean_idx = .mip_idx(attrs$boolean, shape, "boolean"),
      .integer_idx = .mip_idx(attrs$integer, shape, "integer"),
      args = list(),
      .name = nm,
      .latex_name = lnm
    )

    ## Apply initial value if provided.
    ## CVXPY SOURCE: cvxpy/expressions/leaf.py - Leaf.__init__ stores `value`
    ## via the `value` property setter, which validates shape and projects
    ## onto the attribute domain. .validate_leaf_value() is the R equivalent
    ## (defined in leaf.R alongside the value generic).
    if (!is.null(value)) {
      validated <- .validate_leaf_value(obj, value)
      obj@.value <- validated
      obj@.cache$leaf_value <- validated
    }

    obj
  }
)

# -- expr_name ---------------------------------------------------------

method(expr_name, Variable) <- function(x) {
  nm <- x@.name
  if (nchar(nm) == 0L) {
    nm <- x@.cache$.auto_name
    if (is.null(nm)) {
      nm <- paste0(VAR_PREFIX, .id(x))
      x@.cache$.auto_name <- nm
    }
  }
  nm
}

# -- is_constant: Variables are NOT constant ---------------------------
## CVXPY SOURCE: variable.py line 57-58

method(is_constant, Variable) <- function(x) FALSE

# -- variables: returns self -------------------------------------------
## CVXPY SOURCE: variable.py line 69-71

method(variables, Variable) <- function(x) list(x)

# -- parameters: include Parameters present in expression bounds -------
## CVXPY SOURCE: variable.py:78-84

method(parameters, Variable) <- function(x) {
  params <- list()
  bnds <- .attributes(x)$bounds
  if (!is.null(bnds) && is.list(bnds)) {
    for (b in bnds) {
      if (.s7_is(b, Expression)) {
        params <- c(params, parameters(b))
      }
    }
  }
  unique_list(params)
}

# -- DCP/DGP/DPP compliance for expression bounds ---------------------
## CVXPY SOURCE: variable.py:86-108

method(is_dcp, Variable) <- function(x) {
  if (dpp_scope_active()) {
    bnds <- .attributes(x)$bounds
    if (!is.null(bnds) && is.list(bnds)) {
      for (b in bnds) {
        if (.s7_is(b, Expression) && !is_affine(b)) return(FALSE)
      }
    }
  }
  TRUE
}

method(is_dgp, Variable) <- function(x) {
  if (dpp_scope_active()) {
    bnds <- .attributes(x)$bounds
    if (!is.null(bnds) && is.list(bnds)) {
      for (b in bnds) {
        if (.s7_is(b, Expression) && !is_log_log_affine(b)) return(FALSE)
      }
    }
  }
  is_log_log_convex(x) || is_log_log_concave(x)
}

## CVXPY SOURCE: variable.py is_dcp(dpp)/is_dgp(dpp)/is_dpp(context). Expression
## bounds must be affine (dcp) or log-log-affine (dgp) under DPP scope.
method(is_dpp, Variable) <- function(x, context = "dcp") {
  bounds <- .attributes(x)$bounds
  dgp <- identical(tolower(context), "dgp")
  if (!is.null(bounds)) {
    pred <- if (dgp) is_log_log_affine else is_affine
    ok <- with_dpp_scope(all(vapply(bounds, function(b)
      !.s7_is(b, Expression) || pred(b), logical(1))))
    if (!ok) return(FALSE)
  }
  if (dgp) is_log_log_convex(x) || is_log_log_concave(x) else TRUE
}

# -- grad: identity sparse matrix -------------------------------------
## CVXPY SOURCE: variable.py line 61-67

method(grad, Variable) <- function(x) {
  sz <- expr_size(x)
  id_mat <- make_sparse_diagonal_matrix(sz)
  result <- list()
  result[[as.character(x@id)]] <- id_mat
  result
}

# -- canonicalize: create_var LinOp ------------------------------------
## CVXPY SOURCE: variable.py line 73-76

method(canonicalize, Variable) <- function(x) {
  obj <- create_var(.shape(x), .id(x))
  list(obj, list())
}

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

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

# -- sample_bounds (NLP best_of random-restart sampling region) --------
## CVXPY SOURCE: variable.py:39-44,64 (the `sample_bounds` instance attribute).
## A per-variable (low, high) region for random initial-point sampling in
## best_of NLP solves; NULL by default. Stored in the .cache because S7 objects
## are immutable, so mutable per-object state lives there (as leaf_value does).

method(sample_bounds, Variable) <- function(x) {
  x@.cache$sample_bounds        # NULL if never set
}

method(`sample_bounds<-`, Variable) <- function(x, value) {
  if (is.null(value)) {
    x@.cache$sample_bounds <- NULL
    return(x)
  }
  ## Accept c(low, high) or list(low, high); normalize to list(low, high).
  lh <- if (is.list(value)) value else as.list(value)
  if (length(lh) != 2L) {
    cli_abort("{.code sample_bounds} must be a {.code (low, high)} pair.")
  }
  x@.cache$sample_bounds <- list(as.numeric(lh[[1L]]), as.numeric(lh[[2L]]))
  x
}

Try the CVXR package in your browser

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

CVXR documentation built on Aug. 24, 2026, 9:10 a.m.