Nothing
#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/reductions/dgp2dcp/dgp2dcp.R
#####
## CVXPY SOURCE: reductions/dgp2dcp/dgp2dcp.py
## Dgp2Dcp -- reduces DGP problems to DCP problems via log-space transformation
##
## Key design:
## - Inherits from Canonicalization but uses its own tree walk (G1)
## - Per-problem DGP methods closure with variable/parameter caches (G2)
## - Stores dgp_methods on instance .cache for DPP reuse (G8)
## - Stores original problem in inverse_data@.extra (G3)
## dgp_canonicalize generic is defined in dgp_canonicalizers.R (loads before this file)
Dgp2Dcp <- new_class("Dgp2Dcp", parent = Canonicalization, package = "CVXR",
constructor = function() {
if (FALSE) new_object(S7_object()) ## S7 static-check guard
.fast_new(Dgp2Dcp, S7_object(),
.cache = new.env(parent = emptyenv())
)
}
)
## -- reduction_accepts ---------------------------------------------
## CVXPY SOURCE: dgp2dcp.py line 62-65
method(reduction_accepts, Dgp2Dcp) <- function(x, problem, ...) {
is_dgp(problem)
}
## -- reduction_apply -----------------------------------------------
## CVXPY SOURCE: dgp2dcp.py lines 119-128
## Creates per-problem DGP methods, then walks tree via own tree walk.
method(reduction_apply, Dgp2Dcp) <- function(x, problem, ...) {
if (!reduction_accepts(x, problem)) {
cli_abort("The supplied problem is not DGP.")
}
## Create per-problem DGP methods (G2)
dgp_methods <- .make_dgp_methods()
## Store on instance for DPP reuse (G8)
x@.cache$dgp_methods <- dgp_methods
## Original leaf objects keyed by id, for the dict-diff chain rule
## (#3147 part A): var_backward/var_forward multiply by value(orig_var) =
## exp(log_var) = GP-space value; param_* by 1/value(orig_param).
## CVXPY SOURCE: dgp2dcp.py:178 (self._id_to_var).
id_to_var <- list()
for (v in variables(problem)) id_to_var[[as.character(v@id)]] <- v
x@.cache$id_to_var <- id_to_var
id_to_param <- list()
for (p in parameters(problem)) id_to_param[[as.character(p@id)]] <- p
x@.cache$id_to_param <- id_to_param
## Create inverse data
inverse_data <- InverseData(problem)
## Store original problem (G3)
inverse_data@.extra$problem <- problem
## Canonicalize objective via OWN tree walk (G1)
obj_result <- .dgp2dcp_tree(dgp_methods, problem@objective)
canon_objective <- obj_result[[1L]]
## Canonicalize each constraint -- collect chunks, flatten once
n_cons <- length(problem@constraints)
all_chunks <- vector("list", n_cons + 1L)
all_chunks[[1L]] <- obj_result[[2L]]
for (i in seq_len(n_cons)) {
con <- problem@constraints[[i]]
con_result <- .dgp2dcp_tree(dgp_methods, con)
all_chunks[[i + 1L]] <- c(con_result[[2L]], list(con_result[[1L]]))
## Store constraint ID mapping
assign(as.character(.id(con)), .id(con_result[[1L]]),
envir = inverse_data@cons_id_map)
}
canon_constraints <- unlist(all_chunks, recursive = FALSE)
if (is.null(canon_constraints)) canon_constraints <- list()
new_problem <- Problem(canon_objective, canon_constraints)
list(new_problem, inverse_data)
}
## -- reduction_invert ----------------------------------------------
## CVXPY SOURCE: dgp2dcp.py lines 152-160
## Transform solution back from log-space: exp(value) for all primals.
method(reduction_invert, Dgp2Dcp) <- function(x, solution, inverse_data, ...) {
## First apply parent invert (handles cons_id_map remapping)
## S7: call Canonicalization method directly (no callNextMethod in S7)
solution <- method(reduction_invert, Canonicalization)(x, solution, inverse_data, ...)
if (solution@status == SOLVER_ERROR) return(solution)
## Transform primal vars back: exp(log_x) = x
for (vid in names(solution@primal_vars)) {
solution@primal_vars[[vid]] <- exp(solution@primal_vars[[vid]])
}
## Transform objective: f(x) = exp(F(u))
solution@opt_val <- exp(solution@opt_val)
solution
}
## -- update_parameters ----------------------------------------------
## CVXPY SOURCE: dgp2dcp.py lines 67-78
## Called in DPP fast path: transforms original parameter values to log-space.
method(update_parameters, Dgp2Dcp) <- function(x, problem, ...) {
dgp_methods <- x@.cache$dgp_methods
if (is.null(dgp_methods)) return(invisible(NULL))
params_cache <- dgp_methods$params_cache
for (param in parameters(problem)) {
pid <- as.character(.id(param))
if (exists(pid, envir = params_cache, inherits = FALSE)) {
log_param <- get(pid, envir = params_cache, inherits = FALSE)
value(log_param) <- log(value(param))
}
}
invisible(NULL)
}
## ==================================================================
## Own tree walk functions (G1)
## ==================================================================
## .dgp2dcp_tree: recursive bottom-up walk (same structure as .canonicalize_tree
## but calls .dgp2dcp_expr which does NOT skip constants)
.dgp2dcp_tree <- function(dgp_methods, expr) {
n_args <- length(.args(expr))
canon_args <- vector("list", n_args)
constr_chunks <- vector("list", n_args + 1L)
for (i in seq_len(n_args)) {
arg_result <- .dgp2dcp_tree(dgp_methods, .args(expr)[[i]])
canon_args[[i]] <- arg_result[[1L]]
constr_chunks[[i]] <- arg_result[[2L]]
}
node_result <- .dgp2dcp_expr(dgp_methods, expr, canon_args)
constr_chunks[[n_args + 1L]] <- node_result[[2L]]
constrs <- unlist(constr_chunks, recursive = FALSE)
if (is.null(constrs)) constrs <- list()
list(node_result[[1L]], constrs)
}
## .dgp2dcp_expr: canonicalize a single node
## NO constant-skipping (G1). Variable/Parameter dispatch via dgp_methods (G2).
.dgp2dcp_expr <- function(dgp_methods, expr, args) {
## Variable -> stateful variable_canon
if (.s7_is(expr, Variable)) {
return(dgp_methods$variable_canon(expr, args))
}
## Parameter -> stateful parameter_canon
if (.s7_is(expr, Parameter)) {
return(dgp_methods$parameter_canon(expr, args))
}
## S7 dispatch via dgp_canonicalize -- NULL means no method registered
result <- dgp_canonicalize(expr, args)
if (!is.null(result)) return(result)
## Default: copy with canonicalized args
list(expr_copy(expr, args), list())
}
## ==================================================================
## Per-problem DGP methods closure (G2)
## ==================================================================
## Transform a DGP bound to log space. CVXPY SOURCE:
## dgp2dcp/canonicalizers/__init__.py:126-162 (_log_transform_bound).
## A bound `b` on a positive-domain variable maps to log(b) in the log domain.
## Returns list(log_bound, aux_constraints) where aux_constraints is non-empty
## only for parametric Expression bounds (canonicalized through the DGP tree).
.dgp_log_transform_bound <- function(bound, methods) {
if (.s7_is(bound, Expression)) {
if (length(parameters(bound)) > 0L) {
## Parametric bound: canonicalize through the DGP tree to log space.
return(.dgp2dcp_tree(methods, bound))
}
## Parameter-free Expression: evaluate numerically.
return(list(Constant(log(value(bound))), list()))
}
## Numeric vector (already broadcast to n_elem at leaf construction).
## Preserve sentinels: -Inf (no lower bound) stays -Inf; log(Inf) = Inf is fine.
log_b <- bound
neg_inf <- bound == -Inf
log_b[!neg_inf] <- log(bound[!neg_inf])
log_b[neg_inf] <- -Inf
list(log_b, list())
}
.make_dgp_methods <- function() {
## Per-problem caches for dedup
vars_cache <- new.env(hash = TRUE, parent = emptyenv())
params_cache <- new.env(hash = TRUE, parent = emptyenv())
## Self-reference so variable_canon can canonicalize parametric bounds via
## the DGP tree walk (.dgp2dcp_tree).
methods <- NULL
variable_canon <- function(variable, args) {
vid <- as.character(.id(variable))
if (exists(vid, envir = vars_cache, inherits = FALSE)) {
return(list(get(vid, envir = vars_cache, inherits = FALSE), list()))
}
## Swap the positive variable for an unconstrained log-space variable,
## transforming any bounds to log space so the downstream CvxAttr2Constr
## lowers them to constraints. CVXPY SOURCE:
## dgp2dcp/canonicalizers/__init__.py:186-208 (variable_canon).
bounds <- .attributes(variable)$bounds
constrs <- list()
if (!is.null(bounds)) {
lb_res <- .dgp_log_transform_bound(bounds[[1L]], methods)
ub_res <- .dgp_log_transform_bound(bounds[[2L]], methods)
constrs <- c(lb_res[[2L]], ub_res[[2L]])
log_var <- Variable(.shape(variable), var_id = .id(variable),
bounds = list(lb_res[[1L]], ub_res[[1L]]))
} else {
log_var <- Variable(.shape(variable), var_id = .id(variable))
}
assign(vid, log_var, envir = vars_cache)
list(log_var, constrs)
}
parameter_canon <- function(parameter, args) {
pid <- as.character(.id(parameter))
if (exists(pid, envir = params_cache, inherits = FALSE)) {
return(list(get(pid, envir = params_cache, inherits = FALSE), list()))
}
## Create log-space parameter (DPP: may not have value yet)
log_param <- Parameter(.shape(parameter), name = expr_name(parameter))
if (!is.null(value(parameter))) {
value(log_param) <- log(value(parameter))
}
assign(pid, log_param, envir = params_cache)
list(log_param, list())
}
## Populate the self-reference captured by variable_canon (parametric bounds).
methods <- list(
variable_canon = variable_canon,
parameter_canon = parameter_canon,
vars_cache = vars_cache,
params_cache = params_cache
)
methods
}
# -- Dgp2Dcp derivative chain rule (Phase 4.4) --------------------
## CVXPY SOURCE: dgp2dcp.py:79-118.
##
## DGP rewrites every variable as `x = exp(log_var)` and every
## parameter as `p = exp(log_param)`. The chain rule then becomes:
## backward (gradient): d/d(param) = (1 / param) * d/d(log_param)
## d/d(var) = var * d/d(log_var)
## forward (delta): d(log_param) = (1 / param) * d(param)
## d(var) = var * d(log_var)
## Dict-in/dict-out chain rule (#3147 part A). All ops keep operands dimensioned
## (`dim(pv) <- dim(g)` errors on a length mismatch instead of recycling --
## ADR D_19.5 addendum 2). Log leaves share the original variable id (var_id =
## variable@id) but get a FRESH parameter id; we key by those accordingly.
## CVXPY SOURCE: dgp2dcp.py:137-159 (param_backward). inner -> outer, ADDING the
## log-chain gradient to any pre-existing direct gradient (a parameter used both
## as a base log(p) and directly as a power exponent).
method(param_backward, Dgp2Dcp) <- function(x, dparams) {
dgp_methods <- x@.cache$dgp_methods
id_to_param <- x@.cache$id_to_param
if (is.null(dgp_methods)) return(dparams)
params_cache <- dgp_methods$params_cache
for (opid in ls(params_cache, all.names = TRUE)) {
log_param <- get(opid, envir = params_cache, inherits = FALSE)
log_pid <- as.character(.id(log_param))
if (is.null(dparams[[log_pid]])) next
lg <- dparams[[log_pid]]
pv <- value(id_to_param[[opid]]); dim(pv) <- dim(lg) # keep-dims guard
log_grad <- (1 / pv) * lg
dparams[[log_pid]] <- NULL # pop the log id
if (!is.null(dparams[[opid]])) {
base <- dparams[[opid]]; dim(base) <- dim(log_grad) # guard
dparams[[opid]] <- base + log_grad # ADD (dual use)
} else {
dparams[[opid]] <- log_grad
}
}
dparams
}
## CVXPY SOURCE: dgp2dcp.py:161-178 (param_forward). outer -> inner; keep the
## direct delta in place (do not pop param.id) and add the log-space delta.
method(param_forward, Dgp2Dcp) <- function(x, param_deltas) {
dgp_methods <- x@.cache$dgp_methods
id_to_param <- x@.cache$id_to_param
if (is.null(dgp_methods)) return(param_deltas)
params_cache <- dgp_methods$params_cache
for (opid in ls(params_cache, all.names = TRUE)) {
if (is.null(param_deltas[[opid]])) next
log_param <- get(opid, envir = params_cache, inherits = FALSE)
log_pid <- as.character(.id(log_param))
d <- param_deltas[[opid]]
pv <- value(id_to_param[[opid]]); dim(pv) <- dim(d) # keep-dims guard
param_deltas[[log_pid]] <- (1 / pv) * d
}
param_deltas
}
## x = exp(log_var) => dx/d(log_var) = x = value(orig_var). The log var shares
## the original id, so the dict key is unchanged; we multiply in place.
## CVXPY SOURCE: dgp2dcp.py:97-135 (var_backward / var_forward).
method(var_backward, Dgp2Dcp) <- function(x, del_vars) {
id_to_var <- x@.cache$id_to_var
if (is.null(id_to_var)) return(del_vars)
for (vid in names(del_vars)) {
ov <- id_to_var[[vid]]
if (is.null(ov)) next
g <- del_vars[[vid]]
vv <- value(ov); dim(vv) <- dim(g) # keep-dims guard
del_vars[[vid]] <- g * vv
}
del_vars
}
method(var_forward, Dgp2Dcp) <- function(x, dvars) {
id_to_var <- x@.cache$id_to_var
if (is.null(id_to_var)) return(dvars)
for (vid in names(dvars)) {
ov <- id_to_var[[vid]]
if (is.null(ov)) next
d <- dvars[[vid]]
vv <- value(ov); dim(vv) <- dim(d) # keep-dims guard
dvars[[vid]] <- d * vv
}
dvars
}
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.