R/140_problems_objective.R

Defines functions .objective_Ops_handler .div_objective .mul_objective .sub_objectives .add_objectives .negate_objective .check_dpp_context scalar_value

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/problems/objective.R
#####

## CVXPY SOURCE: problems/objective.py
## Objective, Minimize, Maximize -- optimization objective wrappers
##
## CVXPY 1.9 parity notes:
##  - DNLP objective predicates mirror objective.py:159-163 and 236-240:
##    Minimize requires a linearizable-convex target; Maximize requires a
##    linearizable-concave target.

# -- scalar_value: extract scalar from 1x1 matrix ---------------------
## CVXPY SOURCE: interface/matrix_utilities.py::scalar_value

scalar_value <- function(v) {
  if (is.null(v)) return(NULL)
  if (is.numeric(v) && length(v) == 1L) return(as.double(v))
  if (is.matrix(v) || inherits(v, "Matrix")) {
    if (prod(dim(v)) == 1L) return(as.double(v[1L, 1L]))
  }
  as.double(v[1L])
}

# -- Objective base class ---------------------------------------------
## CVXPY SOURCE: objective.py lines 25-116

Objective <- new_class("Objective", parent = Canonical, package = "CVXR",
  properties = list(
    .name = new_property(class = class_character, default = "objective")
  ),
  constructor = function(expr) {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    expr <- as_expr(expr)
    ## Validate scalar
    ## CVXPY SOURCE: objective.py lines 46-49
    if (!expr_is_scalar(expr)) {
      cli_abort("The {.val objective} objective must resolve to a scalar.")
    }
    if (!is_real(expr)) {
      cli_abort("The {.val objective} objective must be real valued.")
    }
    .fast_new(Objective, S7_object(),
      id = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args = list(expr),
      .name = "objective"
    )
  }
)

# -- Objective value --------------------------------------------------
## CVXPY SOURCE: objective.py lines 97-105

method(value, Objective) <- function(x) {
  v <- value(x@args[[1L]])
  if (is.null(v)) return(NULL)
  scalar_value(v)
}

# -- Objective print --------------------------------------------------

method(print, Objective) <- function(x, ...) {
  cat(sprintf("%s %s\n", x@.name, expr_name(x@args[[1L]])))
  invisible(x)
}

# -- Objective is_quadratic / is_qpwa --------------------------------
## CVXPY SOURCE: objective.py lines 107-115

method(is_quadratic, Objective) <- function(x) is_quadratic(x@args[[1L]])
method(is_qpwa, Objective) <- function(x) is_qpwa(x@args[[1L]])

# -- Objective is_dcp default -----------------------------------------

method(is_dcp, Objective) <- function(x) FALSE

# -- Minimize ---------------------------------------------------------
## CVXPY SOURCE: objective.py lines 118-186

#' Create a Minimization Objective
#'
#' Specifies that the objective expression should be minimized. The expression
#' must be convex and scalar for a DCP-compliant problem.
#'
#' @param expr A CVXR expression or numeric value to minimize.
#' @returns A \code{Minimize} object.
#'
#' @examples
#' x <- Variable()
#' obj <- Minimize(x^2 + 1)
#'
#' @seealso \code{\link{Maximize}}, \code{\link{Problem}}
#' @export
Minimize <- new_class("Minimize", parent = Objective, package = "CVXR",
  constructor = function(expr) {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    expr <- as_expr(expr)
    if (!expr_is_scalar(expr)) {
      cli_abort("The {.val minimize} objective must resolve to a scalar.")
    }
    if (!is_real(expr)) {
      cli_abort("The {.val minimize} objective must be real valued.")
    }
    .fast_new(Minimize, S7_object(),
      id = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args = list(expr),
      .name = "minimize"
    )
  }
)

## is_dcp: the objective expression must be convex
## CVXPY SOURCE: objective.py lines 151-157
method(is_dcp, Minimize) <- function(x) is_convex(x@args[[1L]])

## is_dqcp: the objective expression must be quasiconvex
## CVXPY SOURCE: objective.py lines 176-179
method(is_dqcp, Minimize) <- function(x) is_quasiconvex(x@args[[1L]])

## is_dnlp: the objective expression must be linearizable-convex
## CVXPY SOURCE: objective.py lines 159-163
method(is_dnlp, Minimize) <- function(x) is_linearizable_convex(x@args[[1L]])

## is_dgp: the objective expression must be log-log convex
## CVXPY SOURCE: objective.py lines 159-165
method(is_dgp, Minimize) <- function(x) is_log_log_convex(x@args[[1L]])

## is_dpp: check DPP compliance within DPP scope
## CVXPY SOURCE: objective.py lines 167-174
method(is_dpp, Minimize) <- function(x, context = "dcp") {
  .check_dpp_context(context)
  arg <- x@args[[1L]]
  curv <- if (identical(tolower(context), "dgp")) with_dpp_scope(is_log_log_convex(arg))
          else with_dpp_scope(is_convex(arg))
  curv && is_dpp(arg, context)
}

## canonicalize: pass through the expression's canonical form
## CVXPY SOURCE: objective.py lines 146-149
method(canonicalize, Minimize) <- function(x) canonical_form(x@args[[1L]])

## print
method(print, Minimize) <- function(x, ...) {
  cat(sprintf("minimize %s\n", expr_name(x@args[[1L]])))
  invisible(x)
}

# -- Maximize ---------------------------------------------------------
## CVXPY SOURCE: objective.py lines 188-257

#' Create a Maximization Objective
#'
#' Specifies that the objective expression should be maximized. The expression
#' must be concave and scalar for a DCP-compliant problem.
#'
#' @param expr A CVXR expression or numeric value to maximize.
#' @returns A \code{Maximize} object.
#'
#' @examples
#' x <- Variable()
#' obj <- Maximize(-x^2 + 1)
#'
#' @seealso \code{\link{Minimize}}, \code{\link{Problem}}
#' @export
Maximize <- new_class("Maximize", parent = Objective, package = "CVXR",
  constructor = function(expr) {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    expr <- as_expr(expr)
    if (!expr_is_scalar(expr)) {
      cli_abort("The {.val maximize} objective must resolve to a scalar.")
    }
    if (!is_real(expr)) {
      cli_abort("The {.val maximize} objective must be real valued.")
    }
    .fast_new(Maximize, S7_object(),
      id = next_expr_id(),
      .cache = new.env(parent = emptyenv()),
      args = list(expr),
      .name = "maximize"
    )
  }
)

## is_dcp: the objective expression must be concave
## CVXPY SOURCE: objective.py lines 222-228
method(is_dcp, Maximize) <- function(x) is_concave(x@args[[1L]])

## is_dqcp: the objective expression must be quasiconcave
## CVXPY SOURCE: objective.py lines 247-250
method(is_dqcp, Maximize) <- function(x) is_quasiconcave(x@args[[1L]])

## is_dnlp: the objective expression must be linearizable-concave
## CVXPY SOURCE: objective.py lines 236-240
method(is_dnlp, Maximize) <- function(x) is_linearizable_concave(x@args[[1L]])

## is_dgp: the objective expression must be log-log concave
## CVXPY SOURCE: objective.py lines 230-236
method(is_dgp, Maximize) <- function(x) is_log_log_concave(x@args[[1L]])

## is_dpp: check DPP compliance within DPP scope
## CVXPY SOURCE: objective.py lines 238-245
method(is_dpp, Maximize) <- function(x, context = "dcp") {
  .check_dpp_context(context)
  arg <- x@args[[1L]]
  curv <- if (identical(tolower(context), "dgp")) with_dpp_scope(is_log_log_concave(arg))
          else with_dpp_scope(is_concave(arg))
  curv && is_dpp(arg, context)
}

## canonicalize: negate the expression's canonical form
## CVXPY SOURCE: objective.py lines 216-220
method(canonicalize, Maximize) <- function(x) {
  result <- canonical_form(x@args[[1L]])
  obj <- result[[1L]]
  constraints <- result[[2L]]
  list(neg_expr_linop(obj), constraints)
}

## print
method(print, Maximize) <- function(x, ...) {
  cat(sprintf("maximize %s\n", expr_name(x@args[[1L]])))
  invisible(x)
}

# -- Objective arithmetic ---------------------------------------------
## CVXPY SOURCE: objective.py lines 61-105 (Objective.__add__, __mul__, etc.)

## Reject an unrecognized DPP context.
## CVXPY SOURCE: objective.py:173-181 / 250-258 -- both Minimize.is_dpp and
## Maximize.is_dpp raise ValueError("Unsupported context ", context) for
## anything other than 'dcp'/'dgp'.  CVXR previously fell through to the dcp
## branch for ANY non-"dgp" string, so a typo silently answered the wrong
## question instead of erroring.
.check_dpp_context <- function(context) {
  if (!is.character(context) || length(context) != 1L ||
      !(tolower(context) %in% c("dcp", "dgp"))) {
    cli_abort(c(
      "Unsupported DPP context {.val {context}}.",
      "i" = "Use {.val dcp} or {.val dgp}."
    ))
  }
  invisible(TRUE)
}

## Negate: -Minimize(f) -> Maximize(-f), -Maximize(f) -> Minimize(-f)
## CVXPY SOURCE: objective.py Minimize.__neg__ / Maximize.__neg__
.negate_objective <- function(obj) {

  expr <- obj@args[[1L]]
  if (.s7_is(obj, Minimize)) Maximize(-expr) else Minimize(-expr)
}

## Add: both must be same type (Minimize+Minimize or Maximize+Maximize)
## CVXPY SOURCE: objective.py Minimize.__add__ / Maximize.__add__
.add_objectives <- function(o1, o2) {
  if (!.s7_is(o2, Objective))
    cli_abort("Cannot add {.cls {class(o2)[[1L]]}} to an objective.")
  e1 <- o1@args[[1L]]
  e2 <- o2@args[[1L]]
  if (.s7_is(o1, Minimize) && .s7_is(o2, Minimize)) {
    Minimize(e1 + e2)
  } else if (.s7_is(o1, Maximize) && .s7_is(o2, Maximize)) {
    Maximize(e1 + e2)
  } else {
    cli_abort("Cannot add {.cls Minimize} and {.cls Maximize} (violates DCP).")
  }
}

## Subtract: o1 - o2 = o1 + (-o2)
.sub_objectives <- function(o1, o2) {
  .add_objectives(o1, .negate_objective(o2))
}

## Multiply by scalar: sign flip reverses direction
## CVXPY SOURCE: objective.py Objective.__mul__
.mul_objective <- function(obj, scalar) {
  if (!is.numeric(scalar) || length(scalar) != 1L)
    cli_abort("Objective can only be multiplied by a numeric scalar.")
  expr <- obj@args[[1L]]
  ## If (Maximize && negative) or (Minimize && negative), flip direction
  if ((.s7_is(obj, Maximize)) == (scalar < 0)) {
    Minimize(expr * scalar)
  } else {
    Maximize(expr * scalar)
  }
}

## Divide by scalar
.div_objective <- function(obj, scalar) {
  if (!is.numeric(scalar) || length(scalar) != 1L)
    cli_abort("Objective can only be divided by a numeric scalar.")
  .mul_objective(obj, 1.0 / scalar)
}

## -- Ops dispatch for Objective ---------------------------------------
## CVXPY SOURCE: objective.py:61-95 (Objective.__radd__/__sub__/__rsub__/
## __mul__/__rmul__/__div__), :137-145 (Minimize.__add__), :134-135
## (Minimize.__neg__), :213-221 (Maximize.__add__), :210-211 (Maximize.__neg__).
##
## The four helpers above already implemented CVXPY's semantics exactly --
## including the direction flip, `(is Maximize) == (scalar < 0)` -- but nothing
## dispatched to them, so `Minimize(f) * 2` failed with R's "non-numeric
## argument to binary operator". Registered in .onLoad(), mirroring the
## Expression and Problem handlers (globals.R).
##
## ONE DELIBERATE DEVIATION, for internal consistency: CVXPY accepts `0 + obj`
## (via __radd__) but REJECTS `obj + 0` (Minimize.__add__ demands an Objective).
## That asymmetry is an artifact of Python's __add__/__radd__ split, existing so
## sum() can start from 0; it carries no meaning. R's Ops handler sees both
## operands at once and cannot reproduce it without inventing an arbitrary
## rule, and CVXR's own .problem_Ops_handler already accepts either side. So
## the identity is accepted on BOTH sides here. Everything else -- including
## `1 + obj` raising, and Minimize + Maximize being a DCP error -- matches.
#' @keywords internal
.objective_Ops_handler <- function(e1, e2) {
  op <- .Generic
  unary <- (nargs() == 1L)

  if (unary) {
    if (op == "-") return(.negate_objective(e1))
    if (op == "+") return(e1)
    cli_abort("Unary {.val {op}} is not supported on an objective.")
  }

  .is_zero <- function(v) is.numeric(v) && length(v) == 1L && !is.na(v) && v == 0

  switch(op,
    "+" = {
      if (.is_zero(e1)) return(e2)
      if (.is_zero(e2)) return(e1)
      if (!.s7_is(e1, Objective) || !.s7_is(e2, Objective))
        cli_abort("An objective can only be added to another objective, or to {.val 0}.")
      .add_objectives(e1, e2)
    },
    "-" = {
      if (.is_zero(e1)) return(.negate_objective(e2))
      if (.is_zero(e2)) return(e1)
      if (!.s7_is(e1, Objective) || !.s7_is(e2, Objective))
        cli_abort("An objective can only be subtracted from another objective.")
      .sub_objectives(e1, e2)
    },
    "*" = {
      if (.s7_is(e1, Objective) && is.numeric(e2)) .mul_objective(e1, e2)
      else if (is.numeric(e1) && .s7_is(e2, Objective)) .mul_objective(e2, e1)
      else cli_abort("An objective can only be multiplied by a numeric scalar.")
    },
    "/" = {
      if (!.s7_is(e1, Objective) || !is.numeric(e2))
        cli_abort("An objective can only be divided by a numeric scalar.")
      .div_objective(e1, e2)
    },
    cli_abort("Operator {.val {op}} is not supported on an objective.")
  )
}

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.