R/304_zzz_R_specific_masking.R

Defines functions diag outer var sd .reject_stats_args norm

Documented in diag norm outer sd var

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/zzz_R_specific/masking.R
#####

## R-SPECIFIC: Type-dispatching wrappers that mask base/stats functions
## These give CVXR its DSL feel: norm(x), sd(x), var(x), outer(x, y) Just Work.
##
## norm() and diag() are plain functions in base R, promoted to S4 by Matrix.
## We use masking wrappers with Matrix::fn() fallthrough (not base::fn())
## because base::fn fails on S4 Matrix objects. See conflicts/README.md.
##
## sd(), var(), outer() use base/stats fallthrough because base::fn works
## on Matrix objects for these functions.

#' Matrix norm
#'
#' For CVXR expressions, computes the norm atom with the same meaning as
#' base R \code{\link[base]{norm}} for every type: a vector is a one-column
#' or one-row matrix. For any other input the call is forwarded as written
#' to \code{Matrix::norm}, so the result is exactly base R's (including its
#' default type \code{"O"}).
#'
#' @param x An Expression or matrix.
#' @param type Norm type, as in base R: \code{"O"}/\code{"o"}/\code{"1"}
#'   (maximum absolute column sum), \code{"I"}/\code{"i"} (maximum absolute
#'   row sum), \code{"F"}/\code{"f"}/\code{"E"}/\code{"e"} (Frobenius),
#'   \code{"M"}/\code{"m"} (largest absolute entry), \code{"2"} (largest
#'   singular value; the default for an expression only). For an expression,
#'   any other value is passed to \code{\link{cvxr_norm}} as \code{p}.
#' @param ... Additional arguments (unused for expressions).
#' @returns An Expression or numeric value.
#' @seealso \code{\link{cvxr_norm}} for the full-featured version with
#'   \code{axis} and \code{keepdims} arguments
#' @rdname math_atoms
#' @export
norm <- function(x, type = "2", ...) {
  if (inherits(x, "CVXR::Expression")) {
    ## base R norm() types (user, 2026-10-14; was entrywise under D3).
    type <- as.character(type)
    switch(EXPR = type,   # named: the "E" alternative would partially match EXPR
      "O" =, "o" =, "1" = cvxr_norm(x, p = 1),
      "I" =, "i" = cvxr_norm(x, p = Inf),
      "F" =, "f" =, "E" =, "e" = cvxr_norm(x, p = "fro"),
      "M" =, "m" = max_entries(abs(x)),
      "2" = cvxr_norm(x, p = 2),
      cvxr_norm(x, p = type)
    )
  } else {
    ## Forward the call as written, so an omitted type gets the base/Matrix
    ## default ("O"), not this function's expression default "2".
    cl <- match.call()
    cl[[1L]] <- quote(Matrix::norm)
    eval(cl, parent.frame())
  }
}

## A stats-only argument on an expression is an error, as an unknown keyword
## is in CVXPY's std/var (stats.py:42/57).
## (%in%, not intersect(): intersect() costs ~7us per call with names.)
.reject_stats_args <- function(dot_names, stats_args, fn, cvxr_fn) {
  hit <- dot_names %in% stats_args
  if (any(hit)) {
    bad <- dot_names[hit]
    cli_abort(c(
      "{.arg {bad[1L]}} is a {.fn stats::{fn}} argument.",
      "i" = "On an expression, {.fn {fn}} is {.fn {cvxr_fn}}, which takes {.arg axis}, {.arg keepdims} and {.arg ddof}."
    ))
  }
}

#' Standard deviation
#'
#' For CVXR expressions, computes the standard deviation atom (ddof=0 by default,
#' matching CVXPY/numpy convention). For numeric inputs, falls through to
#' \code{\link[stats]{sd}}.
#'
#' @param x An Expression or numeric vector.
#' @param ... For expressions: passed to \code{\link{cvxr_std}}
#'   (axis, keepdims, ddof); \code{na.rm} is an error. For numeric: passed to
#'   \code{\link[stats]{sd}}.
#' @returns An Expression or numeric value.
#' @seealso \code{\link{cvxr_std}} for the full-featured version
#' @rdname math_atoms
#' @export
sd <- function(x, ...) {
  if (inherits(x, "CVXR::Expression")) {
    .reject_stats_args(...names(), "na.rm", "sd", "cvxr_std")
    cvxr_std(x, ...)
  } else {
    stats::sd(x, ...)
  }
}

#' Variance
#'
#' For CVXR expressions, computes the variance atom (ddof=0 by default).
#' For numeric inputs, falls through to \code{\link[stats]{var}}.
#'
#' @param x An Expression or numeric.
#' @param ... For expressions: passed to \code{\link{cvxr_var}}
#'   (axis, keepdims, ddof); \code{y}, \code{na.rm} and \code{use} are errors.
#'   For numeric: passed to \code{\link[stats]{var}}.
#' @returns An Expression or numeric value.
#' @seealso \code{\link{cvxr_var}} for the full-featured version
#' @rdname math_atoms
#' @export
var <- function(x, ...) {
  if (inherits(x, "CVXR::Expression")) {
    .reject_stats_args(...names(), c("y", "na.rm", "use"), "var", "cvxr_var")
    cvxr_var(x, ...)
  } else {
    stats::var(x, ...)
  }
}

#' Outer product
#'
#' For CVXR expressions, computes the outer product of two vectors.
#' For other inputs, falls through to \code{\link[base]{outer}}.
#'
#' @param X An Expression or numeric.
#' @param Y An Expression or numeric.
#' @param ... For non-Expression inputs: passed to \code{\link[base]{outer}}.
#'   For expressions, only \code{FUN = "*"} is accepted; any other
#'   \code{FUN} or argument is an error.
#' @returns An Expression or matrix.
#' @seealso \code{\link{cvxr_outer}} for the CVXR-specific version
#' @rdname math_atoms
#' @export
outer <- function(X, Y, ...) {
  if (inherits(X, "CVXR::Expression") || inherits(Y, "CVXR::Expression")) {
    ## binary_operators.py:682 outer(x, y) is the product only.
    nm <- ...names()
    if (...length() > 1L ||
        (...length() == 1L &&
         (!(is.null(nm) || nm %in% c("", "FUN")) ||
          !(identical(..1, "*") || identical(..1, base::`*`))))) {
      cli_abort(c(
        "On an expression, {.fn outer} is {.fn cvxr_outer}, the product.",
        "i" = "{.arg FUN} must be {.val *}, and no other argument is accepted."
      ))
    }
    cvxr_outer(X, Y)
  } else {
    base::outer(X, Y, ...)
  }
}

#' Diagonal matrix creation / extraction
#'
#' For CVXR expressions, dispatches to \code{\link{DiagVec}} (vector to
#' diagonal matrix) or \code{\link{DiagMat}} (extract diagonal from matrix),
#' matching CVXPY's \code{cp.diag()} behavior. For other inputs, falls
#' through to \code{Matrix::diag}, called exactly as written, which
#' dispatches via S4 for both Matrix and base matrix objects.
#'
#' The \code{k} parameter (off-diagonal offset) is only available for
#' Expression inputs; giving it with a non-Expression \code{x} is an error.
#'
#' @param x An Expression, matrix, vector, or scalar.
#' @param nrow For non-Expression: passed to \code{Matrix::diag}; an error
#'   with an Expression.
#' @param ncol For non-Expression: passed to \code{Matrix::diag}; an error
#'   with an Expression.
#' @param names For non-Expression: passed to \code{Matrix::diag}; an error
#'   with an Expression.
#' @param k Integer diagonal offset for Expressions only. \code{NULL}
#'   (default) means \code{0}, the main diagonal.
#' @returns An Expression, matrix, or vector.
#' @seealso \code{\link{DiagVec}}, \code{\link{DiagMat}}
#' @rdname math_atoms
#' @export
diag <- function(x = 1, nrow, ncol, names = TRUE, k = NULL) {
  if (inherits(x, "CVXR::Expression")) {
    ## diag.py:27 diag(expr, k=0): the Matrix-only arguments are errors.
    bad <- c("nrow", "ncol", "names")[c(!missing(nrow), !missing(ncol), !missing(names))]
    if (length(bad) > 0L) {
      cli_abort(c(
        "{.arg {bad[1L]}} is a {.fn Matrix::diag} argument.",
        "i" = "On an expression, {.fn diag} takes only {.arg k}, the diagonal offset, by name."
      ))
    }
    k <- if (is.null(k)) 0L else as.integer(k)
    if (is_vector(x)) {
      ## Vector -> diagonal matrix (CVXPY: diag_vec)
      DiagVec(.cvxr_vec(x), k = k)
    } else if (x@shape[1L] == x@shape[2L]) {
      ## Square matrix -> extract diagonal (CVXPY: diag_mat)
      DiagMat(x, k = k)
    } else {
      cli_abort("Argument to {.fn diag} must be a vector or square matrix Expression.")
    }
  } else {
    if (!is.null(k))
      cli_abort("{.arg k} is only available when {.arg x} is a CVXR Expression; use {.fn DiagVec} or {.fn DiagMat}.")
    ## Forward the call as written: base diag() branches on nargs(), so a
    ## literal Matrix::diag(x, nrow, ncol, names) changes diag(3) and diag(A).
    cl <- match.call()
    cl[[1L]] <- quote(Matrix::diag)
    cl$k <- NULL
    eval(cl, parent.frame())
  }
}

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.