|
| 1 | +// Copyright 2010-2025 Google LLC |
| 2 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 3 | +// you may not use this file except in compliance with the License. |
| 4 | +// You may obtain a copy of the License at |
| 5 | +// |
| 6 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 7 | +// |
| 8 | +// Unless required by applicable law or agreed to in writing, software |
| 9 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 10 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 11 | +// See the License for the specific language governing permissions and |
| 12 | +// limitations under the License. |
| 13 | + |
| 14 | +#include "ortools/math_opt/cpp/incremental_solver.h" |
| 15 | + |
| 16 | +#include <string> |
| 17 | + |
| 18 | +#include "absl/status/status.h" |
| 19 | +#include "absl/status/statusor.h" |
| 20 | +#include "absl/strings/string_view.h" |
| 21 | +#include "gtest/gtest.h" |
| 22 | +#include "ortools/base/gmock.h" |
| 23 | +#include "ortools/math_opt/cpp/matchers.h" |
| 24 | +#include "ortools/math_opt/cpp/math_opt.h" |
| 25 | + |
| 26 | +namespace operations_research::math_opt { |
| 27 | +namespace { |
| 28 | + |
| 29 | +using ::testing::_; |
| 30 | +using ::testing::Return; |
| 31 | +using ::testing::status::IsOkAndHolds; |
| 32 | +using ::testing::status::StatusIs; |
| 33 | + |
| 34 | +class MockIncrementalSolver final : public IncrementalSolver { |
| 35 | + public: |
| 36 | + MOCK_METHOD(absl::StatusOr<UpdateResult>, Update, (), (override)); |
| 37 | + MOCK_METHOD(absl::StatusOr<SolveResult>, SolveWithoutUpdate, |
| 38 | + (const SolveArguments&), (const, override)); |
| 39 | + MOCK_METHOD(absl::StatusOr<ComputeInfeasibleSubsystemResult>, |
| 40 | + ComputeInfeasibleSubsystemWithoutUpdate, |
| 41 | + (const ComputeInfeasibleSubsystemArguments&), (const, override)); |
| 42 | + MOCK_METHOD(SolverType, solver_type, (), (const, override)); |
| 43 | +}; |
| 44 | + |
| 45 | +TEST(IncrementalSolverTest, SolveWithFailingUpdate) { |
| 46 | + MockIncrementalSolver incremental_solver; |
| 47 | + EXPECT_CALL(incremental_solver, Update()) |
| 48 | + .WillOnce(Return(absl::InternalError("oops"))); |
| 49 | + EXPECT_THAT(incremental_solver.Solve(), |
| 50 | + StatusIs(absl::StatusCode::kInternal, "oops")); |
| 51 | +} |
| 52 | + |
| 53 | +TEST(IncrementalSolverTest, SolveWithFailingSolveWithoutUpdate) { |
| 54 | + MockIncrementalSolver incremental_solver; |
| 55 | + EXPECT_CALL(incremental_solver, Update()) |
| 56 | + .WillOnce(Return(UpdateResult(/*did_update=*/true))); |
| 57 | + EXPECT_CALL(incremental_solver, SolveWithoutUpdate(_)) |
| 58 | + .WillOnce(Return(absl::InternalError("oops"))); |
| 59 | + EXPECT_THAT(incremental_solver.Solve(), |
| 60 | + StatusIs(absl::StatusCode::kInternal, "oops")); |
| 61 | +} |
| 62 | + |
| 63 | +TEST(IncrementalSolverTest, SuccessfulSolve) { |
| 64 | + MockIncrementalSolver incremental_solver; |
| 65 | + EXPECT_CALL(incremental_solver, Update()) |
| 66 | + .WillOnce(Return(UpdateResult(/*did_update=*/true))); |
| 67 | + constexpr double kObjectiveValue = 3.5; |
| 68 | + constexpr absl::string_view kDetail = "found the optimum!"; |
| 69 | + EXPECT_CALL(incremental_solver, SolveWithoutUpdate(_)) |
| 70 | + .WillOnce(Return( |
| 71 | + SolveResult(Termination::Optimal(/*objective_value=*/kObjectiveValue, |
| 72 | + /*detail=*/std::string(kDetail))))); |
| 73 | + ASSERT_OK_AND_ASSIGN(const SolveResult solve_result, |
| 74 | + incremental_solver.Solve()); |
| 75 | + EXPECT_THAT(solve_result.termination, |
| 76 | + TerminationIsOptimal(/*primal_objective_value=*/kObjectiveValue)); |
| 77 | + EXPECT_EQ(solve_result.termination.detail, kDetail); |
| 78 | +} |
| 79 | + |
| 80 | +TEST(IncrementalSolverTest, ComputeInfeasibleSubsystemWithFailingUpdate) { |
| 81 | + MockIncrementalSolver incremental_solver; |
| 82 | + EXPECT_CALL(incremental_solver, Update()) |
| 83 | + .WillOnce(Return(absl::InternalError("oops"))); |
| 84 | + EXPECT_THAT(incremental_solver.ComputeInfeasibleSubsystem(), |
| 85 | + StatusIs(absl::StatusCode::kInternal, "oops")); |
| 86 | +} |
| 87 | + |
| 88 | +TEST(IncrementalSolverTest, |
| 89 | + ComputeInfeasibleSubsystemWithFailingComputeWithoutUpdate) { |
| 90 | + MockIncrementalSolver incremental_solver; |
| 91 | + EXPECT_CALL(incremental_solver, Update()) |
| 92 | + .WillOnce(Return(UpdateResult(/*did_update=*/true))); |
| 93 | + EXPECT_CALL(incremental_solver, ComputeInfeasibleSubsystemWithoutUpdate(_)) |
| 94 | + .WillOnce(Return(absl::InternalError("oops"))); |
| 95 | + EXPECT_THAT(incremental_solver.ComputeInfeasibleSubsystem(), |
| 96 | + StatusIs(absl::StatusCode::kInternal, "oops")); |
| 97 | +} |
| 98 | + |
| 99 | +TEST(IncrementalSolverTest, SuccessfulComputeInfeasibleSubsystem) { |
| 100 | + MockIncrementalSolver incremental_solver; |
| 101 | + EXPECT_CALL(incremental_solver, Update()) |
| 102 | + .WillOnce(Return(UpdateResult(/*did_update=*/true))); |
| 103 | + Model model; |
| 104 | + const Variable v = model.AddBinaryVariable("v"); |
| 105 | + const ModelSubset model_subset = { |
| 106 | + .variable_integrality = {v}, |
| 107 | + }; |
| 108 | + EXPECT_CALL(incremental_solver, ComputeInfeasibleSubsystemWithoutUpdate(_)) |
| 109 | + .WillOnce(Return(ComputeInfeasibleSubsystemResult{ |
| 110 | + .feasibility = FeasibilityStatus::kInfeasible, |
| 111 | + .infeasible_subsystem = model_subset, |
| 112 | + .is_minimal = false, |
| 113 | + })); |
| 114 | + ASSERT_THAT(incremental_solver.ComputeInfeasibleSubsystem(), |
| 115 | + IsOkAndHolds(IsInfeasible( |
| 116 | + /*expected_is_minimal=*/false, |
| 117 | + /*expected_infeasible_subsystem=*/model_subset))); |
| 118 | +} |
| 119 | + |
| 120 | +} // namespace |
| 121 | +} // namespace operations_research::math_opt |
0 commit comments