Nothing
#####
## 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)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.