R/203_reductions_complex2real_complex2real.R

Defines functions .c2r_combine .c2r_split .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) {
  ## `parameters(problem)` is a load-bearing term, not a redundant one:
  ## `problem.constants()` (problem.py:484-499) collects only `Constant` leaves,
  ## and a `Parameter` is not a `Constant`, so without it a problem whose ONLY
  ## complex leaf is a Parameter reports FALSE. CVXR omitted it, and the
  ## consequence was not a missing reduction but an error: `has_complex_params`
  ## (solving_chain.R:650-652) still prepends `EvalParams`, which turns the
  ## complex Parameter into a complex Constant with no Complex2Real downstream,
  ## so the solve died with "Inequality constraints cannot be complex."
  ## Measured on `Minimize(abs(p %*% x)), x >= 1` with p a complex Parameter:
  ## CVXPY 1.9.2 accepts TRUE and solves to 4.123105626514879.
  leaves <- c(variables(problem), parameters(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() {
    if (FALSE) new_object(S7_object())  ## S7 static-check guard
    .fast_new(Complex2Real, 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, parameters,
  ## and constraints.  Parameters need an entry so the derivative
  ## chain-rule overrides (param_backward / param_forward) can split
  ## complex parameter perturbations into real / imag pairs.
  real2imag <- new.env(hash = TRUE, parent = emptyenv())
  for (v in variables(problem)) {
    if (is_complex(v)) {
      assign(as.character(.id(v)), next_expr_id(), envir = real2imag)
    }
  }
  for (p in parameters(problem)) {
    if (is_complex(p)) {
      assign(as.character(.id(p)), next_expr_id(), envir = real2imag)
    }
  }
  for (con in problem@constraints) {
    if (is_complex(con)) {
      assign(as.character(.id(con)), next_expr_id(), envir = real2imag)
    }
  }
  ## Stash on the reduction instance so backward / forward chain-rule
  ## methods can recover the mapping later.
  x@.cache$real2imag <- real2imag
  ## Original complex leaf objects keyed by id -- the dict-diff hooks
  ## (var_backward/var_forward/param_*) need is_complex/is_hermitian/shape to
  ## split or recombine the real/imag parts (#3147 part A). reduction_invert
  ## uses inverse_data$id2var instead.
  id_to_var <- new.env(hash = TRUE, parent = emptyenv())
  for (v in variables(problem)) {
    if (is_complex(v)) assign(as.character(.id(v)), v, envir = id_to_var)
  }
  x@.cache$id_to_var <- id_to_var
  id_to_param <- new.env(hash = TRUE, parent = emptyenv())
  for (p in parameters(problem)) {
    if (is_complex(p)) assign(as.character(.id(p)), p, envir = id_to_param)
  }
  x@.cache$id_to_param <- id_to_param

  ## 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(.id(v)), v, envir = inverse_data$id2var)
  }
  for (con in problem@constraints) {
    assign(as.character(.id(con)), 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_is(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_is(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_is(con, Equality) || .s7_is(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_is(con, PSD)) {
          ## CVXPY SOURCE: complex2real.py:340-349 (CVXPY 1.9.2).
          ## A Hermitian X >> 0 is solved through the real dilation
          ##     Y = [[re(X), -im(X)], [im(X), re(X)]] >> 0.
          ## If D is the dual for the dilated constraint, the dual for the
          ## original is 2*(D[1:n, 1:n] + 1i*D[(n+1):2n, 1:n]).  The factor
          ## of 2 is NOT cosmetic: the dilation doubles the inner product,
          ##     <D, Y> = 2*Re(<D[1:n,1:n] + 1i*D[(n+1):2n,1:n], X>),
          ## so without it the recovered dual is exactly HALF its true value
          ## and violates both KKT and strong duality.  Measured before this
          ## fix, with C = [[2, 1-1i], [1+1i, 3]] and X >> A: CVXR returned
          ## [[1, 0.5+0.5i], [0.5-0.5i, 1.5]] where KKT gives Y == C.
          n <- .arg_shape(con)[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]] <- 2 * (dual[1L:n, 1L:n] +
                                   1i * dual[(n + 1L):nn, 1L:n])
          }
        } else if (.s7_is(con, SOC)) {
          ## Complex SOC dual is NOT recovered.  Mirrors CVXPY
          ## (complex2real.py:56):
          ##     UNIMPLEMENTED_COMPLEX_DUALS = (SOC, OpRelEntrConeQuad)
          ## The complex SOC *primal* canonicalization (soc_canon.R)
          ## works end-to-end; only the dual variable of a complex-
          ## argument SOC constraint is unavailable.  When CVXPY adds
          ## this branch, port it here.  This is the only known
          ## CVXR / CVXPY parity gap in complex dual recovery.
        } 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(.args(expr))
  real_args <- vector("list", n_args)
  imag_args <- vector("list", n_args)
  for (i in seq_len(n_args)) {
    result <- .c2r_canonicalize_tree(.args(expr)[[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(.args(expr)) == 0L) {
    key <- as.character(.id(expr))
    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)
}

# -- Complex2Real derivative chain rule (Phase 4.4) ---------------
## CVXPY SOURCE: complex2real.py:94-160.
##
## Complex2Real splits a complex parameter `p = a + i b` into a pair
## of real parameters (`a`, `b`).  The chain rule:
##   backward (gradient): d/d(p) = d/d(a) + i * d/d(b).
##   forward  (delta):    d(a) = Re(d(p)),  d(b) = Im(d(p)).
##
## For Hermitian parameters the imaginary part is stored compactly
## (strict upper triangle of the skew-symmetric imaginary part); the
## chain rule expands / extracts that compact form on the boundary.
##
## The canonicalization stores the original-id -> imag-id mapping in
## `inverse_data$real2imag`; backward/derivative is run on the
## *original* problem so we look up via `x@.cache$real2imag`, set
## by reduction_apply.

## SPLIT a complex leaf's value into a real part (kept at the original id) and an
## imaginary part (at the fresh real2imag id). Used by var_backward (gradients,
## outer->inner) and param_forward (deltas, outer->inner).
## CVXPY SOURCE: complex2real.py:121-141 / 211-242. Keep-dims throughout
## (ADR D_19.5 addendum 2): every value is a shaped array of the leaf's shape, or
## the compact strict-upper-triangle (k x 1) for the Hermitian imaginary part.
.c2r_split <- function(dict, leaf_env, real2imag) {
  for (id in ls(leaf_env, all.names = TRUE)) {
    if (is.null(dict[[id]])) next
    leaf <- get(id, envir = leaf_env, inherits = FALSE)
    imag_id <- if (exists(id, envir = real2imag, inherits = FALSE))
      as.character(get(id, envir = real2imag, inherits = FALSE)) else NULL
    shp <- .shape(leaf)
    z <- as.complex(dict[[id]])
    dict[[id]] <- array(Re(z), dim = shp)            # real part keeps the id
    if (!is.null(imag_id)) {
      im <- Im(z)
      if (is_hermitian(leaf)) {
        n <- shp[1L]; mat <- matrix(im, n, n)
        tri <- mat[upper.tri(mat, diag = FALSE)]
        dict[[imag_id]] <- array(tri, dim = c(length(tri), 1L))
      } else {
        dict[[imag_id]] <- array(im, dim = shp)
      }
    }
  }
  dict
}

## COMBINE a real part (original id) and imaginary part (real2imag id) back into a
## complex value at the original id, popping the imaginary id. Used by var_forward
## (deltas, inner->outer) and param_backward (gradients, inner->outer).
## CVXPY SOURCE: complex2real.py:143-170 / 172-209.
.c2r_combine <- function(dict, leaf_env, real2imag) {
  for (id in ls(leaf_env, all.names = TRUE)) {
    leaf <- get(id, envir = leaf_env, inherits = FALSE)
    imag_id <- if (exists(id, envir = real2imag, inherits = FALSE))
      as.character(get(id, envir = real2imag, inherits = FALSE)) else NULL
    shp <- .shape(leaf)
    re <- if (!is.null(dict[[id]])) array(as.numeric(dict[[id]]), dim = shp)
          else array(0, dim = shp)
    im_part <- array(0, dim = shp)
    if (!is.null(imag_id) && !is.null(dict[[imag_id]])) {
      raw_im <- as.numeric(dict[[imag_id]])
      if (is_hermitian(leaf)) {
        n <- shp[1L]; full <- matrix(0, n, n)
        full[upper.tri(full, diag = FALSE)] <- raw_im
        full <- full - t(full)
        im_part <- full
      } else {
        im_part <- array(raw_im, dim = shp)
      }
      dict[[imag_id]] <- NULL                          # pop the imag id
    }
    dim(im_part) <- shp                                # keep-dims guard
    dict[[id]] <- re + 1i * im_part                    # array + array, same dim
  }
  dict
}

## Dict-in/dict-out chain rule (#3147 part A). Directional asymmetry: backward
## SPLITS variables but COMBINES parameters; forward is the reverse.
method(var_backward, Complex2Real) <- function(x, del_vars) {
  if (is.null(x@.cache$real2imag)) return(del_vars)
  .c2r_split(del_vars, x@.cache$id_to_var, x@.cache$real2imag)
}
method(var_forward, Complex2Real) <- function(x, dvars) {
  if (is.null(x@.cache$real2imag)) return(dvars)
  .c2r_combine(dvars, x@.cache$id_to_var, x@.cache$real2imag)
}
method(param_backward, Complex2Real) <- function(x, dparams) {
  if (is.null(x@.cache$real2imag)) return(dparams)
  .c2r_combine(dparams, x@.cache$id_to_param, x@.cache$real2imag)
}
method(param_forward, Complex2Real) <- function(x, param_deltas) {
  if (is.null(x@.cache$real2imag)) return(param_deltas)
  .c2r_split(param_deltas, x@.cache$id_to_param, x@.cache$real2imag)
}

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.