Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cplex_interface.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14// Initial version of this code was written by Daniel Junglas (IBM)
15#include <cstdint>
16#include <limits>
17#include <memory>
18
19#include "absl/strings/str_format.h"
20#include "absl/strings/str_split.h"
22#include "ortools/base/timer.h"
24
25extern "C" {
26#include "ilcplex/cplexx.h"
27// This is an undocumented function, setting the objective offset
28// is not supported everywhere (for example it may not be exported if a
29// model is written to a file), but it works in the cases we need here.
30CPXLIBAPI int CPXPUBLIC CPXEsetobjoffset(CPXCENVptr, CPXLPptr, double);
31}
32
33// In case we need to return a double but don't have a value for that
34// we just return a NaN.
35#if !defined(CPX_NAN)
36#define CPX_NAN std::numeric_limits<double>::quiet_NaN()
37#endif
38
39// The argument to this macro is the invocation of a CPXX function that
40// returns a status. If the function returns non-zero the macro aborts
41// the program with an appropriate error message.
42#define CHECK_STATUS(s) \
43 do { \
44 int const status_ = s; \
45 CHECK_EQ(0, status_); \
46 } while (0)
47
48namespace operations_research {
49
50using std::unique_ptr;
51
52// For a model that is extracted to an instance of this class there is a
53// 1:1 corresponence between MPVariable instances and CPLEX columns: the
54// index of an extracted variable is the column index in the CPLEX model.
55// Similiar for instances of MPConstraint: the index of the constraint in
56// the model is the row index in the CPLEX model.
58 public:
59 // NOTE: 'mip' specifies the type of the problem (either continuous or
60 // mixed integer. This type is fixed for the lifetime of the
61 // instance. There are no dynamic changes to the model type.
62 explicit CplexInterface(MPSolver* const solver, bool mip);
64
65 // Sets the optimization direction (min/max).
66 virtual void SetOptimizationDirection(bool maximize);
67
68 // ----- Solve -----
69 // Solve the problem using the parameter values specified.
71
72 // ----- Model modifications and extraction -----
73 // Resets extracted model
74 virtual void Reset();
75
76 virtual void SetVariableBounds(int var_index, double lb, double ub);
77 virtual void SetVariableInteger(int var_index, bool integer);
78 virtual void SetConstraintBounds(int row_index, double lb, double ub);
79
80 virtual void AddRowConstraint(MPConstraint* const ct);
81 virtual void AddVariable(MPVariable* const var);
82 virtual void SetCoefficient(MPConstraint* const constraint,
83 MPVariable const* const variable,
84 double new_value, double old_value);
85
86 // Clear a constraint from all its terms.
87 virtual void ClearConstraint(MPConstraint* const constraint);
88 // Change a coefficient in the linear objective
89 virtual void SetObjectiveCoefficient(MPVariable const* const variable,
90 double coefficient);
91 // Change the constant term in the linear objective.
92 virtual void SetObjectiveOffset(double value);
93 // Clear the objective from all its terms.
94 virtual void ClearObjective();
95
96 // ------ Query statistics on the solution and the solve ------
97 // Number of simplex iterations
98 virtual int64_t iterations() const;
99 // Number of branch-and-bound nodes. Only available for discrete problems.
100 virtual int64_t nodes() const;
101
102 // Returns the basis status of a row.
103 virtual MPSolver::BasisStatus row_status(int constraint_index) const;
104 // Returns the basis status of a column.
105 virtual MPSolver::BasisStatus column_status(int variable_index) const;
106
107 // ----- Misc -----
108
109 // Query problem type.
110 // Remember that problem type is a static property that is set
111 // in the constructor and never changed.
112 virtual bool IsContinuous() const { return IsLP(); }
113 virtual bool IsLP() const { return !mMip; }
114 virtual bool IsMIP() const { return mMip; }
115
117 const std::string& parameters) override;
118 virtual void ExtractNewVariables();
119 virtual void ExtractNewConstraints();
120 virtual void ExtractObjective();
121
122 virtual std::string SolverVersion() const;
123
124 virtual void* underlying_solver() { return reinterpret_cast<void*>(mLp); }
125
126 virtual double ComputeExactConditionNumber() const {
127 if (!IsContinuous()) {
128 LOG(DFATAL) << "ComputeExactConditionNumber not implemented for"
129 << " CPLEX_MIXED_INTEGER_PROGRAMMING";
130 return CPX_NAN;
131 }
132
134 double kappa = CPX_NAN;
135 CHECK_STATUS(CPXXgetdblquality(mEnv, mLp, &kappa, CPX_EXACT_KAPPA));
136 return kappa;
137 }
138 LOG(DFATAL) << "Cannot get exact condition number without solution";
139 return CPX_NAN;
140 }
141
142 protected:
143 // Set all parameters in the underlying solver.
144 virtual void SetParameters(MPSolverParameters const& param);
145 // Set each parameter in the underlying solver.
146 virtual void SetRelativeMipGap(double value);
147 virtual void SetPrimalTolerance(double value);
148 virtual void SetDualTolerance(double value);
149 virtual void SetPresolveMode(int value);
150 virtual void SetScalingMode(int value);
151 virtual void SetLpAlgorithm(int value);
152
153 virtual bool ReadParameterFile(std::string const& filename);
154 virtual std::string ValidFileExtensionForParameterFile() const;
155
156 private:
157 // Mark modeling object "out of sync". This implicitly invalidates
158 // solution information as well. It is the counterpart of
159 // MPSolverInterface::InvalidateSolutionSynchronization
160 void InvalidateModelSynchronization() {
161 mCstat = 0;
162 mRstat = 0;
164 }
165
166 // Transform CPLEX basis status to MPSolver basis status.
167 static MPSolver::BasisStatus xformBasisStatus(int cplex_basis_status);
168
169 private:
170 CPXLPptr mLp;
171 CPXENVptr mEnv;
172 bool const mMip;
173 // Incremental extraction.
174 // Without incremental extraction we have to re-extract the model every
175 // time we perform a solve. Due to the way the Reset() function is
176 // implemented, this will lose MIP start or basis information from a
177 // previous solve. On the other hand, if there is a significant changes
178 // to the model then just re-extracting everything is usually faster than
179 // keeping the low-level modeling object in sync with the high-level
180 // variables/constraints.
181 // Note that incremental extraction is particularly expensive in function
182 // ExtractNewVariables() since there we must scan _all_ old constraints
183 // and update them with respect to the new variables.
184 bool const supportIncrementalExtraction;
185
186 // Use slow and immediate updates or try to do bulk updates.
187 // For many updates to the model we have the option to either perform
188 // the update immediately with a potentially slow operation or to
189 // just mark the low-level modeling object out of sync and re-extract
190 // the model later.
191 enum SlowUpdates {
192 SlowSetCoefficient = 0x0001,
193 SlowClearConstraint = 0x0002,
194 SlowSetObjectiveCoefficient = 0x0004,
195 SlowClearObjective = 0x0008,
196 SlowSetConstraintBounds = 0x0010,
197 SlowSetVariableInteger = 0x0020,
198 SlowSetVariableBounds = 0x0040,
199 SlowUpdatesAll = 0xffff
200 } const slowUpdates;
201 // CPLEX has no method to query the basis status of a single variable.
202 // Hence we query the status only once and cache the array. This is
203 // much faster in case the basis status of more than one row/column
204 // is required.
205 unique_ptr<int[]> mutable mCstat;
206 unique_ptr<int[]> mutable mRstat;
207
208 // Setup the right-hand side of a constraint from its lower and upper bound.
209 static void MakeRhs(double lb, double ub, double& rhs, char& sense,
210 double& range);
211};
212
213// Creates a LP/MIP instance.
215 : MPSolverInterface(solver),
216 mEnv(0),
217 mLp(0),
218 mMip(mip),
219 slowUpdates(static_cast<SlowUpdates>(SlowSetObjectiveCoefficient |
220 SlowClearObjective)),
221 supportIncrementalExtraction(false),
222 mCstat(),
223 mRstat() {
224 int status;
225
226 mEnv = CPXXopenCPLEX(&status);
227 CHECK_STATUS(status);
228 DCHECK(mEnv != nullptr); // should not be NULL if status=0
229
230 char const* name = solver_->name_.c_str();
231 mLp = CPXXcreateprob(mEnv, &status, name);
232 CHECK_STATUS(status);
233 DCHECK(mLp != nullptr); // should not be NULL if status=0
234
235 CHECK_STATUS(CPXXchgobjsen(mEnv, mLp, maximize_ ? CPX_MAX : CPX_MIN));
236 if (mMip) CHECK_STATUS(CPXXchgprobtype(mEnv, mLp, CPXPROB_MILP));
237}
238
240 CHECK_STATUS(CPXXfreeprob(mEnv, &mLp));
241 CHECK_STATUS(CPXXcloseCPLEX(&mEnv));
242}
243
244std::string CplexInterface::SolverVersion() const {
245 // We prefer CPXXversionnumber() over CPXXversion() since the
246 // former will never pose any encoding issues.
247 int version = 0;
248 CHECK_STATUS(CPXXversionnumber(mEnv, &version));
249
250 int const major = version / 1000000;
251 version -= major * 1000000;
252 int const release = version / 10000;
253 version -= release * 10000;
254 int const mod = version / 100;
255 version -= mod * 100;
256 int const fix = version;
257
258 return absl::StrFormat("CPLEX library version %d.%02d.%02d.%02d", major,
259 release, mod, fix);
260}
261
262// ------ Model modifications and extraction -----
263
265 // Instead of explicitly clearing all modeling objects we
266 // just delete the problem object and allocate a new one.
267 CHECK_STATUS(CPXXfreeprob(mEnv, &mLp));
268
269 int status;
270 const char* const name = solver_->name_.c_str();
271 mLp = CPXXcreateprob(mEnv, &status, name);
272 CHECK_STATUS(status);
273 DCHECK(mLp != nullptr); // should not be NULL if status=0
274
275 CHECK_STATUS(CPXXchgobjsen(mEnv, mLp, maximize_ ? CPX_MAX : CPX_MIN));
276 if (mMip) CHECK_STATUS(CPXXchgprobtype(mEnv, mLp, CPXPROB_MILP));
277
279 mCstat = 0;
280 mRstat = 0;
281}
282
285 CPXXchgobjsen(mEnv, mLp, maximize ? CPX_MAX : CPX_MIN);
286}
287
288void CplexInterface::SetVariableBounds(int var_index, double lb, double ub) {
290
291 // Changing the bounds of a variable is fast. However, doing this for
292 // many variables may still be slow. So we don't perform the update by
293 // default. However, if we support incremental extraction
294 // (supportIncrementalExtraction is true) then we MUST perform the
295 // update here or we will lose it.
296
297 if (!supportIncrementalExtraction && !(slowUpdates & SlowSetVariableBounds)) {
298 InvalidateModelSynchronization();
299 } else {
300 if (variable_is_extracted(var_index)) {
301 // Variable has already been extracted, so we must modify the
302 // modeling object.
303 DCHECK_LT(var_index, last_variable_index_);
304 char const lu[2] = {'L', 'U'};
305 double const bd[2] = {lb, ub};
306 CPXDIM const idx[2] = {var_index, var_index};
307 CHECK_STATUS(CPXXchgbds(mEnv, mLp, 2, idx, lu, bd));
308 } else {
309 // Variable is not yet extracted. It is sufficient to just mark
310 // the modeling object "out of sync"
311 InvalidateModelSynchronization();
312 }
313 }
314}
315
316// Modifies integrality of an extracted variable.
317void CplexInterface::SetVariableInteger(int var_index, bool integer) {
319
320 // NOTE: The type of the model (continuous or mixed integer) is
321 // defined once and for all in the constructor. There are no
322 // dynamic changes to the model type.
323
324 // Changing the type of a variable should be fast. Still, doing all
325 // updates in one big chunk right before solve() is usually faster.
326 // However, if we support incremental extraction
327 // (supportIncrementalExtraction is true) then we MUST change the
328 // type of extracted variables here.
329
330 if (!supportIncrementalExtraction &&
331 !(slowUpdates && SlowSetVariableInteger)) {
332 InvalidateModelSynchronization();
333 } else {
334 if (mMip) {
335 if (variable_is_extracted(var_index)) {
336 // Variable is extracted. Change the type immediately.
337 // TODO: Should we check the current type and don't do anything
338 // in case the type does not change?
339 DCHECK_LE(var_index, CPXXgetnumcols(mEnv, mLp));
340 char const type = integer ? CPX_INTEGER : CPX_CONTINUOUS;
341 CHECK_STATUS(CPXXchgctype(mEnv, mLp, 1, &var_index, &type));
342 } else
343 InvalidateModelSynchronization();
344 } else {
345 LOG(DFATAL)
346 << "Attempt to change variable to integer in non-MIP problem!";
347 }
348 }
349}
350
351// Setup the right-hand side of a constraint.
352void CplexInterface::MakeRhs(double lb, double ub, double& rhs, char& sense,
353 double& range) {
354 if (lb == ub) {
355 // Both bounds are equal -> this is an equality constraint
356 rhs = lb;
357 range = 0.0;
358 sense = 'E';
359 } else if (lb > -CPX_INFBOUND && ub < CPX_INFBOUND) {
360 // Both bounds are finite -> this is a ranged constraint
361 // The value of a ranged constraint is allowed to be in
362 // [ rhs[i], rhs[i]+rngval[i] ]
363 // see also the reference documentation for CPXXnewrows()
364 if (ub < lb) {
365 // The bounds for the constraint are contradictory. CPLEX models
366 // a range constraint l <= ax <= u as
367 // ax = l + v
368 // where v is an auxiliary variable the range of which is controlled
369 // by l and u: if l < u then v in [0, u-l]
370 // else v in [u-l, 0]
371 // (the range is specified as the rngval[] argument to CPXXnewrows).
372 // Thus CPLEX cannot represent range constraints with contradictory
373 // bounds and we must error out here.
374 CHECK_STATUS(CPXERR_BAD_ARGUMENT);
375 }
376 rhs = lb;
377 range = ub - lb;
378 sense = 'R';
379 } else if (ub < CPX_INFBOUND ||
380 (std::abs(ub) == CPX_INFBOUND && std::abs(lb) > CPX_INFBOUND)) {
381 // Finite upper, infinite lower bound -> this is a <= constraint
382 rhs = ub;
383 range = 0.0;
384 sense = 'L';
385 } else if (lb > -CPX_INFBOUND ||
386 (std::abs(lb) == CPX_INFBOUND && std::abs(ub) > CPX_INFBOUND)) {
387 // Finite lower, infinite upper bound -> this is a >= constraint
388 rhs = lb;
389 range = 0.0;
390 sense = 'G';
391 } else {
392 // Lower and upper bound are both infinite.
393 // This is used for example in .mps files to specify alternate
394 // objective functions.
395 // Note that the case lb==ub was already handled above, so we just
396 // pick the bound with larger magnitude and create a constraint for it.
397 // Note that we replace the infinite bound by CPX_INFBOUND since
398 // bounds with larger magnitude may cause other CPLEX functions to
399 // fail (for example the export to LP files).
400 DCHECK_GT(std::abs(lb), CPX_INFBOUND);
401 DCHECK_GT(std::abs(ub), CPX_INFBOUND);
402 if (std::abs(lb) > std::abs(ub)) {
403 rhs = (lb < 0) ? -CPX_INFBOUND : CPX_INFBOUND;
404 sense = 'G';
405 } else {
406 rhs = (ub < 0) ? -CPX_INFBOUND : CPX_INFBOUND;
407 sense = 'L';
408 }
409 range = 0.0;
410 }
411}
412
413void CplexInterface::SetConstraintBounds(int index, double lb, double ub) {
415
416 // Changing rhs, sense, or range of a constraint is not too slow.
417 // Still, doing all the updates in one large operation is faster.
418 // Note however that if we do not want to re-extract the full model
419 // for each solve (supportIncrementalExtraction is true) then we MUST
420 // update the constraint here, otherwise we lose this update information.
421
422 if (!supportIncrementalExtraction &&
423 !(slowUpdates & SlowSetConstraintBounds)) {
424 InvalidateModelSynchronization();
425 } else {
426 if (constraint_is_extracted(index)) {
427 // Constraint is already extracted, so we must update its bounds
428 // and its type.
429 DCHECK(mLp != NULL);
430 char sense;
431 double range, rhs;
432 MakeRhs(lb, ub, rhs, sense, range);
433 CHECK_STATUS(CPXXchgrhs(mEnv, mLp, 1, &index, &lb));
434 CHECK_STATUS(CPXXchgsense(mEnv, mLp, 1, &index, &sense));
435 CHECK_STATUS(CPXXchgrngval(mEnv, mLp, 1, &index, &range));
436 } else {
437 // Constraint is not yet extracted. It is sufficient to mark the
438 // modeling object as "out of sync"
439 InvalidateModelSynchronization();
440 }
441 }
442}
443
445 // This is currently only invoked when a new constraint is created,
446 // see MPSolver::MakeRowConstraint().
447 // At this point we only have the lower and upper bounds of the
448 // constraint. We could immediately call CPXXaddrows() here but it is
449 // usually much faster to handle the fully populated constraint in
450 // ExtractNewConstraints() right before the solve.
451 InvalidateModelSynchronization();
452}
453
455 // This is currently only invoked when a new variable is created,
456 // see MPSolver::MakeVar().
457 // At this point the variable does not appear in any constraints or
458 // the objective function. We could invoke CPXXaddcols() to immediately
459 // create the variable here but it is usually much faster to handle the
460 // fully setup variable in ExtractNewVariables() right before the solve.
461 InvalidateModelSynchronization();
462}
463
465 MPVariable const* const variable,
466 double new_value, double) {
468
469 // Changing a single coefficient in the matrix is potentially pretty
470 // slow since that coefficient has to be found in the sparse matrix
471 // representation. So by default we don't perform this update immediately
472 // but instead mark the low-level modeling object "out of sync".
473 // If we want to support incremental extraction then we MUST perform
474 // the modification immediately or we will lose it.
475
476 if (!supportIncrementalExtraction && !(slowUpdates & SlowSetCoefficient)) {
477 InvalidateModelSynchronization();
478 } else {
479 int const row = constraint->index();
480 int const col = variable->index();
482 // If row and column are both extracted then we can directly
483 // update the modeling object
484 DCHECK_LE(row, last_constraint_index_);
485 DCHECK_LE(col, last_variable_index_);
486 CHECK_STATUS(CPXXchgcoef(mEnv, mLp, row, col, new_value));
487 } else {
488 // If either row or column is not yet extracted then we can
489 // defer the update to ExtractModel()
490 InvalidateModelSynchronization();
491 }
492 }
493}
494
496 CPXDIM const row = constraint->index();
497 if (!constraint_is_extracted(row))
498 // There is nothing to do if the constraint was not even extracted.
499 return;
500
501 // Clearing a constraint means setting all coefficients in the corresponding
502 // row to 0 (we cannot just delete the row since that would renumber all
503 // the constraints/rows after it).
504 // Modifying coefficients in the matrix is potentially pretty expensive
505 // since they must be found in the sparse matrix representation. That is
506 // why by default we do not modify the coefficients here but only mark
507 // the low-level modeling object "out of sync".
508
509 if (!(slowUpdates & SlowClearConstraint)) {
510 InvalidateModelSynchronization();
511 } else {
513
514 CPXDIM const len = constraint->coefficients_.size();
515 unique_ptr<CPXDIM[]> rowind(new CPXDIM[len]);
516 unique_ptr<CPXDIM[]> colind(new CPXDIM[len]);
517 unique_ptr<double[]> val(new double[len]);
518 CPXDIM j = 0;
519 const auto& coeffs = constraint->coefficients_;
520 for (auto it(coeffs.begin()); it != coeffs.end(); ++it) {
521 CPXDIM const col = it->first->index();
522 if (variable_is_extracted(col)) {
523 rowind[j] = row;
524 colind[j] = col;
525 val[j] = 0.0;
526 ++j;
527 }
528 }
529 if (j)
531 CPXXchgcoeflist(mEnv, mLp, j, rowind.get(), colind.get(), val.get()));
532 }
533}
534
536 double coefficient) {
537 CPXDIM const col = variable->index();
538 if (!variable_is_extracted(col))
539 // Nothing to do if variable was not even extracted
540 return;
541
543
544 // The objective function is stored as a dense vector, so updating a
545 // single coefficient is O(1). So by default we update the low-level
546 // modeling object here.
547 // If we support incremental extraction then we have no choice but to
548 // perform the update immediately.
549
550 if (supportIncrementalExtraction ||
551 (slowUpdates & SlowSetObjectiveCoefficient)) {
552 CHECK_STATUS(CPXXchgobj(mEnv, mLp, 1, &col, &coefficient));
553 } else
554 InvalidateModelSynchronization();
555}
556
558 // Changing the objective offset is O(1), so we always do it immediately.
560 CHECK_STATUS(CPXEsetobjoffset(mEnv, mLp, value));
561}
562
565
566 // Since the objective function is stored as a dense vector updating
567 // it is O(n), so we usually perform the update immediately.
568 // If we want to support incremental extraction then we have no choice
569 // but to perform the update immediately.
570
571 if (supportIncrementalExtraction || (slowUpdates & SlowClearObjective)) {
572 CPXDIM const cols = CPXXgetnumcols(mEnv, mLp);
573 unique_ptr<CPXDIM[]> ind(new CPXDIM[cols]);
574 unique_ptr<double[]> zero(new double[cols]);
575 CPXDIM j = 0;
576 const auto& coeffs = solver_->objective_->coefficients_;
577 for (auto it(coeffs.begin()); it != coeffs.end(); ++it) {
578 CPXDIM const idx = it->first->index();
579 // We only need to reset variables that have been extracted.
580 if (variable_is_extracted(idx)) {
581 DCHECK_LT(idx, cols);
582 ind[j] = idx;
583 zero[j] = 0.0;
584 ++j;
585 }
586 }
587 if (j > 0) CHECK_STATUS(CPXXchgobj(mEnv, mLp, j, ind.get(), zero.get()));
588 CHECK_STATUS(CPXEsetobjoffset(mEnv, mLp, 0.0));
589 } else
590 InvalidateModelSynchronization();
591}
592
593// ------ Query statistics on the solution and the solve ------
594
596 int iter;
598 if (mMip)
599 return static_cast<int64_t>(CPXXgetmipitcnt(mEnv, mLp));
600 else
601 return static_cast<int64_t>(CPXXgetitcnt(mEnv, mLp));
602}
603
604int64_t CplexInterface::nodes() const {
605 if (mMip) {
607 return static_cast<int64_t>(CPXXgetnodecnt(mEnv, mLp));
608 } else {
609 LOG(DFATAL) << "Number of nodes only available for discrete problems";
611 }
612}
613
614// Transform a CPLEX basis status to an MPSolver basis status.
615MPSolver::BasisStatus CplexInterface::xformBasisStatus(int cplex_basis_status) {
616 switch (cplex_basis_status) {
617 case CPX_AT_LOWER:
619 case CPX_BASIC:
620 return MPSolver::BASIC;
621 case CPX_AT_UPPER:
623 case CPX_FREE_SUPER:
624 return MPSolver::FREE;
625 default:
626 LOG(DFATAL) << "Unknown CPLEX basis status";
627 return MPSolver::FREE;
628 }
629}
630
631// Returns the basis status of a row.
633 if (mMip) {
634 LOG(FATAL) << "Basis status only available for continuous problems";
635 return MPSolver::FREE;
636 }
637
639 if (!mRstat) {
640 CPXDIM const rows = CPXXgetnumrows(mEnv, mLp);
641 unique_ptr<int[]> data(new int[rows]);
642 mRstat.swap(data);
643 CHECK_STATUS(CPXXgetbase(mEnv, mLp, 0, mRstat.get()));
644 }
645 } else
646 mRstat = 0;
647
648 if (mRstat)
649 return xformBasisStatus(mRstat[constraint_index]);
650 else {
651 LOG(FATAL) << "Row basis status not available";
652 return MPSolver::FREE;
653 }
654}
655
656// Returns the basis status of a column.
658 if (mMip) {
659 LOG(FATAL) << "Basis status only available for continuous problems";
660 return MPSolver::FREE;
661 }
662
664 if (!mCstat) {
665 CPXDIM const cols = CPXXgetnumcols(mEnv, mLp);
666 unique_ptr<int[]> data(new int[cols]);
667 mCstat.swap(data);
668 CHECK_STATUS(CPXXgetbase(mEnv, mLp, mCstat.get(), 0));
669 }
670 } else
671 mCstat = 0;
672
673 if (mCstat)
674 return xformBasisStatus(mCstat[variable_index]);
675 else {
676 LOG(FATAL) << "Column basis status not available";
677 return MPSolver::FREE;
678 }
679}
680
681// Extract all variables that have not yet been extracted.
683 // NOTE: The code assumes that a linear expression can never contain
684 // non-zero duplicates.
685
687
688 if (!supportIncrementalExtraction) {
689 // Without incremental extraction ExtractModel() is always called
690 // to extract the full model.
691 CHECK(last_variable_index_ == 0 ||
692 last_variable_index_ == solver_->variables_.size());
693 CHECK(last_constraint_index_ == 0 ||
694 last_constraint_index_ == solver_->constraints_.size());
695 }
696
697 int const last_extracted = last_variable_index_;
698 int const var_count = solver_->variables_.size();
699 CPXDIM newcols = var_count - last_extracted;
700 if (newcols > 0) {
701 // There are non-extracted variables. Extract them now.
702
703 unique_ptr<double[]> obj(new double[newcols]);
704 unique_ptr<double[]> lb(new double[newcols]);
705 unique_ptr<double[]> ub(new double[newcols]);
706 unique_ptr<char[]> ctype(new char[newcols]);
707 unique_ptr<const char*[]> colname(new const char*[newcols]);
708
709 bool have_names = false;
710 for (int j = 0, varidx = last_extracted; j < newcols; ++j, ++varidx) {
711 MPVariable const* const var = solver_->variables_[varidx];
712 lb[j] = var->lb();
713 ub[j] = var->ub();
714 ctype[j] = var->integer() ? CPX_INTEGER : CPX_CONTINUOUS;
715 colname[j] = var->name().empty() ? 0 : var->name().c_str();
716 have_names = have_names || var->name().empty();
717 obj[j] = solver_->objective_->GetCoefficient(var);
718 }
719
720 // Arrays for modifying the problem are setup. Update the index
721 // of variables that will get extracted now. Updating indices
722 // _before_ the actual extraction makes things much simpler in
723 // case we support incremental extraction.
724 // In case of error we just reset the indeces.
725 std::vector<MPVariable*> const& variables = solver_->variables();
726 for (int j = last_extracted; j < var_count; ++j) {
727 CHECK(!variable_is_extracted(variables[j]->index()));
728 set_variable_as_extracted(variables[j]->index(), true);
729 }
730
731 try {
732 bool use_newcols = true;
733
734 if (supportIncrementalExtraction) {
735 // If we support incremental extraction then we must
736 // update existing constraints with the new variables.
737 // To do that we use CPXXaddcols() to actually create the
738 // variables. This is supposed to be faster than combining
739 // CPXXnewcols() and CPXXchgcoeflist().
740
741 // For each column count the size of the intersection with
742 // existing constraints.
743 unique_ptr<CPXDIM[]> collen(new CPXDIM[newcols]);
744 for (CPXDIM j = 0; j < newcols; ++j) collen[j] = 0;
745 CPXNNZ nonzeros = 0;
746 // TODO: Use a bitarray to flag the constraints that actually
747 // intersect new variables?
748 for (int i = 0; i < last_constraint_index_; ++i) {
749 MPConstraint const* const ct = solver_->constraints_[i];
750 CHECK(constraint_is_extracted(ct->index()));
751 const auto& coeffs = ct->coefficients_;
752 for (auto it(coeffs.begin()); it != coeffs.end(); ++it) {
753 int const idx = it->first->index();
755 collen[idx - last_variable_index_]++;
756 ++nonzeros;
757 }
758 }
759 }
760
761 if (nonzeros > 0) {
762 // At least one of the new variables did intersect with an
763 // old constraint. We have to create the new columns via
764 // CPXXaddcols().
765 use_newcols = false;
766 unique_ptr<CPXNNZ[]> begin(new CPXNNZ[newcols + 2]);
767 unique_ptr<CPXDIM[]> cmatind(new CPXDIM[nonzeros]);
768 unique_ptr<double[]> cmatval(new double[nonzeros]);
769
770 // Here is how cmatbeg[] is setup:
771 // - it is initialized as
772 // [ 0, 0, collen[0], collen[0]+collen[1], ... ]
773 // so that cmatbeg[j+1] tells us where in cmatind[] and
774 // cmatval[] we need to put the next nonzero for column
775 // j
776 // - after nonzeros have been setup the array looks like
777 // [ 0, collen[0], collen[0]+collen[1], ... ]
778 // so that it is the correct input argument for CPXXaddcols
779 CPXNNZ* cmatbeg = begin.get();
780 cmatbeg[0] = 0;
781 cmatbeg[1] = 0;
782 ++cmatbeg;
783 for (CPXDIM j = 0; j < newcols; ++j)
784 cmatbeg[j + 1] = cmatbeg[j] + collen[j];
785
786 for (int i = 0; i < last_constraint_index_; ++i) {
787 MPConstraint const* const ct = solver_->constraints_[i];
788 CPXDIM const row = ct->index();
789 const auto& coeffs = ct->coefficients_;
790 for (auto it(coeffs.begin()); it != coeffs.end(); ++it) {
791 int const idx = it->first->index();
793 CPXNNZ const nz = cmatbeg[idx]++;
794 cmatind[nz] = idx;
795 cmatval[nz] = it->second;
796 }
797 }
798 }
799 --cmatbeg;
800 CHECK_STATUS(CPXXaddcols(mEnv, mLp, newcols, nonzeros, obj.get(),
801 cmatbeg, cmatind.get(), cmatval.get(),
802 lb.get(), ub.get(),
803 have_names ? colname.get() : 0));
804 }
805 }
806 if (use_newcols) {
807 // Either incremental extraction is not supported or none of
808 // the new variables did intersect an existing constraint.
809 // We can just use CPXXnewcols() to create the new variables.
810 CHECK_STATUS(CPXXnewcols(mEnv, mLp, newcols, obj.get(), lb.get(),
811 ub.get(), mMip ? ctype.get() : 0,
812 have_names ? colname.get() : 0));
813 } else {
814 // Incremental extraction: we must update the ctype of the
815 // newly created variables (CPXXaddcols() does not allow
816 // specifying the ctype)
817 if (mMip) {
818 // Query the actual number of columns in case we did not
819 // manage to extract all columns.
820 int const cols = CPXXgetnumcols(mEnv, mLp);
821 unique_ptr<CPXDIM[]> ind(new CPXDIM[newcols]);
822 for (int j = last_extracted; j < cols; ++j)
823 ind[j - last_extracted] = j;
824 CHECK_STATUS(CPXXchgctype(mEnv, mLp, cols - last_extracted, ind.get(),
825 ctype.get()));
826 }
827 }
828 } catch (...) {
829 // Undo all changes in case of error.
830 CPXDIM const cols = CPXXgetnumcols(mEnv, mLp);
831 if (cols > last_extracted)
832 (void)CPXXdelcols(mEnv, mLp, last_extracted, cols - 1);
833 std::vector<MPVariable*> const& variables = solver_->variables();
834 int const size = variables.size();
835 for (int j = last_extracted; j < size; ++j)
837 throw;
838 }
839 }
840}
841
842// Extract constraints that have not yet been extracted.
844 // NOTE: The code assumes that a linear expression can never contain
845 // non-zero duplicates.
846
847 if (!supportIncrementalExtraction) {
848 // Without incremental extraction ExtractModel() is always called
849 // to extract the full model.
850 CHECK(last_variable_index_ == 0 ||
851 last_variable_index_ == solver_->variables_.size());
852 CHECK(last_constraint_index_ == 0 ||
853 last_constraint_index_ == solver_->constraints_.size());
854 }
855
856 CPXDIM const offset = last_constraint_index_;
857 CPXDIM const total = solver_->constraints_.size();
858
859 if (total > offset) {
860 // There are constraints that are not yet extracted.
861
863
864 CPXDIM newCons = total - offset;
865 CPXDIM const cols = CPXXgetnumcols(mEnv, mLp);
866 CHECK(last_variable_index_ == 0 || last_variable_index_ == cols);
867 CPXDIM const chunk = 10; // max number of rows to add in one shot
868
869 // Update indices of new constraints _before_ actually extracting
870 // them. In case of error we will just reset the indices.
871 for (CPXDIM c = offset; c < total; ++c)
873
874 try {
875 unique_ptr<CPXDIM[]> rmatind(new CPXDIM[cols]);
876 unique_ptr<double[]> rmatval(new double[cols]);
877 unique_ptr<CPXNNZ[]> rmatbeg(new CPXNNZ[chunk]);
878 unique_ptr<char[]> sense(new char[chunk]);
879 unique_ptr<double[]> rhs(new double[chunk]);
880 unique_ptr<char const*[]> name(new char const*[chunk]);
881 unique_ptr<double[]> rngval(new double[chunk]);
882 unique_ptr<CPXDIM[]> rngind(new CPXDIM[chunk]);
883 bool haveRanges = false;
884
885 // Loop over the new constraints, collecting rows for up to
886 // CHUNK constraints into the arrays so that adding constraints
887 // is faster.
888 for (CPXDIM c = 0; c < newCons; /* nothing */) {
889 // Collect up to CHUNK constraints into the arrays.
890 CPXDIM nextRow = 0;
891 CPXNNZ nextNz = 0;
892 for (/* nothing */; c < newCons && nextRow < chunk; ++c, ++nextRow) {
893 MPConstraint const* const ct = solver_->constraints_[offset + c];
894
895 // Stop if there is not enough room in the arrays
896 // to add the current constraint.
897 if (nextNz + ct->coefficients_.size() > cols) {
898 DCHECK_GT(nextRow, 0);
899 break;
900 }
901
902 // Setup right-hand side of constraint.
903 MakeRhs(ct->lb(), ct->ub(), rhs[nextRow], sense[nextRow],
904 rngval[nextRow]);
905 haveRanges = haveRanges || (rngval[nextRow] != 0.0);
906 rngind[nextRow] = offset + c;
907
908 // Setup left-hand side of constraint.
909 rmatbeg[nextRow] = nextNz;
910 const auto& coeffs = ct->coefficients_;
911 for (auto it(coeffs.begin()); it != coeffs.end(); ++it) {
912 CPXDIM const idx = it->first->index();
913 if (variable_is_extracted(idx)) {
914 DCHECK_LT(nextNz, cols);
915 DCHECK_LT(idx, cols);
916 rmatind[nextNz] = idx;
917 rmatval[nextNz] = it->second;
918 ++nextNz;
919 }
920 }
921
922 // Finally the name of the constraint.
923 name[nextRow] = ct->name().empty() ? 0 : ct->name().c_str();
924 }
925 if (nextRow > 0) {
926 CHECK_STATUS(CPXXaddrows(mEnv, mLp, 0, nextRow, nextNz, rhs.get(),
927 sense.get(), rmatbeg.get(), rmatind.get(),
928 rmatval.get(), 0, name.get()));
929 if (haveRanges) {
931 CPXXchgrngval(mEnv, mLp, nextRow, rngind.get(), rngval.get()));
932 }
933 }
934 }
935 } catch (...) {
936 // Undo all changes in case of error.
937 CPXDIM const rows = CPXXgetnumrows(mEnv, mLp);
938 if (rows > offset) (void)CPXXdelrows(mEnv, mLp, offset, rows - 1);
939 std::vector<MPConstraint*> const& constraints = solver_->constraints();
940 int const size = constraints.size();
941 for (int i = offset; i < size; ++i) set_constraint_as_extracted(i, false);
942 throw;
943 }
944 }
945}
946
947// Extract the objective function.
949 // NOTE: The code assumes that the objective expression does not contain
950 // any non-zero duplicates.
951
952 CPXDIM const cols = CPXXgetnumcols(mEnv, mLp);
953 CHECK(last_variable_index_ == 0 || last_variable_index_ == cols);
954
955 unique_ptr<CPXDIM[]> ind(new CPXDIM[cols]);
956 unique_ptr<double[]> val(new double[cols]);
957 for (CPXDIM j = 0; j < cols; ++j) {
958 ind[j] = j;
959 val[j] = 0.0;
960 }
961
962 const auto& coeffs = solver_->objective_->coefficients_;
963 for (auto it = coeffs.begin(); it != coeffs.end(); ++it) {
964 CPXDIM const idx = it->first->index();
965 if (variable_is_extracted(idx)) {
966 DCHECK_LT(idx, cols);
967 val[idx] = it->second;
968 }
969 }
970
971 CHECK_STATUS(CPXXchgobj(mEnv, mLp, cols, ind.get(), val.get()));
972 CHECK_STATUS(CPXEsetobjoffset(mEnv, mLp, solver_->Objective().offset()));
973}
974
975// ------ Parameters -----
976
978 SetCommonParameters(param);
979 if (mMip) SetMIPParameters(param);
980}
981
983 if (mMip) {
984 CHECK_STATUS(CPXXsetdblparam(mEnv, CPX_PARAM_EPGAP, value));
985 } else {
986 LOG(WARNING) << "The relative MIP gap is only available "
987 << "for discrete problems.";
988 }
989}
990
992 CHECK_STATUS(CPXXsetdblparam(mEnv, CPX_PARAM_EPRHS, value));
993}
994
996 CHECK_STATUS(CPXXsetdblparam(mEnv, CPX_PARAM_EPOPT, value));
997}
998
1000 MPSolverParameters::PresolveValues const presolve =
1001 static_cast<MPSolverParameters::PresolveValues>(value);
1002
1003 switch (presolve) {
1005 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_PREIND, CPX_OFF));
1006 return;
1008 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_PREIND, CPX_ON));
1009 return;
1010 }
1012}
1013
1014// Sets the scaling mode.
1016 MPSolverParameters::ScalingValues const scaling =
1017 static_cast<MPSolverParameters::ScalingValues>(value);
1018
1019 switch (scaling) {
1021 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_SCAIND, -1));
1022 break;
1024 // TODO: 0 is equilibrium scaling (the default), CPLEX also supports
1025 // 1 aggressive scaling
1026 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_SCAIND, 0));
1027 break;
1028 }
1029}
1030
1031// Sets the LP algorithm : primal, dual or barrier. Note that CPLEX offers other
1032// LP algorithm (e.g. network) and automatic selection
1035 static_cast<MPSolverParameters::LpAlgorithmValues>(value);
1036
1037 int alg = CPX_ALG_NONE;
1038
1039 switch (algorithm) {
1041 alg = CPX_ALG_DUAL;
1042 break;
1044 alg = CPX_ALG_PRIMAL;
1045 break;
1047 alg = CPX_ALG_BARRIER;
1048 break;
1049 }
1050
1051 if (alg == CPX_ALG_NONE)
1053 else {
1054 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_LPMETHOD, alg));
1055 if (mMip) {
1056 // For MIP we have to change two more parameters to specify the
1057 // algorithm that is used to solve LP relaxations.
1058 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_STARTALG, alg));
1059 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_SUBALG, alg));
1060 }
1061 }
1062}
1063
1064bool CplexInterface::ReadParameterFile(std::string const& filename) {
1065 // Return true on success and false on error.
1066 return CPXXreadcopyparam(mEnv, filename.c_str()) == 0;
1067}
1068
1070 return ".prm";
1071}
1072
1074 int status;
1075
1076 // Delete chached information
1077 mCstat = 0;
1078 mRstat = 0;
1079
1080 WallTimer timer;
1081 timer.Start();
1082
1083 // Set incrementality
1087 switch (inc) {
1089 Reset(); /* This should not be required but re-extracting everything
1090 * may be faster, so we do it. */
1091 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_ADVIND, 0));
1092 break;
1094 CHECK_STATUS(CPXXsetintparam(mEnv, CPX_PARAM_ADVIND, 2));
1095 break;
1096 }
1097
1098 // Extract the model to be solved.
1099 // If we don't support incremental extraction and the low-level modeling
1100 // is out of sync then we have to re-extract everything. Note that this
1101 // will lose MIP starts or advanced basis information from a previous
1102 // solve.
1103 if (!supportIncrementalExtraction && sync_status_ == MUST_RELOAD) Reset();
1104 ExtractModel();
1105 VLOG(1) << absl::StrFormat("Model build in %.3f seconds.", timer.Get());
1106
1107 // Set log level.
1109 CPXXsetintparam(mEnv, CPX_PARAM_SCRIND, quiet() ? CPX_OFF : CPX_ON));
1110
1111 if (!solver_->solution_hint_.empty()) {
1112 int const sol_count = solver_->solution_hint_.size();
1113 long long int beg[1] = {0};
1114 int* varindices = new int[sol_count];
1115 double* values = new double[sol_count];
1116
1117 for (int i = 0; i < sol_count; ++i) {
1118 varindices[i] = solver_->solution_hint_[i].first->index();
1119 values[i] = solver_->solution_hint_[i].second;
1120 }
1121 CPXXaddmipstarts(mEnv, mLp, 1, sol_count, beg, varindices, values, NULL,
1122 NULL);
1123 }
1124 // Set parameters.
1125 // NOTE: We must invoke SetSolverSpecificParametersAsString() _first_.
1126 // Its current implementation invokes ReadParameterFile() which in
1127 // turn invokes CPXXreadcopyparam(). The latter will _overwrite_
1128 // all current parameter settings in the environment.
1129 solver_->SetSolverSpecificParametersAsString(
1130 solver_->solver_specific_parameter_string_);
1131 SetParameters(param);
1132 if (solver_->time_limit()) {
1133 VLOG(1) << "Setting time limit = " << solver_->time_limit() << " ms.";
1135 CPXXsetdblparam(mEnv, CPX_PARAM_TILIM, solver_->time_limit() * 1e-3));
1136 }
1137
1138 // Solve.
1139 // Do not CHECK_STATUS here since some errors (for example CPXERR_NO_MEMORY)
1140 // still allow us to query useful information.
1141 timer.Restart();
1142 if (mMip) {
1143 status = CPXXmipopt(mEnv, mLp);
1144 } else {
1145 status = CPXXlpopt(mEnv, mLp);
1146 }
1147
1148 // Disable screen output right after solve
1149 (void)CPXXsetintparam(mEnv, CPX_PARAM_SCRIND, CPX_OFF);
1150
1151 if (status) {
1152 VLOG(1) << absl::StrFormat("Failed to optimize MIP. Error %d", status);
1153 // NOTE: We do not return immediately since there may be information
1154 // to grab (for example an incumbent)
1155 } else {
1156 VLOG(1) << absl::StrFormat("Solved in %.3f seconds.", timer.Get());
1157 }
1158
1159 int const cpxstat = CPXXgetstat(mEnv, mLp);
1160 VLOG(1) << absl::StrFormat("CPLEX solution status %d.", cpxstat);
1161
1162 // Figure out what solution we have.
1163 int solnmethod, solntype, pfeas, dfeas;
1164 CHECK_STATUS(CPXXsolninfo(mEnv, mLp, &solnmethod, &solntype, &pfeas, &dfeas));
1165 bool const feasible = pfeas != 0;
1166
1167 // Get problem dimensions for solution queries below.
1168 CPXDIM const rows = CPXXgetnumrows(mEnv, mLp);
1169 CPXDIM const cols = CPXXgetnumcols(mEnv, mLp);
1170 DCHECK_EQ(rows, solver_->constraints_.size());
1171 DCHECK_EQ(cols, solver_->variables_.size());
1172
1173 // Capture objective function value.
1176 if (feasible) {
1177 CHECK_STATUS(CPXXgetobjval(mEnv, mLp, &objective_value_));
1178 if (mMip) {
1179 CHECK_STATUS(CPXXgetbestobjval(mEnv, mLp, &best_objective_bound_));
1180 }
1181 }
1182 VLOG(1) << "objective=" << objective_value_
1183 << ", bound=" << best_objective_bound_;
1184
1185 // Capture primal and dual solutions
1186 if (mMip) {
1187 // If there is a primal feasible solution then capture it.
1188 if (feasible) {
1189 if (cols > 0) {
1190 unique_ptr<double[]> x(new double[cols]);
1191 CHECK_STATUS(CPXXgetx(mEnv, mLp, x.get(), 0, cols - 1));
1192 for (int i = 0; i < solver_->variables_.size(); ++i) {
1193 MPVariable* const var = solver_->variables_[i];
1194 var->set_solution_value(x[i]);
1195 VLOG(3) << var->name() << ": value =" << x[i];
1196 }
1197 }
1198 } else {
1199 for (int i = 0; i < solver_->variables_.size(); ++i)
1200 solver_->variables_[i]->set_solution_value(CPX_NAN);
1201 }
1202
1203 // MIP does not have duals
1204 for (int i = 0; i < solver_->variables_.size(); ++i)
1205 solver_->variables_[i]->set_reduced_cost(CPX_NAN);
1206 for (int i = 0; i < solver_->constraints_.size(); ++i)
1207 solver_->constraints_[i]->set_dual_value(CPX_NAN);
1208 } else {
1209 // Continuous problem.
1210 if (cols > 0) {
1211 unique_ptr<double[]> x(new double[cols]);
1212 unique_ptr<double[]> dj(new double[cols]);
1213 if (feasible) CHECK_STATUS(CPXXgetx(mEnv, mLp, x.get(), 0, cols - 1));
1214 if (dfeas) CHECK_STATUS(CPXXgetdj(mEnv, mLp, dj.get(), 0, cols - 1));
1215 for (int i = 0; i < solver_->variables_.size(); ++i) {
1216 MPVariable* const var = solver_->variables_[i];
1217 var->set_solution_value(x[i]);
1218 bool value = false, dual = false;
1219
1220 if (feasible) {
1221 var->set_solution_value(x[i]);
1222 value = true;
1223 } else
1225 if (dfeas) {
1226 var->set_reduced_cost(dj[i]);
1227 dual = true;
1228 } else
1230 VLOG(3) << var->name() << ":"
1231 << (value ? absl::StrFormat(" value = %f", x[i]) : "")
1232 << (dual ? absl::StrFormat(" reduced cost = %f", dj[i]) : "");
1233 }
1234 }
1235
1236 if (rows > 0) {
1237 unique_ptr<double[]> pi(new double[rows]);
1238 if (dfeas) CHECK_STATUS(CPXXgetpi(mEnv, mLp, pi.get(), 0, rows - 1));
1239 for (int i = 0; i < solver_->constraints_.size(); ++i) {
1240 MPConstraint* const ct = solver_->constraints_[i];
1241 bool dual = false;
1242 if (dfeas) {
1243 ct->set_dual_value(pi[i]);
1244 dual = true;
1245 } else
1247 VLOG(4) << "row " << ct->index() << ":"
1248 << (dual ? absl::StrFormat(" dual = %f", pi[i]) : "");
1249 }
1250 }
1251 }
1252
1253 // Map CPLEX status to more generic solution status in MPSolver
1254 switch (cpxstat) {
1255 case CPX_STAT_OPTIMAL:
1256 case CPXMIP_OPTIMAL:
1258 break;
1259 case CPXMIP_OPTIMAL_TOL:
1260 // To be consistent with the other solvers.
1262 break;
1263 case CPX_STAT_INFEASIBLE:
1264 case CPXMIP_INFEASIBLE:
1266 break;
1267 case CPX_STAT_UNBOUNDED:
1268 case CPXMIP_UNBOUNDED:
1270 break;
1271 case CPX_STAT_INForUNBD:
1272 case CPXMIP_INForUNBD:
1274 break;
1275 default:
1277 break;
1278 }
1279
1281 return result_status_;
1282}
1284 const std::string& parameters) {
1285 if (parameters.empty()) return true;
1286 for (const auto parameter : absl::StrSplit(parameters, absl::ByAnyChar(","),
1287 absl::SkipWhitespace())) {
1288 std::vector<std::string> key_value =
1289 absl::StrSplit(parameter, absl::ByAnyChar("="), absl::SkipWhitespace());
1290 if (key_value.size() != 2) {
1291 LOG(WARNING) << absl::StrFormat(
1292 "Cannot parse parameter '%s'. Expected format is 'parameter/name = "
1293 "value'",
1294 parameter);
1295 continue;
1296 }
1297 std::string identifier = key_value[0];
1298 absl::RemoveExtraAsciiWhitespace(&identifier);
1299
1300 std::string value = key_value[1];
1301 absl::RemoveExtraAsciiWhitespace(&value);
1302
1303 try {
1304 if (identifier.find("LogFile") != std::string::npos) {
1305 CPXXsetlogfilename(mEnv, value.c_str(), "w");
1306 } else {
1307 std::string delimiter = ".";
1308 if (value.find(delimiter) == std::string::npos) {
1309 (void)CPXXsetintparam(mEnv, std::stoi(identifier), std::stoi(value));
1310 } else {
1311 (void)CPXXsetdblparam(mEnv, std::stoi(identifier), std::stod(value));
1312 }
1313 VLOG(2) << absl::StrFormat("Set parameter %s to %s", identifier, value);
1314 }
1315 } catch (...) {
1316 LOG(WARNING) << absl::StrFormat(
1317 "Cannot parse parameter '%s'. Expected format is 'parameter/name = "
1318 "value'",
1319 identifier);
1320 }
1321 }
1322 return true;
1323}
1324
1325namespace {
1326
1327// See MpSolverInterfaceFactoryRepository for details.
1328const void* const kRegisterCplex ABSL_ATTRIBUTE_UNUSED = [] {
1330 [](MPSolver* const solver) { return new CplexInterface(solver, false); },
1332 return nullptr;
1333}();
1334
1335// See MpSolverInterfaceFactoryRepository for details.
1336const void* const kRegisterCplexMip ABSL_ATTRIBUTE_UNUSED = [] {
1338 [](MPSolver* const solver) { return new CplexInterface(solver, true); },
1340 return nullptr;
1341}();
1342
1343} // namespace
1344
1345} // namespace operations_research
double Get() const
Definition timer.h:44
void Restart()
Definition timer.h:34
void Start()
Definition timer.h:30
virtual MPSolver::BasisStatus row_status(int constraint_index) const
virtual void SetOptimizationDirection(bool maximize)
virtual MPSolver::BasisStatus column_status(int variable_index) const
bool SetSolverSpecificParametersAsString(const std::string &parameters) override
virtual double ComputeExactConditionNumber() const
virtual void SetVariableBounds(int var_index, double lb, double ub)
virtual void AddRowConstraint(MPConstraint *const ct)
virtual void ClearConstraint(MPConstraint *const constraint)
virtual void SetVariableInteger(int var_index, bool integer)
virtual MPSolver::ResultStatus Solve(MPSolverParameters const &param)
virtual void SetCoefficient(MPConstraint *const constraint, MPVariable const *const variable, double new_value, double old_value)
virtual std::string SolverVersion() const
virtual void SetPresolveMode(int value)
virtual bool ReadParameterFile(std::string const &filename)
virtual void SetParameters(MPSolverParameters const &param)
virtual void SetObjectiveOffset(double value)
virtual void AddVariable(MPVariable *const var)
virtual void SetConstraintBounds(int row_index, double lb, double ub)
virtual void SetObjectiveCoefficient(MPVariable const *const variable, double coefficient)
virtual void SetDualTolerance(double value)
CplexInterface(MPSolver *const solver, bool mip)
virtual std::string ValidFileExtensionForParameterFile() const
virtual void SetRelativeMipGap(double value)
virtual void SetPrimalTolerance(double value)
void set_dual_value(double dual_value)
double lb() const
Returns the lower bound.
double ub() const
Returns the upper bound.
const std::string & name() const
Returns the name of the constraint.
int index() const
Returns the index of the constraint in the MPSolver::constraints_.
static MPSolverInterfaceFactoryRepository * GetInstance()
void Register(MPSolverInterfaceFactory factory, MPSolver::OptimizationProblemType problem_type, std::function< bool()> is_runtime_ready={})
void set_variable_as_extracted(int var_index, bool extracted)
static constexpr int64_t kUnknownNumberOfIterations
void set_constraint_as_extracted(int ct_index, bool extracted)
virtual void SetIntegerParamToUnsupportedValue(MPSolverParameters::IntegerParam param, int value)
bool variable_is_extracted(int var_index) const
bool constraint_is_extracted(int ct_index) const
static constexpr int64_t kUnknownNumberOfNodes
void SetMIPParameters(const MPSolverParameters &param)
void SetCommonParameters(const MPSolverParameters &param)
PresolveValues
For each categorical parameter, enumeration of possible values.
@ INCREMENTALITY
Advanced usage: incrementality from one solve to the next.
@ PRESOLVE
Advanced usage: presolve mode.
@ LP_ALGORITHM
Algorithm to solve linear programs.
IncrementalityValues
Advanced usage: Incrementality options.
@ INCREMENTALITY_OFF
Start solve from scratch.
ScalingValues
Advanced usage: Scaling options.
int GetIntegerParam(MPSolverParameters::IntegerParam param) const
Returns the value of an integer parameter.
@ FEASIBLE
feasible, or stopped by limit.
@ INFEASIBLE
proven infeasible.
@ ABNORMAL
abnormal, i.e., error of some kind.
The class for variables of a Mathematical Programming (MP) model.
bool integer() const
Returns the integrality requirement of the variable.
double lb() const
Returns the lower bound.
double ub() const
Returns the upper bound.
const std::string & name() const
Returns the name of the variable.
void set_reduced_cost(double reduced_cost)
void set_solution_value(double value)
int index() const
Returns the index of the variable in the MPSolver::variables_.
#define CPX_NAN
CPXLIBAPI int CPXPUBLIC CPXEsetobjoffset(CPXCENVptr, CPXLPptr, double)
#define CHECK_STATUS(s)
OR-Tools root namespace.
ClosedInterval::Iterator begin(ClosedInterval interval)