R/039_atoms_affine_binary_operators.R

Defines functions .binop_name

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

## CVXPY SOURCE: atoms/affine/binary_operators.py
## BinaryOperator, MulExpression, Multiply, DivExpression


# ===================================================================
# BinaryOperator -- base class for binary operations (other than add)
# ===================================================================

BinaryOperator <- new_class("BinaryOperator", parent = AffAtom, package = "CVXR",
  constructor = function(lh_exp, rh_exp, shape) {
    lh_exp <- as_expr(lh_exp)
    rh_exp <- as_expr(rh_exp)
    shape <- validate_shape(shape)
    obj <- new_object(S7_object(),
      id    = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args  = list(lh_exp, rh_exp),
      shape = shape
    )
    validate_arguments(obj)
    obj
  }
)

# -- sign: multiply sign rules ---------------------------------------
## CVXPY SOURCE: binary_operators.py lines 92-95

method(sign_from_args, BinaryOperator) <- function(x) {
  mul_sign(x@args[[1L]], x@args[[2L]])
}

# -- Complex propagation ---------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 97-107

method(is_imag, BinaryOperator) <- function(x) {
  (is_imag(x@args[[1L]]) && is_real(x@args[[2L]])) ||
    (is_real(x@args[[1L]]) && is_imag(x@args[[2L]]))
}

method(is_complex, BinaryOperator) <- function(x) {
  (is_complex(x@args[[1L]]) || is_complex(x@args[[2L]])) &&
    !(is_imag(x@args[[1L]]) && is_imag(x@args[[2L]]))
}


# ===================================================================
# MulExpression -- matrix multiplication (lhs %*% rhs)
# ===================================================================

MulExpression <- new_class("MulExpression", parent = BinaryOperator, package = "CVXR",
  constructor = function(lh_exp, rh_exp) {
    lh_exp <- as_expr(lh_exp)
    rh_exp <- as_expr(rh_exp)
    shape <- mul_shapes(lh_exp@shape, rh_exp@shape)
    obj <- new_object(S7_object(),
      id    = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args  = list(lh_exp, rh_exp),
      shape = shape
    )
    validate_arguments(obj)
    obj
  }
)

# -- shape_from_args -------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 188-191

method(shape_from_args, MulExpression) <- function(x) {
  mul_shapes(x@args[[1L]]@shape, x@args[[2L]]@shape)
}

# -- Convexity: requires one constant arg (with DPP extension) --------
## CVXPY SOURCE: binary_operators.py lines 193-214

method(is_atom_convex, MulExpression) <- function(x) {
  lhs <- x@args[[1L]]
  rhs <- x@args[[2L]]
  if (is_constant(lhs) || is_constant(rhs)) return(TRUE)
  ## DPP rule: product is DPP-convex if one arg is param-affine
  ## and the other is parameter-free.
  if (dpp_scope_active()) {
    return((is_param_affine(lhs) && is_param_free(rhs)) ||
           (is_param_affine(rhs) && is_param_free(lhs)))
  }
  FALSE
}

method(is_atom_concave, MulExpression) <- function(x) {
  is_atom_convex(x)
}

# -- Monotonicity ----------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 231-239
## idx is 1-based (R convention): is_incr(1) checks args[[2]], is_incr(2) checks args[[1]]

method(is_incr, MulExpression) <- function(x, idx, ...) {
  ## self.args[1-idx] in Python (0-based) -> args[[3L - idx]] in R (1-based)
  is_nonneg(x@args[[3L - idx]])
}

method(is_decr, MulExpression) <- function(x, idx, ...) {
  is_nonpos(x@args[[3L - idx]])
}

# -- numeric_value ---------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 180-186

method(numeric_value, MulExpression) <- function(x, values, ...) {
  lhs <- values[[1L]]
  rhs <- values[[2L]]
  ## Handle scalar multiplication
  if (length(lhs) == 1L) return(drop(lhs) * rhs)
  if (length(rhs) == 1L) return(lhs * drop(rhs))
  ## Matrix multiplication
  lhs %*% rhs
}

# -- graph_implementation --------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 295-323

method(graph_implementation, MulExpression) <- function(x, arg_objs, shape, data = NULL, ...) {
  lhs <- arg_objs[[1L]]
  rhs <- arg_objs[[2L]]
  if (is_constant(x@args[[1L]])) {
    list(mul_expr_linop(lhs, rhs, shape), list())
  } else if (is_constant(x@args[[2L]])) {
    list(rmul_expr_linop(lhs, rhs, shape), list())
  } else {
    cli_abort("Product of two non-constant expressions is not DCP.", class = "DCPError")
  }
}

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

## CVXPY SOURCE: binary_operators.py lines 221-229
method(is_atom_log_log_convex, MulExpression) <- function(x) TRUE
method(is_atom_log_log_concave, MulExpression) <- function(x) FALSE

method(expr_name, MulExpression) <- function(x) {
  .binop_name(x, "%*%")
}


# ===================================================================
# Multiply -- elementwise multiplication (lhs * rhs)
# ===================================================================

Multiply <- new_class("Multiply", parent = MulExpression, package = "CVXR",
  constructor = function(lh_exp, rh_exp) {
    lh_exp <- as_expr(lh_exp)
    rh_exp <- as_expr(rh_exp)
    ## Broadcast scalars
    bcast <- broadcast_args(lh_exp, rh_exp)
    lh_exp <- bcast[[1L]]
    rh_exp <- bcast[[2L]]
    ## Shape from broadcasting
    shape <- sum_shapes(list(lh_exp@shape, rh_exp@shape))
    obj <- new_object(S7_object(),
      id    = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args  = list(lh_exp, rh_exp),
      shape = shape
    )
    ## Multiply allows complex (inherits AffAtom validate_arguments)
    obj
  }
)

# -- shape_from_args -------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 371-373

method(shape_from_args, Multiply) <- function(x) {
  sum_shapes(list(x@args[[1L]]@shape, x@args[[2L]]@shape))
}

# -- validate_arguments ----------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 364-369
## Broadcast compatibility already checked by sum_shapes in constructor.

method(validate_arguments, Multiply) <- function(x) {
  invisible(NULL)
}

# -- numeric_value: elementwise --------------------------------------
## CVXPY SOURCE: binary_operators.py lines 355-362

method(numeric_value, Multiply) <- function(x, values, ...) {
  lhs <- values[[1L]]
  rhs <- values[[2L]]
  ## R's * handles sparse matrices correctly
  lhs * rhs
}

# -- PSD/NSD ---------------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 375-385

method(is_psd, Multiply) <- function(x) {
  (is_psd(x@args[[1L]]) && is_psd(x@args[[2L]])) ||
    (is_nsd(x@args[[1L]]) && is_nsd(x@args[[2L]]))
}

method(is_nsd, Multiply) <- function(x) {
  (is_psd(x@args[[1L]]) && is_nsd(x@args[[2L]])) ||
    (is_nsd(x@args[[1L]]) && is_psd(x@args[[2L]]))
}

# -- Quasiconvexity --------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 343-353
method(is_atom_quasiconvex, Multiply) <- function(x) {
  (is_constant(x@args[[1L]]) || is_constant(x@args[[2L]])) ||
    (is_nonneg(x@args[[1L]]) && is_nonpos(x@args[[2L]])) ||
    (is_nonpos(x@args[[1L]]) && is_nonneg(x@args[[2L]]))
}

method(is_atom_quasiconcave, Multiply) <- function(x) {
  (is_constant(x@args[[1L]]) || is_constant(x@args[[2L]])) ||
    (is_nonneg(x@args[[1L]]) && is_nonneg(x@args[[2L]])) ||
    (is_nonpos(x@args[[1L]]) && is_nonpos(x@args[[2L]]))
}

# -- graph_implementation --------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 416-444

method(graph_implementation, Multiply) <- function(x, arg_objs, shape, data = NULL, ...) {
  lhs <- arg_objs[[1L]]
  rhs <- arg_objs[[2L]]
  if (is_constant(x@args[[1L]])) {
    list(multiply_linop(lhs, rhs), list())
  } else if (is_constant(x@args[[2L]])) {
    list(multiply_linop(rhs, lhs), list())
  } else {
    cli_abort("Product of two non-constant expressions is not DCP.", class = "DCPError")
  }
}

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

## CVXPY SOURCE: binary_operators.py lines 335-341
method(is_atom_log_log_convex, Multiply) <- function(x) TRUE
method(is_atom_log_log_concave, Multiply) <- function(x) TRUE

method(expr_name, Multiply) <- function(x) {
  .binop_name(x, "*")
}


# ===================================================================
# DivExpression -- division (lhs / rhs)
# ===================================================================

DivExpression <- new_class("DivExpression", parent = BinaryOperator, package = "CVXR",
  constructor = function(lh_exp, rh_exp) {
    lh_exp <- as_expr(lh_exp)
    rh_exp <- as_expr(rh_exp)
    ## Broadcast scalars
    bcast <- broadcast_args(lh_exp, rh_exp)
    lh_exp <- bcast[[1L]]
    rh_exp <- bcast[[2L]]
    ## Shape is numerator shape
    shape <- lh_exp@shape
    obj <- new_object(S7_object(),
      id    = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args  = list(lh_exp, rh_exp),
      shape = shape
    )
    validate_arguments(obj)
    obj
  }
)

# -- shape_from_args -------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 478-481

method(shape_from_args, DivExpression) <- function(x) x@args[[1L]]@shape

# -- Convexity: requires constant denominator ------------------------
## CVXPY SOURCE: binary_operators.py lines 483-490

method(is_atom_convex, DivExpression) <- function(x) {
  is_constant(x@args[[2L]])
}

method(is_atom_concave, DivExpression) <- function(x) {
  is_atom_convex(x)
}

# -- Quasiconvexity --------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 502-506
method(is_atom_quasiconvex, DivExpression) <- function(x) {
  is_nonneg(x@args[[2L]]) || is_nonpos(x@args[[2L]])
}

method(is_atom_quasiconcave, DivExpression) <- function(x) {
  is_atom_quasiconvex(x)
}

# -- Monotonicity ----------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 508-522
## idx is 1-based (R convention)

method(is_incr, DivExpression) <- function(x, idx, ...) {
  if (idx == 1L) {
    ## d(lhs/rhs)/d(lhs) > 0 when rhs > 0
    is_nonneg(x@args[[2L]])
  } else {
    ## d(lhs/rhs)/d(rhs) > 0 when lhs < 0 (nonpositive)
    is_nonpos(x@args[[1L]])
  }
}

method(is_decr, DivExpression) <- function(x, idx, ...) {
  if (idx == 1L) {
    ## d(lhs/rhs)/d(lhs) < 0 when rhs < 0
    is_nonpos(x@args[[2L]])
  } else {
    ## d(lhs/rhs)/d(rhs) < 0 when lhs > 0 (nonneg)
    is_nonneg(x@args[[1L]])
  }
}

# -- numeric_value ---------------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 460-466

method(numeric_value, DivExpression) <- function(x, values, ...) {
  lhs <- if (inherits(values[[1L]], "sparseMatrix")) as.matrix(values[[1L]]) else values[[1L]]
  rhs <- if (inherits(values[[2L]], "sparseMatrix")) as.matrix(values[[2L]]) else values[[2L]]
  lhs / rhs
}

# -- graph_implementation --------------------------------------------
## CVXPY SOURCE: binary_operators.py lines 524-543

method(graph_implementation, DivExpression) <- function(x, arg_objs, shape, data = NULL, ...) {
  list(div_expr_linop(arg_objs[[1L]], arg_objs[[2L]]), list())
}

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

## CVXPY SOURCE: binary_operators.py lines 492-500
method(is_atom_log_log_convex, DivExpression) <- function(x) TRUE
method(is_atom_log_log_concave, DivExpression) <- function(x) TRUE

method(expr_name, DivExpression) <- function(x) {
  .binop_name(x, "/")
}


# ===================================================================
# Helper for binary operator names
# ===================================================================

.binop_name <- function(x, op_name) {
  lhs_name <- expr_name(x@args[[1L]])
  rhs_name <- expr_name(x@args[[2L]])
  ## Parenthesize AddExpression and DivExpression args
  if (S7_inherits(x@args[[1L]], AddExpression) ||
      S7_inherits(x@args[[1L]], DivExpression)) {
    lhs_name <- sprintf("(%s)", lhs_name)
  }
  if (S7_inherits(x@args[[2L]], AddExpression) ||
      S7_inherits(x@args[[2L]], DivExpression)) {
    rhs_name <- sprintf("(%s)", rhs_name)
  }
  sprintf("%s %s %s", lhs_name, op_name, rhs_name)
}

Try the CVXR package in your browser

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

CVXR documentation built on March 6, 2026, 9:10 a.m.