move math_opt samples; add python code + samples
This commit is contained in:
313
ortools/math_opt/python/solution_test.py
Normal file
313
ortools/math_opt/python/solution_test.py
Normal file
@@ -0,0 +1,313 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2010-2022 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.
|
||||
|
||||
import unittest
|
||||
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, unittest.TestCase):
|
||||
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, unittest.TestCase):
|
||||
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, unittest.TestCase):
|
||||
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, unittest.TestCase):
|
||||
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, unittest.TestCase):
|
||||
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, unittest.TestCase):
|
||||
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__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user