Description
When using larger models it is becoming increasingly apparent that rerpresenting each constraint as a SymPy expression incurs a significant performance penalty when setting up such models. Although SymPy expressions are very flexible and can also be used to represent non-linear constraints the vast majority of constraints are typically linear and can often be pooled as matrices.
In order to pass such matrices efficiently to the solvers I suggest to introduce a new class LinearConstraintsMatrix which can be added to models in a similar fashion as Constraint objects but is really just a wrapper for a collection of constraints in the underlying solver object. In particular, a LinearConstraintsMatrix object would implement methods to directly get/set coefficients in the underlying solver object.
A simpler, but less flexible solution, would be to add an abstract method add_lp_matrix to the Model class which can be implemented by the solver-specific interfaces. For CPLEX, this could look like this:
import numpy as np
def add_lp_matrix(self, lil_matrix, variables, lhs, rhs):
# lil_matrix is a sparse matrix in LIL format
# variables is either a list of names (str) or a list of indices (int)
# adds constraints lhs <= lil_matrix <= rhs to the model
if isinstance(variables[0], int):
var_idx = np.array(variables)
else:
var_idx = np.array(self.problem.variables.get_indices(variables))
senses = ["R"] * len(lhs)
range_values = [0.] * len(lhs)
for i in range(len(lhs)):
if lhs[i] <= -cplex.infinity and rhs[i] >= cplex.infinity: # unbounded constraint
rhs[i] = cplex.infinity
senses[i] = "L"
elif lhs[i] <= -cplex.infinity: # <= rhs[i] constraint
senses[i] = "L"
elif rhs[i] >= cplex.infinity: # >= lhs[i] constraint
senses[i] = "G"
rhs[i] = lhs[i]
elif lhs[i] <= rhs[i]: # lhs[i] <= ... <= rhs[i]
range_values[i] = lhs[i] - rhs[i]
else: # rhs[i] > lhs[i]
raise ValueError
lin_expr = [[var_idx[r].tolist(), d] for r,d in zip(lil_matrix.rows, lil_matrix.data)]
self.problem.linear_constraints.add(lin_expr=lin_expr,
rhs=rhs, senses=senses, range_values=range_values)
However, it would then not be possible to transparently display, modify or remove these constraints. Nonetheless, it would be a simple method for efficienlty adding a large number of constraints in matrix format to a model.
Activity