* bump abseil to 20250814 * bump protobuf to v32.0 * cmake: add ccache auto support * backport flatzinc, math_opt and sat update
505 lines
18 KiB
C++
505 lines
18 KiB
C++
// 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/math_opt/cpp/solution.h"
|
|
|
|
#include <optional>
|
|
|
|
#include "absl/status/status.h"
|
|
#include "gtest/gtest.h"
|
|
#include "ortools/base/gmock.h"
|
|
#include "ortools/math_opt/cpp/enums_testing.h"
|
|
#include "ortools/math_opt/cpp/linear_constraint.h"
|
|
#include "ortools/math_opt/cpp/matchers.h"
|
|
#include "ortools/math_opt/cpp/math_opt.h"
|
|
#include "ortools/math_opt/cpp/objective.h"
|
|
#include "ortools/math_opt/cpp/variable_and_expressions.h"
|
|
#include "ortools/math_opt/solution.pb.h"
|
|
#include "ortools/math_opt/sparse_containers.pb.h"
|
|
#include "ortools/math_opt/storage/model_storage.h"
|
|
|
|
namespace operations_research {
|
|
namespace math_opt {
|
|
namespace {
|
|
|
|
using ::testing::HasSubstr;
|
|
using ::testing::IsEmpty;
|
|
using ::testing::status::IsOkAndHolds;
|
|
using ::testing::status::StatusIs;
|
|
|
|
INSTANTIATE_TYPED_TEST_SUITE_P(SolutionStatus, EnumTest, SolutionStatus);
|
|
|
|
TEST(PrimalSolutionTest, ProtoRoundTripTest) {
|
|
ModelStorage model;
|
|
const Variable x(&model, model.AddVariable("x"));
|
|
const Variable y(&model, model.AddVariable("y"));
|
|
const Objective o =
|
|
Objective::Auxiliary(&model, model.AddAuxiliaryObjective(2));
|
|
|
|
PrimalSolutionProto proto;
|
|
proto.set_objective_value(9.0);
|
|
(*proto.mutable_auxiliary_objective_values())[o.id().value()] = 3.0;
|
|
proto.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_variable_values()->add_ids(i);
|
|
}
|
|
for (double v : {2.0, 1.0}) {
|
|
proto.mutable_variable_values()->add_values(v);
|
|
}
|
|
|
|
PrimalSolution expected = {.variable_values = {{x, 2.0}, {y, 1.0}},
|
|
.objective_value = 9.0,
|
|
.auxiliary_objective_values = {{o, 3.0}},
|
|
.feasibility_status = SolutionStatus::kInfeasible};
|
|
|
|
// Test one way
|
|
EXPECT_THAT(PrimalSolution::FromProto(&model, proto),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
// Test round trip
|
|
EXPECT_THAT(PrimalSolution::FromProto(&model, expected.Proto()),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
}
|
|
|
|
TEST(PrimalSolutionTest, InvalidVariableValues) {
|
|
ModelStorage model;
|
|
model.AddVariable("x");
|
|
|
|
PrimalSolutionProto proto;
|
|
proto.set_objective_value(9.0);
|
|
proto.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
|
|
proto.mutable_variable_values()->add_ids(0);
|
|
|
|
EXPECT_THAT(PrimalSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid variable_values")));
|
|
}
|
|
|
|
TEST(PrimalSolutionTest, InvalidAuxiliaryObjectiveValues) {
|
|
ModelStorage model;
|
|
|
|
PrimalSolutionProto proto;
|
|
(*proto.mutable_auxiliary_objective_values())[0] = 3.0;
|
|
|
|
EXPECT_THAT(PrimalSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid auxiliary_objective_values")));
|
|
}
|
|
|
|
TEST(PrimalSolutionTest, FeasibilityStatusUnspecified) {
|
|
ModelStorage model;
|
|
PrimalSolutionProto proto;
|
|
EXPECT_THAT(PrimalSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("feasibility_status")));
|
|
}
|
|
|
|
TEST(PrimalSolutionTest, GetObjectiveValue) {
|
|
ModelStorage model;
|
|
const Objective p = Objective::Primary(&model);
|
|
const Objective o =
|
|
Objective::Auxiliary(&model, model.AddAuxiliaryObjective(1));
|
|
PrimalSolution solution{.objective_value = 1.0,
|
|
.auxiliary_objective_values = {{o, 2.0}}};
|
|
EXPECT_EQ(solution.get_objective_value(p), 1.0);
|
|
EXPECT_EQ(solution.get_objective_value(o), 2.0);
|
|
}
|
|
|
|
TEST(PrimalSolutionDeathTest, GetObjectiveValueWrongModel) {
|
|
ModelStorage model_a;
|
|
const Variable v_a(&model_a, model_a.AddVariable("v"));
|
|
const Objective o_a =
|
|
Objective::Auxiliary(&model_a, model_a.AddAuxiliaryObjective(1));
|
|
PrimalSolution solution{.objective_value = 1.0,
|
|
.auxiliary_objective_values = {{o_a, 2.0}}};
|
|
|
|
ModelStorage model_b;
|
|
const Objective p_b = Objective::Primary(&model_b);
|
|
const Objective o_b =
|
|
Objective::Auxiliary(&model_b, model_b.AddAuxiliaryObjective(1));
|
|
// This is a documented corner case where we don't CHECK the model.
|
|
EXPECT_EQ(solution.get_objective_value(p_b), 1.0);
|
|
EXPECT_DEATH(solution.get_objective_value(o_b), "");
|
|
|
|
solution.variable_values.insert({v_a, 3.0});
|
|
EXPECT_DEATH(solution.get_objective_value(p_b), "");
|
|
EXPECT_DEATH(solution.get_objective_value(o_b), "");
|
|
}
|
|
|
|
TEST(PrimalRayTest, ProtoRoundTripTest) {
|
|
ModelStorage model;
|
|
const Variable x(&model, model.AddVariable("x"));
|
|
const Variable y(&model, model.AddVariable("y"));
|
|
|
|
PrimalRayProto proto;
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_variable_values()->add_ids(i);
|
|
}
|
|
for (double v : {2.0, 1.0}) {
|
|
proto.mutable_variable_values()->add_values(v);
|
|
}
|
|
|
|
const PrimalRay expected = {.variable_values = {{x, 2.0}, {y, 1.0}}};
|
|
|
|
// Test one way
|
|
EXPECT_THAT(PrimalRay::FromProto(&model, proto),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
// Test round trip
|
|
EXPECT_THAT(PrimalRay::FromProto(&model, expected.Proto()),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
}
|
|
|
|
TEST(PrimalRayTest, InvalidVariableValues) {
|
|
ModelStorage model;
|
|
model.AddVariable("x");
|
|
|
|
PrimalRayProto proto;
|
|
proto.mutable_variable_values()->add_ids(0);
|
|
|
|
EXPECT_THAT(PrimalRay::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid variable_values")));
|
|
}
|
|
|
|
TEST(DualSolutionTest, ProtoRoundTripTest) {
|
|
ModelStorage model;
|
|
const Variable x(&model, model.AddVariable("x"));
|
|
const Variable y(&model, model.AddVariable("y"));
|
|
const LinearConstraint c(&model, model.AddLinearConstraint("c"));
|
|
const LinearConstraint d(&model, model.AddLinearConstraint("d"));
|
|
const QuadraticConstraint e(
|
|
&model, model.AddAtomicConstraint(QuadraticConstraintData{.name = "e"}));
|
|
const QuadraticConstraint f(
|
|
&model, model.AddAtomicConstraint(QuadraticConstraintData{.name = "f"}));
|
|
|
|
DualSolutionProto proto;
|
|
proto.set_objective_value(9.0);
|
|
proto.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_reduced_costs()->add_ids(i);
|
|
}
|
|
for (double v : {2.0, 1.0}) {
|
|
proto.mutable_reduced_costs()->add_values(v);
|
|
}
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_dual_values()->add_ids(i);
|
|
}
|
|
for (double v : {3.0, 4.0}) {
|
|
proto.mutable_dual_values()->add_values(v);
|
|
}
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_quadratic_dual_values()->add_ids(i);
|
|
}
|
|
for (double v : {5.0, 6.0}) {
|
|
proto.mutable_quadratic_dual_values()->add_values(v);
|
|
}
|
|
|
|
const DualSolution expected = {
|
|
.dual_values = {{c, 3.0}, {d, 4.0}},
|
|
.quadratic_dual_values = {{e, 5.0}, {f, 6.0}},
|
|
.reduced_costs = {{x, 2.0}, {y, 1.0}},
|
|
.objective_value = 9.0,
|
|
.feasibility_status = SolutionStatus::kFeasible};
|
|
|
|
// Test one way
|
|
EXPECT_THAT(DualSolution::FromProto(&model, proto),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
// Test round trip
|
|
EXPECT_THAT(DualSolution::FromProto(&model, expected.Proto()),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
}
|
|
|
|
TEST(DualSolutionTest, InvalidDualValues) {
|
|
ModelStorage model;
|
|
model.AddLinearConstraint("c");
|
|
|
|
DualSolutionProto proto;
|
|
proto.set_objective_value(9.0);
|
|
proto.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
|
|
proto.mutable_dual_values()->add_ids(0);
|
|
|
|
EXPECT_THAT(DualSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid dual_values")));
|
|
}
|
|
|
|
TEST(DualSolutionTest, InvalidReducedCosts) {
|
|
ModelStorage model;
|
|
model.AddVariable("x");
|
|
|
|
DualSolutionProto proto;
|
|
proto.set_objective_value(9.0);
|
|
proto.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
|
|
proto.mutable_reduced_costs()->add_ids(0);
|
|
|
|
EXPECT_THAT(DualSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid reduced_costs")));
|
|
}
|
|
|
|
TEST(DualSolutionTest, InvalidQuadraticDualValues) {
|
|
ModelStorage model;
|
|
const QuadraticConstraint c(
|
|
&model, model.AddAtomicConstraint(QuadraticConstraintData{.name = "c"}));
|
|
|
|
DualSolutionProto proto;
|
|
proto.set_objective_value(9.0);
|
|
proto.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
|
|
proto.mutable_quadratic_dual_values()->add_ids(0);
|
|
|
|
EXPECT_THAT(DualSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid quadratic_dual_values")));
|
|
}
|
|
|
|
TEST(DualSolutionTest, FeasibilityStatusUnspecified) {
|
|
ModelStorage model;
|
|
DualSolutionProto proto;
|
|
EXPECT_THAT(DualSolution::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("feasibility_status")));
|
|
}
|
|
|
|
TEST(DualRayTest, ProtoRoundTripTest) {
|
|
ModelStorage model;
|
|
const Variable x(&model, model.AddVariable("x"));
|
|
const Variable y(&model, model.AddVariable("y"));
|
|
const LinearConstraint c(&model, model.AddLinearConstraint("c"));
|
|
const LinearConstraint d(&model, model.AddLinearConstraint("d"));
|
|
|
|
DualRayProto proto;
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_reduced_costs()->add_ids(i);
|
|
}
|
|
for (double v : {2.0, 1.0}) {
|
|
proto.mutable_reduced_costs()->add_values(v);
|
|
}
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_dual_values()->add_ids(i);
|
|
}
|
|
for (double v : {3.0, 4.0}) {
|
|
proto.mutable_dual_values()->add_values(v);
|
|
}
|
|
|
|
const DualRay expected = {.dual_values = {{c, 3.0}, {d, 4.0}},
|
|
.reduced_costs = {{x, 2.0}, {y, 1.0}}};
|
|
|
|
// Test one way
|
|
EXPECT_THAT(DualRay::FromProto(&model, proto),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
// Test round trip
|
|
EXPECT_THAT(DualRay::FromProto(&model, expected.Proto()),
|
|
IsOkAndHolds(IsNear(expected, /*tolerance=*/0)));
|
|
}
|
|
|
|
TEST(DualRayTest, InvalidDualValues) {
|
|
ModelStorage model;
|
|
model.AddLinearConstraint("c");
|
|
|
|
DualRayProto proto;
|
|
proto.mutable_dual_values()->add_ids(0);
|
|
|
|
EXPECT_THAT(DualRay::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid dual_values")));
|
|
}
|
|
|
|
TEST(DualRayTest, InvalidReducedCosts) {
|
|
ModelStorage model;
|
|
model.AddVariable("x");
|
|
|
|
DualRayProto proto;
|
|
proto.mutable_reduced_costs()->add_ids(0);
|
|
|
|
EXPECT_THAT(DualRay::FromProto(&model, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("invalid reduced_costs")));
|
|
}
|
|
|
|
TEST(BasisTest, ProtoRoundTripTest) {
|
|
ModelStorage model;
|
|
const Variable x(&model, model.AddVariable("x"));
|
|
const Variable y(&model, model.AddVariable("y"));
|
|
const LinearConstraint c(&model, model.AddLinearConstraint("c"));
|
|
const LinearConstraint d(&model, model.AddLinearConstraint("d"));
|
|
|
|
BasisProto proto;
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_variable_status()->add_ids(i);
|
|
}
|
|
proto.mutable_variable_status()->add_values(BASIS_STATUS_AT_UPPER_BOUND);
|
|
proto.mutable_variable_status()->add_values(BASIS_STATUS_FREE);
|
|
for (int i : {0, 1}) {
|
|
proto.mutable_constraint_status()->add_ids(i);
|
|
}
|
|
proto.mutable_constraint_status()->add_values(BASIS_STATUS_AT_LOWER_BOUND);
|
|
proto.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
|
|
proto.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
|
|
|
|
const Basis expected = {.constraint_status = {{c, BasisStatus::kAtLowerBound},
|
|
{d, BasisStatus::kBasic}},
|
|
.variable_status = {{x, BasisStatus::kAtUpperBound},
|
|
{y, BasisStatus::kFree}},
|
|
.basic_dual_feasibility = SolutionStatus::kFeasible};
|
|
|
|
EXPECT_OK(expected.CheckModelStorage(&model));
|
|
|
|
// Test one way
|
|
EXPECT_THAT(Basis::FromProto(&model, proto), IsOkAndHolds(BasisIs(expected)));
|
|
// Test round trip
|
|
EXPECT_THAT(Basis::FromProto(&model, expected.Proto()),
|
|
IsOkAndHolds(BasisIs(expected)));
|
|
}
|
|
|
|
TEST(BasisTest, VariablesAndConstraintsDifferentModels) {
|
|
ModelStorage model_a;
|
|
const Variable a_x(&model_a, model_a.AddVariable("x"));
|
|
ModelStorage model_b;
|
|
const LinearConstraint b_c(&model_b, model_b.AddLinearConstraint("c"));
|
|
|
|
const Basis basis = {.constraint_status = {{b_c, BasisStatus::kAtLowerBound}},
|
|
.variable_status = {{a_x, BasisStatus::kAtUpperBound}}};
|
|
|
|
EXPECT_THAT(basis.CheckModelStorage(&model_a),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr(internal::kInputFromInvalidModelStorage)));
|
|
EXPECT_THAT(basis.CheckModelStorage(&model_b),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr(internal::kInputFromInvalidModelStorage)));
|
|
}
|
|
|
|
TEST(BasisTest, FromProtoUnspecifiedBasicDualFeasibility) {
|
|
ModelStorage storage;
|
|
BasisProto proto;
|
|
ASSERT_OK_AND_ASSIGN(const Basis basis, Basis::FromProto(&storage, proto));
|
|
EXPECT_THAT(basis.variable_status, IsEmpty());
|
|
EXPECT_THAT(basis.constraint_status, IsEmpty());
|
|
EXPECT_EQ(basis.basic_dual_feasibility, std::nullopt);
|
|
}
|
|
|
|
TEST(SolutionTest, ProtoRoundTripTest) {
|
|
ModelStorage model;
|
|
const Variable x(&model, model.AddVariable("x"));
|
|
const Variable y(&model, model.AddVariable("y"));
|
|
const LinearConstraint c(&model, model.AddLinearConstraint("c"));
|
|
const LinearConstraint d(&model, model.AddLinearConstraint("d"));
|
|
const Objective o =
|
|
Objective::Auxiliary(&model, model.AddAuxiliaryObjective(2));
|
|
|
|
SolutionProto proto;
|
|
|
|
PrimalSolutionProto& primal_proto = *proto.mutable_primal_solution();
|
|
primal_proto.set_objective_value(9.0);
|
|
(*primal_proto.mutable_auxiliary_objective_values())[o.id().value()] = 2.0;
|
|
primal_proto.set_feasibility_status(SOLUTION_STATUS_INFEASIBLE);
|
|
for (int i : {0, 1}) {
|
|
primal_proto.mutable_variable_values()->add_ids(i);
|
|
}
|
|
for (double v : {2.0, 1.0}) {
|
|
primal_proto.mutable_variable_values()->add_values(v);
|
|
}
|
|
|
|
DualSolutionProto& dual_proto = *proto.mutable_dual_solution();
|
|
dual_proto.set_objective_value(9.0);
|
|
dual_proto.set_feasibility_status(SOLUTION_STATUS_FEASIBLE);
|
|
for (int i : {0, 1}) {
|
|
dual_proto.mutable_reduced_costs()->add_ids(i);
|
|
}
|
|
for (double v : {2.0, 1.0}) {
|
|
dual_proto.mutable_reduced_costs()->add_values(v);
|
|
}
|
|
for (int i : {0, 1}) {
|
|
dual_proto.mutable_dual_values()->add_ids(i);
|
|
}
|
|
for (double v : {3.0, 4.0}) {
|
|
dual_proto.mutable_dual_values()->add_values(v);
|
|
}
|
|
|
|
BasisProto& basis_proto = *proto.mutable_basis();
|
|
for (int i : {0, 1}) {
|
|
basis_proto.mutable_variable_status()->add_ids(i);
|
|
}
|
|
basis_proto.mutable_variable_status()->add_values(
|
|
BASIS_STATUS_AT_UPPER_BOUND);
|
|
basis_proto.mutable_variable_status()->add_values(BASIS_STATUS_FREE);
|
|
for (int i : {0, 1}) {
|
|
basis_proto.mutable_constraint_status()->add_ids(i);
|
|
}
|
|
basis_proto.mutable_constraint_status()->add_values(
|
|
BASIS_STATUS_AT_LOWER_BOUND);
|
|
basis_proto.mutable_constraint_status()->add_values(BASIS_STATUS_BASIC);
|
|
basis_proto.set_basic_dual_feasibility(SOLUTION_STATUS_FEASIBLE);
|
|
|
|
const Solution expected{
|
|
.primal_solution =
|
|
PrimalSolution{.variable_values = {{x, 2.0}, {y, 1.0}},
|
|
.objective_value = 9.0,
|
|
.auxiliary_objective_values = {{o, 2.0}},
|
|
.feasibility_status = SolutionStatus::kInfeasible},
|
|
.dual_solution =
|
|
DualSolution{.dual_values = {{c, 3.0}, {d, 4.0}},
|
|
.reduced_costs = {{x, 2.0}, {y, 1.0}},
|
|
.objective_value = 9.0,
|
|
.feasibility_status = SolutionStatus::kFeasible},
|
|
.basis = Basis{.constraint_status = {{c, BasisStatus::kAtLowerBound},
|
|
{d, BasisStatus::kBasic}},
|
|
.variable_status = {{x, BasisStatus::kAtUpperBound},
|
|
{y, BasisStatus::kFree}},
|
|
.basic_dual_feasibility = SolutionStatus::kFeasible}};
|
|
|
|
// Test one way
|
|
EXPECT_THAT(
|
|
Solution::FromProto(&model, proto),
|
|
IsOkAndHolds(IsNear(expected, SolutionMatcherOptions{.tolerance = 0.0})));
|
|
// Test round trip
|
|
EXPECT_THAT(
|
|
Solution::FromProto(&model, expected.Proto()),
|
|
IsOkAndHolds(IsNear(expected, SolutionMatcherOptions{.tolerance = 0.0})));
|
|
}
|
|
|
|
TEST(SolutionTest, FromProtoInvalidPrimalSolution) {
|
|
ModelStorage storage;
|
|
storage.AddVariable("x");
|
|
SolutionProto proto;
|
|
proto.mutable_primal_solution()->mutable_variable_values()->add_ids(0);
|
|
proto.mutable_primal_solution()->set_feasibility_status(
|
|
SOLUTION_STATUS_FEASIBLE);
|
|
EXPECT_THAT(Solution::FromProto(&storage, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument,
|
|
HasSubstr("primal_solution")));
|
|
}
|
|
|
|
TEST(SolutionTest, FromProtoInvalidDualSolution) {
|
|
ModelStorage storage;
|
|
storage.AddLinearConstraint("c");
|
|
SolutionProto proto;
|
|
proto.mutable_dual_solution()->mutable_dual_values()->add_ids(0);
|
|
proto.mutable_dual_solution()->set_feasibility_status(
|
|
SOLUTION_STATUS_FEASIBLE);
|
|
EXPECT_THAT(
|
|
Solution::FromProto(&storage, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("dual_solution")));
|
|
}
|
|
|
|
TEST(SolutionTest, FromProtoInvalidBasis) {
|
|
ModelStorage storage;
|
|
storage.AddVariable("c");
|
|
SolutionProto proto;
|
|
proto.mutable_basis()->mutable_variable_status()->add_ids(0);
|
|
EXPECT_THAT(Solution::FromProto(&storage, proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("basis")));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace math_opt
|
|
} // namespace operations_research
|