R/193_reductions_complex2real_complex2real.R

Defines functions .c2r_canonicalize_expr .c2r_canonicalize_tree complex2real_accepts

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/reductions/complex2real/complex2real.R
#####

## CVXPY SOURCE: reductions/complex2real/complex2real.py
## Complex2Real -- lifts complex numbers to a real representation
##
## This is NOT a Canonicalization subclass. It extends Reduction directly
## and implements its own tree-walking canonicalize_tree() method.
## Each node returns a (real_part, imag_part) pair -- not a single expression.


## -- S7 generic for complex-to-real canonicalization ------------------
## Replaces COMPLEX_CANON_METHODS environment lookup.
## Default: assert no imaginary args, return copy with real args.
##
## INHERITANCE SAFETY: Same invariant as dcp_canonicalize -- every subclass
## of a C2R-registered atom must have its own explicit method.

#' Complex-to-real canonicalization dispatch
#'
#' Each complex-aware atom registers its own method. The default asserts all
#' imag_args are NULL and returns list(expr_copy(expr, real_args), NULL).
#' @noRd
c2r_canonicalize <- new_generic("c2r_canonicalize", "expr",
  function(expr, real_args, imag_args, real2imag) {
    S7_dispatch()
  }
)

method(c2r_canonicalize, S7_object) <- function(expr, real_args, imag_args, real2imag) {
  if (!all(vapply(imag_args, is.null, logical(1L)))) {
    cls_name <- short_class_name(expr)
    cli_abort("Complex canonicalization not implemented for {.cls {cls_name}}.")
  }
  list(expr_copy(expr, real_args), NULL)
}

#' Predicate: does this expression class have a C2R canonicalizer?
#'
#' Used for leaf caching in .c2r_canonicalize_expr().
#' @noRd
has_c2r_canon <- new_generic("has_c2r_canon", "expr",
  function(expr) {
    S7_dispatch()
  }
)

method(has_c2r_canon, S7_object) <- function(expr) FALSE

## -- C2R method registrations ----------------------------------------
## Separable (affine) atoms
## Bmat is a plain function (not a class) -- no registration needed; it composes HStack/VStack
method(c2r_canonicalize, AddExpression)  <- c2r_separable_canon
method(c2r_canonicalize, Cumsum)         <- c2r_separable_canon
method(c2r_canonicalize, DiagMat)        <- c2r_separable_canon
method(c2r_canonicalize, DiagVec)        <- c2r_separable_canon
method(c2r_canonicalize, HStack)         <- c2r_separable_canon
method(c2r_canonicalize, Index)          <- c2r_separable_canon
method(c2r_canonicalize, Promote)        <- c2r_separable_canon
method(c2r_canonicalize, Reshape)        <- c2r_separable_canon
method(c2r_canonicalize, SumEntries)     <- c2r_separable_canon
method(c2r_canonicalize, Transpose)      <- c2r_separable_canon
method(c2r_canonicalize, NegExpression)  <- c2r_separable_canon
method(c2r_canonicalize, UpperTri)       <- c2r_separable_canon
method(c2r_canonicalize, VStack)         <- c2r_separable_canon
method(c2r_canonicalize, Trace)          <- c2r_trace_canon

## Binary (multiplication-like) atoms
method(c2r_canonicalize, Convolve)       <- c2r_binary_canon
method(c2r_canonicalize, DivExpression)  <- c2r_binary_canon
method(c2r_canonicalize, Kron)           <- c2r_binary_canon
method(c2r_canonicalize, MulExpression)  <- c2r_binary_canon
method(c2r_canonicalize, Multiply)       <- c2r_binary_canon

## Complex-specific atoms
method(c2r_canonicalize, Conj_)          <- c2r_conj_canon
method(c2r_canonicalize, Imag_)          <- c2r_imag_canon
method(c2r_canonicalize, Real_)          <- c2r_real_canon
method(c2r_canonicalize, hermitian_wrap) <- c2r_hermitian_wrap_canon

## Leaves
method(c2r_canonicalize, Variable)       <- c2r_variable_canon
method(c2r_canonicalize, Constant)       <- c2r_constant_canon
method(c2r_canonicalize, Parameter)      <- c2r_param_canon

## Constraints
method(c2r_canonicalize, Inequality)     <- c2r_inequality_canon
method(c2r_canonicalize, PSD)            <- c2r_psd_canon
method(c2r_canonicalize, SOC)            <- c2r_soc_canon
method(c2r_canonicalize, Equality)       <- c2r_equality_canon
method(c2r_canonicalize, Zero)           <- c2r_zero_canon

## Abs and norms
method(c2r_canonicalize, Abs)            <- c2r_abs_canon
method(c2r_canonicalize, Norm1)          <- c2r_pnorm_canon
method(c2r_canonicalize, NormInf)        <- c2r_pnorm_canon
method(c2r_canonicalize, Pnorm)          <- c2r_pnorm_canon
method(c2r_canonicalize, PnormApprox)    <- c2r_pnorm_canon

## Matrix atoms
method(c2r_canonicalize, LambdaMax)      <- c2r_hermitian_canon
method(c2r_canonicalize, LogDet)         <- c2r_norm_nuc_canon
method(c2r_canonicalize, NormNuc)        <- c2r_norm_nuc_canon
method(c2r_canonicalize, SigmaMax)       <- c2r_hermitian_canon
method(c2r_canonicalize, QuadForm)       <- c2r_quad_canon
method(c2r_canonicalize, QuadOverLin)    <- c2r_quad_over_lin_canon
method(c2r_canonicalize, MatrixFrac)     <- c2r_matrix_frac_canon
method(c2r_canonicalize, LambdaSumLargest) <- c2r_lambda_sum_largest_canon

## has_c2r_canon registrations for leaf caching
method(has_c2r_canon, AddExpression)  <- function(expr) TRUE
method(has_c2r_canon, Cumsum)         <- function(expr) TRUE
method(has_c2r_canon, DiagMat)        <- function(expr) TRUE
method(has_c2r_canon, DiagVec)        <- function(expr) TRUE
method(has_c2r_canon, HStack)         <- function(expr) TRUE
method(has_c2r_canon, Index)          <- function(expr) TRUE
method(has_c2r_canon, Promote)        <- function(expr) TRUE
method(has_c2r_canon, Reshape)        <- function(expr) TRUE
method(has_c2r_canon, SumEntries)     <- function(expr) TRUE
method(has_c2r_canon, Transpose)      <- function(expr) TRUE
method(has_c2r_canon, NegExpression)  <- function(expr) TRUE
method(has_c2r_canon, UpperTri)       <- function(expr) TRUE
method(has_c2r_canon, VStack)         <- function(expr) TRUE
method(has_c2r_canon, Trace)          <- function(expr) TRUE
method(has_c2r_canon, Convolve)       <- function(expr) TRUE
method(has_c2r_canon, DivExpression)  <- function(expr) TRUE
method(has_c2r_canon, Kron)           <- function(expr) TRUE
method(has_c2r_canon, MulExpression)  <- function(expr) TRUE
method(has_c2r_canon, Multiply)       <- function(expr) TRUE
method(has_c2r_canon, Conj_)          <- function(expr) TRUE
method(has_c2r_canon, Imag_)          <- function(expr) TRUE
method(has_c2r_canon, Real_)          <- function(expr) TRUE
method(has_c2r_canon, hermitian_wrap) <- function(expr) TRUE
method(has_c2r_canon, Variable)       <- function(expr) TRUE
method(has_c2r_canon, Constant)       <- function(expr) TRUE
method(has_c2r_canon, Parameter)      <- function(expr) TRUE
method(has_c2r_canon, Inequality)     <- function(expr) TRUE
method(has_c2r_canon, PSD)            <- function(expr) TRUE
method(has_c2r_canon, SOC)            <- function(expr) TRUE
method(has_c2r_canon, Equality)       <- function(expr) TRUE
method(has_c2r_canon, Zero)           <- function(expr) TRUE
method(has_c2r_canon, Abs)            <- function(expr) TRUE
method(has_c2r_canon, Norm1)          <- function(expr) TRUE
method(has_c2r_canon, NormInf)        <- function(expr) TRUE
method(has_c2r_canon, Pnorm)          <- function(expr) TRUE
method(has_c2r_canon, PnormApprox)    <- function(expr) TRUE
method(has_c2r_canon, LambdaMax)      <- function(expr) TRUE
method(has_c2r_canon, LogDet)         <- function(expr) TRUE
method(has_c2r_canon, NormNuc)        <- function(expr) TRUE
method(has_c2r_canon, SigmaMax)       <- function(expr) TRUE
method(has_c2r_canon, QuadForm)       <- function(expr) TRUE
method(has_c2r_canon, QuadOverLin)    <- function(expr) TRUE
method(has_c2r_canon, MatrixFrac)     <- function(expr) TRUE
method(has_c2r_canon, LambdaSumLargest) <- function(expr) TRUE

# -- complex2real_accepts -------------------------------------------
## CVXPY SOURCE: complex2real.py lines 42-44

complex2real_accepts <- function(problem) {
  leaves <- c(variables(problem), constants(problem))
  any(vapply(leaves, is_complex, logical(1L)))
}

# -- Complex2Real class ---------------------------------------------
## CVXPY SOURCE: complex2real.py lines 47-309

Complex2Real <- new_class("Complex2Real", parent = Reduction, package = "CVXR",
  constructor = function() {
    new_object(S7_object(),
      .cache = new.env(parent = emptyenv())
    )
  }
)

## -- accepts -----------------------------------------------------
method(reduction_accepts, Complex2Real) <- function(x, problem, ...) {
  complex2real_accepts(problem)
}

## -- apply -------------------------------------------------------
## CVXPY SOURCE: complex2real.py lines 162-198
method(reduction_apply, Complex2Real) <- function(x, problem, ...) {
  ## Build real2imag mapping for all complex variables and constraints
  real2imag <- new.env(hash = TRUE, parent = emptyenv())
  for (v in variables(problem)) {
    if (is_complex(v)) {
      assign(as.character(v@id), next_expr_id(), envir = real2imag)
    }
  }
  for (con in problem@constraints) {
    if (is_complex(con)) {
      assign(as.character(con@id), next_expr_id(), envir = real2imag)
    }
  }

  ## Build inverse_data as a plain list
  inverse_data <- list(
    real2imag = real2imag,
    id2var    = new.env(hash = TRUE, parent = emptyenv()),
    id2cons   = new.env(hash = TRUE, parent = emptyenv())
  )
  ## Populate id2var and id2cons
  for (v in variables(problem)) {
    assign(as.character(v@id), v, envir = inverse_data$id2var)
  }
  for (con in problem@constraints) {
    assign(as.character(con@id), con, envir = inverse_data$id2cons)
  }

  leaf_map <- new.env(hash = TRUE, parent = emptyenv())

  ## Canonicalize objective
  obj_result <- .c2r_canonicalize_tree(problem@objective, real2imag, leaf_map)
  real_obj <- obj_result[[1L]]
  ## imag_obj must be NULL for a real-valued objective
  ## (CVXPY asserts this)

  ## Canonicalize constraints -- collect chunks, flatten once
  n_cons <- length(problem@constraints)
  constr_chunks <- vector("list", 2L * n_cons)
  for (i in seq_len(n_cons)) {
    con_result <- .c2r_canonicalize_tree(problem@constraints[[i]], real2imag, leaf_map)
    real_constrs <- con_result[[1L]]
    imag_constrs <- con_result[[2L]]
    ## Normalize to list (canonicalizer may return single constraint or list)
    if (S7_inherits(real_constrs, Constraint)) {
      constr_chunks[[2L * i - 1L]] <- list(real_constrs)
    } else if (is.list(real_constrs)) {
      constr_chunks[[2L * i - 1L]] <- real_constrs
    }
    if (S7_inherits(imag_constrs, Constraint)) {
      constr_chunks[[2L * i]] <- list(imag_constrs)
    } else if (is.list(imag_constrs)) {
      constr_chunks[[2L * i]] <- imag_constrs
    }
  }
  constrs <- unlist(constr_chunks, recursive = FALSE)
  if (is.null(constrs)) constrs <- list()

  new_problem <- Problem(real_obj, constrs)
  list(new_problem, inverse_data)
}

## -- invert ------------------------------------------------------
## CVXPY SOURCE: complex2real.py lines 200-276
method(reduction_invert, Complex2Real) <- function(x, solution, inverse_data, ...) {
  pvars <- list()
  dvars <- list()
  real2imag <- inverse_data$real2imag
  id2var <- inverse_data$id2var
  id2cons <- inverse_data$id2cons

  if (solution@status %in% SOLUTION_PRESENT) {
    ## -- Primal variables --
    for (vid in ls(id2var)) {
      var <- get(vid, envir = id2var)
      if (is_real(var)) {
        pvars[[vid]] <- solution@primal_vars[[vid]]
      } else if (is_imag(var)) {
        imag_id <- as.character(get(vid, envir = real2imag))
        pvars[[vid]] <- 1i * solution@primal_vars[[imag_id]]
      } else if (is_complex(var) && is_hermitian(var)) {
        pvars[[vid]] <- solution@primal_vars[[vid]]
        imag_id <- as.character(get(vid, envir = real2imag))
        if (!is.null(solution@primal_vars[[imag_id]])) {
          imag_val <- solution@primal_vars[[imag_id]]
          ## Reconstruct skew-symmetric matrix from strict upper triangle
          imag_val <- value(vec_to_upper_tri(Constant(imag_val), strict = TRUE))
          imag_val <- imag_val - t(imag_val)
          pvars[[vid]] <- pvars[[vid]] + 1i * imag_val
        }
      } else if (is_complex(var)) {
        pvars[[vid]] <- solution@primal_vars[[vid]]
        imag_id <- as.character(get(vid, envir = real2imag))
        if (!is.null(solution@primal_vars[[imag_id]])) {
          pvars[[vid]] <- pvars[[vid]] + 1i * solution@primal_vars[[imag_id]]
        }
      }
    }

    ## -- Dual variables --
    if (length(solution@dual_vars) > 0L) {
      for (cid in ls(id2cons)) {
        con <- get(cid, envir = id2cons)

        if (is_real(con)) {
          dvars[[cid]] <- solution@dual_vars[[cid]]
        } else if (is_imag(con)) {
          imag_id <- as.character(get(cid, envir = real2imag))
          dvars[[cid]] <- 1i * solution@dual_vars[[imag_id]]
        } else if (S7_inherits(con, Equality) || S7_inherits(con, Zero)) {
          imag_id <- as.character(get(cid, envir = real2imag))
          if (!is.null(solution@dual_vars[[imag_id]])) {
            dvars[[cid]] <- solution@dual_vars[[cid]] +
              1i * solution@dual_vars[[imag_id]]
          } else {
            dvars[[cid]] <- solution@dual_vars[[cid]]
          }
        } else if (S7_inherits(con, PSD)) {
          n <- con@args[[1L]]@shape[1L]
          dual <- solution@dual_vars[[cid]]
          if (is.null(dual)) {
            dvars[[cid]] <- NULL
          } else {
            ## Dual may be vector or matrix; ensure it's a (2n, 2n) matrix
            nn <- 2L * n
            if (is.null(dim(dual))) dual <- matrix(dual, nn, nn)
            dvars[[cid]] <- dual[1L:n, 1L:n] + 1i * dual[(n + 1L):nn, 1L:n]
          }
        } else if (S7_inherits(con, SOC)) {
          ## Skip -- unimplemented in CVXPY too
        } else {
          cli_abort("Unknown constraint type {.val {short_class_name(con)}} in Complex2Real invert.")
        }
      }
    }
  }

  Solution(status = solution@status,
           opt_val = solution@opt_val,
           primal_vars = pvars,
           dual_vars = dvars,
           attr = solution@attr)
}

# -- canonicalize_tree ----------------------------------------------
## CVXPY SOURCE: complex2real.py lines 278-292
## Recursive bottom-up walk: each node returns (real_part, imag_part)

.c2r_canonicalize_tree <- function(expr, real2imag, leaf_map) {
  n_args <- length(expr@args)
  real_args <- vector("list", n_args)
  imag_args <- vector("list", n_args)
  for (i in seq_len(n_args)) {
    result <- .c2r_canonicalize_tree(expr@args[[i]], real2imag, leaf_map)
    ## Use single-bracket assignment: [[<- with NULL deletes the element!
    real_args[i] <- list(result[[1L]])
    imag_args[i] <- list(result[[2L]])
  }
  .c2r_canonicalize_expr(expr, real_args, imag_args, real2imag, leaf_map)
}

# -- canonicalize_expr ----------------------------------------------
## CVXPY SOURCE: complex2real.py lines 294-309
## Dispatch to canonicalizer; fallback: assert no imag args, copy with real args

.c2r_canonicalize_expr <- function(expr, real_args, imag_args, real2imag, leaf_map) {
  ## Cache leaves -- only canonicalize once
  if (has_c2r_canon(expr) && length(expr@args) == 0L) {
    key <- as.character(expr@id)
    if (exists(key, envir = leaf_map, inherits = FALSE)) {
      return(get(key, envir = leaf_map, inherits = FALSE))
    }
    result <- c2r_canonicalize(expr, real_args, imag_args, real2imag)
    assign(key, result, envir = leaf_map)
    return(result)
  }
  ## S7 dispatch -- default method asserts no imag args and returns copy
  c2r_canonicalize(expr, real_args, imag_args, real2imag)
}

## DEFERRED: Derivative chain-rule overrides (complex2real.py lines 94-131)
## Complex2Real splits complex parameters into real/imaginary parts, so the
## chain rule for derivatives requires:
##
## param_backward(x, param, dparams) -- recombine real+imag gradients
## param_forward(x, param, delta) -- split delta into real+imag parts
##
## Deferred: derivative API depends on diffcp. See notes/derivative_api_deferred.md.

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.