Files
ortools-clone/ortools/math_opt/python/solution_test.py

320 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2024-01-04 13:43:15 +01:00
# Copyright 2010-2024 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.
from absl.testing import absltest
from ortools.math_opt import solution_pb2
from ortools.math_opt.python import model
from ortools.math_opt.python import solution
from ortools.math_opt.python.testing import compare_proto
class ParsePrimalSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
2024-04-12 11:36:36 +02:00
def test_empty_primal_solution_proto_round_trip(self) -> None:
mod = model.Model(name="test_model")
empty_solution = solution.PrimalSolution(
objective_value=2.0,
feasibility_status=solution.SolutionStatus.UNDETERMINED,
)
empty_proto = empty_solution.to_proto()
expected_proto = solution_pb2.PrimalSolutionProto()
expected_proto.objective_value = 2.0
expected_proto.feasibility_status = solution_pb2.SOLUTION_STATUS_UNDETERMINED
self.assert_protos_equiv(expected_proto, empty_proto)
round_trip_solution = solution.parse_primal_solution(empty_proto, mod)
self.assertEmpty(round_trip_solution.variable_values)
def test_primal_solution_proto_round_trip(self) -> None:
mod = model.Model(name="test_model")
x = mod.add_binary_variable(name="x")
mod.add_binary_variable(name="y")
z = mod.add_binary_variable(name="z")
proto = solution_pb2.PrimalSolutionProto(
objective_value=2.0,
feasibility_status=solution_pb2.SOLUTION_STATUS_FEASIBLE,
)
proto.variable_values.ids[:] = [0, 2]
proto.variable_values.values[:] = [1.0, 0.0]
actual = solution.parse_primal_solution(proto, mod)
self.assertDictEqual({x: 1.0, z: 0.0}, actual.variable_values)
self.assertEqual(2.0, actual.objective_value)
self.assertEqual(solution.SolutionStatus.FEASIBLE, actual.feasibility_status)
self.assert_protos_equiv(proto, actual.to_proto())
def test_primal_solution_unspecified_feasibility(self) -> None:
mod = model.Model(name="test_model")
proto = solution_pb2.PrimalSolutionProto(
objective_value=2.0,
feasibility_status=solution_pb2.SOLUTION_STATUS_UNSPECIFIED,
)
with self.assertRaisesRegex(
ValueError, "Primal solution feasibility.*UNSPECIFIED"
):
solution.parse_primal_solution(proto, mod)
class ParsePrimalRayTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
2024-04-12 11:36:36 +02:00
def test_parse(self) -> None:
mod = model.Model(name="test_model")
x = mod.add_binary_variable(name="x")
y = mod.add_binary_variable(name="y")
proto = solution_pb2.PrimalRayProto()
proto.variable_values.ids[:] = [0, 1]
proto.variable_values.values[:] = [1.0, 1.0]
actual = solution.parse_primal_ray(proto, mod)
self.assertDictEqual({x: 1.0, y: 1.0}, actual.variable_values)
class ParseDualSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
2024-04-12 11:36:36 +02:00
def test_empty_primal_solution_proto_round_trip(self) -> None:
mod = model.Model(name="test_model")
empty_solution = solution.DualSolution(
objective_value=2.0,
feasibility_status=solution.SolutionStatus.UNDETERMINED,
)
empty_proto = empty_solution.to_proto()
expected_proto = solution_pb2.DualSolutionProto()
expected_proto.objective_value = 2.0
expected_proto.feasibility_status = solution_pb2.SOLUTION_STATUS_UNDETERMINED
self.assert_protos_equiv(expected_proto, empty_proto)
round_trip_solution = solution.parse_dual_solution(empty_proto, mod)
self.assertEmpty(round_trip_solution.dual_values)
self.assertEmpty(round_trip_solution.reduced_costs)
def test_no_obj(self) -> None:
mod = model.Model(name="test_model")
x = mod.add_binary_variable(name="x")
y = mod.add_binary_variable(name="y")
c = mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
d = mod.add_linear_constraint(lb=0.0, ub=1.0, name="d")
proto = solution_pb2.DualSolutionProto()
proto.dual_values.ids[:] = [0, 1]
proto.dual_values.values[:] = [0.0, 1.0]
proto.reduced_costs.ids[:] = [0, 1]
proto.reduced_costs.values[:] = [10.0, 0.0]
proto.feasibility_status = solution_pb2.SOLUTION_STATUS_FEASIBLE
actual = solution.parse_dual_solution(proto, mod)
self.assertDictEqual({x: 10.0, y: 0.0}, actual.reduced_costs)
self.assertDictEqual({c: 0.0, d: 1.0}, actual.dual_values)
self.assertIsNone(actual.objective_value)
self.assertEqual(solution.SolutionStatus.FEASIBLE, actual.feasibility_status)
self.assert_protos_equiv(proto, actual.to_proto())
def test_with_obj(self) -> None:
mod = model.Model(name="test_model")
c = mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
proto = solution_pb2.DualSolutionProto(objective_value=3.0)
proto.dual_values.ids[:] = [0]
proto.dual_values.values[:] = [5.0]
proto.feasibility_status = solution_pb2.SOLUTION_STATUS_INFEASIBLE
actual = solution.parse_dual_solution(proto, mod)
self.assertEmpty(actual.reduced_costs)
self.assertDictEqual({c: 5.0}, actual.dual_values)
self.assertEqual(3.0, actual.objective_value)
self.assertEqual(solution.SolutionStatus.INFEASIBLE, actual.feasibility_status)
self.assert_protos_equiv(proto, actual.to_proto())
def test_dual_solution_unspecified_feasibility(self) -> None:
mod = model.Model(name="test_model")
proto = solution_pb2.DualSolutionProto(
objective_value=2.0,
feasibility_status=solution_pb2.SOLUTION_STATUS_UNSPECIFIED,
)
with self.assertRaisesRegex(
ValueError, "Dual solution feasibility.*UNSPECIFIED"
):
solution.parse_dual_solution(proto, mod)
class ParseDualRayTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
2024-04-12 11:36:36 +02:00
def test_parse(self) -> None:
mod = model.Model(name="test_model")
x = mod.add_binary_variable(name="x")
y = mod.add_binary_variable(name="y")
c = mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
d = mod.add_linear_constraint(lb=0.0, ub=1.0, name="d")
proto = solution_pb2.DualRayProto()
proto.dual_values.ids[:] = [0, 1]
proto.dual_values.values[:] = [0.0, 1.0]
proto.reduced_costs.ids[:] = [0, 1]
proto.reduced_costs.values[:] = [10.0, 0.0]
actual = solution.parse_dual_ray(proto, mod)
self.assertDictEqual({x: 10.0, y: 0.0}, actual.reduced_costs)
self.assertDictEqual({c: 0.0, d: 1.0}, actual.dual_values)
class BasisTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
2024-04-12 11:36:36 +02:00
def test_empty_basis_proto_round_trip(self) -> None:
mod = model.Model(name="test_model")
empty_basis = solution.Basis()
empty_proto = empty_basis.to_proto()
expected_proto = solution_pb2.BasisProto()
expected_proto.basic_dual_feasibility = (
solution_pb2.SOLUTION_STATUS_UNDETERMINED
)
self.assert_protos_equiv(expected_proto, empty_proto)
round_trip_basis = solution.parse_basis(empty_proto, mod)
self.assertEmpty(round_trip_basis.constraint_status)
self.assertEmpty(round_trip_basis.variable_status)
def test_basis_proto_round_trip(self) -> None:
mod = model.Model(name="test_model")
x = mod.add_binary_variable(name="x")
y = mod.add_binary_variable(name="y")
c = mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
d = mod.add_linear_constraint(lb=0.0, ub=1.0, name="d")
basis = solution.Basis()
basis.variable_status[x] = solution.BasisStatus.AT_LOWER_BOUND
basis.variable_status[y] = solution.BasisStatus.BASIC
basis.constraint_status[c] = solution.BasisStatus.BASIC
basis.constraint_status[d] = solution.BasisStatus.AT_UPPER_BOUND
basis.basic_dual_feasibility = solution.SolutionStatus.FEASIBLE
basis_proto = basis.to_proto()
expected_proto = solution_pb2.BasisProto()
expected_proto.constraint_status.ids[:] = [0, 1]
expected_proto.constraint_status.values[:] = [
solution_pb2.BASIS_STATUS_BASIC,
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND,
]
expected_proto.variable_status.ids[:] = [0, 1]
expected_proto.variable_status.values[:] = [
solution_pb2.BASIS_STATUS_AT_LOWER_BOUND,
solution_pb2.BASIS_STATUS_BASIC,
]
expected_proto.basic_dual_feasibility = solution_pb2.SOLUTION_STATUS_FEASIBLE
self.assert_protos_equiv(expected_proto, basis_proto)
round_trip_basis = solution.parse_basis(basis_proto, mod)
self.assertDictEqual(
{c: solution.BasisStatus.BASIC, d: solution.BasisStatus.AT_UPPER_BOUND},
round_trip_basis.constraint_status,
)
self.assertDictEqual(
{x: solution.BasisStatus.AT_LOWER_BOUND, y: solution.BasisStatus.BASIC},
round_trip_basis.variable_status,
)
def test_constraint_status_unspecified(self) -> None:
mod = model.Model(name="test_model")
mod.add_binary_variable(name="x")
mod.add_binary_variable(name="y")
mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
mod.add_linear_constraint(lb=0.0, ub=1.0, name="d")
basis_proto = solution_pb2.BasisProto()
basis_proto.constraint_status.ids[:] = [0, 1]
basis_proto.constraint_status.values[:] = [
solution_pb2.BASIS_STATUS_UNSPECIFIED,
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND,
]
basis_proto.variable_status.ids[:] = [0, 1]
basis_proto.variable_status.values[:] = [
solution_pb2.BASIS_STATUS_AT_LOWER_BOUND,
solution_pb2.BASIS_STATUS_BASIC,
]
basis_proto.basic_dual_feasibility = solution_pb2.SOLUTION_STATUS_FEASIBLE
with self.assertRaisesRegex(ValueError, "Constraint basis.*UNSPECIFIED"):
solution.parse_basis(basis_proto, mod)
def test_variable_status_unspecified(self) -> None:
mod = model.Model(name="test_model")
mod.add_binary_variable(name="x")
mod.add_binary_variable(name="y")
mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
mod.add_linear_constraint(lb=0.0, ub=1.0, name="d")
basis_proto = solution_pb2.BasisProto()
basis_proto.constraint_status.ids[:] = [0, 1]
basis_proto.constraint_status.values[:] = [
solution_pb2.BASIS_STATUS_BASIC,
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND,
]
basis_proto.variable_status.ids[:] = [0, 1]
basis_proto.variable_status.values[:] = [
solution_pb2.BASIS_STATUS_UNSPECIFIED,
solution_pb2.BASIS_STATUS_BASIC,
]
basis_proto.basic_dual_feasibility = solution_pb2.SOLUTION_STATUS_FEASIBLE
with self.assertRaisesRegex(ValueError, "Variable basis.*UNSPECIFIED"):
solution.parse_basis(basis_proto, mod)
def test_basic_dual_feasibility_unspecified(self) -> None:
mod = model.Model(name="test_model")
mod.add_binary_variable(name="x")
mod.add_binary_variable(name="y")
mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
mod.add_linear_constraint(lb=0.0, ub=1.0, name="d")
basis_proto = solution_pb2.BasisProto()
basis_proto.constraint_status.ids[:] = [0, 1]
basis_proto.constraint_status.values[:] = [
solution_pb2.BASIS_STATUS_BASIC,
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND,
]
basis_proto.variable_status.ids[:] = [0, 1]
basis_proto.variable_status.values[:] = [
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND,
solution_pb2.BASIS_STATUS_BASIC,
]
basis_proto.basic_dual_feasibility = solution_pb2.SOLUTION_STATUS_UNSPECIFIED
with self.assertRaisesRegex(ValueError, "Basic dual feasibility.*UNSPECIFIED"):
solution.parse_basis(basis_proto, mod)
class ParseSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
2024-04-12 11:36:36 +02:00
def test_solution_proto_round_trip(self) -> None:
mod = model.Model(name="test_model")
mod.add_variable()
mod.add_variable()
mod.add_linear_constraint()
mod.add_linear_constraint()
primal_solution = solution_pb2.PrimalSolutionProto(
objective_value=2.0,
feasibility_status=solution_pb2.SOLUTION_STATUS_INFEASIBLE,
)
primal_solution.variable_values.ids[:] = [0, 1]
primal_solution.variable_values.values[:] = [1.0, 0.0]
dual_solution = solution_pb2.DualSolutionProto(
objective_value=2.0,
feasibility_status=solution_pb2.SOLUTION_STATUS_FEASIBLE,
)
dual_solution.dual_values.ids[:] = [0, 1]
dual_solution.dual_values.values[:] = [0.0, 1.0]
dual_solution.reduced_costs.ids[:] = [0, 1]
dual_solution.reduced_costs.values[:] = [10.0, 0.0]
basis = solution_pb2.BasisProto()
basis.constraint_status.ids[:] = [0, 1]
basis.constraint_status.values[:] = [
solution_pb2.BASIS_STATUS_BASIC,
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND,
]
basis.variable_status.ids[:] = [0, 1]
basis.variable_status.values[:] = [
solution_pb2.BASIS_STATUS_AT_LOWER_BOUND,
solution_pb2.BASIS_STATUS_BASIC,
]
basis.basic_dual_feasibility = solution_pb2.SOLUTION_STATUS_FEASIBLE
proto = solution_pb2.SolutionProto(
primal_solution=primal_solution,
dual_solution=dual_solution,
basis=basis,
)
actual = solution.parse_solution(proto, mod)
self.assert_protos_equiv(proto, actual.to_proto())
if __name__ == "__main__":
absltest.main()