R/039_atoms_elementwise_elementwise.R

Defines functions .elemwise_grad_to_diag .elemwise_promote

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/atoms/elementwise/elementwise.R
#####

## CVXPY SOURCE: atoms/elementwise/elementwise.py
## Elementwise -- abstract base class for elementwise atoms
##
## Elementwise atoms operate on each element independently and produce
## output with the same shape as the broadcasted arguments.


Elementwise <- new_class("Elementwise", parent = Atom, package = "CVXR",
  constructor = function(args, id = NULL) {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    if (is.null(id)) id <- next_expr_id()
    args <- lapply(args, as_expr)
    if (length(args) == 0L) {
      cli_abort("No arguments given to {.cls Elementwise}.")
    }
    ## shape_from_args: elementwise uses sum_shapes (broadcasting)
    shape <- sum_shapes(lapply(args, function(a) .shape(a)))
    obj <- .fast_new(Elementwise, S7_object(),
      id    = as.integer(id),
      .cache = new.env(parent = emptyenv()),
      args  = args,
      shape = shape
    )
    validate_arguments(obj)
    obj
  }
)

# -- shape_from_args: broadcasting ---------------------------------
## CVXPY SOURCE: elementwise.py lines 30-33
method(shape_from_args, Elementwise) <- function(x) {
  sum_shapes(lapply(.args(x), function(a) .shape(a)))
}

# -- validate_arguments: check broadcastable shapes ----------------
## CVXPY SOURCE: elementwise.py lines 35-41
method(validate_arguments, Elementwise) <- function(x) {
  ## Verify shapes are broadcastable (sum_shapes will abort on mismatch)
  sum_shapes(lapply(.args(x), function(a) .shape(a)))
  ## Call Atom's validate_arguments (rejects complex arguments)
  ## NOTE: Can't use NextMethod() with S7 generics -- call Atom's method directly
  if (.any_args(x, is_complex)) {
    cli_abort("Arguments to {.cls {class(x)[[1L]]}} cannot be complex.")
  }
  invisible(NULL)
}

# -- is_symmetric --------------------------------------------------
## CVXPY SOURCE: elementwise.py:44-48 (CVXPY 1.9.2)
## 1.9.2 guarded this with `if self.ndim == 2 and shape[0] == shape[1]`,
## falling back to the Expression default otherwise, because indexing
## `shape[1]` raised IndexError on a scalar or 1-D argument.  CVXR needs no
## such guard and is left as-is: an R shape is ALWAYS length 2 (a scalar is
## c(1,1), a vector c(n,1)), so the subscript cannot go out of range, and
## the non-square branch already returns FALSE, which is what the upstream
## fallback yields for every non-scalar.  Verified against all five
## assertions of test_elementwise_is_symmetric.
method(is_symmetric, Elementwise) <- function(x) {
  symm_args <- .all_args(x, is_symmetric)
  .shape(x)[1L] == .shape(x)[2L] && symm_args
}

# -- Helper: promote LinOp if needed ------------------------------
## CVXPY SOURCE: elementwise.py lines 63-82
## Used by graph_implementation in subclasses
.elemwise_promote <- function(arg, shape) {
  if (!identical(arg$shape, as.integer(shape))) {
    promote_linop(arg, shape)
  } else {
    arg
  }
}

# -- Helper: elementwise grad to diagonal sparse matrix -----------
## CVXPY SOURCE: elementwise.py:50-61 (elementwise.elemwise_grad_to_diag).
## Takes a same-shape derivative array `value`, ravels it in column-major
## order, and embeds it as the diagonal of a (rows x cols) sparse matrix.
## Used by every elementwise atom's .grad hook.
.elemwise_grad_to_diag <- function(value, rows, cols) {
  ## Column-major ravel: as.numeric() on a base-R array/matrix flattens
  ## by column, matching numpy's ravel(order='F').
  v <- as.numeric(value)
  n <- length(v)
  Matrix::sparseMatrix(
    i = seq_len(n),
    j = seq_len(n),
    x = v,
    dims = c(as.integer(rows), as.integer(cols)),
    repr = "C"
  )
}

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.