25#include "absl/log/check.h"
26#include "absl/memory/memory.h"
27#include "absl/status/status.h"
28#include "absl/status/statusor.h"
29#include "absl/strings/str_cat.h"
30#include "absl/strings/str_join.h"
31#include "absl/time/clock.h"
32#include "absl/time/time.h"
33#include "absl/types/span.h"
53struct SharedSolveContext {
66 std::exception_ptr callbackException;
75template <
typename ProtoT,
typename CbT>
77 using proto_type =
typename ProtoT::proto_type;
78 SharedSolveContext* ctx;
80 ScopedCallback(ScopedCallback
const&) =
delete;
81 ScopedCallback(ScopedCallback&&) =
delete;
82 ScopedCallback& operator=(ScopedCallback
const&) =
delete;
83 ScopedCallback& operator=(ScopedCallback&&) =
delete;
88 template <
typename FuncPtr>
92 template <
typename R,
typename... Args>
93 struct ExWrapper<R (*)(
XPRSprob,
void*, Args...)> {
95 static auto low_level_cb(
XPRSprob prob,
void* cbdata, Args... args)
try {
96 return ProtoT::glueFn(prob, cbdata, args...);
99 ScopedCallback* cb =
reinterpret_cast<ScopedCallback*
>(cbdata);
101 cb->SetCallbackException(std::current_exception());
102 if constexpr (std::is_convertible_v<R, int>)
return static_cast<int>(1);
105 const proto_type low_level_cb = ExWrapper<proto_type>::low_level_cb;
110 ScopedCallback() : ctx(nullptr) {}
112 inline absl::Status Add(SharedSolveContext* context, CbT cb) {
115 ProtoT::Add(ctx->xpress, low_level_cb,
reinterpret_cast<void*
>(
this)));
117 return absl::OkStatus();
120 inline void Interrupt(
int reason) {
121 CHECK_OK(ctx->xpress->Interrupt(reason));
124 inline void SetCallbackException(std::exception_ptr ex) {
125 const absl::MutexLock lock(&ctx->mutex);
126 if (!ctx->callbackException) ctx->callbackException = ex;
131 ProtoT::Remove(ctx->xpress, low_level_cb,
reinterpret_cast<void*
>(
this));
151#define DEFINE_SCOPED_CB(CB_NAME, ORTOOLS_CB, CB_RET_TYPE, ARGS) \
152 CB_RET_TYPE CB_NAME##GlueFn ARGS; \
153 struct CB_NAME##Traits { \
154 using proto_type = CB_RET_TYPE(XPRS_CC*) ARGS; \
155 static constexpr proto_type glueFn = CB_NAME##GlueFn; \
156 static absl::Status Add(Xpress* xpress, proto_type fn, void* data) { \
157 return xpress->AddCb##CB_NAME(fn, data, 0); \
159 static void Remove(Xpress* xpress, proto_type fn, void* data) { \
160 CHECK_OK(xpress->RemoveCb##CB_NAME(fn, data)); \
163 using CB_NAME##ScopedCb = ScopedCallback<CB_NAME##Traits, ORTOOLS_CB>; \
164 CB_RET_TYPE CB_NAME##GlueFn ARGS
170 (
XPRSprob prob,
void* cbdata,
char const* msg,
int len,
172 auto cb =
reinterpret_cast<MessageScopedCb*
>(cbdata);
182 cb->or_tools_cb(std::vector<std::string>{
""});
186 std::vector<std::string> lines;
191 while (start <= len) {
193 while (
end < len && msg[
end] !=
'\n') {
197 lines.emplace_back(msg, start,
end - start);
203 cb->or_tools_cb(lines);
211 auto cb =
reinterpret_cast<ChecktimeScopedCb*
>(cbdata);
216 if (cb->or_tools_cb->IsInterrupted()) {
223static void stdoutMessageCallback(std::vector<std::string>
const& lines) {
224 for (
auto& l : lines) std::cout << l <<
'\n';
271class ScopedSolverContext {
273 SharedSolveContext shared_ctx;
275 MessageScopedCb messageCallback;
277 ChecktimeScopedCb checktimeCallback;
279 std::function<void()> removeInterrupterCallback;
283 std::variant<int64_t, double, std::string> value;
291 std::vector<OneControl> modifiedControls;
294 ScopedSolverContext(Xpress* xpress) : removeInterrupterCallback(nullptr) {
295 shared_ctx.xpress = xpress;
297 absl::Status Set(
int id, int32_t value) {
return Set(
id, int64_t(value)); }
298 absl::Status Set(
int id, int64_t value) {
300 modifiedControls.push_back({id, old});
302 return absl::OkStatus();
304 absl::Status Set(
int id,
double value) {
306 modifiedControls.push_back({id, old});
308 return absl::OkStatus();
310 absl::Status Set(
int id, std::string
const& value) {
312 modifiedControls.push_back({id, old});
314 return absl::OkStatus();
318 const SolveInterrupter* interrupter) {
319 if (message_callback)
330 SolveInterrupter::CallbackId
const id =
331 interrupter->AddInterruptionCallback(
333 removeInterrupterCallback = [=] {
334 interrupter->RemoveInterruptionCallback(
id);
343 return absl::OkStatus();
346 absl::Status ApplyParameters(
const SolveParametersProto& parameters,
348 std::string* export_model,
bool* force_postsolve,
349 bool* stop_after_lp) {
350 std::vector<std::string> warnings;
352 if (parameters.enable_output()) {
355 if (!message_callback) {
357 messageCallback.Add(&shared_ctx, stdoutMessageCallback));
360 absl::Duration time_limit = absl::InfiniteDuration();
361 if (parameters.has_time_limit()) {
365 if (time_limit < absl::InfiniteDuration()) {
368 if (parameters.has_iteration_limit()) {
378 if (parameters.has_node_limit()) {
381 if (parameters.has_cutoff_limit()) {
384 if (parameters.has_objective_limit()) {
391 warnings.emplace_back(
392 "XpressSolver does not support objective_limit; use cutoff_limit "
395 if (parameters.has_best_bound_limit()) {
396 warnings.emplace_back(
"XpressSolver does not support best_bound_limit");
398 if (parameters.has_solution_limit()) {
401 if (parameters.has_threads() && parameters.threads() > 0)
403 if (parameters.has_random_seed()) {
406 if (parameters.has_absolute_gap_tolerance())
409 if (parameters.has_relative_gap_tolerance())
412 if (parameters.has_solution_pool_size()) {
413 warnings.emplace_back(
"XpressSolver does not support solution_pool_size");
417 switch (parameters.lp_algorithm()) {
437 int presolvePasses = -1;
438 switch (parameters.presolve()) {
455 if (presolvePasses > 0)
459 switch (parameters.cuts()) {
478 switch (parameters.heuristics()) {
496 parameters.xpress().parameters()) {
497 std::string
const& name = parameter.name();
498 std::string
const& value = parameter.value();
503 if (name ==
"EXPORT_MODEL") {
504 if (export_model) *export_model = value;
506 }
else if (name ==
"FORCE_POSTSOLVE") {
507 if (!absl::SimpleAtoi(value, &l))
509 <<
"value " << value <<
" for FORCE_POSTSOLVE"
510 <<
" is not an integer";
511 if (force_postsolve) *force_postsolve = l != 0;
513 }
else if (name ==
"STOP_AFTER_LP") {
514 if (!absl::SimpleAtoi(value, &l))
516 <<
"value " << value <<
" for STOP_AFTER_LP"
517 <<
" is not an integer";
518 if (stop_after_lp) *stop_after_lp = l != 0;
522 shared_ctx.xpress->GetControlInfo(name.c_str(), &
id, &type));
526 if (!absl::SimpleAtoi(value, &l))
528 <<
"value " << value <<
" for " << name
529 <<
" is not an integer";
530 if (type ==
XPRS_TYPE_INT && (l > std::numeric_limits<int>::max() ||
531 l < std::numeric_limits<int>::min()))
533 <<
"value " << value <<
" for " << name
534 <<
" is out of range";
538 if (!absl::SimpleAtod(value, &d))
540 <<
"value " << value <<
" for " << name
541 <<
" is not a floating pointer number";
549 <<
"bad control type for " << name;
553 if (!warnings.empty()) {
554 return absl::InvalidArgumentError(absl::StrJoin(warnings,
"; "));
556 return absl::OkStatus();
558 absl::Status ApplyModelParameters(
559 ModelSolveParametersProto
const& model_parameters,
564 XpressSolver::LinearConstraintData>
const&
565 linear_constraints_map,
574 if (model_parameters.has_initial_basis()) {
580 if (state & ((1 << 1) | (1 << 2))) {
582 <<
"cannot set basis for model in presolved space (consider "
585 auto const& basis = model_parameters.initial_basis();
586 std::vector<int> xpress_var_basis_status(cols);
587 for (
const auto [
id, value] :
MakeView(basis.variable_status())) {
588 xpress_var_basis_status[variables_map.at(
id)] =
592 std::vector<int> xpress_constr_basis_status(rows);
593 for (
const auto [
id, value] :
MakeView(basis.constraint_status())) {
594 xpress_constr_basis_status[linear_constraints_map.at(
id)
600 xpress_constr_basis_status, xpress_var_basis_status));
602 std::vector<int> colind;
607 if (model_parameters.solution_hints_size() > 0) {
608 unsigned int cnt = 0;
609 std::vector<double> mipStart;
610 colind.reserve(cols);
611 mipStart.reserve(cols);
612 for (
auto const& hint : model_parameters.solution_hints()) {
615 for (
const auto [
id, value] :
MakeView(hint.variable_values())) {
616 colind.push_back(variables_map.at(
id));
617 mipStart.push_back(value);
619 if (mipStart.size() > cols)
621 <<
"more solution hints than columns";
624 mipStart, colind, absl::StrCat(
"SolutionHint", cnt).c_str()));
630 if (model_parameters.has_branching_priorities()) {
631 auto const& prios = model_parameters.branching_priorities();
633 colind.reserve(prios.ids_size());
634 std::vector<int> priority;
635 priority.reserve(prios.ids_size());
636 for (
const auto [
id, prio] :
MakeView(prios)) {
637 colind.push_back(variables_map.at(
id));
641 if (prio < 0 || prio > 1000)
643 <<
"Xpress only allows branching priorities in [0,1000]";
649 absl::MakeSpan(colind), absl::MakeSpan(priority), std::nullopt,
650 std::nullopt, std::nullopt));
654 if (model_parameters.has_primary_objective_parameters()) {
655 auto const& p = model_parameters.primary_objective_parameters();
659 if (p.has_objective_degradation_absolute_tolerance()) {
662 p.objective_degradation_absolute_tolerance()));
664 if (p.has_objective_degradation_relative_tolerance()) {
667 p.objective_degradation_relative_tolerance()));
669 if (p.has_time_limit()) {
671 if (objectives_map.size() > 0) {
673 <<
"Xpress does not support per-objective time limits";
683 for (
auto const& [
id, p] :
684 model_parameters.auxiliary_objective_parameters()) {
685 if (p.has_objective_degradation_absolute_tolerance()) {
688 p.objective_degradation_absolute_tolerance()));
690 if (p.has_objective_degradation_relative_tolerance()) {
693 p.objective_degradation_relative_tolerance()));
695 if (p.has_time_limit()) {
697 <<
"Xpress does not support per-objective time limits";
701 if (model_parameters.lazy_linear_constraint_ids_size() > 0) {
702 std::vector<int> delayedRows;
703 delayedRows.reserve(rows);
704 for (
auto const& idx : model_parameters.lazy_linear_constraint_ids()) {
705 delayedRows.push_back(linear_constraints_map.at(idx).constraint_index);
707 if (delayedRows.size() > rows)
709 <<
"more lazy constraints than rows";
714 return absl::OkStatus();
717 void Interrupt(
int reason) { CHECK_OK(shared_ctx.xpress->Interrupt(reason)); }
719 void ReraiseException() {
720 if (shared_ctx.callbackException) {
721 std::exception_ptr ex = shared_ctx.callbackException;
722 shared_ctx.callbackException =
nullptr;
723 std::rethrow_exception(ex);
727 ~ScopedSolverContext() {
728 for (
auto it = modifiedControls.rbegin(); it != modifiedControls.rend();
730 switch (it->value.index()) {
731 case OneControl::INT_CONTROL:
732 CHECK_OK(shared_ctx.xpress->SetIntControl64(
733 it->id, std::get<int64_t>(it->value)));
735 case OneControl::DBL_CONTROL:
736 CHECK_OK(shared_ctx.xpress->SetDblControl(
737 it->id, std::get<double>(it->value)));
739 case OneControl::STR_CONTROL:
740 CHECK_OK(shared_ctx.xpress->SetStrControl(
741 it->id, std::get<std::string>(it->value).c_str()));
745 if (removeInterrupterCallback) removeInterrupterCallback();
747 if (shared_ctx.callbackException)
748 std::rethrow_exception(shared_ctx.callbackException);
753enum class SingletonType {
767absl::StatusOr<std::optional<XpressSolver::VarId>> ExtractSingleton(
769 double const constant = expr.offset();
770 if (expr.ids_size() == 1 && constant == 0.0) {
772 double const coef = expr.coefficients(0);
774 case SingletonType::SOS:
778 <<
"Xpress does not support coefficient " << coef
779 <<
" in SOS (consider using auxiliary variables?)";
782 case SingletonType::SOCBound:
783 case SingletonType::SOCNorm:
788 <<
"Xpress does not support coefficient " << coef
789 <<
" in a second order cone constraint "
790 << (type == SingletonType::SOCBound ?
"bound" :
"norm")
791 <<
" (consider using auxiliary variables?)";
795 if (p_coef) *p_coef = coef;
796 return std::optional<XpressSolver::VarId>(expr.ids(0));
797 }
else if (expr.ids_size() == 0) {
800 case SingletonType::SOS:
805 <<
"Xpress does not support constant expressions in SOS "
806 "(consider using auxiliary variables?)";
807 case SingletonType::SOCBound:
809 if (constant < 0.0) {
811 <<
"Xpress does not support constant " << constant
812 <<
" in a second order cone constraint bound (consider using "
813 "auxiliary variables?)";
816 case SingletonType::SOCNorm:
820 <<
"Xpress does not support constants in a second order cone "
821 "constraint norm (consider using auxiliary variables?)";
823 if (p_coef) *p_coef = constant;
827 static char const*
const name[] = {
"SOS",
828 "second order cone constraint bound",
829 "second order cone constraint norm"};
831 <<
"Xpress does not support general linear expressions in "
832 << name[
static_cast<int>(type)]
833 <<
" (consider using auxiliary variables?)";
842 static std::string
const& GetName(T
const& container,
int i) {
843 return container.names(i);
848template <
typename K,
typename V>
849struct NameResolver<google::protobuf::Map<K, V>> {
850 static std::string
const& GetName(
851 google::protobuf::Map<K, V>
const& container,
852 typename google::protobuf::Map<K, V>::const_iterator
const& i) {
853 return i->second.name();
861template <
typename T,
typename I>
862absl::Status AddNames(
Xpress* xpress,
int type,
int offset, I
begin, I
end,
863 T
const& container) {
864 std::vector<char> buffer;
865 int i = 0, start = 0;
867 std::string
const& name = NameResolver<T>::GetName(container,
begin);
868 char const* c_name = name.c_str();
869 buffer.insert(buffer.end(), c_name, c_name + name.size() + 1);
871 if (buffer.size() > 1024 * 1024) {
873 xpress->AddNames(type, buffer, offset + start, offset + i));
882 xpress->AddNames(type, buffer, offset + start, offset + i - 1));
884 return absl::OkStatus();
910 return absl::InvalidArgumentError(
"Xpress is not correctly installed.");
923 absl::WrapUnique(
new XpressSolver(std::move(xpr), extract_names));
925 return xpress_solver;
928absl::Status XpressSolver::LoadModel(
const ModelProto& input_model) {
929 CHECK(xpress_ !=
nullptr);
939 absl::flat_hash_set<AuxiliaryObjectiveId> prios = {
942 auto const prio = obj.priority();
943 if (!prios.insert(prio).second) {
945 <<
"repeated objective priority: " << prio;
955 return absl::OkStatus();
958absl::Status XpressSolver::AddNewVariables(
962 const int num_new_variables = new_variables.lower_bounds().size();
963 std::vector<char> variable_type(num_new_variables);
966 bool have_integers =
false;
967 for (
int j = 0; j < num_new_variables; ++j) {
968 const VarId id = new_variables.ids(j);
970 if (new_variables.integers(j)) {
974 have_integers =
true;
979 if (!have_integers) {
982 variable_type.clear();
985 xpress_->AddVars(num_new_variables, {}, new_variables.lower_bounds(),
986 new_variables.upper_bounds(), variable_type));
988 if (extract_names_) {
990 num_old_variables, 0, num_new_variables,
994 return absl::OkStatus();
997XpressSolver::XpressSolver(std::unique_ptr<Xpress> g_xpress,
bool extract_names)
998 : xpress_(std::move(g_xpress)), extract_names_(extract_names) {}
1000void XpressSolver::ExtractBounds(
double lb,
double ub,
char& sense,
double& rhs,
1005 const bool lb_is_xprs_neg_inf = lb <= kMinusInf;
1006 const bool ub_is_xprs_pos_inf = ub >= kPlusInf;
1007 if (lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
1018 }
else if (lb_is_xprs_neg_inf && !ub_is_xprs_pos_inf) {
1021 }
else if (!lb_is_xprs_neg_inf && ub_is_xprs_pos_inf) {
1024 }
else if (lb == ub) {
1034absl::Status XpressSolver::AddNewLinearConstraints(
1039 const int num_new_constraints = constraints.lower_bounds().size();
1040 std::vector<char> constraint_sense;
1041 constraint_sense.reserve(num_new_constraints);
1042 std::vector<double> constraint_rhs;
1043 constraint_rhs.reserve(num_new_constraints);
1044 std::vector<double> constraint_rng;
1045 constraint_rng.reserve(num_new_constraints);
1047 for (
int i = 0;
i < num_new_constraints; ++
i) {
1048 const int64_t
id = constraints.ids(i);
1051 constraint_data.lower_bound = constraints.lower_bounds(i);
1052 constraint_data.upper_bound = constraints.upper_bounds(i);
1053 constraint_data.constraint_index =
i + n_constraints;
1057 ExtractBounds(constraint_data.lower_bound, constraint_data.upper_bound,
1059 constraint_sense.emplace_back(sense);
1060 constraint_rhs.emplace_back(rhs);
1061 constraint_rng.emplace_back(rng);
1065 xpress_->AddConstrs(constraint_sense, constraint_rhs, constraint_rng));
1066 if (extract_names_) {
1068 0, num_new_constraints, constraints));
1070 return absl::OkStatus();
1073absl::Status XpressSolver::AddObjective(
1075 std::optional<AuxiliaryObjectiveId> objective_id,
bool multiobj) {
1076 double weight = 1.0;
1077 bool haveId = objective_id.has_value();
1085 if (objective.priority() <= INT_MIN || objective.priority() > INT_MAX) {
1087 <<
"Xpress only supports 32bit signed integers as objective "
1089 << objective.priority();
1097 }
else if (!objective_id.has_value()) {
1100 is_multiobj_ =
true;
1108 if (objective.maximize() != (objsen < 0.0)) {
1116 const int num_terms = objective.quadratic_coefficients().row_ids().size();
1117 if (num_terms > 0) {
1118 if (multiobj && objective_id.has_value()) {
1120 <<
"Xpress does not support quadratic terms in anything but the "
1123 std::vector<int> first_var_index(num_terms);
1124 std::vector<int> second_var_index(num_terms);
1125 std::vector<double> coefficients(num_terms);
1126 for (
int k = 0; k < num_terms; ++k) {
1127 const int64_t row_id = objective.quadratic_coefficients().row_ids(k);
1128 const int64_t column_id =
1129 objective.quadratic_coefficients().column_ids(k);
1130 first_var_index[k] = variables_map_.at(row_id);
1131 second_var_index[k] = variables_map_.at(column_id);
1134 double m = first_var_index[k] == second_var_index[k] ? 2 : 1;
1135 coefficients[k] = objective.quadratic_coefficients().coefficients(k) * m;
1138 first_var_index, second_var_index, coefficients));
1142 std::vector<int> index;
1143 index.reserve(objective.linear_coefficients().ids_size());
1144 for (
const int64_t
id : objective.linear_coefficients().ids()) {
1145 index.push_back(variables_map_.at(
id));
1149 if (!objective_id.has_value()) {
1152 objective.offset(), index, objective.linear_coefficients().values()));
1156 static_cast<int>(-objective.priority())));
1163 xpress_->AddObjective(
1164 objective.offset(),
static_cast<int>(index.size()),
1165 absl::MakeSpan(index), objective.linear_coefficients().values(),
1167 static_cast<int>(-objective.priority()), weight));
1172 objective.offset(), index, objective.linear_coefficients().values()));
1175 return absl::OkStatus();
1199absl::Status XpressSolver::AddSOS(
1200 const google::protobuf::Map<AnyConstraintId, SosConstraintProto>& sets,
1202 if (sets.empty())
return absl::OkStatus();
1203 std::vector<XPRSint64> start;
1204 std::vector<int> colind;
1205 std::vector<double> refval;
1207 int const num_old_sets = nextId;
1208 auto* sosmap = sos1 ? &sos1_map_ : &sos2_map_;
1209 for (
auto const& [sosId, sos] : sets) {
1210 start.push_back(colind.size());
1211 auto count = sos.expressions_size();
1212 bool const has_weight = sos.weights_size() > 0;
1213 for (
decltype(count) i = 0;
i < count; ++
i) {
1214 auto const& expr = sos.expressions(i);
1215 double const weight = has_weight ? sos.weights(i) : (
i + 1);
1219 ExtractSingleton(expr, SingletonType::SOS,
nullptr));
1220 colind.push_back(variables_map_.at(
x.value()));
1221 refval.push_back(weight);
1226 std::vector<char> settype(start.size(), sos1 ?
'1' :
'2');
1228 if (extract_names_) {
1230 sets.begin(), sets.end(), sets));
1232 return absl::OkStatus();
1236 std::vector<int>& colind,
1237 std::vector<double>& coef) {
1241 auto terms = expr.ids_size();
1242 colind.reserve(colind.size() + terms);
1243 coef.reserve(coef.size() + terms);
1244 for (
decltype(terms) i = 0;
i < terms; ++
i) {
1245 colind.push_back(variables_map_.at(expr.ids(i)));
1246 coef.push_back(expr.values(i));
1251 std::vector<int>& lin_colind,
1252 std::vector<double>& lin_coef,
1253 std::vector<int>& quad_col1,
1254 std::vector<int>& quad_col2,
1255 std::vector<double>& quad_coef) {
1259 auto const& lin = expr.linear_terms();
1260 auto linTerms = lin.ids_size();
1261 lin_colind.reserve(lin_colind.size() + linTerms);
1262 lin_coef.reserve(lin_coef.size() + linTerms);
1263 for (
decltype(linTerms) i = 0;
i < linTerms; ++
i) {
1264 lin_colind.push_back(variables_map_.at(lin.ids(i)));
1265 lin_coef.push_back(lin.values(i));
1267 auto const& quad = expr.quadratic_terms();
1268 auto quadTerms = quad.row_ids_size();
1269 quad_col1.reserve(quad_col1.size() + quadTerms);
1270 quad_col2.reserve(quad_col2.size() + quadTerms);
1271 quad_coef.reserve(quad_coef.size() + quadTerms);
1272 for (
decltype(quadTerms) i = 0;
i < quadTerms; ++
i) {
1273 int const col1 = variables_map_.at(quad.row_ids(i));
1274 int const col2 = variables_map_.at(quad.column_ids(i));
1275 double coef = quad.coefficients(i);
1276 if (col1 != col2) coef *= 0.5;
1277 quad_col1.push_back(col1);
1278 quad_col2.push_back(col2);
1279 quad_coef.push_back(coef);
1284absl::Status XpressSolver::AddIndicators(
1287 if (indicators.empty())
return absl::OkStatus();
1289 size_t count = indicators.size();
1290 std::vector<double> rhs(count);
1291 std::vector<double> rng(count);
1292 std::vector<char> sense(count);
1293 std::vector<XPRSint64> start(count);
1294 std::vector<int> colind;
1295 std::vector<double> rowcoef;
1297 std::vector<int> i_rowind(count);
1298 std::vector<int> i_colind(count);
1299 std::vector<int> i_complement(count);
1301 int min_icol = std::numeric_limits<int>::max();
1302 int max_icol = std::numeric_limits<int>::min();
1303 bool check_types =
false;
1305 for (
auto const& [ortoolsId, indicator] : indicators) {
1306 start[next] = colind.size();
1307 ExtractBounds(indicator.lower_bound(), indicator.upper_bound(), sense[next],
1308 rhs[next], rng[next]);
1313 <<
"indicator constraint on ranged constraint";
1316 ExtractLinear(indicator.expression(), colind, rowcoef);
1318 i_rowind[next] = oldRows + next;
1319 if (indicator.has_indicator_id()) {
1320 i_colind[next] = variables_map_.at(indicator.indicator_id());
1321 if (i_colind[next] < min_icol) min_icol = i_colind[next];
1322 if (i_colind[next] > max_icol) max_icol = i_colind[next];
1323 i_complement[next] = indicator.activate_on_zero() ? -1 : 1;
1328 i_colind[next] = -1;
1329 i_complement[next] = 0;
1334 data.constraint_index = oldRows + next;
1335 data.lower_bound = indicator.lower_bound();
1336 data.upper_bound = indicator.upper_bound();
1344 std::vector<double> orig_lb(1 + max_icol - min_icol);
1345 std::vector<double> orig_ub(1 + max_icol - min_icol);
1346 std::vector<char> orig_type(1 + max_icol - min_icol);
1348 xpress_->GetLB(absl::MakeSpan(orig_lb), min_icol, max_icol));
1350 xpress_->GetUB(absl::MakeSpan(orig_ub), min_icol, max_icol));
1352 xpress_->GetColType(absl::MakeSpan(orig_type), min_icol, max_icol));
1353 std::vector<int> colind_bnd;
1354 std::vector<double> new_bds;
1355 std::vector<char> new_bdtype;
1356 std::vector<int> colind_type;
1357 std::vector<char> new_type;
1358 for (
auto i : i_colind) {
1359 if (i < 0)
continue;
1360 int const idx =
i - min_icol;
1361 switch (orig_type[idx]) {
1367 if (orig_lb[idx] >= 0.0 && orig_lb[idx] <= 1.0 &&
1368 orig_ub[idx] >= 0.0 && orig_ub[idx] <= 1.0) {
1369 double const l = ceil(orig_lb[idx]);
1370 double const u =
floor(orig_ub[idx]);
1379 colind_bnd.push_back(i);
1380 colind_bnd.push_back(i);
1381 new_bds.push_back(l);
1382 new_bds.push_back(u);
1383 new_bdtype.push_back(
'L');
1384 new_bdtype.push_back(
'U');
1385 colind_type.push_back(i);
1388 nonbinary_indicator_ =
true;
1393 nonbinary_indicator_ =
true;
1400 if (colind_type.size()) {
1403 if (colind_bnd.size()) {
1407 RETURN_IF_ERROR(xpress_->AddRows(sense, rhs, rng, start, colind, rowcoef));
1408 RETURN_IF_ERROR(xpress_->SetIndicators(i_rowind, i_colind, i_complement));
1409 return absl::OkStatus();
1412absl::Status XpressSolver::AddQuadraticConstraints(
1415 std::vector<int> lin_colind;
1416 std::vector<double> lin_coef;
1417 std::vector<int> quad_col1;
1418 std::vector<int> quad_col2;
1419 std::vector<double> quad_coef;
1421 for (
const auto& [ortoolsId, quad] : constraints) {
1427 ExtractBounds(quad.lower_bound(), quad.upper_bound(), sense, rhs, rng);
1433 ExtractQuadratic(quad, lin_colind, lin_coef, quad_col1, quad_col2,
1435 RETURN_IF_ERROR(xpress_->AddQRow(sense, rhs, rng, lin_colind, lin_coef,
1436 quad_col1, quad_col2, quad_coef));
1439 data.constraint_index = next;
1440 data.lower_bound = quad.lower_bound();
1441 data.upper_bound = quad.upper_bound();
1444 return absl::OkStatus();
1450absl::Status XpressSolver::AddSecondOrderConeConstraints(
1451 const google::protobuf::Map<SecondOrderConeConstraintId,
1453 std::vector<int> cols;
1454 std::vector<double> coefs;
1456 for (
auto const& [ortoolsId, soc] : constraints) {
1460 auto const& ub = soc.upper_bound();
1463 ExtractSingleton(ub, SingletonType::SOCBound, &coef));
1464 if (x0.has_value()) {
1465 cols.push_back(variables_map_.at(x0.value()));
1466 coefs.push_back(-coef * coef);
1471 for (
auto const& arg : soc.arguments_to_norm()) {
1473 ExtractSingleton(arg, SingletonType::SOCNorm, &coef));
1474 cols.push_back(variables_map_.at(
x.value()));
1475 coefs.push_back(coef * coef);
1477 RETURN_IF_ERROR(xpress_->AddQRow(
'L', rhs, 0.0, {}, {}, cols, cols, coefs));
1479 data.constraint_index = next;
1480 data.lower_bound = kMinusInf;
1481 data.upper_bound = 0.0;
1484 return absl::OkStatus();
1487absl::Status XpressSolver::ChangeCoefficients(
1489 const int num_coefficients = matrix.row_ids().size();
1490 std::vector<int> row_index;
1491 row_index.reserve(num_coefficients);
1492 std::vector<int> col_index;
1493 col_index.reserve(num_coefficients);
1494 for (
int k = 0; k < num_coefficients; ++k) {
1495 row_index.push_back(
1496 linear_constraints_map_.at(matrix.row_ids(k)).constraint_index);
1497 col_index.push_back(variables_map_.at(matrix.column_ids(k)));
1499 return xpress_->ChgCoeffs(row_index, col_index, matrix.coefficients());
1508 force_postsolve_ =
false;
1517 const absl::Time start = absl::Now();
1526 ListInvertedBounds());
1530 if (nonbinary_indicator_) {
1532 <<
"indicator variable is not binary";
1537 ScopedSolverContext solveContext(xpress_.get());
1538 RETURN_IF_ERROR(solveContext.AddCallbacks(message_callback, interrupter));
1539 std::string export_model =
"";
1540 RETURN_IF_ERROR(solveContext.ApplyParameters(parameters, message_callback,
1541 &export_model, &force_postsolve_,
1544 model_parameters, variables_map_, linear_constraints_map_,
1550 if (export_model.length() > 0) {
1551 if (export_model.length() >= 4 &&
1552 export_model.compare(export_model.length() - 4, 4,
".svf") == 0) {
1564 xpress_->Optimize(stop_after_lp_ ?
"l" :
"", &solvestatus_, &solstatus_));
1570 solveContext.ReraiseException();
1572 xpress_->GetSolution(&primal_sol_avail_, std::nullopt, 0, -1));
1573 RETURN_IF_ERROR(xpress_->GetDuals(&dual_sol_avail_, std::nullopt, 0, -1));
1583 if (force_postsolve_)
1588 ExtractSolveResultProto(start, model_parameters, parameters));
1600 return solve_result;
1603absl::StatusOr<SolveResultProto> XpressSolver::ExtractSolveResultProto(
1607 RETURN_IF_ERROR(AppendSolution(result, model_parameters, solve_parameters));
1613 ConvertTerminationReason(best_primal_bound, best_dual_bound));
1617absl::StatusOr<double> XpressSolver::GetBestPrimalBound()
const {
1626 return objsen * kPlusInf;
1629absl::StatusOr<double> XpressSolver::GetBestDualBound()
const {
1644 return objsen * kMinusInf;
1655 solution.mutable_primal_solution()->mutable_auxiliary_objective_values();
1656 for (
auto const& [ortoolsId, xpressId] : objectives_map_) {
1658 xpress_->CalculateObjectiveN(xpressId,
nullptr));
1659 (*objvals)[ortoolsId] = thisobj;
1661 return absl::OkStatus();
1664absl::Status XpressSolver::AppendSolution(
1670 std::vector<double>
x(nVars);
1673 xpress_->GetSolution(&avail, absl::MakeSpan(
x), 0, nVars - 1));
1676 solution.mutable_primal_solution()->set_feasibility_status(
1677 getPrimalSolutionStatus());
1680 solution.mutable_primal_solution()->set_objective_value(objval);
1682 XpressVectorToSparseDoubleVector(
1684 *
solution.mutable_primal_solution()->mutable_variable_values(),
1685 model_parameters.variable_values_filter());
1686 *solve_result.add_solutions() = std::move(
solution);
1691 std::vector<double> primals(nVars);
1692 std::vector<double> duals(nCons);
1693 std::vector<double> reducedCosts(nVars);
1704 (optimizetypeused_ ==
1708 ->GetLpSol(absl::MakeSpan(primals), absl::MakeSpan(duals),
1709 absl::MakeSpan(reducedCosts))
1716 if (isPrimalFeasible()) {
1721 xpress_->GetSolution(
nullptr, absl::MakeSpan(primals), 0, nVars - 1));
1722 solution.mutable_primal_solution()->set_feasibility_status(
1723 getPrimalSolutionStatus());
1725 solution.mutable_primal_solution()->set_objective_value(primalBound);
1726 XpressVectorToSparseDoubleVector(
1727 primals, variables_map_,
1728 *
solution.mutable_primal_solution()->mutable_variable_values(),
1729 model_parameters.variable_values_filter());
1731 }
else if (storeSolutions) {
1735 solution.mutable_primal_solution()->set_feasibility_status(
1738 solution.mutable_primal_solution()->set_objective_value(primalBound);
1739 XpressVectorToSparseDoubleVector(
1740 primals, variables_map_,
1741 *
solution.mutable_primal_solution()->mutable_variable_values(),
1742 model_parameters.variable_values_filter());
1745 if (isDualFeasible()) {
1751 xpress_->GetDuals(
nullptr, absl::MakeSpan(duals), 0, nCons - 1));
1753 nullptr, absl::MakeSpan(reducedCosts), 0, nVars - 1));
1754 solution.mutable_dual_solution()->set_feasibility_status(
1755 getDualSolutionStatus());
1757 solution.mutable_dual_solution()->set_objective_value(dualBound);
1758 XpressVectorToSparseDoubleVector(
1759 duals, linear_constraints_map_,
1760 *
solution.mutable_dual_solution()->mutable_dual_values(),
1761 model_parameters.dual_values_filter());
1762 XpressVectorToSparseDoubleVector(
1763 reducedCosts, variables_map_,
1764 *
solution.mutable_dual_solution()->mutable_reduced_costs(),
1765 model_parameters.reduced_costs_filter());
1766 }
else if (storeSolutions) {
1770 solution.mutable_dual_solution()->set_feasibility_status(
1773 solution.mutable_dual_solution()->set_objective_value(dualBound);
1774 XpressVectorToSparseDoubleVector(
1775 duals, linear_constraints_map_,
1776 *
solution.mutable_dual_solution()->mutable_dual_values(),
1777 model_parameters.dual_values_filter());
1778 XpressVectorToSparseDoubleVector(
1779 reducedCosts, variables_map_,
1780 *
solution.mutable_dual_solution()->mutable_reduced_costs(),
1781 model_parameters.reduced_costs_filter());
1786 if (basis.has_value()) {
1787 *
solution.mutable_basis() = std::move(*basis);
1789 *solve_result.add_solutions() = std::move(
solution);
1791 return absl::OkStatus();
1794bool XpressSolver::isPrimalFeasible()
const {
1799bool XpressSolver::isDualFeasible()
const {
1806 switch (solvestatus_) {
1814 switch (solstatus_) {
1838absl::StatusOr<std::optional<BasisProto>> XpressSolver::GetBasisIfAvailable(
1840 std::vector<int> xprs_variable_basis_status;
1841 std::vector<int> xprs_constraint_basis_status;
1844 ->GetBasis(xprs_constraint_basis_status, xprs_variable_basis_status)
1846 return std::nullopt;
1851 for (
auto [variable_id, xprs_variable_index] : variables_map_) {
1852 basis.mutable_variable_status()->add_ids(variable_id);
1854 xprs_variable_basis_status[xprs_variable_index],
false);
1856 return absl::InternalError(
1857 absl::StrCat(
"Invalid Xpress variable basis status: ",
1858 xprs_variable_basis_status[xprs_variable_index]));
1860 basis.mutable_variable_status()->add_values(variable_status);
1864 for (
auto [constraint_id, xprs_ct_index] : linear_constraints_map_) {
1865 basis.mutable_constraint_status()->add_ids(constraint_id);
1867 xprs_constraint_basis_status[xprs_ct_index.constraint_index],
true);
1869 return absl::InternalError(absl::StrCat(
1870 "Invalid Xpress constraint basis status: ",
1871 xprs_constraint_basis_status[xprs_ct_index.constraint_index]));
1873 basis.mutable_constraint_status()->add_values(status);
1877 basis.set_basic_dual_feasibility(
1882absl::StatusOr<SolveStatsProto> XpressSolver::GetSolveStats(
1883 absl::Time start)
const {
1884 SolveStatsProto solve_stats;
1886 solve_stats.mutable_solve_time()));
1888 int simplex_iters = 0;
1889 int barrier_iters = 0;
1890 int first_order_iters = 0;
1908 solve_stats.set_simplex_iterations(simplex_iters);
1909 solve_stats.set_barrier_iterations(barrier_iters);
1910 solve_stats.set_first_order_iterations(first_order_iters);
1913 solve_stats.set_node_count(nodes);
1918template <
typename T>
1919void XpressSolver::XpressVectorToSparseDoubleVector(
1920 absl::Span<const double> xpress_values,
const T& map,
1923 SparseVectorFilterPredicate predicate(filter);
1924 for (
auto [
id, xpress_data] : map) {
1925 const double value = xpress_values[get_model_index(xpress_data)];
1926 if (predicate.AcceptsAndUpdate(
id, value)) {
1928 result.add_values(value);
1933absl::StatusOr<TerminationProto> XpressSolver::ConvertTerminationReason(
1934 double best_primal_bound,
double best_dual_bound)
const {
1937 bool const isMax = objsen < 0.0;
1938 bool checkSolStatus =
false;
1953 isMax,
LIMIT_CUTOFF, std::nullopt,
"Objective limit (LP_CUTOFF)");
1963 "Objective limit (LP_CUTOFF_IN_DUAL)");
1968 "Problem contains quadratic data, which is "
1969 "not convex (XPRS_LP_NONCONVEX)");
1974 switch (solvestatus_) {
1977 "Problem solve has not started");
1980 checkSolStatus =
true;
1983 switch (stopStatus) {
1997 "Numerical issues");
2000 "Problem solve failed");
2004 checkSolStatus =
true;
2007 if (checkSolStatus) {
2009 switch (solstatus_) {
2011 switch (stopStatus) {
2014 isMax,
LIMIT_TIME, best_dual_bound,
"Time limit hit");
2021 isMax,
LIMIT_NODE, best_dual_bound,
"Node limit hit");
2027 isMax,
LIMIT_OTHER, best_dual_bound,
"Work limit hit");
2033 isMax,
LIMIT_MEMORY, best_dual_bound,
"Memory limit hit");
2035 if (stop_after_lp_) {
2040 "Stopped after LP relaxation");
2051 switch (stopStatus) {
2054 best_primal_bound, best_dual_bound,
2059 best_primal_bound, best_dual_bound,
2063 best_primal_bound, best_dual_bound,
2067 best_primal_bound, best_dual_bound,
2071 best_primal_bound, best_dual_bound,
2078 best_primal_bound, best_dual_bound,
2079 "Memory limit hit");
2081 if (stop_after_lp_) {
2085 isMax,
LIMIT_NODE, best_primal_bound, best_dual_bound,
2086 "Stopped after LP relaxation");
2113absl::StatusOr<ComputeInfeasibleSubsystemResultProto>
2118 ScopedSolverContext solveContext(xpress_.get());
2119 RETURN_IF_ERROR(solveContext.AddCallbacks(message_callback, interrupter));
2120 RETURN_IF_ERROR(solveContext.ApplyParameters(parameters, message_callback,
2121 nullptr,
nullptr,
nullptr));
2123 return absl::UnimplementedError(
2124 "XpressSolver does not compute an infeasible subsystem (yet)");
2127absl::StatusOr<InvertedBounds> XpressSolver::ListInvertedBounds()
const {
2132 for (
const auto& [
id, index] : variables_map_) {
2133 if (var_lbs[index] > var_ubs[index]) {
2134 inverted_bounds.
variables.push_back(
id);
2141 for (
const auto& [
id, cstr_data] : linear_constraints_map_) {
2142 if (cstr_data.lower_bound > cstr_data.upper_bound) {
2150 return inverted_bounds;
#define ASSIGN_OR_RETURN(lhs, rexpr)
#define RETURN_IF_ERROR(expr)
const ::operations_research::math_opt::VariablesProto & variables() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::QuadraticConstraintProto > & quadratic_constraints() const
const ::operations_research::math_opt::LinearConstraintsProto & linear_constraints() const
const ::std::string & name() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::SosConstraintProto > & sos1_constraints() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::SosConstraintProto > & sos2_constraints() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::SecondOrderConeConstraintProto > & second_order_cone_constraints() const
const ::operations_research::math_opt::ObjectiveProto & objective() const
const ::operations_research::math_opt::SparseDoubleMatrixProto & linear_constraint_matrix() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::ObjectiveProto > & auxiliary_objectives() const
const ::google::protobuf::Map<::int64_t, ::operations_research::math_opt::IndicatorConstraintProto > & indicator_constraints() const
::int64_t priority() const
::operations_research::math_opt::TerminationProto *PROTOBUF_NONNULL mutable_termination()
::operations_research::math_opt::SolveStatsProto *PROTOBUF_NONNULL mutable_solve_stats()
const ::operations_research::math_opt::XpressInitializerProto & xpress() const
std::function< void(const std::vector< std::string > &)> MessageCallback
std::function< absl::StatusOr< CallbackResultProto >( const CallbackDataProto &)> Callback
bool extract_names() const
bool has_extract_names() const
XpressParametersProto_Parameter Parameter
absl::StatusOr< SolveResultProto > Solve(const SolveParametersProto ¶meters, const ModelSolveParametersProto &model_parameters, MessageCallback message_cb, const CallbackRegistrationProto &callback_registration, Callback cb, const SolveInterrupter *interrupter) override
absl::StatusOr< bool > Update(const ModelUpdateProto &model_update) override
absl::StatusOr< ComputeInfeasibleSubsystemResultProto > ComputeInfeasibleSubsystem(const SolveParametersProto ¶meters, MessageCallback message_cb, const SolveInterrupter *interrupter) override
int64_t AuxiliaryObjectiveId
int64_t LinearConstraintId
static absl::StatusOr< std::unique_ptr< XpressSolver > > New(const ModelProto &input_model, const SolverInterface::InitArgs &init_args)
int XpressMultiObjectiveIndex
static absl::StatusOr< std::unique_ptr< Xpress > > New(absl::string_view model_name)
void InsertOrDie(Collection *const collection, const typename Collection::value_type &value)
auto & InsertKeyOrDie(Collection *const collection, const typename Collection::value_type::first_type &key)
TerminationProto TerminateForReason(const TerminationReasonProto reason, const absl::string_view detail)
absl::Status ModelIsSupported(const ModelProto &model, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ TERMINATION_REASON_OTHER_ERROR
@ TERMINATION_REASON_NO_SOLUTION_FOUND
@ TERMINATION_REASON_NUMERICAL_ERROR
constexpr SupportedProblemStructures kXpressSupportedStructures
absl::Status ModelSolveParametersAreSupported(const ModelSolveParametersProto &model_parameters, const SupportedProblemStructures &support_menu, const absl::string_view solver_name)
@ SOLUTION_STATUS_UNDETERMINED
@ SOLUTION_STATUS_INFEASIBLE
@ SOLUTION_STATUS_UNSPECIFIED
@ SOLUTION_STATUS_FEASIBLE
@ LP_ALGORITHM_DUAL_SIMPLEX
@ LP_ALGORITHM_FIRST_ORDER
@ LP_ALGORITHM_UNSPECIFIED
@ LP_ALGORITHM_PRIMAL_SIMPLEX
TerminationProto OptimalTerminationProto(const double finite_primal_objective, const double dual_objective, const absl::string_view detail)
@ FEASIBILITY_STATUS_FEASIBLE
@ FEASIBILITY_STATUS_UNDETERMINED
TerminationProto FeasibleTerminationProto(const bool is_maximize, const LimitProto limit, const double primal_objective, const std::optional< double > optional_dual_objective, const absl::string_view detail)
std::function< void(const std::vector< std::string > &)> MessageCallback
TerminationProto NoSolutionFoundTerminationProto(const bool is_maximize, const LimitProto limit, const std::optional< double > optional_dual_objective, const absl::string_view detail)
absl::Status CheckRegisteredCallbackEvents(const CallbackRegistrationProto ®istration, const absl::flat_hash_set< CallbackEventProto > &supported_events)
SparseVectorView< T > MakeView(absl::Span< const int64_t > ids, const Collection &values)
ElementId< ElementType::kQuadraticConstraint > QuadraticConstraintId
@ BASIS_STATUS_AT_UPPER_BOUND
@ BASIS_STATUS_UNSPECIFIED
@ BASIS_STATUS_AT_LOWER_BOUND
TerminationProto InfeasibleTerminationProto(bool is_maximize, const FeasibilityStatusProto dual_feasibility_status, const absl::string_view detail)
ElementId< ElementType::kIndicatorConstraint > IndicatorConstraintId
TerminationProto UnboundedTerminationProto(const bool is_maximize, const absl::string_view detail)
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
ClosedInterval::Iterator end(ClosedInterval interval)
bool XpressIsCorrectlyInstalled()
ClosedInterval::Iterator begin(ClosedInterval interval)
inline ::absl::StatusOr< absl::Duration > DecodeGoogleApiProto(const google::protobuf::Duration &proto)
inline ::absl::StatusOr< google::protobuf::Duration > EncodeGoogleApiProto(absl::Duration d)
StatusBuilder InvalidArgumentErrorBuilder()
#define MATH_OPT_REGISTER_SOLVER(solver_type, solver_factory)
std::vector< int64_t > linear_constraints
std::vector< int64_t > variables
absl::Status ToStatus() const
SolverInitializerProto streamable
#define XPRS_SOLAVAILABLE_FEASIBLE
#define XPRS_OBJECTIVE_WEIGHT
#define XPRS_STOP_GENERICERROR
#define XPRS_SOLVESTATUS_FAILED
#define XPRS_LP_CUTOFF_IN_DUAL
#define XPRS_SOLVESTATUS_COMPLETED
#define XPRS_ORIGINALROWS
#define XPRS_ORIGINALSETS
#define XPRS_OBJECTIVE_RELTOL
#define XPRS_OBJECTIVE_PRIORITY
#define XPRS_ORIGINALCOLS
#define XPRS_STOP_ITERLIMIT
#define XPRS_BARHGMAXRESTARTS
#define XPRS_LP_NONCONVEX
#define XPRS_SOLAVAILABLE_OPTIMAL
struct xo_prob_struct * XPRSprob
#define XPRS_PRESOLVEPASSES
#define XPRS_GREATER_EQUAL
#define XPRS_OPTIMIZETYPEUSED
#define XPRS_SOLSTATUS_FEASIBLE
#define XPRS_LP_UNFINISHED
#define XPRS_STOP_LICENSELOST
#define XPRS_LP_UNSTARTED
#define XPRS_STOP_NUMERICALERROR
#define XPRS_PRESOLVESTATE
#define XPRS_SOLSTATUS_UNBOUNDED
#define XPRS_STOP_NODELIMIT
#define XPRS_SOLSTATUS_INFEASIBLE
#define XPRS_STOP_TIMELIMIT
#define XPRS_SOLVESTATUS_STOPPED
#define XPRS_HEUREMPHASIS
#define XPRS_BARITERLIMIT
#define XPRS_STOP_MEMORYERROR
#define XPRS_STOP_WORKLIMIT
#define XPRS_LP_UNBOUNDED
#define XPRS_SOLSTATUS_OPTIMAL
#define XPRS_OBJECTIVE_ABSTOL
#define XPRS_SOLAVAILABLE_NOTFOUND
#define XPRS_SOLVESTATUS_UNSTARTED
#define XPRS_MIPABSCUTOFF
#define XPRS_NAMES_COLUMN
#define XPRS_SOLSTATUS_NOTFOUND
#define DEFINE_SCOPED_CB(CB_NAME, ORTOOLS_CB, CB_RET_TYPE, ARGS)