Skip to content

Conversation

@djunglas
Copy link
Contributor

This pull requests adds support for a lot of MIP features for the Xpress solver in ortools:

  1. Integer variables
  2. Solution hints (interpreted as MIP starts)
  3. Lazy constraints
  4. Indicator constraints
  5. SOS constraints (only on singleton variables, not on expressions)
  6. Second order cone constraints (only on singleton variables, not on expressions)
  7. Message callbacks
  8. Interrupters
  9. Setting arbitrary Xpress specific parameters (via new XpressParameters class)
  10. Support writing out models as they are optimized using EXPORT_MODEL parameter

We also changed the LP part: we now always call XPRSoptimize(), which is a more modern way of using Xpress and letting Xpress decide what is the best algorithm to run.

Even with this pull request we still do not support the following things for the Xpress solver

  • The callback
  • Computing minimal infeasible subsystems
  • The Update() function (for incremental solving)

We had to adjust some tests because it seemed they did not honor all the parameters passed into them. It is probably a good idea to double check these changes very carefully.

@google-cla
Copy link

google-cla bot commented Oct 21, 2025

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@lperron
Copy link
Collaborator

lperron commented Oct 21, 2025

can you rebase this against main ? The directory structure has changed. In particular, the xpress environment code is now in ortools/third_party_solvers/

@Mizux Mizux added Solver: XPRESS XPRESS Solver related issue Solver: MathOpt MathOpt related issue labels Oct 21, 2025
djunglas and others added 23 commits October 22, 2025 07:37
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
Co-authored-by: Francesco Cavaliere <[email protected]>
@djunglas djunglas force-pushed the xpress-use-optimizer-function branch from b56c9cd to 440c4b8 Compare October 22, 2025 07:43
@djunglas
Copy link
Contributor Author

can you rebase this against main ? The directory structure has changed. In particular, the xpress environment code is now in ortools/third_party_solvers/

@lperron, I rebased on main as requested (luckily this was easy).
About the CLA I am supposed to sign: do these things expire? I am pretty sure I contributed before and signed a CLA for that purpose. I can sign a new one but if I remember correctly then last time this required a longish discussion with our legal department, so if the old CLA can be made working, I would prefer that.

@djunglas djunglas changed the base branch from stable to main October 22, 2025 07:46
@lperron
Copy link
Collaborator

lperron commented Oct 22, 2025 via email

@Mizux Mizux requested review from Mizux and lperron October 24, 2025 15:28
Copy link
Contributor

@pet-mit pet-mit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for this amazing work!

It's a very big PR and I can only imagine the amount of work it took you guys to develop and test all this!

I tried to review as much as possible the code overall; I could not read every single line of code since there is a lot of changes (especially in g_xpress & xpress_solver).

I did, however, use Xpress 9.6.0 to run the unit tests on my machine & do small manual tests for some features. This allowed me to leave you some feedback.

I have not yet tested all this with real-life problems; we'll come back here if anything pops up!

As a side note, your PR is very big, and I noticed that you did not list all the new features in its decription, so I made you a list of forgotten features:

  • Multi-objective support (but only the main one can contain quadratic terms)
  • Quadratic constraints support
  • First order support
  • Support for the following generic solve parameters: presolve, cut-off, thread count, time limit, node limit, absolute and relative gap tolerance, cuts, heuristics
  • Extra Xpress-specific parameter to force post-solve ("FORCE_POSTSOLVE")

Best,
Peter

// size (nVars, nCons, and nVars respectively)
absl::Status GetLpSol(absl::Span<double> primals, absl::Span<double> duals,
absl::Span<double> reducedCosts);
absl::Status MipOptimize();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can delete this function


// We test SOS1 constraints with both explicit weights and default weights.
TEST_P(SimpleLogicalConstraintTest, CanBuildSos1Model) {
if (GetParam().solver_type == SolverType::kXpress) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest adding test suite parameters for these skipped tests (like supports_integer_variables, supports_indicator_constraints, etc).
maybe we can get feedback from someone on the OR-Tools team on this.

Comment on lines +1107 to +1113
* Also, ortools supports SOSs with identical elements and assumes that
* something like { x, x } is reduced to just { x }. This is debatable:
* If you consider { x, x } a set, then clearly it is the same as { x }.
* But if you consider "at most one of x and x may be non-zero", then { x, x }
* implies x=0. This is not how ortools interprets SOSs with duplicate entries
* (see the tests).
* We do not check for duplicate entries here, but Xpress will choke on them.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concerning identical elements, I agree that it should be up to the OR-Tools user to ensure that they provide "clean" inputs, i.e. without duplicates (Gurobi did stop supporting this in their API). This is a pretty advanced feature, and one may assume that the user is being / must be careful when using it. In other words, I would expect this function to fail in case of duplicates.
I suggest that we debate this with the OR-Tools team and remove this comment.

Comment on lines +1104 to +1106
* Note that in ortools an SOS constraint is made up from expressions and not
* just variables. Here, we only support SOSs in which each expression is just
* a single variable with coefficient 1.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at what Google did for Gurobi; we'd have to create a variable equal to the expression first, and then use that variable to define the SOS, right? (asking in case we add this feature later)

Copy link
Contributor

@pet-mit pet-mit Oct 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that you've added support for MIP, hints, branching priorities, and lazy constraints, you should activate the relevant tests. First, remove the following lines from this file:

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SimpleMipTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(MipSolutionHintTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LazyConstraintsTest);
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(BranchPrioritiesTest);

Then, activate these tests by instantiating the tests suites. I tested it ou on my PC by adding the following code:

INSTANTIATE_TEST_SUITE_P(XpressSimpleMipTest, SimpleMipTest,
                         testing::Values(SolverType::kXpress));

INSTANTIATE_TEST_SUITE_P(
    XpressMipSolutionHintTest, MipSolutionHintTest,
    testing::Values(SolutionHintTestParams(
        SolverType::kXpress, SolveParameters(),
        SolverType::kXpress, SolveParameters(),
        "User solution (.*) stored")));

INSTANTIATE_TEST_SUITE_P(XpressLazyConstraintsTest, LazyConstraintsTest,
                         testing::Values(LazyConstraintsTestParams(
                             SolverType::kXpress, SolveParameters())));

INSTANTIATE_TEST_SUITE_P(XpressBranchPrioritiesTest, BranchPrioritiesTest,
                         testing::Values(BranchPrioritiesTestParams(
                             SolverType::kXpress, SolveParameters())));

unfortunately, some added tests fail and should be looked into in detail.
As for the test suites, I'm not 100% sure I was exhaustive, I suggest that you also take a look at the other tests marked with a GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST. (unfortunately, I think that you won't be able to enable TimeLimitTest yet, since it requires Callback event support)

std::vector<double> quad_coef;
ASSIGN_OR_RETURN(int next, xpress_->GetIntAttr(XPRS_ORIGINALROWS));
for (const auto& [ortoolsId, quad] : constraints) {
// Xpress has no function to multiple quadratic rows in one shot, so we
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Xpress has no function to multiple quadratic rows in one shot, so we
// Xpress has no function to add multiple quadratic rows in one shot, so we

/*qp_support=*/QpSupportType::kConvexQp,
/*supports_incrementalism_not_modifying_qp=*/false,
/*supports_qp_incrementalism=*/false,
/*use_integer_variables=*/i != 0));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be clearer (IMO) if it was just seperated into two true/false cases, like with the other parameters

} else {
// Auxiliary objective in multi-objective. Xpress does not support
// different objective senses for different objectives. So if the sense
// does not match we set the weight to -1.0 to inver the objective
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// does not match we set the weight to -1.0 to inver the objective
// does not match we set the weight to -1.0 to invert the objective

export_model.compare(export_model.length() - 4, 4, ".svf") == 0) {
RETURN_IF_ERROR(xpress_->SaveAs(export_model.c_str()));
} else {
RETURN_IF_ERROR(xpress_->WriteProb(export_model.c_str()));
Copy link
Contributor

@pet-mit pet-mit Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe consider extracting row & column names first? otherwise the file is not easy to read (rows & columns keep their default names, i.e. R1, R2, ... & C1, C2, ...)

return util::InvalidArgumentErrorBuilder()
<< "more lazy constraints than rows";

RETURN_IF_ERROR(shared_ctx.xpress->LoadDelayedRows(delayedRows));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed on a manually-built case that Xpress does not behave well if the indicator constraint is associated to a binary variable that has lb=ub. I guess Xpress treats such a variable as continuous or integer under the hood. I feel that this can be a blocking issue for some users (for example if the user wants to add a binary variable with its lb & ub values read form some data source or equation evaluation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Solver: MathOpt MathOpt related issue Solver: XPRESS XPRESS Solver related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants