R/253_transforms_scalarize.R

Defines functions .scalarize_log_sum_exp .scalarize_max .scalarize_targets_and_priorities .scalarize_weighted_sum

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/transforms/scalarize.R
#####

## CVXPY SOURCE: transforms/scalarize.py
## Scalarize -- combine several objectives into one for multi-objective problems.
##
## CVXPY exposes these as the submodule `cvxpy.transforms.scalarize`, called as
## `scalarize.weighted_sum(...)`, `scalarize.max(...)`, etc. R has no submodule
## namespacing, so we export a single list object `scalarize` whose members
## mirror the Python functions 1:1 -- `scalarize$weighted_sum(...)`,
## `scalarize$max(...)`, `scalarize$log_sum_exp(...)`,
## `scalarize$targets_and_priorities(...)`. The element `max` deliberately
## shadows base `max` only inside the list, so base R is unaffected.
##
## Objective arithmetic (negate / add / multiply-by-scalar) is provided by the
## helpers in problems/objective.R (.negate_objective, .add_objectives,
## .mul_objective); these mirror CVXPY's Objective.__neg__/__add__/__mul__.


## weighted_sum: sum_i weights[i] * objectives[i].
## CVXPY SOURCE: scalarize.py:21-32
.scalarize_weighted_sum <- function(objectives, weights) {
  num_objs <- length(objectives)
  scaled <- lapply(seq_len(num_objs), function(i) {
    .mul_objective(objectives[[i]], weights[[i]])
  })
  Reduce(.add_objectives, scaled)
}

## targets_and_priorities: penalize each objective within [target, limit].
## CVXPY SOURCE: scalarize.py:35-138
.scalarize_targets_and_priorities <- function(objectives, priorities, targets,
                                              limits = NULL, off_target = 1e-5) {
  if (length(objectives) != length(priorities)) {
    cli_abort("Number of objectives and priorities must match.")
  }
  if (length(objectives) != length(targets)) {
    cli_abort("Number of objectives and targets must match.")
  }
  if (!is.null(limits) && length(objectives) != length(limits)) {
    cli_abort("Number of objectives and limits must match.")
  }
  if (off_target < 0) {
    cli_abort("The off_target argument must be nonnegative.")
  }

  num_objs <- length(objectives)
  new_objs <- vector("list", num_objs)
  obj_is_min <- logical(num_objs)

  for (i in seq_len(num_objs)) {
    obj <- objectives[[i]]
    tar <- targets[[i]]
    lim <- if (!is.null(limits)) limits[[i]] else NULL

    ## Negative priority flips the objective sense (and its target/limit).
    if (priorities[[i]] < 0) {
      obj <- .negate_objective(obj)
      tar <- -tar
      if (!is.null(lim)) lim <- -lim
    }

    is_min <- .s7_is(obj, Minimize)
    sign <- if (is_min) 1 else -1
    obj_is_min[i] <- is_min

    obj_arg <- obj@args[[1L]]
    delta <- sign * (obj_arg - tar)
    expr <- sign * (abs(priorities[[i]]) - off_target) * pos(delta)
    expr <- expr + off_target * obj_arg
    if (!is.null(limits)) {
      expr <- expr + sign * indicator(list(sign * obj_arg <= sign * lim))
    }
    new_objs[[i]] <- expr
  }
  obj_expr <- Reduce(`+`, new_objs)

  ## When all objectives agree on direction, respect that direction rather than
  ## relying solely on curvature (ambiguous for affine expressions, e.g. when
  ## priority <= off_target).  CVXPY SOURCE: scalarize.py:118-138
  if (all(obj_is_min)) {
    if (is_convex(obj_expr)) return(Minimize(obj_expr))
    cli_abort("Scalarized objective is not convex.")
  } else if (all(!obj_is_min)) {
    if (is_concave(obj_expr)) return(Maximize(obj_expr))
    cli_abort("Scalarized objective is not concave.")
  } else {
    if (is_convex(obj_expr)) return(Minimize(obj_expr))
    if (is_concave(obj_expr)) return(Maximize(obj_expr))
    cli_abort("Scalarized objective is neither convex nor concave.")
  }
}

## max: minimize the largest weighted objective term.
## CVXPY SOURCE: scalarize.py:140-151
.scalarize_max <- function(objectives, weights) {
  num_objs <- length(objectives)
  terms <- lapply(seq_len(num_objs), function(i) {
    .mul_objective(objectives[[i]], weights[[i]])@args[[1L]]
  })
  Minimize(do.call(Maximum, terms))
}

## log_sum_exp: smooth max interpolating weighted_sum (gamma->0) and max
## (gamma->inf). CVXPY SOURCE: scalarize.py:154-178
.scalarize_log_sum_exp <- function(objectives, weights, gamma = 1.0) {
  num_objs <- length(objectives)
  terms <- lapply(seq_len(num_objs), function(i) {
    .mul_objective(objectives[[i]], weights[[i]])@args[[1L]]
  })
  stacked <- do.call(vstack, terms)
  Minimize(log_sum_exp(gamma * stacked) / gamma)
}

#' Scalarize multiple objectives into a single objective
#'
#' Transforms for combining several \code{Minimize}/\code{Maximize} objectives
#' into one objective for multi-objective optimization. Mirrors CVXPY's
#' \code{cvxpy.transforms.scalarize} submodule; access members with \code{$}:
#'
#' \itemize{
#'   \item \code{scalarize$weighted_sum(objectives, weights)} -- weighted sum of
#'     objectives.
#'   \item \code{scalarize$targets_and_priorities(objectives, priorities,
#'     targets, limits = NULL, off_target = 1e-5)} -- penalize each objective
#'     within a \code{[target, limit]} range; a negative priority flips the
#'     objective sense.
#'   \item \code{scalarize$max(objectives, weights)} -- minimize the largest
#'     weighted objective term.
#'   \item \code{scalarize$log_sum_exp(objectives, weights, gamma = 1.0)} --
#'     smooth maximum; \code{gamma -> 0} approaches \code{weighted_sum},
#'     \code{gamma -> Inf} approaches \code{max}.
#' }
#'
#' @format A named list of four functions.
#' @examples
#' x <- Variable()
#' objs <- list(Minimize(square(x)), Minimize(square(x - 1)))
#' obj <- scalarize$weighted_sum(objs, c(1, 1))
#' \dontrun{psolve(Problem(obj))}
#' @export
scalarize <- list(
  weighted_sum           = .scalarize_weighted_sum,
  targets_and_priorities = .scalarize_targets_and_priorities,
  max                    = .scalarize_max,
  log_sum_exp            = .scalarize_log_sum_exp
)

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.