AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
cplex_solver_interface.h
1#ifndef LP_CPLEX_SOLVER_INTERFACE_H
2#define LP_CPLEX_SOLVER_INTERFACE_H
3
4#include "downward/lp/lp_solver.h"
5#include "downward/lp/solver_interface.h"
6
7#include "downward/algorithms/named_vector.h"
8#include "downward/utils/memory.h"
9
10#include <cplex.h>
11#include <cstring>
12
13#include <span>
14
15namespace lp {
16template <typename T>
17static T* to_cplex_array(std::vector<T>& v)
18{
19 /*
20 CPLEX expects a non-nullptr even for empty arrays but the C++ standard
21 does not guarantee any particular value for data for empty vectors (see
22 issue1111).
23 */
24 if (v.empty()) {
25 static T dummy;
26 return &dummy;
27 } else {
28 return v.data();
29 }
30}
31
32class CplexSolverInterface : public SolverInterface {
33 CPXENVptr env;
34 CPXLPptr problem;
35 bool is_mip;
36 int num_permanent_constraints;
37
38 /*
39 Our public interface allows using constraints of the form
40 LB <= expression <= UB
41 In cases where LB > UB, this constraint is trivially unsatisfiable.
42 CPLEX does not represent constraints like this and instead uses range
43 values, where the constraint is represented like this
44 expression - RNG = LB
45 where RNG is a variable restricted to take values from 0 to (UB - LB).
46 If LB > UB, the semantic instead is that RNG takes negative values between
47 (UB - LB) and 0. This means that in CPLEX, the constraint never is
48 trivially unsolvable. We still set the range value and the right-hand side
49 as described above but use negative range values to represent trivially
50 unsatisfiable constraints. The following two counters track how many such
51 constraints we have in the permanent and the temporary constraints.
52 */
53 int num_unsatisfiable_constraints;
54 int num_unsatisfiable_temp_constraints;
55
56 /*
57 Matrix data in CPLEX format for loading a new problem. Matrix entries are
58 stored in sparse form: non-zero coefficients are either stored
59 column-by-column (in that case the column is the major dimension and the
60 row is the minor dimension) or row-by-row (in that case the row is the
61 major dimension and the column is the minor dimension).
62
63 TODO: eventually, we might want to create our LP classes directly in this
64 format, so no additional conversion is necessary.
65 */
66 class CplexMatrix {
67 /*
68 Non-zero entries in the matrix.
69 */
70 std::vector<double> coefficients;
71 /*
72 Parallel vector to coefficients: specifies the minor dimension of the
73 corresponding entry.
74
75 For example, say entries are stored column-by-column, and there are
76 4 non-zero entries in columns 0 and 1. If column 2 now as a 4.5 in
77 row 1 and a 7.2 in row 5, then
78 coefficients[4] = 4.5, indices[4] = 1
79 coefficients[5] = 7.2, indices[5] = 5.
80 */
81 std::vector<int> indices;
82 /*
83 Specifies the starts of the major dimension in the two vectors above.
84 In the example above, starts[2] = 4 because the entries for column 2
85 start at index 4.
86 */
87 std::vector<int> starts;
88 /*
89 Specifies the number of non-zero entries within a major dimension.
90 In the example above, counts[2] = 2 because there are two non-zero
91 entries for column 2 (4.5 and 7.2).
92 */
93 std::vector<int> counts;
94
95 public:
96 /*
97 When loading a whole LP, column-by-column data better matches CPLEX's
98 internal data structures, so we prefer this encoding.
99 */
100 void assign_column_by_column(
101 std::span<const LPConstraint> constraints,
102 int num_cols);
103 /*
104 When adding constraints, a row-by-row encoding is more useful.
105 In row form, counts are not used. Instead, starts has an additional
106 entry at the end and counts[i] is always assumed to be
107 (starts[i+1] - starts[i]).
108 */
109 void assign_row_by_row(std::span<const LPConstraint> constraints);
110
111 double* get_coefficients() { return to_cplex_array(coefficients); }
112 int* get_indices() { return to_cplex_array(indices); }
113 int* get_starts() { return to_cplex_array(starts); }
114 int* get_counts() { return to_cplex_array(counts); }
115 int get_num_nonzeros() { return coefficients.size(); }
116 };
117
118 class CplexColumnsInfo {
119 // Lower bound for each column (variable)
120 std::vector<double> lb;
121 // Upper bound for each column (variable)
122 std::vector<double> ub;
123 // Type of each column (continuos, integer)
124 std::vector<char> type;
125 // Objective value of each column (variable)
126 std::vector<double> objective;
127
128 public:
129 void assign(const named_vector::NamedVector<LPVariable>& variables);
130 double* get_lb() { return to_cplex_array(lb); }
131 double* get_ub() { return to_cplex_array(ub); }
132 char* get_type() { return to_cplex_array(type); }
133 double* get_objective() { return to_cplex_array(objective); }
134 };
135
136 class CplexRowsInfo {
137 // Right-hand side value of a row
138 std::vector<double> rhs;
139 // Sense of a row (Greater or equal, Less or equal, Equal, or Range)
140 std::vector<char> sense;
141 /*
142 If the sense of a row is Range, then its value is restricted to the
143 interval (RHS, RHS + range_value).
144 */
145 std::vector<double> range_values;
146 /*
147 In case not all rows specify a sense, this gives the indices of the
148 rows that are ranged rows.
149 */
150 std::vector<int> range_indices;
151
152 public:
153 void assign(
154 std::span<const LPConstraint> constraints,
155 int offset = 0,
156 bool dense_range_values = true);
157 double* get_rhs() { return to_cplex_array(rhs); }
158 char* get_sense() { return to_cplex_array(sense); }
159 double* get_range_values() { return to_cplex_array(range_values); }
160 int* get_range_indices() { return to_cplex_array(range_indices); }
161 int get_num_ranged_rows() { return range_indices.size(); }
162 };
163
164 class CplexNameData {
165 std::vector<char*> names;
166 std::vector<int> indices;
167
168 public:
169 template <typename T>
170 explicit CplexNameData(const named_vector::NamedVector<T>& values)
171 {
172 if (values.has_names()) {
173 names.reserve(values.size());
174 indices.reserve(values.size());
175 int num_values = values.size();
176 for (int i = 0; i < num_values; ++i) {
177 const std::string& name = values.get_name(i);
178 if (!name.empty()) {
179 // CPLEX copies the names, so the const_cast should be
180 // fine.
181 names.push_back(const_cast<char*>(name.data()));
182 indices.push_back(i);
183 }
184 }
185 }
186 }
187
188 int size() { return names.size(); }
189 int* get_indices()
190 {
191 if (indices.empty()) {
192 return nullptr;
193 } else {
194 return indices.data();
195 }
196 }
197 char** get_names()
198 {
199 if (names.empty()) {
200 return nullptr;
201 } else {
202 return names.data();
203 }
204 }
205 };
206
207 /*
208 We could create these objects locally but we want to keep them around to
209 avoid reallocation of the vectors in case we load multiple LPs. We don't
210 do this for name data as names are expensive anyway and should only be
211 used in debug mode where the additional allocation is not problematic.
212 */
213 CplexMatrix matrix;
214 CplexColumnsInfo columns;
215 CplexRowsInfo rows;
216 std::vector<int> objective_indices;
217
218 /*
219 We store a copy of the current constraint bounds. We need to know the
220 current bounds when changing bounds, and accessing them through the CPLEX
221 interface has a significant overhead. Storing these vectors overlaps with
222 storing CplexRowsInfo above. The difference is that CplexRowsInfo stores
223 more information and we reuse it for temporary constraints, while we want
224 to keep the following vectors always synchronized with the full LP
225 (permanent and temporary constraints).
226 */
227 std::vector<double> constraint_lower_bounds;
228 std::vector<double> constraint_upper_bounds;
229
230 bool is_trivially_unsolvable() const;
231 void change_constraint_bounds(int index, double lb, double ub);
232
233public:
234 CplexSolverInterface();
235 virtual ~CplexSolverInterface() override;
236
237 virtual void load_problem(const LinearProgram& lp) override;
238 virtual void add_temporary_constraints(
239 const named_vector::NamedVector<LPConstraint>& constraints) override;
240 virtual void clear_temporary_constraints() override;
241 virtual double get_infinity() const override;
242 virtual void set_objective_coefficients(
243 const std::vector<double>& coefficients) override;
244 virtual void
245 set_objective_coefficient(int index, double coefficient) override;
246 virtual void set_constraint_lower_bound(int index, double bound) override;
247 virtual void set_constraint_upper_bound(int index, double bound) override;
248 virtual void set_variable_lower_bound(int index, double bound) override;
249 virtual void set_variable_upper_bound(int index, double bound) override;
250 virtual void set_mip_gap(double gap) override;
251 virtual void solve() override;
252 virtual void write_lp(const std::string& filename) const override;
253 virtual void print_failure_analysis() const override;
254 virtual bool is_infeasible() const override;
255 virtual bool is_unbounded() const override;
256 virtual bool has_optimal_solution() const override;
257 virtual double get_objective_value() const override;
258 virtual std::vector<double> extract_solution() const override;
259 virtual int get_num_variables() const override;
260 virtual int get_num_constraints() const override;
261 virtual bool has_temporary_constraints() const override;
262 virtual void print_statistics() const override;
263
264 virtual std::vector<double> extract_dual_solution() const override;
265
266 virtual void add_variable(
267 const LPVariable& var,
268 const std::vector<int>& ids,
269 const std::vector<double>& coefs,
270 std::string_view name = "") override;
271
272 virtual void
273 add_constraint(const LPConstraint& constraint, std::string_view name = "")
274 override;
275};
276} // namespace lp
277#endif