backport from main

* rename swig files .i in .swig
* update constraint_solver and routing
* backport math_opt changes
* move dynamic loading to ortools/third_party_solvers
This commit is contained in:
Corentin Le Molgat
2025-07-23 15:07:49 +02:00
committed by Mizux Seiha
parent 03e2551e61
commit a7f49a2585
202 changed files with 4734 additions and 7260 deletions

View File

@@ -72,6 +72,7 @@ cc_library(
srcs = ["sat_solver_utils.cc"],
hdrs = ["sat_solver_utils.h"],
deps = [
":preprocessor",
"//ortools/glop:parameters_cc_proto",
"//ortools/glop:preprocessor",
"//ortools/linear_solver:linear_solver_cc_proto",
@@ -110,6 +111,19 @@ cc_library(
],
)
cc_library(
name = "scip_params",
srcs = ["scip_params.cc"],
hdrs = ["scip_params.h"],
deps = [
"//ortools/linear_solver:scip_helper_macros",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/strings:str_format",
"@scip",
],
)
cc_library(
name = "scip_proto_solver",
srcs = ["scip_proto_solver.cc"],
@@ -121,10 +135,10 @@ cc_library(
deps = [
"//ortools/base",
"//ortools/base:timer",
"//ortools/gscip:legacy_scip_params",
"//ortools/linear_solver:linear_solver_cc_proto",
"//ortools/linear_solver:model_validator",
"//ortools/linear_solver:scip_helper_macros",
"//ortools/linear_solver/proto_solver:scip_params",
"//ortools/util:lazy_mutable_copy",
"@abseil-cpp//absl/cleanup",
"@abseil-cpp//absl/container:btree",
@@ -146,9 +160,10 @@ cc_library(
hdrs = ["gurobi_proto_solver.h"],
deps = [
"//ortools/base:timer",
"//ortools/gurobi:environment",
"//ortools/linear_solver:gurobi_util",
"//ortools/linear_solver:linear_solver_cc_proto",
"//ortools/linear_solver:model_validator",
"//ortools/third_party_solvers:gurobi_environment",
"//ortools/util:lazy_mutable_copy",
"@abseil-cpp//absl/base:core_headers",
"@abseil-cpp//absl/cleanup",
@@ -183,24 +198,20 @@ cc_library(
)
cc_library(
name = "xpress_proto_solver",
srcs = ["xpress_proto_solver.cc"],
hdrs = ["xpress_proto_solver.h"],
name = "preprocessor",
srcs = ["preprocessor.cc"],
hdrs = ["preprocessor.h"],
deps = [
"//ortools/base:timer",
"//ortools/linear_solver:linear_solver_cc_proto",
"//ortools/linear_solver:model_validator",
"//ortools/util:lazy_mutable_copy",
"//ortools/xpress:environment",
"@abseil-cpp//absl/base:core_headers",
"@abseil-cpp//absl/cleanup",
"//ortools/glop:preprocessor",
"//ortools/lp_data",
"//ortools/lp_data:base",
"//ortools/lp_data:lp_utils",
"//ortools/lp_data:sparse",
"//ortools/lp_data:sparse_column",
"//ortools/util:fp_utils",
"//ortools/util:return_macros",
"//ortools/util:stats",
"@abseil-cpp//absl/log",
"@abseil-cpp//absl/log:check",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
"@abseil-cpp//absl/strings:str_format",
"@abseil-cpp//absl/time",
"@abseil-cpp//absl/types:optional",
],
)

View File

@@ -36,12 +36,12 @@
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "ortools/base/status_macros.h"
#include "ortools/base/timer.h"
#include "ortools/gurobi/environment.h"
#include "ortools/linear_solver/gurobi_util.h"
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/linear_solver/model_validator.h"
#include "ortools/third_party_solvers/gurobi_environment.h"
#include "ortools/util/lazy_mutable_copy.h"
namespace operations_research {

View File

@@ -14,13 +14,11 @@
#ifndef OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_GUROBI_PROTO_SOLVER_H_
#define OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_GUROBI_PROTO_SOLVER_H_
#include <string>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "ortools/gurobi/environment.h"
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/third_party_solvers/gurobi_environment.h"
#include "ortools/util/lazy_mutable_copy.h"
namespace operations_research {

View File

@@ -0,0 +1,492 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/linear_solver/proto_solver/preprocessor.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <deque>
#include <limits>
#include <utility>
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "ortools/lp_data/lp_data.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/lp_data/lp_utils.h"
#include "ortools/lp_data/sparse.h"
#include "ortools/lp_data/sparse_column.h"
#include "ortools/util/fp_utils.h"
#include "ortools/util/return_macros.h"
#include "ortools/util/stats.h"
using ::operations_research::glop::ColIndex;
using ::operations_research::glop::ColToRowIndex;
using ::operations_research::glop::Fractional;
using ::operations_research::glop::kInfinity;
using ::operations_research::glop::LinearProgram;
using ::operations_research::glop::ProblemStatus;
using ::operations_research::glop::RowIndex;
using ::operations_research::glop::SparseColumn;
using ::operations_research::glop::SparseMatrix;
using ::operations_research::glop::StrictITIVector;
using ::operations_research::glop::SumWithNegativeInfiniteAndOneMissing;
using ::operations_research::glop::SumWithPositiveInfiniteAndOneMissing;
namespace operations_research {
// Helper function to check the bounds of the SetVariableBounds() and
// SetConstraintBounds() functions.
inline bool AreBoundsValid(Fractional lower_bound, Fractional upper_bound) {
if (std::isnan(lower_bound)) return false;
if (std::isnan(upper_bound)) return false;
if (lower_bound == kInfinity && upper_bound == kInfinity) return false;
if (lower_bound == -kInfinity && upper_bound == -kInfinity) return false;
if (lower_bound > upper_bound) return false;
return true;
}
// --------------------------------------------------------
// IntegerBoundsPreprocessor
// --------------------------------------------------------
bool IntegerBoundsPreprocessor::Run(LinearProgram* linear_program) {
SCOPED_INSTRUCTION_COUNT(time_limit_);
RETURN_VALUE_IF_NULL(linear_program, false);
const Fractional tolerance = integer_solution_tolerance_;
// Make integer the bounds of integer variables.
// NOTE(user): it may happen that the new bound will be less strict (but at
// most it will be off by integer_solution_tolerance).
int64_t num_changed_bounds = 0;
for (ColIndex col : linear_program->IntegerVariablesList()) {
const Fractional lb =
ceil(linear_program->variable_lower_bounds()[col] - tolerance);
const Fractional ub =
floor(linear_program->variable_upper_bounds()[col] + tolerance);
if (!AreBoundsValid(lb, ub)) {
status_ = glop::ProblemStatus::PRIMAL_INFEASIBLE;
return false;
}
if (lb != linear_program->variable_lower_bounds()[col] ||
ub != linear_program->variable_upper_bounds()[col]) {
num_changed_bounds++;
}
linear_program->SetVariableBounds(col, lb, ub);
}
VLOG(2) << "IntegerBoundsPreprocessor changed " << num_changed_bounds
<< " variable bounds.";
// Make integer the bounds of integer constraints, if it makes them stricter.
const SparseMatrix& transpose = linear_program->GetTransposeSparseMatrix();
num_changed_bounds = 0;
for (RowIndex row = RowIndex(0); row < linear_program->num_constraints();
++row) {
bool integer_constraint = true;
for (const SparseColumn::Entry var : transpose.column(RowToColIndex(row))) {
// Don't affect the constraint if it has a non-integer variable.
if (!linear_program->IsVariableInteger(RowToColIndex(var.row()))) {
integer_constraint = false;
break;
}
// Don't affect the constraint if it has a non-integer coefficient. Note
// that we require each coefficient to be precisely an integer in order to
// avoid floating point errors.
//
// TODO(user): checking integer constraints can go further, e.g.,
// x + 2 * y = 4 for binary x and y can never be satisfied. But this
// perhaps starts to resemble bound propagation, which might be too much
// for a lightweighted preprocessor like this one.
if (round(var.coefficient()) != var.coefficient()) {
integer_constraint = false;
break;
}
}
if (integer_constraint) {
const Fractional lb =
std::ceil(linear_program->constraint_lower_bounds()[row] - tolerance);
const Fractional ub = std::floor(
linear_program->constraint_upper_bounds()[row] + tolerance);
if (!AreBoundsValid(lb, ub)) {
status_ = ProblemStatus::PRIMAL_INFEASIBLE;
return false;
}
if (lb != linear_program->constraint_lower_bounds()[row] ||
ub != linear_program->constraint_upper_bounds()[row]) {
num_changed_bounds++;
}
linear_program->SetConstraintBounds(row, lb, ub);
}
}
VLOG(2) << "IntegerBoundsPreprocessor changed " << num_changed_bounds
<< " constraint bounds.";
DCHECK(linear_program->BoundsOfIntegerVariablesAreInteger(tolerance));
DCHECK(linear_program->BoundsOfIntegerConstraintsAreInteger(tolerance));
return false;
}
// --------------------------------------------------------
// BoundPropagationPreprocessor
// --------------------------------------------------------
// TODO(user): This preprocessor is not as efficient as it could be because each
// time we process a constraint, we rescan all the involved variables. Make it
// more efficient if it becomes needed. Note that this kind of propagation is
// probably something we want to do each time we take a branch in the mip
// search, so probably an efficient class for this will be created at some
// point.
bool BoundPropagationPreprocessor::Run(LinearProgram* linear_program) {
SCOPED_INSTRUCTION_COUNT(time_limit_);
RETURN_VALUE_IF_NULL(linear_program, false);
const Fractional tolerance = integer_solution_tolerance_;
// Starts by adding all the row in the 'to_process' queue.
StrictITIVector<RowIndex, bool> in_queue(linear_program->num_constraints(),
false);
std::deque<RowIndex> to_process;
for (RowIndex row(0); row < linear_program->num_constraints(); ++row) {
to_process.push_back(row);
in_queue[row] = true;
}
// This preprocessor will need to access the constraints row by row.
const SparseMatrix& transpose = linear_program->GetTransposeSparseMatrix();
// Now process all the rows until none are left, or a limit on the number of
// processed rows is reached. The limit is mainly here to prevent infinite
// loops on corner cases problems. It should not be reached often in practice.
const int kMaxNumberOfProcessedRows =
linear_program->num_constraints().value() * 10;
for (int i = 0; i < kMaxNumberOfProcessedRows && !to_process.empty(); ++i) {
const RowIndex row = to_process.front();
in_queue[row] = false;
to_process.pop_front();
// For each variable of a constraint on n variables, we want the bound
// implied by the (n - 1) other variables and the constraint bounds. We use
// two handy utility classes that allow us to do that efficiently while
// dealing properly with infinite bounds.
SumWithNegativeInfiniteAndOneMissing lb_sum;
SumWithPositiveInfiniteAndOneMissing ub_sum;
// Initialize the sums.
bool skip = false;
for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
const ColIndex col = RowToColIndex(e.row());
Fractional entry_lb =
e.coefficient() * linear_program->variable_lower_bounds()[col];
Fractional entry_ub =
e.coefficient() * linear_program->variable_upper_bounds()[col];
if (e.coefficient() < 0.0) std::swap(entry_lb, entry_ub);
if (entry_lb == kInfinity || entry_ub == -kInfinity) {
// TODO(user): our SumWithOneMissing does not deal well with infinity of
// the wrong sign. For now when this happen we skip this constraint.
// Note however than the other implied bounds could still be used.
skip = true;
break;
}
lb_sum.Add(entry_lb);
ub_sum.Add(entry_ub);
}
if (skip) continue;
// The inequality
// constraint_lb <= sum(entries) <= constraint_ub
// can be rewritten as:
// sum(entries) + (-activity) = 0,
// where (-activity) has bounds [-constraint_ub, -constraint_lb].
// We use this latter convention to simplify our code.
lb_sum.Add(-linear_program->constraint_upper_bounds()[row]);
ub_sum.Add(-linear_program->constraint_lower_bounds()[row]);
// Process the variables one by one and check if the implied bounds are
// more restrictive.
for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
const ColIndex col = RowToColIndex(e.row());
const Fractional coeff = e.coefficient();
const Fractional var_lb = linear_program->variable_lower_bounds()[col];
const Fractional var_ub = linear_program->variable_upper_bounds()[col];
Fractional entry_lb = coeff * var_lb;
Fractional entry_ub = coeff * var_ub;
if (coeff < 0.0) std::swap(entry_lb, entry_ub);
// If X is the variable with index col and Y the sum of all the other
// variables and of (-activity), then coeff * X + Y = 0. Since Y's bounds
// are [lb_sum without X, ub_sum without X], it is easy to derive the
// implied bounds on X.
Fractional implied_lb = -ub_sum.SumWithout(entry_ub) / coeff;
Fractional implied_ub = -lb_sum.SumWithout(entry_lb) / coeff;
if (coeff < 0.0) std::swap(implied_lb, implied_ub);
// If the variable is integer, make the implied bounds integer.
if (linear_program->IsVariableInteger(col)) {
implied_lb = std::ceil(implied_lb - tolerance);
implied_ub = std::floor(implied_ub + tolerance);
}
// more restrictive? If yes, sets the bounds, and add all the impacted
// row back into to_process if they are not already there.
if (implied_lb > var_lb || implied_ub < var_ub) {
Fractional new_lb = std::max(implied_lb, var_lb);
Fractional new_ub = std::min(implied_ub, var_ub);
if (new_lb > new_ub) {
// TODO(user): Investigate what tolerance we should use here.
if (new_lb - tolerance > new_ub) {
status_ = ProblemStatus::PRIMAL_INFEASIBLE;
return false;
} else {
// We choose the nearest integer for an integer variable, or the
// middle value for a non-integer one.
if (linear_program->IsVariableInteger(col)) {
new_lb = new_ub = round(new_lb);
} else {
new_lb = new_ub = (new_lb + new_ub) / 2.0;
}
}
}
// This extra test avoids reprocessing many rows for no reason.
// It can be false if we run into the case new_lb > new_ub above.
if (new_ub != var_ub || new_lb != var_lb) {
linear_program->SetVariableBounds(col, new_lb, new_ub);
for (SparseColumn::Entry e : linear_program->GetSparseColumn(col)) {
if (!in_queue[e.row()]) {
to_process.push_back(e.row());
in_queue[e.row()] = true;
}
}
}
}
}
}
if (!to_process.empty()) {
LOG_FIRST_N(WARNING, 10)
<< "Propagation limit reached in the BoundPropagationPreprocessor, "
<< "maybe the limit should be increased.";
}
DCHECK(linear_program->BoundsOfIntegerVariablesAreInteger(
integer_solution_tolerance_));
DCHECK(linear_program->BoundsOfIntegerConstraintsAreInteger(
integer_solution_tolerance_));
return false;
}
// --------------------------------------------------------
// ImpliedIntegerPreprocessor
// --------------------------------------------------------
bool ImpliedIntegerPreprocessor::Run(LinearProgram* linear_program) {
SCOPED_INSTRUCTION_COUNT(time_limit_);
RETURN_VALUE_IF_NULL(linear_program, false);
int64_t num_implied_integer_variables = 0;
const Fractional tolerance = integer_solution_tolerance_;
for (ColIndex col(0); col < linear_program->num_variables(); ++col) {
DCHECK_EQ(linear_program->GetFirstSlackVariable(), glop::kInvalidCol);
// Skip the integer variables.
if (linear_program->GetVariableType(col) !=
LinearProgram::VariableType::CONTINUOUS) {
continue;
}
const bool is_implied_integer =
VariableOccursInAtLeastOneEqualityConstraint(*linear_program, col)
? AnyEqualityConstraintImpliesIntegrality(*linear_program, col)
: AllInequalityConstraintsImplyIntegrality(*linear_program, col);
if (is_implied_integer) {
linear_program->SetVariableType(
col, LinearProgram::VariableType::IMPLIED_INTEGER);
num_implied_integer_variables++;
VLOG(2) << "Marked col " << col << " implied integer.";
// We need to tighten its bounds if they are not integer, otherwise
// other preprocessor complains.
const Fractional lb =
std::ceil(linear_program->variable_lower_bounds()[col] - tolerance);
const Fractional ub =
std::floor(linear_program->variable_upper_bounds()[col] + tolerance);
if (!AreBoundsValid(lb, ub)) {
status_ = ProblemStatus::PRIMAL_INFEASIBLE;
return false;
}
linear_program->SetVariableBounds(col, lb, ub);
}
}
VLOG(2) << "ImpliedIntegerPreprocessor detected "
<< num_implied_integer_variables << " implied integer variables.";
DCHECK(linear_program->BoundsOfIntegerVariablesAreInteger(
integer_solution_tolerance_));
// TODO(user): Because this presolve step detects new integer variables and
// does not tighten the bounds of a constraint if all its variables become
// integer, this invariant is currently not enforced:
// DCHECK(linear_program->BoundsOfIntegerConstraintsAreInteger(
// integer_solution_tolerance_));
return false; // Does not require postsolve.
}
bool ImpliedIntegerPreprocessor::AnyEqualityConstraintImpliesIntegrality(
const LinearProgram& linear_program, ColIndex variable) {
for (const SparseColumn::Entry entry :
linear_program.GetSparseColumn(variable)) {
// Process only equality constraints.
if (linear_program.constraint_upper_bounds()[entry.row()] ==
linear_program.constraint_lower_bounds()[entry.row()]) {
if (ConstraintSupportsImpliedIntegrality(linear_program, variable,
entry.row())) {
return true;
}
}
}
return false;
}
bool ImpliedIntegerPreprocessor::AllInequalityConstraintsImplyIntegrality(
const LinearProgram& linear_program, ColIndex variable) {
// Check variable bounds.
Fractional lower_bound = linear_program.variable_lower_bounds()[variable];
Fractional upper_bound = linear_program.variable_upper_bounds()[variable];
if (!IsIntegerWithinTolerance(lower_bound, integer_solution_tolerance_) ||
!IsIntegerWithinTolerance(upper_bound, integer_solution_tolerance_)) {
// The bounds are not integer.
// We cannot deduce anything if the variable as an objective.
//
// TODO(user): Actually we can if the bound that minimize the cost is
// integer but not the other. Improve the code.
if (linear_program.objective_coefficients()[variable] != 0.0) return false;
// No objective. If the variable domain contains an integer point, then
// there is a chance for this variable to be integer. This is because if the
// condition on the constraints below is true, then the constraints will
// always imply the variable to be inside a [integer_lb, integer_ub] domain.
// And if the intersection of this domain with the variable domain is
// non-empty, then it contains one or more integer points and we can always
// set the variable to one of these integer values.
if (std::ceil(lower_bound) > std::floor(upper_bound)) return false;
}
// Primal detection for each constraint containing variable.
for (const SparseColumn::Entry entry :
linear_program.GetSparseColumn(variable)) {
if (!ConstraintSupportsImpliedIntegrality(linear_program, variable,
entry.row())) {
return false;
}
}
return true;
}
bool ImpliedIntegerPreprocessor::ConstraintSupportsImpliedIntegrality(
const LinearProgram& linear_program, ColIndex variable, RowIndex row) {
const SparseMatrix& coefficients_transpose =
linear_program.GetTransposeSparseMatrix();
const Fractional variable_coefficient = coefficients_transpose.LookUpValue(
ColToRowIndex(variable), RowToColIndex(row));
for (const SparseColumn::Entry entry :
coefficients_transpose.column(RowToColIndex(row))) {
const ColIndex col = RowToColIndex(entry.row());
if (col == variable) continue;
// Check if the variables in the row are all integers.
if (!linear_program.IsVariableInteger(col)) {
return false;
}
// Check if the coefficient ratios are all integers.
const Fractional coefficient_ratio =
entry.coefficient() / variable_coefficient;
if (!IsIntegerWithinTolerance(coefficient_ratio,
integer_solution_tolerance_)) {
return false;
}
}
// Check if the constraint bound ratios are integers.
// Note that we ignore infinities.
if (linear_program.constraint_lower_bounds()[row] != -kInfinity) {
const Fractional constraint_lower_bound_ratio =
linear_program.constraint_lower_bounds()[row] / variable_coefficient;
if (!IsIntegerWithinTolerance(constraint_lower_bound_ratio,
integer_solution_tolerance_)) {
return false;
}
}
if (linear_program.constraint_upper_bounds()[row] != kInfinity) {
const Fractional constraint_upper_bound_ratio =
linear_program.constraint_upper_bounds()[row] / variable_coefficient;
if (!IsIntegerWithinTolerance(constraint_upper_bound_ratio,
integer_solution_tolerance_)) {
return false;
}
}
return true;
}
bool ImpliedIntegerPreprocessor::VariableOccursInAtLeastOneEqualityConstraint(
const LinearProgram& linear_program, ColIndex variable) {
for (const SparseColumn::Entry entry :
linear_program.GetSparseColumn(variable)) {
// Check if the constraint is an equality.
if (linear_program.constraint_upper_bounds()[entry.row()] ==
linear_program.constraint_lower_bounds()[entry.row()]) {
return true;
}
}
return false;
}
// --------------------------------------------------------
// ReduceCostOverExclusiveOrConstraintPreprocessor
// --------------------------------------------------------
bool ReduceCostOverExclusiveOrConstraintPreprocessor::Run(
LinearProgram* linear_program) {
SCOPED_INSTRUCTION_COUNT(time_limit_);
RETURN_VALUE_IF_NULL(linear_program, false);
const RowIndex num_constraints = linear_program->num_constraints();
const SparseMatrix& transpose = linear_program->GetTransposeSparseMatrix();
for (RowIndex row(0); row < num_constraints; ++row) {
if (linear_program->constraint_lower_bounds()[row] != 1.0) continue;
if (linear_program->constraint_upper_bounds()[row] != 1.0) continue;
Fractional min_cost = std::numeric_limits<Fractional>::infinity();
bool constraint_is_exclusive_or = true;
for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
const ColIndex var = RowToColIndex(e.row());
if (!linear_program->IsVariableInteger(var) ||
linear_program->variable_lower_bounds()[var] != 0.0 ||
linear_program->variable_upper_bounds()[var] != 1.0 ||
e.coefficient() != 1.0) {
constraint_is_exclusive_or = false;
break;
}
min_cost =
std::min(min_cost, linear_program->objective_coefficients()[var]);
}
if (constraint_is_exclusive_or && min_cost > 0.0 &&
glop::IsFinite(min_cost)) {
for (const SparseColumn::Entry e : transpose.column(RowToColIndex(row))) {
const ColIndex var = RowToColIndex(e.row());
const Fractional cost = linear_program->objective_coefficients()[var];
linear_program->SetObjectiveCoefficient(var, cost - min_cost);
}
linear_program->SetObjectiveOffset(linear_program->objective_offset() +
min_cost);
}
}
return false;
}
} // namespace operations_research

View File

@@ -0,0 +1,198 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_PREPROCESSOR_H_
#define OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_PREPROCESSOR_H_
#include "ortools/glop/preprocessor.h"
#include "ortools/lp_data/lp_data.h"
#include "ortools/lp_data/lp_types.h"
namespace operations_research {
// --------------------------------------------------------
// IntegerBoundsPreprocessor
// --------------------------------------------------------
// Makes the bounds of integer variables integer. Makes the bounds of
// constraints involving only integer variables with integer coefficients
// integer.
class IntegerBoundsPreprocessor : public glop::Preprocessor {
public:
IntegerBoundsPreprocessor(const glop::GlopParameters* parameters,
glop::Fractional integer_solution_tolerance)
: glop::Preprocessor(parameters),
integer_solution_tolerance_(integer_solution_tolerance) {}
IntegerBoundsPreprocessor(const IntegerBoundsPreprocessor&) = delete;
IntegerBoundsPreprocessor& operator=(const IntegerBoundsPreprocessor&) =
delete;
~IntegerBoundsPreprocessor() override = default;
bool Run(glop::LinearProgram* linear_program) override;
void RecoverSolution(glop::ProblemSolution* /*solution*/) const override {}
private:
const glop::Fractional integer_solution_tolerance_;
};
// --------------------------------------------------------
// BoundPropagationPreprocessor
// --------------------------------------------------------
// It is possible to compute "implied" bounds on a variable from the bounds of
// all the other variables and the constraints in which this variable take
// place. These "implied" bounds can be used to restrict the variable bounds.
// This preprocessor just does that until no more bounds can be propagated or
// a given limit on the number of propagations is reached.
//
// Note(user): In particular, this preprocessor will remove any singleton row.
//
// Note(user): This seems like a general LP preprocessor but it is really
// difficult to postsolve it correctly in the LP context when one wants to have
// a basic optimal solution at the end. By contrast, in Mip context one is happy
// with any form of an optimal solution at the end, thus restoring the full
// solution is trivial. Consequently, bound propagation is implemented as a mip
// preprocessor.
class BoundPropagationPreprocessor : public glop::Preprocessor {
public:
BoundPropagationPreprocessor(const glop::GlopParameters* parameters,
glop::Fractional integer_solution_tolerance)
: glop::Preprocessor(parameters),
integer_solution_tolerance_(integer_solution_tolerance) {}
BoundPropagationPreprocessor(const BoundPropagationPreprocessor&) = delete;
BoundPropagationPreprocessor& operator=(const BoundPropagationPreprocessor&) =
delete;
~BoundPropagationPreprocessor() override = default;
bool Run(glop::LinearProgram* linear_program) override;
void RecoverSolution(glop::ProblemSolution* /*solution*/) const override {}
private:
const glop::Fractional integer_solution_tolerance_;
};
// --------------------------------------------------------
// ImpliedIntegerPreprocessor
// --------------------------------------------------------
// In this preprocessor, we find continuous variables which can only take
// integer values and mark them as integer variables.
//
// There are two methods for detecting implied integer variables: 1) primal
// and 2) dual detection. If the variable appears in at least one equality
// constraint then we use primal detection otherwise we use dual detection.
class ImpliedIntegerPreprocessor : public glop::Preprocessor {
public:
explicit ImpliedIntegerPreprocessor(
const glop::GlopParameters* parameters,
glop::Fractional integer_solution_tolerance)
: glop::Preprocessor(parameters),
integer_solution_tolerance_(integer_solution_tolerance) {}
ImpliedIntegerPreprocessor(const ImpliedIntegerPreprocessor&) = delete;
ImpliedIntegerPreprocessor& operator=(const ImpliedIntegerPreprocessor&) =
delete;
~ImpliedIntegerPreprocessor() override = default;
// TODO(user): When some variable are detected to be implied integer, other
// can in turn be detected as such. Change the code to reach a fixed point.
// Calling this multiple time has a similar effect, but is a lot less
// efficient and can require O(num_variables) calls to reach the fix point.
bool Run(glop::LinearProgram* linear_program) override;
void RecoverSolution(glop::ProblemSolution* /*solution*/) const override {}
private:
// Returns true if the given variable is implied integer. This method is used
// for continuous variables appearing in at least one equality constraint.
// This is sometimes called "primal" detection in the literature.
//
// For each equality constraint s in which the given continuous variable x_j
// appears, this method checks the primal detection criteria by using
// ConstraintSupportsImpliedIntegrality() method.
bool AnyEqualityConstraintImpliesIntegrality(
const glop::LinearProgram& linear_program, glop::ColIndex variable);
// Returns true if given variable is implied integer variable. This method is
// used for continuous variables for which primal detection is not applicable
// i.e. all constraints containing the given variable are inequalities. This
// is sometimes called "dual" detection in the literature.
//
// This method checks the following for the givan continuous variable x_j.
// a) The lower and upper bound of x_j are integers or the variable has no
// cost and its domain contains an integer point.
// b) For all constraint containing x_j, when treated as equality under primal
// detection, implies x_j as integer variable.
// If both conditions are satisfied then the variable x_j is implied integer
// variable.
bool AllInequalityConstraintsImplyIntegrality(
const glop::LinearProgram& linear_program, glop::ColIndex variable);
// Returns true if the following conditions are satisfied.
//
// Let the constraint be lb_s <= \sum_{i=1..n}(a_si*x_i) + a_sj*x_j <= ub_s
// a) lb_s / a_sj and ub_s / a_sj are integers.
// b) a_si / a_sj is integer for all i.
// c) x_i are all integer variables.
bool ConstraintSupportsImpliedIntegrality(
const glop::LinearProgram& linear_program, glop::ColIndex variable,
glop::RowIndex row);
// Returns true if the variable occurs in at least one equality constraint.
bool VariableOccursInAtLeastOneEqualityConstraint(
const glop::LinearProgram& linear_program, glop::ColIndex variable);
private:
const glop::Fractional integer_solution_tolerance_;
};
// --------------------------------------------------------
// ReduceCostOverExclusiveOrConstraintPreprocessor
// --------------------------------------------------------
// For an "exclusive or" constraint (sum Boolean = 1), if all the costs of the
// Boolean variables are positive, then we can subtract the cost of the one
// with minimum cost from the cost of all the others. We can do that for all
// such constraints one by one.
//
// ex: if x,y,z are Boolean variables with respective cost 1,2,1 and x+y+z=1,
// then we can change their costs to 0,1,0 and add 1 to the objective offset
// without changing the cost of any feasible solution.
//
// This seems pretty trivial, but can have a big impact depending on the
// technique we use to solve the MIP. It also makes the objective sparser which
// can only be a good thing.
//
// TODO(user): In more generality, in presence of an exclusive or constraint we
// can shift the cost by any value (even negative), so it may be good to
// maximize the number of coefficients at zero. To investigate.
class ReduceCostOverExclusiveOrConstraintPreprocessor
: public glop::Preprocessor {
public:
explicit ReduceCostOverExclusiveOrConstraintPreprocessor(
const glop::GlopParameters* mip_parameters)
: glop::Preprocessor(mip_parameters) {}
ReduceCostOverExclusiveOrConstraintPreprocessor(
const ReduceCostOverExclusiveOrConstraintPreprocessor&) = delete;
ReduceCostOverExclusiveOrConstraintPreprocessor& operator=(
const ReduceCostOverExclusiveOrConstraintPreprocessor&) = delete;
~ReduceCostOverExclusiveOrConstraintPreprocessor() override = default;
bool Run(glop::LinearProgram* linear_program) override;
void RecoverSolution(glop::ProblemSolution* /*solution*/) const override {}
};
} // namespace operations_research
#endif // OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_PREPROCESSOR_H_

View File

@@ -21,6 +21,7 @@
#include "absl/log/check.h"
#include "ortools/glop/parameters.pb.h"
#include "ortools/glop/preprocessor.h"
#include "ortools/linear_solver/proto_solver/preprocessor.h"
#include "ortools/lp_data/lp_data.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/lp_data/proto_utils.h"
@@ -29,9 +30,10 @@
namespace operations_research {
#define ADD_LP_PREPROCESSOR(name) \
names.push_back(#name); \
lp_preprocessors.push_back(std::make_unique<name>(&glop_params));
#define ADD_LP_PREPROCESSOR(name, ...) \
names.push_back(#name); \
lp_preprocessors.push_back( \
std::make_unique<name>(&glop_params __VA_OPT__(, ) __VA_ARGS__));
glop::ProblemStatus ApplyMipPresolveSteps(
const glop::GlopParameters& glop_params, MPModelProto* model,
@@ -60,13 +62,13 @@ glop::ProblemStatus ApplyMipPresolveSteps(
// These presolve might change the problem size.
//
// TODO(user): transform the hint instead of disabling presolve.
std::vector<std::string> names;
std::vector<std::unique_ptr<glop::Preprocessor>> lp_preprocessors;
const std::string header =
"Running basic LP presolve, initial problem dimensions: ";
if (!hint_is_present) {
const std::string header =
"Running basic LP presolve, initial problem dimensions: ";
SOLVER_LOG(logger, "");
SOLVER_LOG(logger, header, lp.GetDimensionString());
std::vector<std::string> names;
std::vector<std::unique_ptr<glop::Preprocessor>> lp_preprocessors;
ADD_LP_PREPROCESSOR(glop::FixedVariablePreprocessor);
ADD_LP_PREPROCESSOR(glop::SingletonPreprocessor);
ADD_LP_PREPROCESSOR(glop::ForcingAndImpliedFreeConstraintPreprocessor);
@@ -77,19 +79,30 @@ glop::ProblemStatus ApplyMipPresolveSteps(
// for the conversion, it is better to have tight bounds even if the bound
// propagator is supposed to undo what this presolve would have done.
ADD_LP_PREPROCESSOR(glop::UnconstrainedVariablePreprocessor);
}
for (int i = 0; i < lp_preprocessors.size(); ++i) {
if (time_limit->LimitReached()) break;
auto& preprocessor = lp_preprocessors[i];
preprocessor->SetTimeLimit(time_limit.get());
preprocessor->UseInMipContext();
const bool need_postsolve = preprocessor->Run(&lp);
names[i].resize(header.size(), ' '); // padding.
SOLVER_LOG(logger, names[i], lp.GetDimensionString());
const glop::ProblemStatus status = preprocessor->status();
if (status != glop::ProblemStatus::INIT) return status;
if (need_postsolve) for_postsolve->push_back(std::move(preprocessor));
}
// These preprocessors do not need postsolve.
ADD_LP_PREPROCESSOR(IntegerBoundsPreprocessor, 1e-6);
ADD_LP_PREPROCESSOR(BoundPropagationPreprocessor, 1e-6);
ADD_LP_PREPROCESSOR(ImpliedIntegerPreprocessor, 1e-6);
// We need to re-run this after the ImpliedIntegerPreprocessor because the
// latter does not round the bounds of the constraints involving only
// integer variables and coefficients.
ADD_LP_PREPROCESSOR(IntegerBoundsPreprocessor, 1e-6);
ADD_LP_PREPROCESSOR(ReduceCostOverExclusiveOrConstraintPreprocessor);
for (int i = 0; i < lp_preprocessors.size(); ++i) {
if (time_limit->LimitReached()) break;
auto& preprocessor = lp_preprocessors[i];
preprocessor->SetTimeLimit(time_limit.get());
preprocessor->UseInMipContext();
const bool need_postsolve = preprocessor->Run(&lp);
names[i].resize(header.size(), ' '); // padding.
SOLVER_LOG(logger, names[i], lp.GetDimensionString());
const glop::ProblemStatus status = preprocessor->status();
if (status != glop::ProblemStatus::INIT) return status;
if (need_postsolve) for_postsolve->push_back(std::move(preprocessor));
}
// Finally, we make sure all domains contain zero.

View File

@@ -0,0 +1,126 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/linear_solver/proto_solver/scip_params.h"
#include <cstdint>
#include <string>
#include <vector>
#include "absl/status/status.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "ortools/base/logging.h"
#include "ortools/linear_solver/scip_helper_macros.h"
#include "scip/scip.h"
#include "scip/scip_numerics.h"
#include "scip/scip_param.h"
#include "scip/struct_paramset.h"
#include "scip/type_paramset.h"
namespace operations_research {
absl::Status LegacyScipSetSolverSpecificParameters(absl::string_view parameters,
SCIP* scip) {
for (const auto parameter : absl::StrSplit(parameters, absl::ByAnyChar(",\n"),
absl::SkipWhitespace())) {
std::vector<std::string> key_value = absl::StrSplit(
parameter, absl::ByAnyChar("= "), absl::SkipWhitespace());
if (key_value.size() != 2) {
return absl::InvalidArgumentError(
absl::StrFormat("Cannot parse parameter '%s'. Expected format is "
"'parameter/name = value'",
parameter));
}
std::string name = key_value[0];
absl::RemoveExtraAsciiWhitespace(&name);
std::string value = key_value[1];
absl::RemoveExtraAsciiWhitespace(&value);
const double infinity = SCIPinfinity(scip);
SCIP_PARAM* param = SCIPgetParam(scip, name.c_str());
if (param == nullptr) {
return absl::InvalidArgumentError(
absl::StrFormat("Invalid parameter name '%s'", name));
}
switch (param->paramtype) {
case SCIP_PARAMTYPE_BOOL: {
bool parsed_value;
if (absl::SimpleAtob(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(
SCIPsetBoolParam(scip, name.c_str(), parsed_value));
VLOG(2) << absl::StrFormat("Set parameter %s to %s", name, value);
continue;
}
break;
}
case SCIP_PARAMTYPE_INT: {
int parsed_value;
if (absl::SimpleAtoi(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(
SCIPsetIntParam(scip, name.c_str(), parsed_value));
VLOG(2) << absl::StrFormat("Set parameter %s to %s", name, value);
continue;
}
break;
}
case SCIP_PARAMTYPE_LONGINT: {
int64_t parsed_value;
if (absl::SimpleAtoi(value, &parsed_value)) {
RETURN_IF_SCIP_ERROR(SCIPsetLongintParam(
scip, name.c_str(), static_cast<SCIP_Longint>(parsed_value)));
VLOG(2) << absl::StrFormat("Set parameter %s to %s", name, value);
continue;
}
break;
}
case SCIP_PARAMTYPE_REAL: {
double parsed_value;
if (absl::SimpleAtod(value, &parsed_value)) {
if (parsed_value > infinity) parsed_value = infinity;
RETURN_IF_SCIP_ERROR(
SCIPsetRealParam(scip, name.c_str(), parsed_value));
VLOG(2) << absl::StrFormat("Set parameter %s to %s", name, value);
continue;
}
break;
}
case SCIP_PARAMTYPE_CHAR: {
if (value.size() == 1) {
RETURN_IF_SCIP_ERROR(SCIPsetCharParam(scip, name.c_str(), value[0]));
VLOG(2) << absl::StrFormat("Set parameter %s to %s", name, value);
continue;
}
break;
}
case SCIP_PARAMTYPE_STRING: {
if (value.front() == '"' && value.back() == '"') {
value.erase(value.begin());
value.erase(value.end() - 1);
}
RETURN_IF_SCIP_ERROR(
SCIPsetStringParam(scip, name.c_str(), value.c_str()));
VLOG(2) << absl::StrFormat("Set parameter %s to %s", name, value);
continue;
}
}
return absl::InvalidArgumentError(
absl::StrFormat("Invalid parameter value '%s'", parameter));
}
return absl::OkStatus();
}
} // namespace operations_research

View File

@@ -11,17 +11,20 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_XPRESS_PROTO_SOLVER_H_
#define OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_XPRESS_PROTO_SOLVER_H_
#ifndef OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_SCIP_PARAMS_H_
#define OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_SCIP_PARAMS_H_
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/util/lazy_mutable_copy.h"
#include <string>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "scip/scip.h"
#include "scip/type_scip.h"
namespace operations_research {
// Solves the input request.
MPSolutionResponse XPressSolveProto(LazyMutableCopy<MPModelRequest> request);
absl::Status LegacyScipSetSolverSpecificParameters(absl::string_view parameters,
SCIP* scip);
}
} // namespace operations_research
#endif // OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_XPRESS_PROTO_SOLVER_H_
#endif // OR_TOOLS_LINEAR_SOLVER_PROTO_SOLVER_SCIP_PARAMS_H_

View File

@@ -40,9 +40,9 @@
#include "absl/time/time.h"
#include "ortools/base/status_macros.h"
#include "ortools/base/timer.h"
#include "ortools/gscip/legacy_scip_params.h"
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/linear_solver/model_validator.h"
#include "ortools/linear_solver/proto_solver/scip_params.h"
#include "ortools/linear_solver/scip_helper_macros.h"
#include "ortools/util/lazy_mutable_copy.h"
#include "scip/cons_and.h"

View File

@@ -1,970 +0,0 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/linear_solver/proto_solver/xpress_proto_solver.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <limits>
#include <memory>
#include <numeric>
#include <string>
#include <vector>
#include "absl/base/attributes.h"
#include "absl/cleanup/cleanup.h"
#include "absl/log/check.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "absl/types/optional.h"
#include "ortools/base/logging.h"
#include "ortools/base/status_macros.h"
#include "ortools/base/timer.h"
#include "ortools/linear_solver/linear_solver.pb.h"
#include "ortools/linear_solver/model_validator.h"
#include "ortools/util/lazy_mutable_copy.h"
#include "ortools/xpress/environment.h"
namespace operations_research {
// namespace {
// constexpr int XPRS_OK = 0;
// bool XPressCodeToInvalidResponse(int error_code, const char* source_file,
// int source_line, const char* statement,
// XPRSprob prob, MPSolutionResponse* response)
// {
// if (error_code == XPRS_OK) return true;
// response->set_status();
// response->set_status_message(absl::StrFormat(
// "XPress error code %d (file '%s', line %d) on '%s': %s", error_code,
// source_file, source_line, statement, XPRSgeterrormsg(prob)));
// return false;
// }
// int AddIndicatorConstraint(const MPGeneralConstraintProto& gen_cst,
// XPRSprob xpress_model,
// std::vector<int>* tmp_variables,
// std::vector<double>* tmp_coefficients) {
// CHECK(xpress_model != nullptr);
// CHECK(tmp_variables != nullptr);
// CHECK(tmp_coefficients != nullptr);
// const auto& ind_cst = gen_cst.indicator_constraint();
// MPConstraintProto cst = ind_cst.constraint();
// if (cst.lower_bound() > -std::numeric_limits<double>::infinity()) {
// int status = XPRSaddgenconstrIndicator(
// xpress_model, gen_cst.name().c_str(), ind_cst.var_index(),
// ind_cst.var_value(), cst.var_index_size(),
// cst.mutable_var_index()->mutable_data(),
// cst.mutable_coefficient()->mutable_data(),
// cst.upper_bound() == cst.lower_bound() ? XPRS_EQUAL
// : XPRS_GREATER_EQUAL,
// cst.lower_bound());
// if (status != XPRS_OK) return status;
// }
// if (cst.upper_bound() < std::numeric_limits<double>::infinity() &&
// cst.lower_bound() != cst.upper_bound()) {
// return XPRSaddgenconstrIndicator(xpress_model, gen_cst.name().c_str(),
// ind_cst.var_index(),
// ind_cst.var_value(),
// cst.var_index_size(),
// cst.mutable_var_index()->mutable_data(),
// cst.mutable_coefficient()->mutable_data(),
// XPRS_LESS_EQUAL, cst.upper_bound());
// }
// return XPRS_OK;
// }
// int AddSosConstraint(const MPSosConstraint& sos_cst, XPRSprob xpress_model,
// std::vector<int>* tmp_variables,
// std::vector<double>* tmp_weights) {
// CHECK(xpress_model != nullptr);
// CHECK(tmp_variables != nullptr);
// CHECK(tmp_weights != nullptr);
// tmp_variables->resize(sos_cst.var_index_size(), 0);
// for (int v = 0; v < sos_cst.var_index_size(); ++v) {
// (*tmp_variables)[v] = sos_cst.var_index(v);
// }
// tmp_weights->resize(sos_cst.var_index_size(), 0);
// if (sos_cst.weight_size() == sos_cst.var_index_size()) {
// for (int w = 0; w < sos_cst.weight_size(); ++w) {
// (*tmp_weights)[w] = sos_cst.weight(w);
// }
// } else {
// DCHECK_EQ(sos_cst.weight_size(), 0);
// // XPress requires variable weights in their SOS constraints.
// std::iota(tmp_weights->begin(), tmp_weights->end(), 1);
// }
// std::vector<int> types = {sos_cst.type() == MPSosConstraint::SOS1_DEFAULT
// ? XPRS_SOS_TYPE1
// : XPRS_SOS_TYPE2};
// std::vector<int> begins = {0};
// return XPRSaddsos(xpress_model, /*numsos=*/1,
// /*nummembers=*/sos_cst.var_index_size(),
// /*types=*/types.data(),
// /*beg=*/begins.data(), /*ind=*/tmp_variables->data(),
// /*weight*/ tmp_weights->data());
// }
// int AddQuadraticConstraint(const MPGeneralConstraintProto& gen_cst,
// XPRSprob xpress_model) {
// CHECK(xpress_model != nullptr);
// constexpr double kInfinity = std::numeric_limits<double>::infinity();
// CHECK(gen_cst.has_quadratic_constraint());
// const MPQuadraticConstraint& quad_cst = gen_cst.quadratic_constraint();
// auto addqconstr = [](XPRSprob xpress_model, MPQuadraticConstraint quad_cst,
// char sense, double rhs, const std::string& name) {
// return XPRSaddqconstr(
// xpress_model,
// /*numlnz=*/quad_cst.var_index_size(),
// /*lind=*/quad_cst.mutable_var_index()->mutable_data(),
// /*lval=*/quad_cst.mutable_coefficient()->mutable_data(),
// /*numqnz=*/quad_cst.qvar1_index_size(),
// /*qrow=*/quad_cst.mutable_qvar1_index()->mutable_data(),
// /*qcol=*/quad_cst.mutable_qvar2_index()->mutable_data(),
// /*qval=*/quad_cst.mutable_qcoefficient()->mutable_data(),
// /*sense=*/sense,
// /*rhs=*/rhs,
// /*QCname=*/name.c_str());
// };
// if (quad_cst.has_lower_bound() && quad_cst.lower_bound() > -kInfinity) {
// const int xprs_status =
// addqconstr(xpress_model, gen_cst.quadratic_constraint(),
// XPRS_GREATER_EQUAL, quad_cst.lower_bound(),
// gen_cst.has_name() ? gen_cst.name() + "_lb" : "");
// if (xprs_status != XPRS_OK) return xprs_status;
// }
// if (quad_cst.has_upper_bound() && quad_cst.upper_bound() < kInfinity) {
// const int xprs_status =
// addqconstr(xpress_model, gen_cst.quadratic_constraint(),
// XPRS_LESS_EQUAL, quad_cst.upper_bound(),
// gen_cst.has_name() ? gen_cst.name() + "_ub" : "");
// if (xprs_status != XPRS_OK) return xprs_status;
// }
// return XPRS_OK;
// }
// int AddAndConstraint(const MPGeneralConstraintProto& gen_cst,
// XPRSprob xpress_model, std::vector<int>* tmp_variables)
// {
// CHECK(xpress_model != nullptr);
// CHECK(tmp_variables != nullptr);
// auto and_cst = gen_cst.and_constraint();
// return XPRSaddgenconstrAnd(
// xpress_model,
// /*name=*/gen_cst.name().c_str(),
// /*resvar=*/and_cst.resultant_var_index(),
// /*nvars=*/and_cst.var_index_size(),
// /*vars=*/and_cst.mutable_var_index()->mutable_data());
// }
// int AddOrConstraint(const MPGeneralConstraintProto& gen_cst,
// XPRSprob xpress_model, std::vector<int>* tmp_variables) {
// CHECK(xpress_model != nullptr);
// CHECK(tmp_variables != nullptr);
// auto or_cst = gen_cst.or_constraint();
// return XPRSaddgenconstrOr(
// xpress_model,
// /*name=*/gen_cst.name().c_str(),
// /*resvar=*/or_cst.resultant_var_index(),
// /*nvars=*/or_cst.var_index_size(),
// /*vars=*/or_cst.mutable_var_index()->mutable_data());
// }
// int AddMinConstraint(const MPGeneralConstraintProto& gen_cst,
// XPRSprob xpress_model, std::vector<int>* tmp_variables)
// {
// CHECK(xpress_model != nullptr);
// CHECK(tmp_variables != nullptr);
// auto min_cst = gen_cst.min_constraint();
// return XPRSaddgenconstrMin(
// xpress_model,
// /*name=*/gen_cst.name().c_str(),
// /*resvar=*/min_cst.resultant_var_index(),
// /*nvars=*/min_cst.var_index_size(),
// /*vars=*/min_cst.mutable_var_index()->mutable_data(),
// /*constant=*/min_cst.has_constant()
// ? min_cst.constant()
// : std::numeric_limits<double>::infinity());
// }
// int AddMaxConstraint(const MPGeneralConstraintProto& gen_cst,
// XPRSprob xpress_model, std::vector<int>* tmp_variables)
// {
// CHECK(xpress_model != nullptr);
// CHECK(tmp_variables != nullptr);
// auto max_cst = gen_cst.max_constraint();
// return XPRSaddgenconstrMax(
// xpress_model,
// /*name=*/gen_cst.name().c_str(),
// /*resvar=*/max_cst.resultant_var_index(),
// /*nvars=*/max_cst.var_index_size(),
// /*vars=*/max_cst.mutable_var_index()->mutable_data(),
// /*constant=*/max_cst.has_constant()
// ? max_cst.constant()
// : -std::numeric_limits<double>::infinity());
// }
// } // namespace
// std::string SetSolverSpecificParameters(absl::string_view parameters,
// XPRSprob xpress) {
// if (parameters.empty()) return absl::OkStatus();
// std::vector<std::string> error_messages;
// for (absl::string_view line : absl::StrSplit(parameters, '\n')) {
// // Empty lines are simply ignored.
// if (line.empty()) continue;
// // Comment tokens end at the next new-line, or the end of the string.
// // The first character must be '#'
// if (line[0] == '#') continue;
// for (absl::string_view token :
// absl::StrSplit(line, ',', absl::SkipWhitespace())) {
// if (token.empty()) continue;
// std::vector<std::string> key_value =
// absl::StrSplit(token, absl::ByAnyChar(" ="),
// absl::SkipWhitespace());
// // If one parameter fails, we keep processing the list of parameters.
// if (key_value.size() != 2) {
// const std::string current_message =
// absl::StrCat("Cannot parse parameter '", token,
// "'. Expected format is 'ParameterName value' or "
// "'ParameterName=value'");
// error_messages.push_back(current_message);
// continue;
// }
// const int xpress_code =
// XPRSsetparam(xpress, key_value[0].c_str(), key_value[1].c_str());
// if (xpress_code != XPRS_OK) {
// const std::string current_message = absl::StrCat(
// "Error setting parameter '", key_value[0], "' to value '",
// key_value[1], "': ", XPRSgeterrormsg(xpress));
// error_messages.push_back(current_message);
// continue;
// }
// VLOG(2) << absl::StrCat("Set parameter '", key_value[0], "' to value
// '",
// key_value[1]);
// }
// }
// if (error_messages.empty()) return "";
// return absl::StrJoin(error_messages, "\n");
// }
MPSolutionResponse XPressSolveProto(LazyMutableCopy<MPModelRequest> request) {
MPSolutionResponse response;
response.set_status(MPSolverResponseStatus::MPSOLVER_SOLVER_TYPE_UNAVAILABLE);
// const absl::optional<LazyMutableCopy<MPModelProto>> optional_model =
// ExtractValidMPModelOrPopulateResponseStatus(request, &response);
// if (!optional_model) return response;
// const MPModelProto& model = optional_model->get();
// // We set `xpress_env` to point to a new environment if no existing one
// is
// // provided. We must make sure that we free this environment when we exit
// this
// // function.
// bool xpress_env_was_created = false;
// auto xpress_env_deleter = absl::MakeCleanup([&]() {
// if (xpress_env_was_created && xpress_env != nullptr) {
// XPRSfreeenv(xpress_env);
// }
// });
// if (xpress_env == nullptr) {
// ASSIGN_OR_RETURN(xpress_env, GetXPressEnv());
// xpress_env_was_created = true;
// }
// XPRSprob xpress_model = nullptr;
// auto xpress_model_deleter = absl::MakeCleanup([&]() {
// const int error_code = XPRSfreemodel(xpress_model);
// LOG_IF(DFATAL, error_code != XPRS_OK)
// << "XPRSfreemodel failed with error " << error_code << ": "
// << XPRSgeterrormsg(xpress_env);
// });
// // `xpress_env` references ther XPRSenv argument.
// #define RETURN_IF_XPRESS_ERROR(x) \
// RETURN_IF_ERROR( \
// if (!XPressCodeToInvalidResponse(x, __FILE__, __LINE__, #x, xpress,
// &response)) { \
// return response; \
// })
// RETURN_IF_XPRESS_ERROR(XPRSnewmodel(xpress_env, &xpress_model,
// model.name().c_str(),
// /*numvars=*/0,
// /*obj=*/nullptr,
// /*lb=*/nullptr,
// /*ub=*/nullptr,
// /*vtype=*/nullptr,
// /*varnames=*/nullptr));
// XPRSprob const model_env = XPRSgetenv(xpress_model);
// if (request.has_solver_specific_parameters()) {
// const auto parameters_status = SetSolverSpecificParameters(
// request.solver_specific_parameters(), model_env);
// if (!parameters_status.ok()) {
// response.set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
// response.set_status_str(
// std::string(parameters_status.message())); // NOLINT
// return response;
// }
// }
// if (request.solver_time_limit_seconds() > 0) {
// RETURN_IF_XPRESS_ERROR(
// XPRSsetdblparam(model_env, XPRS_DBL_PAR_TIMELIMIT,
// request.solver_time_limit_seconds()));
// }
// RETURN_IF_XPRESS_ERROR(
// XPRSsetintparam(model_env, XPRS_INT_PAR_OUTPUTFLAG,
// request.enable_internal_solver_output()));
// const int variable_size = model.variable_size();
// bool has_integer_variables = false;
// {
// std::vector<double> obj_coeffs(variable_size, 0);
// std::vector<double> lb(variable_size);
// std::vector<double> ub(variable_size);
// std::vector<char> ctype(variable_size);
// std::vector<const char*> varnames(variable_size);
// for (int v = 0; v < variable_size; ++v) {
// const MPVariableProto& variable = model.variable(v);
// obj_coeffs[v] = variable.objective_coefficient();
// lb[v] = variable.lower_bound();
// ub[v] = variable.upper_bound();
// ctype[v] = variable.is_integer() &&
// request.solver_type() ==SolutionRes
// : XPRS_CONTINUOUS;
// if (variable.is_integer()) has_integer_variables = true;
// if (!variable.name().empty()) varnames[v] = variable.name().c_str();
// }
// RETURN_IF_XPRESS_ERROR(
// XPRSaddvars(xpress_model, variable_size, 0, nullptr, nullptr,
// nullptr,
// /*obj=*/obj_coeffs.data(),
// /*lb=*/lb.data(), /*ub=*/ub.data(),
// /*vtype=*/ctype.data(),
// /*varnames=*/const_cast<char**>(varnames.data())));
// // Set solution hints if any.
// for (int i = 0; i < model.solution_hint().var_index_size(); ++i) {
// RETURN_IF_XPRESS_ERROR(XPRSsetdblattrelement(
// xpress_model, XPRS_DBL_ATTR_START, model.solution_hint().var_inde
// const absl::optional<LazyMutableCopy<MPModelProto>>
// optional_model =
// ExtractValidMPModelOrPopulateResponseStatus(request, &response);
// if (!optional_model) return response;
// const MPModelProto& model = optional_model->get();
// // We set `xpress_env` to point to a new environment if no existing one
// is
// // provided. We must make sure that we free this environment when we exit
// this
// // function.
// bool xpress_env_was_created = false;
// auto xpress_env_deleter = absl::MakeCleanup([&]() {
// if (xpress_env_was_created && xpress_env != nullptr) {
// XPRSfreeenv(xpress_env);
// }
// });
// if (xpress_env == nullptr) {
// ASSIGN_OR_RETURN(xpress_env, GetXPressEnv());
// xpress_env_was_created = true;
// }
// XPRSprob xpress_model = nullptr;
// auto xpress_model_deleter = absl::MakeCleanup([&]() {
// const int error_code = XPRSfreemodel(xpress_model);
// LOG_IF(DFATAL, error_code != XPRS_OK)
// << "XPRSfreemodel failed with error " << error_code << ": "
// << XPRSgeterrormsg(xpress_env);
// });
// // `xpress_env` references ther XPRSenv argument.
// #define RETURN_IF_XPRESS_ERROR(x) \
// RETURN_IF_ERROR( \
// XPressCodeToUtilStatus(x, __FILE__, __LINE__, #x, xpress_env));
// RETURN_IF_XPRESS_ERROR(XPRSnewmodel(xpress_env, &xpress_model,
// model.name().c_str(),
// /*numvars=*/0,
// /*obj=*/nullptr,
// /*lb=*/nullptr,
// /*ub=*/nullptr,
// /*vtype=*/nullptr,
// /*varnames=*/nullptr));
// XPRSprob const model_env = XPRSgetenv(xpress_model);
// if (request.has_solver_specific_parameters()) {
// const auto parameters_status = SetSolverSpecificParameters(
// request.solver_specific_parameters(), model_env);
// if (!parameters_status.ok()) {
// response.set_status(MPSOLVER_MODEL_INVALID_SOLVER_PARAMETERS);
// response.set_status_str(
// std::string(parameters_status.message())); // NOLINT
// return response;
// }
// }
// if (request.solver_time_limit_seconds() > 0) {
// RETURN_IF_XPRESS_ERROR(
// XPRSsetdblparam(model_env, XPRS_DBL_PAR_TIMELIMIT,
// request.solver_time_limit_seconds()));
// }
// RETURN_IF_XPRESS_ERROR(
// XPRSsetintparam(model_env, XPRS_INT_PAR_OUTPUTFLAG,
// request.enable_internal_solver_output()));
// const int variable_size = model.variable_size();
// bool has_integer_variables = false;
// {
// std::vector<double> obj_coeffs(variable_size, 0);
// std::vector<double> lb(variable_size);
// std::vector<double> ub(variable_size);
// std::vector<char> ctype(variable_size);
// std::vector<const char*> varnames(variable_size);
// for (int v = 0; v < variable_size; ++v) {
// const MPVariableProto& variable = model.variable(v);
// obj_coeffs[v] = variable.objective_coefficient();
// lb[v] = variable.lower_bound();
// ub[v] = variable.upper_bound();
// ctype[v] = variable.is_integer() &&
// request.solver_type() ==
// MPModelRequest::XPRESS_MIXED_INTEGER_PROGRAMMING
// ? XPRS_INTEGER
// : XPRS_CONTINUOUS;
// if (variable.is_integer()) has_integer_variables = true;
// if (!variable.name().empty()) varnames[v] = variable.name().c_str();
// }
// RETURN_IF_XPRESS_ERROR(
// XPRSaddvars(xpress_model, variable_size, 0, nullptr, nullptr,
// nullptr,
// /*obj=*/obj_coeffs.data(),
// /*lb=*/lb.data(), /*ub=*/ub.data(),
// /*vtype=*/ctype.data(),
// /*varnames=*/const_cast<char**>(varnames.data())));
// // Set solution hints if any.
// for (int i = 0; i < model.solution_hint().var_index_size(); ++i) {
// RETURN_IF_XPRESS_ERROR(XPRSsetdblattrelement(
// xpress_model, XPRS_DBL_ATTR_START,
// model.solution_hint().var_index(i),
// model.solution_hint().var_value(i)));
// }
// }
// {
// std::vector<int> ct_variables;
// std::vector<double> ct_coefficients;
// for (int c = 0; c < model.constraint_size(); ++c) {
// const MPConstraintProto& constraint = model.constraint(c);
// const int size = constraint.var_index_size();
// ct_variables.resize(size, 0);
// ct_coefficients.resize(size, 0);
// for (int i = 0; i < size; ++i) {
// ct_variables[i] = constraint.var_index(i);
// ct_coefficients[i] = constraint.coefficient(i);
// }
// // Using XPRSaddrangeconstr for constraints that don't require it
// adds
// // a slack which is not always removed by presolve.
// if (constraint.lower_bound() == constraint.upper_bound()) {
// RETURN_IF_XPRESS_ERROR(XPRSaddconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*sense=*/XPRS_EQUAL, /*rhs=*/constraint.lower_bound(),
// /*constrname=*/constraint.name().c_str()));
// } else if (constraint.lower_bound() ==
// -std::numeric_limits<double>::infinity()) {
// RETURN_IF_XPRESS_ERROR(XPRSaddconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*sense=*/XPRS_LESS_EQUAL, /*rhs=*/constraint.upper_bound(),
// /*constrname=*/constraint.name().c_str()));
// } else if (constraint.upper_bound() ==
// std::numeric_limits<double>::infinity()) {
// RETURN_IF_XPRESS_ERROR(XPRSaddconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*sense=*/XPRS_GREATER_EQUAL, /*rhs=*/constraint.lower_bound(),
// /*constrname=*/constraint.name().c_str()));
// } else {
// RETURN_IF_XPRESS_ERROR(XPRSaddrangeconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*lower=*/constraint.lower_bound(),
// /*upper=*/constraint.upper_bound(),
// /*constrname=*/constraint.name().c_str()));
// }
// }
// for (const auto& gen_cst : model.general_constraint()) {
// switch (gen_cst.general_constraint_case()) {
// case MPGeneralConstraintProto::kIndicatorConstraint: {
// RETURN_IF_XPRESS_ERROR(AddIndicatorConstraint(
// gen_cst, xpress_model, &ct_variables, &ct_coefficients));
// break;
// }
// case MPGeneralConstraintProto::kSosConstraint: {
// RETURN_IF_XPRESS_ERROR(AddSosConstraint(gen_cst.sos_constraint(),
// xpress_model,
// &ct_variables,
// &ct_coefficients));
// break;
// }
// case MPGeneralConstraintProto::kQuadraticConstraint: {
// RETURN_IF_XPRESS_ERROR(AddQuadraticConstraint(gen_cst,
// xpress_model)); break;
// }
// case MPGeneralConstraintProto::kAbsConstraint: {
// RETURN_IF_XPRESS_ERROR(XPRSaddgenconstrAbs(
// xpress_model,
// /*name=*/gen_cst.name().c_str(),
// /*resvar=*/gen_cst.abs_constraint().resultant_var_index(),
// /*argvar=*/gen_cst.abs_constraint().var_index()));
// break;
// }
// case MPGeneralConstraintProto::kAndConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddAndConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// case MPGeneralConstraintProto::kOrConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddOrConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// case MPGeneralConstraintProto::kMinConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddMinConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// case MPGeneralConstraintProto::kMaxConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddMaxConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// default:
// return absl::UnimplementedError(
// absl::StrFormat("General constraints of type %i not
// supported.",
// gen_cst.general_constraint_case()));
// }
// }
// }
// RETURN_IF_XPRESS_ERROR(XPRSsetintattr(xpress_model,
// XPRS_INT_ATTR_MODELSENSE,
// model.maximize() ? -1 : 1));
// RETURN_IF_XPRESS_ERROR(XPRSsetdblattr(xpress_model, XPRS_DBL_ATTR_OBJCON,
// model.objective_offset()));
// if (model.has_quadratic_objective()) {
// MPQuadraticObjective qobj = model.quadratic_objective();
// if (qobj.coefficient_size() > 0) {
// RETURN_IF_XPRESS_ERROR(
// XPRSaddqpterms(xpress_model, /*numqnz=*/qobj.coefficient_size(),
// /*qrow=*/qobj.mutable_qvar1_index()->mutable_data(),
// /*qcol=*/qobj.mutable_qvar2_index()->mutable_data(),
// /*qval=*/qobj.mutable_coefficient()->mutable_data()));
// }
// }
// RETURN_IF_XPRESS_ERROR(XPRSupdatemodel(xpress_model));
// const absl::Time time_before = absl::Now();
// UserTimer user_timer;
// user_timer.Start();
// RETURN_IF_XPRESS_ERROR(XPRSoptimize(xpress_model));
// const absl::Duration solving_duration = absl::Now() - time_before;
// user_timer.Stop();
// VLOG(1) << "Finished solving in XPressSolveProto(), walltime = "
// << solving_duration << ", usertime = " <<
// user_timer.GetDuration();
// response.mutable_solve_info()->set_solve_wall_time_seconds(
// absl::ToDoubleSeconds(solving_duration));
// response.mutable_solve_info()->set_solve_user_time_seconds(
// absl::ToDoubleSeconds(user_timer.GetDuration()));
// int optimization_status = 0;
// RETURN_IF_XPRESS_ERROR(
// XPRSgetintattr(xpress_model, XPRS_INT_ATTR_STATUS,
// &optimization_status));
// int solution_count = 0;
// RETURN_IF_XPRESS_ERROR(
// XPRSgetintattr(xpress_model, XPRS_INT_ATTR_SOLCOUNT,
// &solution_count));
// switch (optimization_status) {
// case XPRS_OPTIMAL:
// response.set_status(MPSOLVER_OPTIMAL);
// break;
// case XPRS_INF_OR_UNBD:
// DLOG(INFO) << "XPress solve returned XPRS_INF_OR_UNBD, which we treat
// as "
// "INFEASIBLE even though it may mean UNBOUNDED.";
// response.set_status_str(
// "The model may actually be unbounded: XPress returned "
// "XPRS_INF_OR_UNBD");
// ABSL_FALLTHROUGH_INTENDED;
// case XPRS_INFEASIBLE:
// response.set_status(MPSOLVER_INFEASIBLE);
// break;
// case XPRS_UNBOUNDED:
// response.set_status(MPSOLVER_UNBOUNDED);
// break;
// default: {
// if (solution_count > 0) {
// response.set_status(MPSOLVER_FEASIBLE);
// } else {
// response.set_status(MPSOLVER_NOT_SOLVED);
// response.set_status_str(
// absl::StrFormat("XPress status code %d", optimization_status));
// }
// break;
// }
// }
// if (solution_count > 0 && (response.status() == MPSOLVER_FEASIBLE ||
// response.status() == MPSOLVER_OPTIMAL)) {
// double objective_value = 0;
// RETURN_IF_XPRESS_ERROR(
// XPRSgetdblattr(xpress_model, XPRS_DBL_ATTR_OBJVAL,
// &objective_value));
// response.set_objective_value(objective_value);
// double best_objective_bound = 0;
// const int error = XPRSgetdblattr(xpress_model, XPRS_DBL_ATTR_OBJBOUND,
// &best_objective_bound);
// if (response.status() == MPSOLVER_OPTIMAL &&
// error == XPRS_ERROR_DATA_NOT_AVAILABLE) {
// // If the presolve deletes all variables, there's no best bound.
// response.set_best_objective_bound(objective_value);
// } else {
// RETURN_IF_XPRESS_ERROR(error);
// response.set_best_objective_bound(best_objective_bound);
// }
// response.mutable_variable_value()->Resize(variable_size, 0);
// RETURN_IF_XPRESS_ERROR(
// XPRSgetdblattrarray(xpress_model, XPRS_DBL_ATTR_X, 0,
// variable_size,
// response.mutable_variable_value()->mutable_data()));
// // NOTE, XPressSolveProto() is exposed to external clients via MPSolver
// API,
// // which assumes the solution values of integer variables are rounded
// to
// // integer values.
// auto round_values_of_integer_variables_fn =
// [&](google::protobuf::RepeatedField<double>* values) {
// for (int v = 0; v < variable_size; ++v) {
// if (model.variable(v).is_integer()) {
// (*values)[v] = std::round((*values)[v]);
// }
// }
// };
// round_values_of_integer_variables_fn(response.mutable_variable_value());
// if (!has_integer_variables && model.general_constraint_size() == 0) {
// response.mutable_dual_value()->Resize(model.constraint_size(), 0);
// RETURN_IF_XPRESS_ERROR(XPRSgetdblattrarray(
// xpress_model, XPRS_DBL_ATTR_PI, 0, model.constraint_size(),
// response.mutable_dual_value()->mutable_data()));
// }
// const int additional_solutions = std::min(
// solution_count,
// std::min(request.populate_additional_solutions_up_to(),
// std::numeric_limits<int32_t>::max() - 1) +
// 1);
// for (int i = 1; i < additional_solutions; ++i) {
// RETURN_IF_XPRESS_ERROR(
// XPRSsetintparam(model_env, XPRS_INT_PAR_SOLUTIONNUMBER, i));
// MPSolution* solution = response.add_additional_solutions();
// solution->mutable_variable_value()->Resize(variable_size, 0);
// double objective_value = 0;
// RETURN_IF_XPRESS_ERROR(XPRSgetdblattr(
// xpress_model, XPRS_DBL_ATTR_POOLOBJVAL, &objective_value));
// solution->set_objective_value(objective_value);
// RETURN_IF_XPRESS_ERROR(XPRSgetdblattrarray(
// xpress_model, XPRS_DBL_ATTR_XN, 0, variable_size,
// solution->mutable_variable_value()->mutable_data()));
// round_values_of_integer_variables_fn(solution->mutable_variable_value());
// }
// }
// #undef RETURN_IF_XPRESS_ERRORx(i),
// model.solution_hint().var_value(i)));
// }
// }
// {
// std::vector<int> ct_variables;
// std::vector<double> ct_coefficients;
// for (int c = 0; c < model.constraint_size(); ++c) {
// const MPConstraintProto& constraint = model.constraint(c);
// const int size = constraint.var_index_size();
// ct_variables.resize(size, 0);
// ct_coefficients.resize(size, 0);
// for (int i = 0; i < size; ++i) {
// ct_variables[i] = constraint.var_index(i);
// ct_coefficients[i] = constraint.coefficient(i);
// }
// // Using XPRSaddrangeconstr for constraints that don't require it
// adds
// // a slack which is not always removed by presolve.
// if (constraint.lower_bound() == constraint.upper_bound()) {
// RETURN_IF_XPRESS_ERROR(XPRSaddconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*sense=*/XPRS_EQUAL, /*rhs=*/constraint.lower_bound(),
// /*constrname=*/constraint.name().c_str()));
// } else if (constraint.lower_bound() ==
// -std::numeric_limits<double>::infinity()) {
// RETURN_IF_XPRESS_ERROR(XPRSaddconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*sense=*/XPRS_LESS_EQUAL, /*rhs=*/constraint.upper_bound(),
// /*constrname=*/constraint.name().c_str()));
// } else if (constraint.upper_bound() ==
// std::numeric_limits<double>::infinity()) {
// RETURN_IF_XPRESS_ERROR(XPRSaddconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*sense=*/XPRS_GREATER_EQUAL, /*rhs=*/constraint.lower_bound(),
// /*constrname=*/constraint.name().c_str()));
// } else {
// RETURN_IF_XPRESS_ERROR(XPRSaddrangeconstr(
// xpress_model, /*numnz=*/size, /*cind=*/ct_variables.data(),
// /*cval=*/ct_coefficients.data(),
// /*lower=*/constraint.lower_bound(),
// /*upper=*/constraint.upper_bound(),
// /*constrname=*/constraint.name().c_str()));
// }
// }
// for (const auto& gen_cst : model.general_constraint()) {
// switch (gen_cst.general_constraint_case()) {
// case MPGeneralConstraintProto::kIndicatorConstraint: {
// RETURN_IF_XPRESS_ERROR(AddIndicatorConstraint(
// gen_cst, xpress_model, &ct_variables, &ct_coefficients));
// break;
// }
// case MPGeneralConstraintProto::kSosConstraint: {
// RETURN_IF_XPRESS_ERROR(AddSosConstraint(gen_cst.sos_constraint(),
// xpress_model,
// &ct_variables,
// &ct_coefficients));
// break;
// }
// case MPGeneralConstraintProto::kQuadraticConstraint: {
// RETURN_IF_XPRESS_ERROR(AddQuadraticConstraint(gen_cst,
// xpress_model)); break;
// }
// case MPGeneralConstraintProto::kAbsConstraint: {
// RETURN_IF_XPRESS_ERROR(XPRSaddgenconstrAbs(
// xpress_model,
// /*name=*/gen_cst.name().c_str(),
// /*resvar=*/gen_cst.abs_constraint().resultant_var_index(),
// /*argvar=*/gen_cst.abs_constraint().var_index()));
// break;
// }
// case MPGeneralConstraintProto::kAndConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddAndConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// case MPGeneralConstraintProto::kOrConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddOrConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// case MPGeneralConstraintProto::kMinConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddMinConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// case MPGeneralConstraintProto::kMaxConstraint: {
// RETURN_IF_XPRESS_ERROR(
// AddMaxConstraint(gen_cst, xpress_model, &ct_variables));
// break;
// }
// default:
// return absl::UnimplementedError(
// absl::StrFormat("General constraints of type %i not
// supported.",
// gen_cst.general_constraint_case()));
// }
// }
// }
// RETURN_IF_XPRESS_ERROR(XPRSsetintattr(xpress_model,
// XPRS_INT_ATTR_MODELSENSE,
// model.maximize() ? -1 : 1));
// RETURN_IF_XPRESS_ERROR(XPRSsetdblattr(xpress_model, XPRS_DBL_ATTR_OBJCON,
// model.objective_offset()));
// if (model.has_quadratic_objective()) {
// MPQuadraticObjective qobj = model.quadratic_objective();
// if (qobj.coefficient_size() > 0) {
// RETURN_IF_XPRESS_ERROR(
// XPRSaddqpterms(xpress_model, /*numqnz=*/qobj.coefficient_size(),
// /*qrow=*/qobj.mutable_qvar1_index()->mutable_data(),
// /*qcol=*/qobj.mutable_qvar2_index()->mutable_data(),
// /*qval=*/qobj.mutable_coefficient()->mutable_data()));
// }
// }
// RETURN_IF_XPRESS_ERROR(XPRSupdatemodel(xpress_model));
// const absl::Time time_before = absl::Now();
// UserTimer user_timer;
// user_timer.Start();
// RETURN_IF_XPRESS_ERROR(XPRSoptimize(xpress_model));
// const absl::Duration solving_duration = absl::Now() - time_before;
// user_timer.Stop();
// VLOG(1) << "Finished solving in XPressSolveProto(), walltime = "
// << solving_duration << ", usertime = " <<
// user_timer.GetDuration();
// response.mutable_solve_info()->set_solve_wall_time_seconds(
// absl::ToDoubleSeconds(solving_duration));
// response.mutable_solve_info()->set_solve_user_time_seconds(
// absl::ToDoubleSeconds(user_timer.GetDuration()));
// int optimization_status = 0;
// RETURN_IF_XPRESS_ERROR(
// XPRSgetintattr(xpress_model, XPRS_INT_ATTR_STATUS,
// &optimization_status));
// int solution_count = 0;
// RETURN_IF_XPRESS_ERROR(
// XPRSgetintattr(xpress_model, XPRS_INT_ATTR_SOLCOUNT,
// &solution_count));
// switch (optimization_status) {
// case XPRS_OPTIMAL:
// response.set_status(MPSOLVER_OPTIMAL);
// break;
// case XPRS_INF_OR_UNBD:
// DLOG(INFO) << "XPress solve returned XPRS_INF_OR_UNBD, which we treat
// as "
// "INFEASIBLE even though it may mean UNBOUNDED.";
// response.set_status_str(
// "The model may actually be unbounded: XPress returned "
// "XPRS_INF_OR_UNBD");
// ABSL_FALLTHROUGH_INTENDED;
// case XPRS_INFEASIBLE:
// response.set_status(MPSOLVER_INFEASIBLE);
// break;
// case XPRS_UNBOUNDED:
// response.set_status(MPSOLVER_UNBOUNDED);
// break;
// default: {
// if (solution_count > 0) {
// response.set_status(MPSOLVER_FEASIBLE);
// } else {
// response.set_status(MPSOLVER_NOT_SOLVED);
// response.set_status_str(
// absl::StrFormat("XPress status code %d", optimization_status));
// }
// break;
// }
// }
// if (solution_count > 0 && (response.status() == MPSOLVER_FEASIBLE ||
// response.status() == MPSOLVER_OPTIMAL)) {
// double objective_value = 0;
// RETURN_IF_XPRESS_ERROR(
// XPRSgetdblattr(xpress_model, XPRS_DBL_ATTR_OBJVAL,
// &objective_value));
// response.set_objective_value(objective_value);
// double best_objective_bound = 0;
// const int error = XPRSgetdblattr(xpress_model, XPRS_DBL_ATTR_OBJBOUND,
// &best_objective_bound);
// if (response.status() == MPSOLVER_OPTIMAL &&
// error == XPRS_ERROR_DATA_NOT_AVAILABLE) {
// // If the presolve deletes all variables, there's no best bound.
// response.set_best_objective_bound(objective_value);
// } else {
// RETURN_IF_XPRESS_ERROR(error);
// response.set_best_objective_bound(best_objective_bound);
// }
// response.mutable_variable_value()->Resize(variable_size, 0);
// RETURN_IF_XPRESS_ERROR(
// XPRSgetdblattrarray(xpress_model, XPRS_DBL_ATTR_X, 0,
// variable_size,
// response.mutable_variable_value()->mutable_data()));
// // NOTE, XPressSolveProto() is exposed to external clients via MPSolver
// API,
// // which assumes the solution values of integer variables are rounded
// to
// // integer values.
// auto round_values_of_integer_variables_fn =
// [&](google::protobuf::RepeatedField<double>* values) {
// for (int v = 0; v < variable_size; ++v) {
// if (model.variable(v).is_integer()) {
// (*values)[v] = std::round((*values)[v]);
// }
// }
// };
// round_values_of_integer_variables_fn(response.mutable_variable_value());
// if (!has_integer_variables && model.general_constraint_size() == 0) {
// response.mutable_dual_value()->Resize(model.constraint_size(), 0);
// RETURN_IF_XPRESS_ERROR(XPRSgetdblattrarray(
// xpress_model, XPRS_DBL_ATTR_PI, 0, model.constraint_size(),
// response.mutable_dual_value()->mutable_data()));
// }
// const int additional_solutions = std::min(
// solution_count,
// std::min(request.populate_additional_solutions_up_to(),
// std::numeric_limits<int32_t>::max() - 1) +
// 1);
// for (int i = 1; i < additional_solutions; ++i) {
// RETURN_IF_XPRESS_ERROR(
// XPRSsetintparam(model_env, XPRS_INT_PAR_SOLUTIONNUMBER, i));
// MPSolution* solution = response.add_additional_solutions();
// solution->mutable_variable_value()->Resize(variable_size, 0);
// double objective_value = 0;
// RETURN_IF_XPRESS_ERROR(XPRSgetdblattr(
// xpress_model, XPRS_DBL_ATTR_POOLOBJVAL, &objective_value));
// solution->set_objective_value(objective_value);
// RETURN_IF_XPRESS_ERROR(XPRSgetdblattrarray(
// xpress_model, XPRS_DBL_ATTR_XN, 0, variable_size,
// solution->mutable_variable_value()->mutable_data()));
// round_values_of_integer_variables_fn(solution->mutable_variable_value());
// }
// }
// #undef RETURN_IF_XPRESS_ERROR
return response;
}
} // namespace operations_research