Skip to content

Commit 94a4e6f

Browse files
authored
Merge pull request #138 from cvxgrp/better_memory_mgmt
Better memory mgmt
2 parents 6cf8eb6 + 9fbc9a0 commit 94a4e6f

File tree

13 files changed

+182
-30
lines changed

13 files changed

+182
-30
lines changed

DESCRIPTION

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: CVXR
22
Type: Package
33
Title: Disciplined Convex Optimization
4-
Version: 1.0-12
4+
Version: 1.0-13
55
VignetteBuilder: knitr
66
Authors@R: c(
77
person("Anqi", "Fu",
@@ -63,7 +63,7 @@ LinkingTo: Rcpp, RcppEigen
6363
License: Apache License 2.0 | file LICENSE
6464
LazyData: true
6565
Collate:
66-
'CVXR.R'
66+
'CVXR-package.R'
6767
'data.R'
6868
'globals.R'
6969
'generics.R'
@@ -101,7 +101,7 @@ Collate:
101101
'CVXcanon-R6.R'
102102
'Deque.R'
103103
'canonInterface.R'
104-
RoxygenNote: 7.2.3
104+
RoxygenNote: 7.3.1
105105
Encoding: UTF-8
106106
Enhances:
107107
Rcplex,

NEWS.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
# CVXR 1.0-13
2+
3+
* Address inefficient processing of cones for MOSEK (Issue 137
4+
reported by aszekMosek)
5+
* Fix `extract_quadratic_coeffs` to use sparse matrix and sweep in
6+
place for better memory use (reported by Marissa Reitsma)
7+
18
# CVXR 1.0-12
29

310
* `Rmosek` to be removed from CRAN, so moved to drat repo

R/CVXR.R renamed to R/CVXR-package.R

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@
2020
#' @import Matrix
2121
#' @importFrom stats rnorm runif
2222
#' @aliases CVXR-package CVXR
23-
#' @docType package
2423
#' @author Anqi Fu, Balasubramanian Narasimhan, John Miller, Steven Diamond, Stephen Boyd
25-
#'
26-
#' Maintainer: Anqi Fu<[email protected]>
27-
#' @keywords package
28-
NULL
24+
#' Maintainer: Anqi Fu<[email protected]>
25+
#' @keywords internal
26+
"_PACKAGE"
2927

3028

3129

R/RcppExports.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@
2424
.Call('_CVXR_cpp_convolve', PACKAGE = 'CVXR', xa, xb)
2525
}
2626

27+
.sweep_dgCmat_in_place <- function(A, c_part) {
28+
invisible(.Call('_CVXR_multiply_dgCMatrix_vector', PACKAGE = 'CVXR', A, c_part))
29+
}
30+
31+
.sweep_in_place <- function(P, c_part) {
32+
invisible(.Call('_CVXR_sweep_in_place', PACKAGE = 'CVXR', P, c_part))
33+
}
34+
2735
#' Create a new LinOp object.
2836
#'
2937
#' @return an external ptr (Rcpp::XPtr) to a LinOp object instance.

R/coeff_extractor.R

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,14 +126,30 @@ setMethod("extract_quadratic_coeffs", "CoeffExtractor", function(object, affine_
126126
orig_id <- as.character(id(quad_forms[[var_id]][[3]]@args[[1]]))
127127
var_offset <- affine_id_map[[var_id]][[1]]
128128
var_size <- affine_id_map[[var_id]][[2]]
129+
ind_seq <- seq.int(from = var_offset + 1, length.out = var_size)
129130
if(!any(is.na(value(quad_forms[[var_id]][[3]]@P)))) {
130131
P <- value(quad_forms[[var_id]][[3]]@P)
131-
if(is(P, "sparseMatrix"))
132-
P <- as.matrix(P)
133-
c_part <- c[1, (var_offset + 1):(var_offset + var_size)]
134-
P <- sweep(P, MARGIN = 2, FUN = "*", c_part)
135-
} else
136-
P <- sparseMatrix(i = 1:var_size, j = 1:var_size, x = c[1, (var_offset + 1):(var_offset + var_size)])
132+
## Assuming P is a matrix or a sparsematrix
133+
## Force P to be sparse
134+
if (is.matrix(P)) {
135+
ind <- which(P != 0, arr.ind = TRUE)
136+
P <- sparseMatrix(p = c(0L, cumsum(tabulate(ind[, 2L], ncol(P)))),
137+
i = ind[, 1L] - 1L,
138+
x = P[ind], dims = dim(P), index1 = FALSE)
139+
}
140+
## if(is(P, "sparseMatrix")) {
141+
## P <- as.matrix(P)
142+
##c_part <- c[1, (var_offset + 1):(var_offset + var_size)]
143+
c_part <- c[1L, ind_seq]
144+
## P <- sweep(P, MARGIN = 2, FUN = "*", c_part)
145+
##if(is(P, "sparseMatrix"))
146+
.Call('_CVXR_multiply_dgCMatrix_vector', PACKAGE = 'CVXR', P, c_part)
147+
##else
148+
## .Call('_CVXR_sweep_in_place', PACKAGE = 'CVXR', P, c_part)
149+
} else {
150+
##P <- sparseMatrix(i = 1:var_size, j = 1:var_size, x = c[1, (var_offset + 1):(var_offset + var_size)])
151+
P <- sparseMatrix(i = seq_len(var_size), j = seq_len(var_size), x = c[1L, ind_seq])
152+
}
137153
if(orig_id %in% names(coeffs)) {
138154
coeffs[[orig_id]]$P <- coeffs[[orig_id]]$P + P
139155
coeffs[[orig_id]]$q <- coeffs[[orig_id]]$q + rep(0, nrow(P))
@@ -145,13 +161,16 @@ setMethod("extract_quadratic_coeffs", "CoeffExtractor", function(object, affine_
145161
} else {
146162
var_offset <- affine_id_map[[var_id]][[1]]
147163
var_size <- as.integer(prod(affine_var_dims[[var_id]]))
164+
ind_seq <- seq.int(from = var_offset + 1, length.out = var_size)
148165
if(var_id %in% names(coeffs)) {
149166
coeffs[[var_id]]$P <- coeffs[[var_id]]$P + sparseMatrix(i = c(), j = c(), dims = c(var_size, var_size))
150-
coeffs[[var_id]]$q <- coeffs[[var_id]]$q + c[1, (var_offset + 1):(var_offset + var_size)]
167+
##coeffs[[var_id]]$q <- coeffs[[var_id]]$q + c[1, (var_offset + 1):(var_offset + var_size)]
168+
coeffs[[var_id]]$q <- coeffs[[var_id]]$q + c[1L, ind_seq]
151169
} else {
152170
coeffs[[var_id]] <- list()
153171
coeffs[[var_id]]$P <- sparseMatrix(i = c(), j = c(), dims = c(var_size, var_size))
154-
coeffs[[var_id]]$q <- c[1, (var_offset + 1):(var_offset + var_size)]
172+
## coeffs[[var_id]]$q <- c[1, (var_offset + 1):(var_offset + var_size)]
173+
coeffs[[var_id]]$q <- c[1L, ind_seq]
155174
}
156175
}
157176
}
@@ -470,4 +489,4 @@ restore_quad_forms <- function(expr, quad_forms) {
470489
# }
471490
# Ps <- lapply(Ps, function(P) { Matrix(P, sparse = TRUE) })
472491
# list(Ps, Matrix(Q, sparse = TRUE), R)
473-
# }
492+
# }

R/conic_solvers.R

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,14 @@ ConicSolver.get_spacing_matrix <- function(dim, spacing, offset) {
156156
## return(mat)
157157
## }
158158

159+
reshape_sparse_mat_by_row <- function(M, new_dim) {
160+
## Note, no check on new_dim for efficiency
161+
m <- t(M)
162+
dim(m) <- rev(new_dim)
163+
t(m)
164+
}
165+
166+
159167
#' @param constr A \linkS4class{Constraint} to format.
160168
#' @param exp_cone_order A list indicating how the exponential cone arguments are ordered.
161169
#' @describeIn ConicSolver Return a list representing a cone program whose problem data tensors
@@ -191,11 +199,22 @@ setMethod("reduction_format_constr", "ConicSolver", function(object, problem, co
191199
# coeffs[[2]][gap:(2*(gap-1)),]
192200
# TODO: Keep X_coeff sparse while reshaping!
193201
X_coeff <- coeffs[[2]]
194-
reshaped <- matrix(t(X_coeff), nrow = nrow(coeffs[[1]]), byrow = TRUE)
195-
stacked <- -cbind(coeffs[[1]], reshaped)
196-
stacked <- matrix(t(stacked), nrow = nrow(coeffs[[1]]) + nrow(X_coeff), ncol = ncol(coeffs[[1]]), byrow = TRUE)
197-
198-
offset <- cbind(offsets[[1]], matrix(t(offsets[[2]]), nrow = nrow(offsets[[1]]), byrow = TRUE))
202+
203+
## reshaped <- matrix(t(X_coeff), nrow = nrow(coeffs[[1]]), byrow = TRUE)
204+
cfs1 <- coeffs[[1]]
205+
new_r <- nrow(cfs1)
206+
new_c <- prod(dim(X_coeff)) / new_r
207+
reshaped <- reshape_sparse_mat_by_row(X_coeff, new_dim = c(new_r, new_c))
208+
209+
stacked <- -cbind(cfs1, reshaped)
210+
## stacked <- matrix(t(stacked), nrow = nrow(coeffs[[1]]) + nrow(X_coeff), ncol = ncol(coeffs[[1]]), byrow = TRUE)
211+
stacked <- reshape_sparse_mat_by_row(
212+
stacked,
213+
new_dim = c(new_r + nrow(X_coeff), ncol(cfs1))
214+
)
215+
216+
offset <- cbind(offsets[[1]],
217+
matrix(t(offsets[[2]]), nrow = nrow(offsets[[1]]), byrow = TRUE))
199218
offset <- matrix(t(offset), ncol = 1)
200219
return(list(Matrix(stacked, sparse = TRUE), as.vector(offset)))
201220
} else if(inherits(constr, "ExpCone")) {
@@ -2088,7 +2107,8 @@ setMethod("perform", signature(object = "MOSEK", problem = "Problem"), function(
20882107
coeff_offs <- ConicSolver.get_coeff_offset(problem@objective@args[[1]])
20892108
c <- coeff_offs[[1]]
20902109
constant <- coeff_offs[[2]]
2091-
data[[C_KEY]] <- as.vector(c)
2110+
#data[[C_KEY]] <- as.vector(c)
2111+
data[[C_KEY]] <- c
20922112
inv_data$n0 <- length(data[[C_KEY]])
20932113
data[[OBJ_OFFSET]] <- constant[1]
20942114
data[[DIMS]] <- list()

R/dcp2cone.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,15 @@ setMethod("stuffed_objective", signature(object = "ConeMatrixStuffing", problem
5858
C <- CR[[1]]
5959
R <- CR[[2]]
6060

61-
c <- matrix(C, ncol = 1) # TODO: Check if converted to dense matrix and flattened like in CVXPY
61+
##c <- matrix(C, ncol = 1) # TODO: Check if converted to dense matrix and flattened like in CVXPY
62+
6263
boolint <- extract_mip_idx(variables(problem))
6364
boolean <- boolint[[1]]
6465
integer <- boolint[[2]]
6566
# x <- Variable(extractor@N, boolean = boolean, integer = integer)
6667
x <- Variable(extractor@N, 1, boolean = boolean, integer = integer)
6768

68-
new_obj <- t(c) %*% x + 0
69+
new_obj <- C %*% x + 0
6970

7071
return(list(new_obj, x, R[1]))
7172
})

R/dgp2dcp.R

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,8 @@ setMethod("names", signature(x = "DgpCanonMethods"), function(x) { names(Dgp2Dcp
489489

490490
# TODO: How to implement this with S4 setMethod? Signature is x = "DgpCanonMethods", i = "character", j = "missing".
491491
'[[.DgpCanonMethods' <- function(x, i, j, ..., exact = TRUE) { do.call("$", list(x, i)) }
492+
## Here's an implementation using S4!
493+
## setMethod(f = "[[", signature = c(object = "DgpCanonMethods", i = "character", j = "missing") function(x, i, j) [email protected][[i]] )
492494

493495
#' @param name The name of the atom or expression to canonicalize.
494496
#' @describeIn DgpCanonMethods Returns either a canonicalized variable or

R/generics.R

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,12 @@ setGeneric("is_dcp", function(object) { standardGeneric("is_dcp") })
220220
#' @export
221221
setGeneric("is_dgp", function(object) { standardGeneric("is_dgp") })
222222

223+
## #' DGP canonical methods indexing
224+
## #' @param A \linkS4class{DgpCanonMethods} object
225+
## #' @param a variable index of type character
226+
## #' @export
227+
## setGeneric("[[", function(object, i, j) standardGeneric("[["))
228+
223229
#'
224230
#' Size of Expression
225231
#'

man/CVXR-package.Rd

Lines changed: 12 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/RcppConv.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "CVXR.h"
2+
using namespace Rcpp;
23

34
// Source: Rcpp Introduction
45

@@ -14,3 +15,61 @@ Rcpp::NumericVector cpp_convolve(Rcpp::NumericVector xa, Rcpp::NumericVector xb)
1415
iab[i + j] += ia[i] * ib[j];
1516
return xab;
1617
}
18+
19+
// [[Rcpp::export(.sweep_dgCmat_in_place)]]
20+
void multiply_dgCMatrix_vector(S4 A, NumericVector c_part) {
21+
// Get the slot values from the dgCMatrix object
22+
IntegerVector i = A.slot("i");
23+
IntegerVector p = A.slot("p");
24+
NumericVector x_values = A.slot("x");
25+
26+
// Get the number of columns in the matrix
27+
int n = p.length() - 1;
28+
int k = c_part.length();
29+
30+
// Check if the length of the vector matches the number of columns or for a scalar
31+
if (k != n && k != 1) {
32+
stop("mutiply_dgCMatrix_vector: Incompatible dimensions");
33+
}
34+
35+
if (k == 1) { // c_part is a scalar
36+
for (int i = 0; i < x_values.length(); ++i) {
37+
x_values[i] *= c_part[0];
38+
}
39+
} else {
40+
// Perform in-place multiplication
41+
for (int col = 0; col < n; ++col) {
42+
int start = p[col];
43+
int end = p[col + 1];
44+
45+
for (int idx = start; idx < end; ++idx) {
46+
x_values[idx] *= c_part[col];
47+
}
48+
}
49+
}
50+
51+
}
52+
53+
// [[Rcpp::export(.sweep_in_place)]]
54+
void sweep_in_place(Rcpp::NumericMatrix P, Rcpp::NumericVector c_part) {
55+
int nrow = P.nrow();
56+
int ncol = P.ncol();
57+
int k = c_part.length();
58+
if (ncol != k && k != 1) {
59+
Rcpp::stop("sweep_in_place: Incompatible dimensions");
60+
}
61+
62+
if (k == 1) { // x is a scalar
63+
for (int j = 0; j < ncol; j++) {
64+
for (int i = 0; i < nrow; i++) {
65+
P(i, j) = P(i, j) * c_part[0];
66+
}
67+
}
68+
} else {
69+
for (int j = 0; j < ncol; j++) {
70+
for (int i = 0; i < nrow; i++) {
71+
P(i, j) = P(i, j) * c_part[j];
72+
}
73+
}
74+
}
75+
}

src/RcppExports.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,28 @@ BEGIN_RCPP
4949
return rcpp_result_gen;
5050
END_RCPP
5151
}
52+
// multiply_dgCMatrix_vector
53+
void multiply_dgCMatrix_vector(S4 A, NumericVector c_part);
54+
RcppExport SEXP _CVXR_multiply_dgCMatrix_vector(SEXP ASEXP, SEXP c_partSEXP) {
55+
BEGIN_RCPP
56+
Rcpp::RNGScope rcpp_rngScope_gen;
57+
Rcpp::traits::input_parameter< S4 >::type A(ASEXP);
58+
Rcpp::traits::input_parameter< NumericVector >::type c_part(c_partSEXP);
59+
multiply_dgCMatrix_vector(A, c_part);
60+
return R_NilValue;
61+
END_RCPP
62+
}
63+
// sweep_in_place
64+
void sweep_in_place(Rcpp::NumericMatrix P, Rcpp::NumericVector c_part);
65+
RcppExport SEXP _CVXR_sweep_in_place(SEXP PSEXP, SEXP c_partSEXP) {
66+
BEGIN_RCPP
67+
Rcpp::RNGScope rcpp_rngScope_gen;
68+
Rcpp::traits::input_parameter< Rcpp::NumericMatrix >::type P(PSEXP);
69+
Rcpp::traits::input_parameter< Rcpp::NumericVector >::type c_part(c_partSEXP);
70+
sweep_in_place(P, c_part);
71+
return R_NilValue;
72+
END_RCPP
73+
}
5274
// LinOp__new
5375
SEXP LinOp__new();
5476
RcppExport SEXP _CVXR_LinOp__new() {
@@ -415,6 +437,8 @@ static const R_CallMethodDef CallEntries[] = {
415437
{"_CVXR_build_matrix_0", (DL_FUNC) &_CVXR_build_matrix_0, 2},
416438
{"_CVXR_build_matrix_1", (DL_FUNC) &_CVXR_build_matrix_1, 3},
417439
{"_CVXR_cpp_convolve", (DL_FUNC) &_CVXR_cpp_convolve, 2},
440+
{"_CVXR_multiply_dgCMatrix_vector", (DL_FUNC) &_CVXR_multiply_dgCMatrix_vector, 2},
441+
{"_CVXR_sweep_in_place", (DL_FUNC) &_CVXR_sweep_in_place, 2},
418442
{"_CVXR_LinOp__new", (DL_FUNC) &_CVXR_LinOp__new, 0},
419443
{"_CVXR_LinOp__get_sparse", (DL_FUNC) &_CVXR_LinOp__get_sparse, 1},
420444
{"_CVXR_LinOp__set_sparse", (DL_FUNC) &_CVXR_LinOp__set_sparse, 2},

tests/manual/test-vignette.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ test_that("Test non-negative least squares", {
5252
coeff <- cbind(b, beta_ols, beta_nnls)
5353
colnames(coeff) <- c("Actual", "OLS", "NNLS")
5454
rownames(coeff) <- paste("beta", 1:length(b)-1, sep = "")
55-
barplot(t(coeff), ylab = "Coefficients", beside = TRUE, legend = TRUE)
55+
barplot(t(coeff), ylab = "Coefficients", beside = TRUE, legend.text = TRUE)
5656
})
5757

5858
test_that("Test censored regression", {

0 commit comments

Comments
 (0)