R/189_reductions_dcp2cone_canonicalizers_quad_quad_over_lin_canon.R

Defines functions .qol_block_indices qol_quad_canon

#####
## DO NOT EDIT THIS FILE!! EDIT THE SOURCE INSTEAD: rsrc_tree/reductions/dcp2cone/canonicalizers/quad/quad_over_lin_canon.R
#####

## CVXPY SOURCE: reductions/dcp2cone/canonicalizers/quad/quad_over_lin_canon.py
## Builds I/y as the P matrix. With `axis` set, attaches one block of flat
## indices per output element (`block_indices`) so the extractor can scale
## each block by its own coefficient (quad_over_lin_canon.py:39-51, 64-125).


qol_quad_canon <- function(expr, args, solver_context = NULL) {
  affine_expr <- args[[1L]]
  y <- args[[2L]]
  ## y must be constant scalar (guarded by quad_canonicalize below).
  y_val <- as.numeric(value(y))
  n <- prod(.shape(affine_expr))
  ## Keep P sparse: ddiMatrix has implicit unit diagonal that
  ## Matrix::summary() doesn't report; convert to dgCMatrix for
  ## explicit entries. Avoids O(n^2) dense matrix from as.matrix().
  quad_mat <- as(as(Matrix::Diagonal(n) / y_val, "generalMatrix"), "CsparseMatrix")

  ## CVXPY SOURCE: quad_over_lin_canon.py:39-51 -- block indices when axis
  ## is specified (claims-audit row C0670; before 1.9.2.9141 the axis case
  ## was handed to the SOC canon, so a QP-only solver refused it).
  block_indices <- NULL
  if (!is.null(expr@axis)) {
    block_indices <- .qol_block_indices(.shape(affine_expr), expr@axis)
  }

  ## CVXPY v1.9.3 fix: #3384 -- a variable with a dim-reducing attribute
  ## (symmetric/PSD/NSD/diag/sparsity) is substituted by an affine expression
  ## in CvxAttr2Constr, so it must go through the auxiliary variable.
  if (.s7_is(affine_expr, Variable) && !.cvxattr_has_dim_reducing_attr(affine_expr)) {
    list(SymbolicQuadForm(affine_expr, Constant(quad_mat), expr,
                          block_indices = block_indices), list())
  } else {
    t <- Variable(shape = .shape(affine_expr))
    list(SymbolicQuadForm(t, Constant(quad_mat), expr,
                          block_indices = block_indices),
         list(affine_expr == t))
  }
}

## CVXPY SOURCE: quad_over_lin_canon.py:64-125 -- _compute_block_indices.
## block_indices[[j]] is the set of flat (column-major, as the canon backend
## vectorizes) indices of the input that output element j reduces over. Flat
## indices are 1-based here because coeff_quad_form consumes them against
## Matrix::summary() triplets. Two-dimensional only, as every CVXR axis atom
## (`.validate_axis(axis, 2L)` in quad_over_lin.R): R axis 2 (column-wise,
## CVXPY axis 0) gives one contiguous block per column; R axis 1 (row-wise,
## CVXPY axis 1) gives one strided block per row.
.qol_block_indices <- function(shape, axis) {
  m <- as.integer(shape[1L])
  n <- as.integer(if (length(shape) >= 2L) shape[2L] else 1L)
  if (axis == 2L) {
    lapply(seq_len(n), function(j) (j - 1L) * m + seq_len(m))
  } else {
    lapply(seq_len(m), function(i) i + m * (seq_len(n) - 1L))
  }
}

method(quad_canonicalize, QuadOverLin) <- function(expr, args, ...) {
  ## CVXPY 1.9 parity: decide from the canonicalized denominator, not
  ## expr@args[[1L]] being PWL.  For quad_over_lin(exp(x), 1), exp(x)
  ## has already canonicalized to an affine variable plus ExpCone.
  ## (dcp2cone.py:235: `not args[1].is_constant()` is the only guard; the
  ## axis case is handled inside the quad canon via block_indices.)
  if (!is_constant(args[[2L]])) return(NULL)
  qol_quad_canon(expr, args)
}

## Membership marker for the CSE cache: `affine_above` is part of a subtree's
## cache key only when the quad branch could fire inside it
## (`.affine_above_relevant`, dcp2cone.R). CVXPY reads this off its
## `quad_canon_methods` dict; CVXR needs an explicit predicate per class.
method(has_quad_canon, QuadOverLin) <- function(expr) TRUE

Try the CVXR package in your browser

Any scripts or data that you put into this service are public.

CVXR documentation built on Oct. 19, 2026, 5:07 p.m.