Skip to content

Commit 6990ecc

Browse files
author
Steven Diamond
committed
refactoring matrix data refactor
1 parent 9503d7b commit 6990ecc

File tree

7 files changed

+227
-65
lines changed

7 files changed

+227
-65
lines changed

cvxpy/lin_ops/lin_utils.py

+21
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,24 @@ def get_expr_vars(operator):
489489
for arg in operator.args:
490490
vars_ += get_expr_vars(arg)
491491
return vars_
492+
493+
def get_expr_params(operator):
494+
"""Get a list of the parameters in the operator.
495+
496+
Parameters
497+
----------
498+
operator : LinOp
499+
The operator to extract the parameters from.
500+
501+
Returns
502+
-------
503+
list
504+
A list of parameter objects.
505+
"""
506+
if operator.type is lo.PARAM:
507+
return [operator.data.parameters()]
508+
else:
509+
vars_ = []
510+
for arg in operator.args:
511+
vars_ += get_expr_params(arg)
512+
return vars_

cvxpy/problems/problem_data.py cvxpy/problems/matrix_data.py

+28-26
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@
1717
along with CVXPY. If not, see <http://www.gnu.org/licenses/>.
1818
"""
1919

20+
import cvxpy.interface as intf
2021
import cvxpy.lin_ops as lo
2122
import cvxpy.lin_ops.lin_utils as lu
2223
import cvxpy.lin_ops.lin_to_matrix as op2mat
24+
import scipy.sparse as sp
2325

24-
class ProblemData(object):
25-
"""The data for a convex optimization problem.
26+
class MatrixData(object):
27+
"""The matrices for the conic form convex optimization problem.
2628
2729
Attributes
2830
----------
@@ -43,6 +45,7 @@ class ProblemData(object):
4345
vec_intf : interface
4446
The matrix interface to use for creating the constant vector.
4547
"""
48+
4649
def __init__(self, var_offsets, x_length, objective,
4750
eq_constr, ineq_constr, nonlin_constr,
4851
matrix_intf, vec_intf):
@@ -57,13 +60,9 @@ def __init__(self, var_offsets, x_length, objective,
5760
self.nonlin_constr = nonlin_constr
5861
self.matrix_intf = matrix_intf
5962
self.vec_intf = vec_intf
60-
# Cache everything possible.
61-
self._cache_all()
6263

63-
def _cache_all(self):
64-
"""Caches all the data possible.
65-
"""
66-
self.c_COO, self.offset = self._init_matrix_cache(self._dummy_constr)
64+
# Cache everything possible.
65+
self.c_COO, self.offset = self._init_matrix_cache(self._dummy_constr)
6766
self._lin_matrix(self._dummy_constr, self.c_COO,
6867
self.offset, caching=True)
6968
# Equaliy constraints.
@@ -80,7 +79,7 @@ def get_objective(self):
8079
"""
8180
c, offset = self._cache_to_matrix(self._dummy_constr, self.c_COO,
8281
self.offset)
83-
offset = intf.matrix_intf.scalar_value(offset)
82+
offset = self.matrix_intf.scalar_value(offset)
8483
return c.T, offset
8584

8685
def get_eq_constr(self):
@@ -93,6 +92,10 @@ def get_ineq_constr(self):
9392
"""
9493
return self._cache_to_matrix(self.eq_constr, self.G_COO, self.h)
9594

95+
def get_nonlin_constr(self):
96+
"""Returns the oracle function for the nonlinear constraints.
97+
"""
98+
return self.F
9699

97100
def _constr_matrix_size(self, constraints):
98101
"""Returns the dimensions of the constraint matrix.
@@ -120,12 +123,12 @@ def _init_matrix_cache(self, constraints):
120123
-------
121124
((V, I, J), array)
122125
"""
123-
rows = _constr_matrix_size(constraints)[0]
126+
rows = self._constr_matrix_size(constraints)[0]
124127
COO = ([], [], [])
125128
const_vec = self.vec_intf.zeros(rows, 1)
126129
return (COO, const_vec)
127130

128-
def _lin_matrix(self, constraints, COO, const_vec, caching=False):
131+
def _lin_matrix(self, constraints, coo_tup, const_vec, caching=False):
129132
"""Computes a matrix and vector representing a list of constraints.
130133
131134
In the matrix, each constraint is given a block of rows.
@@ -137,7 +140,7 @@ def _lin_matrix(self, constraints, COO, const_vec, caching=False):
137140
----------
138141
constraints : list
139142
A list of constraints in the matrix.
140-
COO : tuple
143+
coo_tup : tuple
141144
A (V, I, J) triplet.
142145
const_vec : array
143146
The constant term.
@@ -149,23 +152,22 @@ def _lin_matrix(self, constraints, COO, const_vec, caching=False):
149152
tuple
150153
A (matrix, vector) tuple.
151154
"""
152-
V, I, J = COO
153155
vert_offset = 0
154156
for constr in constraints:
155157
# Process the constraint if it has a parameter and not caching
156158
# or it doesn't have a parameter and caching.
157159
if lu.get_expr_params(constr.expr) != caching:
158-
self._process_constr(constr, V, I, J, const_vec, vert_offset)
160+
self._process_constr(constr, coo_tup, const_vec, vert_offset)
159161
vert_offset += constr.size[0]*constr.size[1]
160162

161-
def _cache_to_matrix(self, constraints, COO, const_vec):
163+
def _cache_to_matrix(self, constraints, coo_tup, const_vec):
162164
"""Converts the cached representation of the constraints matrix.
163165
164166
Parameters
165167
----------
166168
constraints : list
167169
A list of constraints in the matrix.
168-
COO : tuple
170+
coo_tup : tuple
169171
A (V, I, J) triplet.
170172
const_vec : array
171173
The constant term.
@@ -176,36 +178,34 @@ def _cache_to_matrix(self, constraints, COO, const_vec):
176178
"""
177179
rows, cols = self._constr_matrix_size(constraints)
178180
# Create the constraints matrix.
179-
V, I, J = COO
181+
V, I, J = coo_tup
180182
if len(V) > 0:
181183
matrix = sp.coo_matrix((V, (I, J)), (rows, cols))
182184
# Convert the constraints matrix to the correct type.
183-
matrix = matrix_intf.const_to_matrix(matrix, convert_scalars=True)
185+
matrix = self.matrix_intf.const_to_matrix(matrix,
186+
convert_scalars=True)
184187
else: # Empty matrix.
185-
matrix = matrix_intf.zeros(rows, cols)
188+
matrix = self.matrix_intf.zeros(rows, cols)
186189
# Convert 2D ND arrays to 1D
187190
if self.vec_intf is intf.DEFAULT_INTERFACE:
188191
const_vec = intf.from_2D_to_1D(const_vec)
189192
return (matrix, -const_vec)
190193

191-
def _process_constr(self, constr, V, I, J, const_vec, vert_offset):
194+
def _process_constr(self, constr, coo_tup, const_vec, vert_offset):
192195
"""Extract the coefficients from a constraint.
193196
194197
Parameters
195198
----------
196199
constr : LinConstr
197200
The linear constraint to process.
198-
V : list
199-
A list of values in the COO sparse matrix.
200-
I : list
201-
A list of rows in the COO sparse matrix.
202-
J : list
203-
A list of columns in the COO sparse matrix.
201+
coo_tup : tuple
202+
A (V, I, J) triplet.
204203
const_vec : array
205204
The constant vector.
206205
vert_offset : int
207206
The row offset of the constraint.
208207
"""
208+
V, I, J = coo_tup
209209
coeffs = op2mat.get_coefficients(constr.expr)
210210
for id_, block in coeffs:
211211
vert_start = vert_offset
@@ -255,6 +255,8 @@ def _nonlin_matrix(self):
255255
constr.place_x0(big_x, self.var_offsets, self.vec_intf)
256256

257257
def F(x=None, z=None):
258+
"""Oracle for function value, gradient, and Hessian.
259+
"""
258260
if x is None:
259261
return rows, big_x
260262
big_f = self.vec_intf.zeros(rows, 1)

cvxpy/problems/objective.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def value(self):
6767
return self._expr.value
6868

6969
@staticmethod
70-
def _primal_to_result(result):
70+
def primal_to_result(result):
7171
"""The value of the objective given the solver primal value.
7272
"""
7373
return result

cvxpy/problems/problem.py

+45-37
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,19 @@
2424
from cvxpy.error import SolverError
2525
import cvxpy.lin_ops.lin_utils as lu
2626
import cvxpy.lin_ops as lo
27-
import cvxpy.lin_ops.tree_mat as tree_mat
27+
import cvxpy.lin_ops.lin_to_matrix as op2mat
2828
from cvxpy.constraints import (EqConstraint, LeqConstraint,
29-
SOC, SOC_Elemwise, SDP, ExpCone)
29+
SOC, SDP, ExpCone)
3030
from cvxpy.problems.objective import Minimize, Maximize
3131
from cvxpy.problems.kktsolver import get_kktsolver
32-
import cvxpy.problems.iterative as iterative
3332
from cvxpy.problems.problem_data import ProblemData
3433

3534
from collections import OrderedDict
3635
import warnings
37-
import itertools
38-
import numbers
3936
import cvxopt
4037
import cvxopt.solvers
4138
import ecos
4239
import numpy as np
43-
import scipy.sparse as sp
4440

4541
# Attempt to import SCS.
4642
try:
@@ -69,16 +65,29 @@ class Problem(u.Canonical):
6965

7066
def __init__(self, objective, constraints=None):
7167
if constraints is None:
72-
constraints = []
68+
constraints = ()
7369
# Check that objective is Minimize or Maximize.
7470
if not isinstance(objective, (Minimize, Maximize)):
7571
raise TypeError("Problem objective must be Minimize or Maximize.")
76-
self.objective = objective
77-
self.constraints = constraints
72+
# Constraints and objective are immutable.
73+
self._objective = objective
74+
self._constraints = tuple(constraints)
7875
self._value = None
7976
self._status = None
8077
self._cached_data = {}
8178

79+
@property
80+
def objective(self):
81+
"""Getter for objective.
82+
"""
83+
return self._objective
84+
85+
@property
86+
def constraints(self):
87+
"""Getter for constraints tuple.
88+
"""
89+
return self._constraints
90+
8291
@property
8392
def value(self):
8493
"""The value from the last time the problem was solved.
@@ -104,7 +113,8 @@ def is_dcp(self):
104113
"""
105114
return all(exp.is_dcp() for exp in self.constraints + [self.objective])
106115

107-
def _filter_constraints(self, constraints):
116+
@staticmethod
117+
def _filter_constraints(constraints):
108118
"""Separate the constraints by type.
109119
110120
Parameters
@@ -156,7 +166,8 @@ def canonicalize(self):
156166

157167
return (obj, constr_map)
158168

159-
def _presolve(self, objective, constr_map):
169+
@staticmethod
170+
def _presolve(objective, constr_map):
160171
"""Eliminates unnecessary constraints and short circuits the solver
161172
if possible.
162173
@@ -192,10 +203,11 @@ def _presolve(self, objective, constr_map):
192203
coeff = op2mat.get_constant_coeff(constr.expr)
193204
sign = intf.sign(coeff)
194205
# For equality constraint, coeff must be zero.
195-
# For inequality (i.e. <= 0) constraint, coeff must be negative.
206+
# For inequality (i.e. <= 0) constraint,
207+
# coeff must be negative.
196208
if key is s.EQ and not sign.is_zero() or \
197-
key is s.LEQ and not sign.is_negative():
198-
return s.INFEASIBLE
209+
key is s.LEQ and not sign.is_negative():
210+
return s.INFEASIBLE
199211
else:
200212
new_constraints.append(constr)
201213
constr_map[key] = new_constraints
@@ -355,22 +367,16 @@ def get_problem_data(self, solver):
355367
# Raise an error if the solver cannot handle the problem.
356368
self._validate_solver(constr_map, solver)
357369
dims = self._format_for_solver(constr_map, solver)
370+
# TODO factor out.
358371
all_ineq = constr_map[s.EQ] + constr_map[s.LEQ]
372+
# CVXOPT can have variables that only live in NonLinearConstraints.
373+
nonlinear = constr_map[s.EXP] if solver == s.CVXOPT else []
359374
var_offsets, var_sizes, x_length = self._get_var_offsets(objective,
360-
all_ineq)
361-
362-
if solver == s.ECOS and not (constr_map[s.SDP] or constr_map[s.EXP]):
363-
args, offset = self._ecos_problem_data(objective, constr_map, dims,
364-
var_offsets, x_length)
365-
elif solver == s.CVXOPT and not constr_map[s.EXP]:
366-
args, offset = self._cvxopt_problem_data(objective, constr_map, dims,
367-
var_offsets, x_length)
368-
elif solver == s.SCS:
369-
args, offset = self._scs_problem_data(objective, constr_map, dims,
370-
var_offsets, x_length)
371-
else:
372-
raise SolverError("Cannot return problem data for the solver %s." % solver)
373-
return args
375+
all_ineq,
376+
nonlinear)
377+
return SOLVERS[solver].get_problem_data(self._cached_data, objective,
378+
constr_map, dims,
379+
var_offsets, x_length)
374380

375381
def _solve(self, solver=None, ignore_dcp=False, verbose=False, **kwargs):
376382
"""Solves a DCP compliant optimization problem.
@@ -425,7 +431,8 @@ def _solve(self, solver=None, ignore_dcp=False, verbose=False, **kwargs):
425431
all_ineq,
426432
nonlinear)
427433
if solver in s.SOLVERS:
428-
result = solve_methods[solver](objective, constr_map, dims,
434+
result = SOLVERS[solver].solve(self._cached_data, objective,
435+
constr_map, dims,
429436
var_offsets, x_length,
430437
verbose, kwargs)
431438
else:
@@ -453,7 +460,7 @@ def _ecos_problem_data(self, objective, constr_map, dims,
453460
454461
Parameters
455462
----------
456-
objective: Expression
463+
objective: LinOp
457464
The canonicalized objective.
458465
constr_map: dict
459466
A dict of the canonicalized constraints.
@@ -520,7 +527,7 @@ def _ecos_solve(self, objective, constr_map, dims,
520527
status = s.SOLVER_STATUS[s.ECOS][results['info']['exitFlag']]
521528
if status in s.SOLUTION_PRESENT:
522529
primal_val = results['info']['pcost']
523-
value = self.objective._primal_to_result(
530+
value = self.objective.primal_to_result(
524531
primal_val - obj_offset)
525532
return (status, value,
526533
results['x'], results['y'], results['z'])
@@ -602,7 +609,7 @@ def _cvxopt_solve(self, objective, constr_map, dims,
602609
"""
603610
prob_data = self._cvxopt_problem_data(objective, constr_map, dims,
604611
var_offsets, x_length)
605-
c, G, h, dims, A, b, F = prob_data[0]
612+
c, G, h, dims, A, b = prob_data[0]
606613
obj_offset = prob_data[1]
607614
# Save original cvxopt solver options.
608615
old_options = cvxopt.solvers.options
@@ -632,14 +639,14 @@ def _cvxopt_solve(self, objective, constr_map, dims,
632639
kktsolver=kktsolver)
633640
status = s.SOLVER_STATUS[s.CVXOPT][results['status']]
634641
# Catch exceptions in CVXOPT and convert them to solver errors.
635-
except ValueError as e:
642+
except ValueError:
636643
status = s.SOLVER_ERROR
637644

638645
# Restore original cvxopt solver options.
639646
cvxopt.solvers.options = old_options
640647
if status in s.SOLUTION_PRESENT:
641648
primal_val = results['primal objective']
642-
value = self.objective._primal_to_result(
649+
value = self.objective.primal_to_result(
643650
primal_val - obj_offset)
644651
if constr_map[s.EXP]:
645652
ineq_dual = results['zl']
@@ -746,11 +753,12 @@ def _handle_no_solution(self, status):
746753
constr.save_value(None)
747754
# Set the problem value.
748755
if status in [s.INFEASIBLE, s.INFEASIBLE_INACCURATE]:
749-
self._value = self.objective._primal_to_result(np.inf)
756+
self._value = self.objective.primal_to_result(np.inf)
750757
elif status in [s.UNBOUNDED, s.UNBOUNDED_INACCURATE]:
751-
self._value = self.objective._primal_to_result(-np.inf)
758+
self._value = self.objective.primal_to_result(-np.inf)
752759

753-
def _get_var_offsets(self, objective, constraints, nonlinear=None):
760+
@staticmethod
761+
def _get_var_offsets(objective, constraints, nonlinear=None):
754762
"""Maps each variable to a horizontal offset.
755763
756764
Parameters

0 commit comments

Comments
 (0)