math_opt: export from google3
This commit is contained in:
@@ -144,9 +144,11 @@ from ortools.math_opt.python.solution import Basis
|
||||
from ortools.math_opt.python.solution import BasisStatus
|
||||
from ortools.math_opt.python.solution import DualRay
|
||||
from ortools.math_opt.python.solution import DualSolution
|
||||
from ortools.math_opt.python.solution import optional_solution_status_to_proto
|
||||
from ortools.math_opt.python.solution import parse_basis
|
||||
from ortools.math_opt.python.solution import parse_dual_ray
|
||||
from ortools.math_opt.python.solution import parse_dual_solution
|
||||
from ortools.math_opt.python.solution import parse_optional_solution_status
|
||||
from ortools.math_opt.python.solution import parse_primal_ray
|
||||
from ortools.math_opt.python.solution import parse_primal_solution
|
||||
from ortools.math_opt.python.solution import parse_solution
|
||||
|
||||
@@ -100,9 +100,6 @@ class ModelParametersTest(compare_proto.MathOptProtoAssertions, absltest.TestCas
|
||||
expected.initial_basis.variable_status.values.append(
|
||||
solution_pb2.BASIS_STATUS_AT_UPPER_BOUND
|
||||
)
|
||||
expected.initial_basis.basic_dual_feasibility = (
|
||||
solution_pb2.SOLUTION_STATUS_UNDETERMINED
|
||||
)
|
||||
self.assert_protos_equiv(expected, actual)
|
||||
|
||||
|
||||
|
||||
@@ -58,6 +58,24 @@ class SolutionStatus(enum.Enum):
|
||||
INFEASIBLE = solution_pb2.SOLUTION_STATUS_INFEASIBLE
|
||||
|
||||
|
||||
def parse_optional_solution_status(
|
||||
proto: solution_pb2.SolutionStatusProto,
|
||||
) -> Optional[SolutionStatus]:
|
||||
"""Converts a proto SolutionStatus to an optional Python SolutionStatus."""
|
||||
return (
|
||||
None
|
||||
if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED
|
||||
else SolutionStatus(proto)
|
||||
)
|
||||
|
||||
|
||||
def optional_solution_status_to_proto(
|
||||
status: Optional[SolutionStatus],
|
||||
) -> solution_pb2.SolutionStatusProto:
|
||||
"""Converts an optional Python SolutionStatus to a proto SolutionStatus."""
|
||||
return solution_pb2.SOLUTION_STATUS_UNSPECIFIED if status is None else status.value
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class PrimalSolution:
|
||||
"""A solution to the optimization problem in a Model.
|
||||
@@ -312,12 +330,13 @@ class Basis:
|
||||
For two-sided LPs it may be different in some edge cases (e.g. incomplete
|
||||
solves with primal simplex). For more details see
|
||||
go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting
|
||||
basis via ModelSolveParameters.initial_basis, this value is ignored. It is
|
||||
only relevant for the basis returned by Solution.basis. This is an
|
||||
advanced status. For single-sided LPs it should be equal to the
|
||||
feasibility status of the associated dual solution. For two-sided LPs it
|
||||
may be different in some edge cases (e.g. incomplete solves with primal
|
||||
simplex). For more details see go/mathopt-basis-advanced#dualfeasibility.
|
||||
basis via ModelSolveParameters.initial_basis, this value is ignored and
|
||||
can be None. It is only relevant for the basis returned by Solution.basis,
|
||||
and it is never None when returned from solve(). This is an advanced
|
||||
status. For single-sided LPs it should be equal to the feasibility status
|
||||
of the associated dual solution. For two-sided LPs it may be different in
|
||||
some edge cases (e.g. incomplete solves with primal simplex). For more
|
||||
details see go/mathopt-basis-advanced#dualfeasibility.
|
||||
"""
|
||||
|
||||
variable_status: Dict[model.Variable, BasisStatus] = dataclasses.field(
|
||||
@@ -326,7 +345,7 @@ class Basis:
|
||||
constraint_status: Dict[model.LinearConstraint, BasisStatus] = dataclasses.field(
|
||||
default_factory=dict
|
||||
)
|
||||
basic_dual_feasibility: SolutionStatus = SolutionStatus.UNDETERMINED
|
||||
basic_dual_feasibility: Optional[SolutionStatus] = None
|
||||
|
||||
def to_proto(self) -> solution_pb2.BasisProto:
|
||||
"""Returns an equivalent proto for the basis."""
|
||||
@@ -335,7 +354,9 @@ class Basis:
|
||||
constraint_status=_to_sparse_basis_status_vector_proto(
|
||||
self.constraint_status
|
||||
),
|
||||
basic_dual_feasibility=self.basic_dual_feasibility.value,
|
||||
basic_dual_feasibility=optional_solution_status_to_proto(
|
||||
self.basic_dual_feasibility
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -354,10 +375,9 @@ def parse_basis(proto: solution_pb2.BasisProto, mod: model.Model) -> Basis:
|
||||
result.constraint_status[mod.get_linear_constraint(cid)] = BasisStatus(
|
||||
status_proto
|
||||
)
|
||||
status_proto = proto.basic_dual_feasibility
|
||||
if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
|
||||
raise ValueError("Basic dual feasibility status should not be UNSPECIFIED")
|
||||
result.basic_dual_feasibility = SolutionStatus(status_proto)
|
||||
result.basic_dual_feasibility = parse_optional_solution_status(
|
||||
proto.basic_dual_feasibility
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
@@ -19,6 +19,18 @@ from ortools.math_opt.python import solution
|
||||
from ortools.math_opt.python.testing import compare_proto
|
||||
|
||||
|
||||
class SolutionStatusTest(absltest.TestCase):
|
||||
|
||||
def test_optional_status_round_trip(self):
|
||||
for status in solution_pb2.SolutionStatusProto.values():
|
||||
self.assertEqual(
|
||||
status,
|
||||
solution.optional_solution_status_to_proto(
|
||||
solution.parse_optional_solution_status(status)
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class ParsePrimalSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
def test_empty_primal_solution_proto_round_trip(self) -> None:
|
||||
@@ -164,13 +176,11 @@ class BasisTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
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)
|
||||
self.assertIsNone(round_trip_basis.basic_dual_feasibility)
|
||||
|
||||
def test_basis_proto_round_trip(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
@@ -252,24 +262,9 @@ class BasisTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
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)
|
||||
basis = solution.parse_basis(basis_proto, mod)
|
||||
self.assertIsNone(basis.basic_dual_feasibility)
|
||||
|
||||
|
||||
class ParseSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
@@ -37,8 +37,9 @@ class SolverResources:
|
||||
MOE:begin_intracomment_strip
|
||||
|
||||
The go/uoss server will use these parameters to do a bin-packing of all
|
||||
requests. They are generally used as soft-limits though instead of
|
||||
hard-limits and a solve may be able to consume more resources than requested.
|
||||
requests. Parameter cpu is a soft-limit, the solve may still be able to use
|
||||
more CPUs. The ram parameter is an hard-limit, an out-of-memory error will
|
||||
occur if the solve attempts to use more memory.
|
||||
|
||||
MOE:end_intracomment_strip
|
||||
|
||||
@@ -58,9 +59,12 @@ class SolverResources:
|
||||
better to consult each solver documentation to set this parameter. Note
|
||||
that if the SolveParameters.threads is not set then this parameter should
|
||||
also be left unset.
|
||||
ram: The limit of RAM for the solve in bytes. Must be finite and >=1.0 (even
|
||||
though it should in practice be much larger).
|
||||
"""
|
||||
|
||||
cpu: Optional[float] = None
|
||||
ram: Optional[float] = None
|
||||
|
||||
def to_proto(self) -> rpc_pb2.SolverResourcesProto:
|
||||
return rpc_pb2.SolverResourcesProto(cpu=self.cpu)
|
||||
return rpc_pb2.SolverResourcesProto(cpu=self.cpu, ram=self.ram)
|
||||
|
||||
@@ -34,6 +34,12 @@ class SolverResourcesTest(compare_proto.MathOptProtoAssertions, absltest.TestCas
|
||||
rpc_pb2.SolverResourcesProto(cpu=3.5),
|
||||
)
|
||||
|
||||
def test_to_proto_with_ram(self):
|
||||
self.assert_protos_equiv(
|
||||
solver_resources.SolverResources(ram=50 * 1024 * 1024).to_proto(),
|
||||
rpc_pb2.SolverResourcesProto(ram=50 * 1024 * 1024),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
absltest.main()
|
||||
|
||||
Reference in New Issue
Block a user