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:
committed by
Mizux Seiha
parent
03e2551e61
commit
a7f49a2585
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
492
ortools/linear_solver/proto_solver/preprocessor.cc
Normal file
492
ortools/linear_solver/proto_solver/preprocessor.cc
Normal 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
|
||||
198
ortools/linear_solver/proto_solver/preprocessor.h
Normal file
198
ortools/linear_solver/proto_solver/preprocessor.h
Normal 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_
|
||||
@@ -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.
|
||||
|
||||
126
ortools/linear_solver/proto_solver/scip_params.cc
Normal file
126
ortools/linear_solver/proto_solver/scip_params.cc
Normal 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
|
||||
@@ -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_
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user