math_opt: Export from google3
This commit is contained in:
@@ -28,6 +28,7 @@ py_library(
|
||||
":errors",
|
||||
":expressions",
|
||||
":hash_model_storage",
|
||||
":init_arguments",
|
||||
":message_callback",
|
||||
":model",
|
||||
":model_parameters",
|
||||
@@ -160,6 +161,7 @@ py_library(
|
||||
":callback",
|
||||
":compute_infeasible_subsystem_result",
|
||||
":errors",
|
||||
":init_arguments",
|
||||
":message_callback",
|
||||
":model",
|
||||
":model_parameters",
|
||||
@@ -210,3 +212,12 @@ py_library(
|
||||
srcs = ["errors.py"],
|
||||
deps = ["//ortools/math_opt:rpc_py_pb2"],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = "init_arguments",
|
||||
srcs = ["init_arguments.py"],
|
||||
deps = [
|
||||
"//ortools/math_opt:parameters_py_pb2",
|
||||
"//ortools/math_opt/solvers:gurobi_py_pb2",
|
||||
],
|
||||
)
|
||||
|
||||
180
ortools/math_opt/python/init_arguments.py
Normal file
180
ortools/math_opt/python/init_arguments.py
Normal file
@@ -0,0 +1,180 @@
|
||||
# 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.
|
||||
|
||||
"""Configures the instantiation of the underlying solver."""
|
||||
|
||||
import dataclasses
|
||||
from typing import Optional
|
||||
|
||||
from ortools.math_opt import parameters_pb2
|
||||
from ortools.math_opt.solvers import gurobi_pb2
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableGScipInitArguments:
|
||||
"""Streamable GScip specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class GurobiISVKey:
|
||||
"""The Gurobi ISV key, an alternative to license files.
|
||||
|
||||
Contact Gurobi for details.
|
||||
|
||||
Attributes:
|
||||
name: A string, typically a company/organization.
|
||||
application_name: A string, typically a project.
|
||||
expiration: An int, a value of 0 indicates no expiration.
|
||||
key: A string, the secret.
|
||||
"""
|
||||
|
||||
name: str = ""
|
||||
application_name: str = ""
|
||||
expiration: int = 0
|
||||
key: str = ""
|
||||
|
||||
def to_proto(self) -> gurobi_pb2.GurobiInitializerProto.ISVKey:
|
||||
"""Returns a protocol buffer equivalent of this."""
|
||||
return gurobi_pb2.GurobiInitializerProto.ISVKey(
|
||||
name=self.name,
|
||||
application_name=self.application_name,
|
||||
expiration=self.expiration,
|
||||
key=self.key,
|
||||
)
|
||||
|
||||
|
||||
def gurobi_isv_key_from_proto(
|
||||
proto: gurobi_pb2.GurobiInitializerProto.ISVKey,
|
||||
) -> GurobiISVKey:
|
||||
"""Returns an equivalent GurobiISVKey to the input proto."""
|
||||
return GurobiISVKey(
|
||||
name=proto.name,
|
||||
application_name=proto.application_name,
|
||||
expiration=proto.expiration,
|
||||
key=proto.key,
|
||||
)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableGurobiInitArguments:
|
||||
"""Streamable Gurobi specific parameters for solver instantiation."""
|
||||
|
||||
isv_key: Optional[GurobiISVKey] = None
|
||||
|
||||
def to_proto(self) -> gurobi_pb2.GurobiInitializerProto:
|
||||
"""Returns a protocol buffer equivalent of this."""
|
||||
return gurobi_pb2.GurobiInitializerProto(
|
||||
isv_key=self.isv_key.to_proto() if self.isv_key else None
|
||||
)
|
||||
|
||||
|
||||
def streamable_gurobi_init_arguments_from_proto(
|
||||
proto: gurobi_pb2.GurobiInitializerProto,
|
||||
) -> StreamableGurobiInitArguments:
|
||||
"""Returns an equivalent StreamableGurobiInitArguments to the input proto."""
|
||||
result = StreamableGurobiInitArguments()
|
||||
if proto.HasField("isv_key"):
|
||||
result.isv_key = gurobi_isv_key_from_proto(proto.isv_key)
|
||||
return result
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableGlopInitArguments:
|
||||
"""Streamable Glop specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableCpSatInitArguments:
|
||||
"""Streamable CP-SAT specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamablePdlpInitArguments:
|
||||
"""Streamable Pdlp specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableGlpkInitArguments:
|
||||
"""Streamable GLPK specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableOsqpInitArguments:
|
||||
"""Streamable OSQP specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableEcosInitArguments:
|
||||
"""Streamable Ecos specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableScsInitArguments:
|
||||
"""Streamable Scs specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableHighsInitArguments:
|
||||
"""Streamable Highs specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableSantoriniInitArguments:
|
||||
"""Streamable Santorini specific parameters for solver instantiation."""
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class StreamableSolverInitArguments:
|
||||
"""Solver initialization parameters that can be sent to another process.
|
||||
|
||||
Attributes:
|
||||
gscip: Initialization parameters specific to GScip.
|
||||
gurobi: Initialization parameters specific to Gurobi.
|
||||
glop: Initialization parameters specific to GLOP.
|
||||
cp_sat: Initialization parameters specific to CP-SAT.
|
||||
pdlp: Initialization parameters specific to PDLP.
|
||||
glpk: Initialization parameters specific to GLPK.
|
||||
osqp: Initialization parameters specific to OSQP.
|
||||
ecos: Initialization parameters specific to ECOS.
|
||||
scs: Initialization parameters specific to SCS.
|
||||
highs: Initialization parameters specific to HiGHS.
|
||||
santorini: Initialization parameters specific to Santorini.
|
||||
"""
|
||||
|
||||
gscip: Optional[StreamableGScipInitArguments] = None
|
||||
gurobi: Optional[StreamableGurobiInitArguments] = None
|
||||
glop: Optional[StreamableGlopInitArguments] = None
|
||||
cp_sat: Optional[StreamableCpSatInitArguments] = None
|
||||
pdlp: Optional[StreamablePdlpInitArguments] = None
|
||||
glpk: Optional[StreamableGlpkInitArguments] = None
|
||||
osqp: Optional[StreamableOsqpInitArguments] = None
|
||||
ecos: Optional[StreamableEcosInitArguments] = None
|
||||
scs: Optional[StreamableScsInitArguments] = None
|
||||
highs: Optional[StreamableHighsInitArguments] = None
|
||||
santorini: Optional[StreamableSantoriniInitArguments] = None
|
||||
|
||||
def to_proto(self) -> parameters_pb2.SolverInitializerProto:
|
||||
"""Returns a protocol buffer equivalent of this."""
|
||||
return parameters_pb2.SolverInitializerProto(
|
||||
gurobi=self.gurobi.to_proto() if self.gurobi else None
|
||||
)
|
||||
|
||||
|
||||
def streamable_solver_init_arguments_from_proto(
|
||||
proto: parameters_pb2.SolverInitializerProto,
|
||||
) -> StreamableSolverInitArguments:
|
||||
"""Returns an equivalent StreamableSolverInitArguments to the input proto."""
|
||||
result = StreamableSolverInitArguments()
|
||||
if proto.HasField("gurobi"):
|
||||
result.gurobi = streamable_gurobi_init_arguments_from_proto(proto.gurobi)
|
||||
return result
|
||||
102
ortools/math_opt/python/init_arguments_test.py
Normal file
102
ortools/math_opt/python/init_arguments_test.py
Normal file
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
# 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 parameters_pb2
|
||||
from ortools.math_opt.python import init_arguments
|
||||
from ortools.math_opt.python.testing import compare_proto
|
||||
from ortools.math_opt.solvers import gurobi_pb2
|
||||
|
||||
|
||||
class GurobiISVKeyTest(absltest.TestCase, compare_proto.MathOptProtoAssertions):
|
||||
|
||||
def test_proto_conversions(self) -> None:
|
||||
isv = init_arguments.GurobiISVKey(
|
||||
name="cat", application_name="hat", expiration=4, key="bat"
|
||||
)
|
||||
proto_isv = gurobi_pb2.GurobiInitializerProto.ISVKey(
|
||||
name="cat", application_name="hat", expiration=4, key="bat"
|
||||
)
|
||||
self.assert_protos_equiv(isv.to_proto(), proto_isv)
|
||||
self.assertEqual(init_arguments.gurobi_isv_key_from_proto(proto_isv), isv)
|
||||
|
||||
|
||||
class StreamableGurobiInitArgumentsTest(
|
||||
absltest.TestCase, compare_proto.MathOptProtoAssertions
|
||||
):
|
||||
|
||||
def test_proto_conversions_isv_key_set(self) -> None:
|
||||
init = init_arguments.StreamableGurobiInitArguments(
|
||||
isv_key=init_arguments.GurobiISVKey(
|
||||
name="cat", application_name="hat", expiration=4, key="bat"
|
||||
)
|
||||
)
|
||||
proto_init = gurobi_pb2.GurobiInitializerProto(
|
||||
isv_key=gurobi_pb2.GurobiInitializerProto.ISVKey(
|
||||
name="cat", application_name="hat", expiration=4, key="bat"
|
||||
)
|
||||
)
|
||||
self.assert_protos_equiv(init.to_proto(), proto_init)
|
||||
self.assertEqual(
|
||||
init_arguments.streamable_gurobi_init_arguments_from_proto(proto_init),
|
||||
init,
|
||||
)
|
||||
|
||||
def test_proto_conversions_isv_key_not_set(self) -> None:
|
||||
init = init_arguments.StreamableGurobiInitArguments()
|
||||
proto_init = gurobi_pb2.GurobiInitializerProto()
|
||||
self.assert_protos_equiv(init.to_proto(), proto_init)
|
||||
self.assertEqual(
|
||||
init_arguments.streamable_gurobi_init_arguments_from_proto(proto_init),
|
||||
init,
|
||||
)
|
||||
|
||||
|
||||
class StreamableSolverInitArgumentsTest(
|
||||
absltest.TestCase, compare_proto.MathOptProtoAssertions
|
||||
):
|
||||
|
||||
def test_proto_conversions_gurobi_set(self) -> None:
|
||||
init = init_arguments.StreamableSolverInitArguments(
|
||||
gurobi=init_arguments.StreamableGurobiInitArguments(
|
||||
isv_key=init_arguments.GurobiISVKey(
|
||||
name="cat", application_name="hat", expiration=4, key="bat"
|
||||
)
|
||||
)
|
||||
)
|
||||
proto_init = parameters_pb2.SolverInitializerProto(
|
||||
gurobi=gurobi_pb2.GurobiInitializerProto(
|
||||
isv_key=gurobi_pb2.GurobiInitializerProto.ISVKey(
|
||||
name="cat", application_name="hat", expiration=4, key="bat"
|
||||
)
|
||||
)
|
||||
)
|
||||
self.assert_protos_equiv(init.to_proto(), proto_init)
|
||||
self.assertEqual(
|
||||
init_arguments.streamable_solver_init_arguments_from_proto(proto_init),
|
||||
init,
|
||||
)
|
||||
|
||||
def test_proto_conversions_gurobi_not_set(self) -> None:
|
||||
init = init_arguments.StreamableSolverInitArguments()
|
||||
proto_init = parameters_pb2.SolverInitializerProto()
|
||||
self.assert_protos_equiv(init.to_proto(), proto_init)
|
||||
self.assertEqual(
|
||||
init_arguments.streamable_solver_init_arguments_from_proto(proto_init),
|
||||
init,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
absltest.main()
|
||||
@@ -65,6 +65,26 @@ from ortools.math_opt.python.errors import status_proto_to_exception
|
||||
from ortools.math_opt.python.expressions import evaluate_expression
|
||||
from ortools.math_opt.python.expressions import fast_sum
|
||||
from ortools.math_opt.python.hash_model_storage import HashModelStorage
|
||||
from ortools.math_opt.python.init_arguments import gurobi_isv_key_from_proto
|
||||
from ortools.math_opt.python.init_arguments import GurobiISVKey
|
||||
from ortools.math_opt.python.init_arguments import (
|
||||
streamable_gurobi_init_arguments_from_proto,
|
||||
)
|
||||
from ortools.math_opt.python.init_arguments import (
|
||||
streamable_solver_init_arguments_from_proto,
|
||||
)
|
||||
from ortools.math_opt.python.init_arguments import StreamableCpSatInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableEcosInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableGlopInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableGlpkInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableGScipInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableGurobiInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableHighsInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableOsqpInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamablePdlpInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableSantoriniInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableScsInitArguments
|
||||
from ortools.math_opt.python.init_arguments import StreamableSolverInitArguments
|
||||
from ortools.math_opt.python.message_callback import list_message_callback
|
||||
from ortools.math_opt.python.message_callback import log_messages
|
||||
from ortools.math_opt.python.message_callback import printer_message_callback
|
||||
|
||||
@@ -22,6 +22,7 @@ from absl.testing import absltest
|
||||
from ortools.math_opt.python import callback
|
||||
from ortools.math_opt.python import expressions
|
||||
from ortools.math_opt.python import hash_model_storage
|
||||
from ortools.math_opt.python import init_arguments
|
||||
from ortools.math_opt.python import mathopt
|
||||
from ortools.math_opt.python import message_callback
|
||||
from ortools.math_opt.python import model
|
||||
@@ -48,6 +49,7 @@ _MODULES_TO_CHECK: List[types.ModuleType] = [
|
||||
callback,
|
||||
expressions,
|
||||
hash_model_storage,
|
||||
init_arguments,
|
||||
message_callback,
|
||||
model,
|
||||
model_parameters,
|
||||
|
||||
@@ -325,6 +325,18 @@ class Termination:
|
||||
problem_status: ProblemStatus = ProblemStatus()
|
||||
objective_bounds: ObjectiveBounds = ObjectiveBounds()
|
||||
|
||||
def to_proto(self) -> result_pb2.TerminationProto:
|
||||
"""Returns an equivalent protocol buffer to this Termination."""
|
||||
return result_pb2.TerminationProto(
|
||||
reason=self.reason.value,
|
||||
limit=(
|
||||
result_pb2.LIMIT_UNSPECIFIED if self.limit is None else self.limit.value
|
||||
),
|
||||
detail=self.detail,
|
||||
problem_status=self.problem_status.to_proto(),
|
||||
objective_bounds=self.objective_bounds.to_proto(),
|
||||
)
|
||||
|
||||
|
||||
def parse_termination(
|
||||
termination_proto: result_pb2.TerminationProto,
|
||||
@@ -930,6 +942,39 @@ class SolveResult:
|
||||
f"variable_status: {type(variables).__name__!r}"
|
||||
)
|
||||
|
||||
def to_proto(self) -> result_pb2.SolveResultProto:
|
||||
"""Returns an equivalent protocol buffer for a SolveResult."""
|
||||
proto = result_pb2.SolveResultProto(
|
||||
termination=self.termination.to_proto(),
|
||||
solutions=[s.to_proto() for s in self.solutions],
|
||||
primal_rays=[r.to_proto() for r in self.primal_rays],
|
||||
dual_rays=[r.to_proto() for r in self.dual_rays],
|
||||
solve_stats=self.solve_stats.to_proto(),
|
||||
)
|
||||
|
||||
# Ensure that at most solver has solver specific output.
|
||||
existing_solver_specific_output = None
|
||||
|
||||
def has_solver_specific_output(solver_name: str) -> None:
|
||||
nonlocal existing_solver_specific_output
|
||||
if existing_solver_specific_output is not None:
|
||||
raise ValueError(
|
||||
"found solver specific output for both"
|
||||
f" {existing_solver_specific_output} and {solver_name}"
|
||||
)
|
||||
existing_solver_specific_output = solver_name
|
||||
|
||||
if self.gscip_specific_output is not None:
|
||||
has_solver_specific_output("gscip")
|
||||
proto.gscip_output.CopyFrom(self.gscip_specific_output)
|
||||
if self.osqp_specific_output is not None:
|
||||
has_solver_specific_output("osqp")
|
||||
proto.osqp_output.CopyFrom(self.osqp_specific_output)
|
||||
if self.pdlp_specific_output is not None:
|
||||
has_solver_specific_output("pdlp")
|
||||
proto.pdlp_output.CopyFrom(self.pdlp_specific_output)
|
||||
return proto
|
||||
|
||||
|
||||
def _get_problem_status(
|
||||
result_proto: result_pb2.SolveResultProto,
|
||||
|
||||
@@ -16,6 +16,8 @@ import datetime
|
||||
import math
|
||||
|
||||
from absl.testing import absltest
|
||||
from ortools.pdlp import solve_log_pb2
|
||||
from ortools.gscip import gscip_pb2
|
||||
from ortools.math_opt import result_pb2
|
||||
from ortools.math_opt import solution_pb2
|
||||
from ortools.math_opt import sparse_containers_pb2
|
||||
@@ -23,9 +25,10 @@ from ortools.math_opt.python import model
|
||||
from ortools.math_opt.python import result
|
||||
from ortools.math_opt.python import solution
|
||||
from ortools.math_opt.python.testing import compare_proto
|
||||
from ortools.math_opt.solvers import osqp_pb2
|
||||
|
||||
|
||||
class ParseTerminationReason(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
class TerminationTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
def test_termination_unspecified(self) -> None:
|
||||
termination_proto = result_pb2.TerminationProto(
|
||||
@@ -54,7 +57,19 @@ class ParseTerminationReason(compare_proto.MathOptProtoAssertions, absltest.Test
|
||||
):
|
||||
result.parse_termination(termination_proto)
|
||||
|
||||
def test_termination_ok(self) -> None:
|
||||
def test_termination_ok_proto_round_trip(self) -> None:
|
||||
termination = result.Termination(
|
||||
reason=result.TerminationReason.NO_SOLUTION_FOUND,
|
||||
limit=result.Limit.OTHER,
|
||||
detail="detail",
|
||||
problem_status=result.ProblemStatus(
|
||||
primal_status=result.FeasibilityStatus.FEASIBLE,
|
||||
dual_status=result.FeasibilityStatus.INFEASIBLE,
|
||||
primal_or_dual_infeasible=False,
|
||||
),
|
||||
objective_bounds=result.ObjectiveBounds(primal_bound=10, dual_bound=20),
|
||||
)
|
||||
|
||||
termination_proto = result_pb2.TerminationProto(
|
||||
reason=result_pb2.TERMINATION_REASON_NO_SOLUTION_FOUND,
|
||||
limit=result_pb2.LIMIT_OTHER,
|
||||
@@ -68,22 +83,12 @@ class ParseTerminationReason(compare_proto.MathOptProtoAssertions, absltest.Test
|
||||
primal_bound=10, dual_bound=20
|
||||
),
|
||||
)
|
||||
termination = result.parse_termination(termination_proto)
|
||||
self.assertEqual(termination.reason, result.TerminationReason.NO_SOLUTION_FOUND)
|
||||
self.assertEqual(termination.limit, result.Limit.OTHER)
|
||||
self.assertEqual(termination.detail, "detail")
|
||||
self.assertEqual(
|
||||
termination.problem_status,
|
||||
result.ProblemStatus(
|
||||
primal_status=result.FeasibilityStatus.FEASIBLE,
|
||||
dual_status=result.FeasibilityStatus.INFEASIBLE,
|
||||
primal_or_dual_infeasible=False,
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
termination.objective_bounds,
|
||||
result.ObjectiveBounds(primal_bound=10, dual_bound=20),
|
||||
)
|
||||
|
||||
# Test proto-> Termination
|
||||
self.assertEqual(result.parse_termination(termination_proto), termination)
|
||||
|
||||
# Test Termination -> proto
|
||||
self.assert_protos_equiv(termination.to_proto(), termination_proto)
|
||||
|
||||
|
||||
class ParseProblemStatus(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
@@ -617,83 +622,105 @@ def _make_undetermined_result_proto() -> result_pb2.SolveResultProto:
|
||||
primal_bound=math.inf,
|
||||
dual_bound=-math.inf,
|
||||
),
|
||||
),
|
||||
solutions=[
|
||||
solution_pb2.SolutionProto(
|
||||
primal_solution=solution_pb2.PrimalSolutionProto(
|
||||
objective_value=2.0,
|
||||
variable_values=sparse_containers_pb2.SparseDoubleVectorProto(
|
||||
ids=[0], values=[1.0]
|
||||
),
|
||||
feasibility_status=solution_pb2.SOLUTION_STATUS_UNDETERMINED,
|
||||
)
|
||||
)
|
||||
],
|
||||
)
|
||||
)
|
||||
proto.solve_stats.problem_status.primal_status = (
|
||||
result_pb2.FEASIBILITY_STATUS_UNDETERMINED
|
||||
)
|
||||
proto.solve_stats.problem_status.dual_status = (
|
||||
result_pb2.FEASIBILITY_STATUS_UNDETERMINED
|
||||
)
|
||||
proto.solve_stats.problem_status.primal_or_dual_infeasible = False
|
||||
proto.solve_stats.best_primal_bound = math.inf
|
||||
proto.solve_stats.best_dual_bound = -math.inf
|
||||
proto.solve_stats.solve_time.FromTimedelta(datetime.timedelta(minutes=2))
|
||||
return proto
|
||||
|
||||
|
||||
def _make_undetermined_solve_result() -> result.SolveResult:
|
||||
return result.SolveResult(
|
||||
termination=result.Termination(
|
||||
reason=result.TerminationReason.NO_SOLUTION_FOUND,
|
||||
limit=result.Limit.TIME,
|
||||
problem_status=result.ProblemStatus(
|
||||
primal_status=result.FeasibilityStatus.UNDETERMINED,
|
||||
dual_status=result.FeasibilityStatus.UNDETERMINED,
|
||||
),
|
||||
objective_bounds=result.ObjectiveBounds(
|
||||
primal_bound=math.inf, dual_bound=-math.inf
|
||||
),
|
||||
),
|
||||
solve_stats=result.SolveStats(solve_time=datetime.timedelta(minutes=2)),
|
||||
)
|
||||
|
||||
|
||||
class SolveResultTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
def test_solve_result_gscip_output(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
mod.add_binary_variable()
|
||||
res = _make_undetermined_solve_result()
|
||||
res.gscip_specific_output = gscip_pb2.GScipOutput(status_detail="gscip_detail")
|
||||
|
||||
proto = _make_undetermined_result_proto()
|
||||
proto.gscip_output.status_detail = "gscip_detail"
|
||||
res = result.parse_solve_result(proto, mod)
|
||||
assert res.gscip_specific_output is not None
|
||||
self.assertEqual("gscip_detail", res.gscip_specific_output.status_detail)
|
||||
|
||||
def test_solve_result_no_gscip_output(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
mod.add_binary_variable()
|
||||
proto = _make_undetermined_result_proto()
|
||||
res = result.parse_solve_result(proto, mod)
|
||||
self.assertIsNone(res.gscip_specific_output)
|
||||
# proto -> result
|
||||
actual_res = result.parse_solve_result(proto, mod)
|
||||
self.assertIsNotNone(actual_res.gscip_specific_output)
|
||||
assert actual_res.gscip_specific_output is not None
|
||||
self.assertEqual("gscip_detail", actual_res.gscip_specific_output.status_detail)
|
||||
self.assertIsNone(actual_res.pdlp_specific_output)
|
||||
self.assertIsNone(actual_res.osqp_specific_output)
|
||||
|
||||
# result -> proto
|
||||
self.assert_protos_equiv(res.to_proto(), proto)
|
||||
|
||||
def test_solve_result_osqp_output(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
mod.add_binary_variable()
|
||||
proto = _make_undetermined_result_proto()
|
||||
proto.osqp_output.initialized_underlying_solver = False
|
||||
res = result.parse_solve_result(proto, mod)
|
||||
assert res.osqp_specific_output is not None
|
||||
self.assertFalse(res.osqp_specific_output.initialized_underlying_solver)
|
||||
res = _make_undetermined_solve_result()
|
||||
res.osqp_specific_output = osqp_pb2.OsqpOutput(
|
||||
initialized_underlying_solver=True
|
||||
)
|
||||
|
||||
def test_solve_result_no_osqp_output(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
mod.add_binary_variable()
|
||||
proto = _make_undetermined_result_proto()
|
||||
res = result.parse_solve_result(proto, mod)
|
||||
self.assertIsNone(res.osqp_specific_output)
|
||||
proto.osqp_output.initialized_underlying_solver = True
|
||||
|
||||
# proto -> result
|
||||
actual_res = result.parse_solve_result(proto, mod)
|
||||
self.assertIsNotNone(actual_res.osqp_specific_output)
|
||||
assert actual_res.osqp_specific_output is not None
|
||||
self.assertTrue(actual_res.osqp_specific_output.initialized_underlying_solver)
|
||||
self.assertIsNone(actual_res.pdlp_specific_output)
|
||||
self.assertIsNone(actual_res.gscip_specific_output)
|
||||
|
||||
# result -> proto
|
||||
self.assert_protos_equiv(res.to_proto(), proto)
|
||||
|
||||
def test_solve_result_pdlp_output(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
mod.add_binary_variable()
|
||||
proto = _make_undetermined_result_proto()
|
||||
proto.pdlp_output.convergence_information.corrected_dual_objective = 2.0
|
||||
res = result.parse_solve_result(proto, mod)
|
||||
assert res.pdlp_specific_output is not None
|
||||
self.assertEqual(
|
||||
res.pdlp_specific_output.convergence_information.corrected_dual_objective,
|
||||
2.0,
|
||||
res = _make_undetermined_solve_result()
|
||||
res.pdlp_specific_output = result_pb2.SolveResultProto.PdlpOutput(
|
||||
convergence_information=solve_log_pb2.ConvergenceInformation(
|
||||
primal_objective=1.0
|
||||
)
|
||||
)
|
||||
|
||||
def test_solve_result_no_pdlp_output(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
mod.add_binary_variable()
|
||||
proto = _make_undetermined_result_proto()
|
||||
res = result.parse_solve_result(proto, mod)
|
||||
self.assertIsNone(res.pdlp_specific_output)
|
||||
proto.pdlp_output.convergence_information.primal_objective = 1.0
|
||||
|
||||
# proto -> result
|
||||
actual_res = result.parse_solve_result(proto, mod)
|
||||
self.assertIsNotNone(actual_res.pdlp_specific_output)
|
||||
assert actual_res.pdlp_specific_output is not None
|
||||
self.assertEqual(
|
||||
actual_res.pdlp_specific_output.convergence_information.primal_objective,
|
||||
1.0,
|
||||
)
|
||||
self.assertIsNone(actual_res.osqp_specific_output)
|
||||
self.assertIsNone(actual_res.gscip_specific_output)
|
||||
|
||||
# result -> proto
|
||||
self.assert_protos_equiv(res.to_proto(), proto)
|
||||
|
||||
def test_multiple_solver_specific_outputs_error(self) -> None:
|
||||
res = _make_undetermined_solve_result()
|
||||
res.gscip_specific_output = gscip_pb2.GScipOutput(status_detail="gscip_detail")
|
||||
res.osqp_specific_output = osqp_pb2.OsqpOutput(
|
||||
initialized_underlying_solver=False
|
||||
)
|
||||
with self.assertRaisesRegex(ValueError, "solver specific output"):
|
||||
res.to_proto()
|
||||
|
||||
def test_solve_result_from_proto_missing_bounds_in_termination(
|
||||
self,
|
||||
@@ -1048,6 +1075,78 @@ class SolveResultTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
self.assertEqual(20, res.termination.objective_bounds.dual_bound)
|
||||
self.assertIsNone(res.gscip_specific_output)
|
||||
|
||||
def test_to_proto_round_trip(self) -> None:
|
||||
mod = model.Model(name="test_model")
|
||||
x = mod.add_binary_variable(name="x")
|
||||
c = mod.add_linear_constraint(lb=0.0, ub=1.0, name="c")
|
||||
|
||||
s = solution.Solution(
|
||||
primal_solution=solution.PrimalSolution(
|
||||
variable_values={x: 1.0},
|
||||
objective_value=2.0,
|
||||
feasibility_status=solution.SolutionStatus.FEASIBLE,
|
||||
)
|
||||
)
|
||||
r = result.SolveResult(
|
||||
termination=result.Termination(
|
||||
reason=result.TerminationReason.FEASIBLE,
|
||||
limit=result.Limit.TIME,
|
||||
problem_status=result.ProblemStatus(
|
||||
primal_status=result.FeasibilityStatus.FEASIBLE,
|
||||
dual_status=result.FeasibilityStatus.UNDETERMINED,
|
||||
),
|
||||
),
|
||||
solve_stats=result.SolveStats(
|
||||
node_count=3, solve_time=datetime.timedelta(seconds=4)
|
||||
),
|
||||
solutions=[s],
|
||||
primal_rays=[solution.PrimalRay(variable_values={x: 4.0})],
|
||||
dual_rays=[solution.DualRay(reduced_costs={x: 5.0}, dual_values={c: 6.0})],
|
||||
)
|
||||
|
||||
s_proto = solution_pb2.SolutionProto(
|
||||
primal_solution=solution_pb2.PrimalSolutionProto(
|
||||
objective_value=2.0,
|
||||
feasibility_status=solution_pb2.SOLUTION_STATUS_FEASIBLE,
|
||||
variable_values=sparse_containers_pb2.SparseDoubleVectorProto(
|
||||
ids=[0], values=[1.0]
|
||||
),
|
||||
)
|
||||
)
|
||||
r_proto = result_pb2.SolveResultProto(
|
||||
termination=result_pb2.TerminationProto(
|
||||
reason=result_pb2.TERMINATION_REASON_FEASIBLE,
|
||||
limit=result_pb2.LIMIT_TIME,
|
||||
problem_status=result_pb2.ProblemStatusProto(
|
||||
primal_status=result_pb2.FEASIBILITY_STATUS_FEASIBLE,
|
||||
dual_status=result_pb2.FEASIBILITY_STATUS_UNDETERMINED,
|
||||
),
|
||||
),
|
||||
solve_stats=result_pb2.SolveStatsProto(node_count=3),
|
||||
solutions=[s_proto],
|
||||
primal_rays=[
|
||||
solution_pb2.PrimalRayProto(
|
||||
variable_values=sparse_containers_pb2.SparseDoubleVectorProto(
|
||||
ids=[0], values=[4.0]
|
||||
)
|
||||
)
|
||||
],
|
||||
dual_rays=[
|
||||
solution_pb2.DualRayProto(
|
||||
reduced_costs=sparse_containers_pb2.SparseDoubleVectorProto(
|
||||
ids=[0], values=[5.0]
|
||||
),
|
||||
dual_values=sparse_containers_pb2.SparseDoubleVectorProto(
|
||||
ids=[0], values=[6.0]
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
r_proto.solve_stats.solve_time.FromTimedelta(datetime.timedelta(seconds=4))
|
||||
|
||||
self.assert_protos_equiv(r.to_proto(), r_proto)
|
||||
self.assertEqual(result.parse_solve_result(r_proto, mod), r)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
absltest.main()
|
||||
|
||||
@@ -164,6 +164,14 @@ class PrimalRay:
|
||||
default_factory=dict
|
||||
)
|
||||
|
||||
def to_proto(self) -> solution_pb2.PrimalRayProto:
|
||||
"""Returns an equivalent proto to this PrimalRay."""
|
||||
return solution_pb2.PrimalRayProto(
|
||||
variable_values=sparse_containers.to_sparse_double_vector_proto(
|
||||
self.variable_values
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def parse_primal_ray(proto: solution_pb2.PrimalRayProto, mod: model.Model) -> PrimalRay:
|
||||
"""Returns an equivalent PrimalRay from the input proto."""
|
||||
@@ -278,6 +286,17 @@ class DualRay:
|
||||
)
|
||||
reduced_costs: Dict[model.Variable, float] = dataclasses.field(default_factory=dict)
|
||||
|
||||
def to_proto(self) -> solution_pb2.DualRayProto:
|
||||
"""Returns an equivalent proto to this PrimalRay."""
|
||||
return solution_pb2.DualRayProto(
|
||||
dual_values=sparse_containers.to_sparse_double_vector_proto(
|
||||
self.dual_values
|
||||
),
|
||||
reduced_costs=sparse_containers.to_sparse_double_vector_proto(
|
||||
self.reduced_costs
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def parse_dual_ray(proto: solution_pb2.DualRayProto, mod: model.Model) -> DualRay:
|
||||
"""Returns an equivalent DualRay from the input proto."""
|
||||
|
||||
@@ -76,17 +76,24 @@ class ParsePrimalSolutionTest(compare_proto.MathOptProtoAssertions, absltest.Tes
|
||||
solution.parse_primal_solution(proto, mod)
|
||||
|
||||
|
||||
class ParsePrimalRayTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
class PrimalRayTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
def test_parse(self) -> None:
|
||||
def test_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")
|
||||
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)
|
||||
ray = solution.PrimalRay(variable_values={x: 1.0, y: 1.0})
|
||||
ray_proto = solution_pb2.PrimalRayProto()
|
||||
ray_proto.variable_values.ids[:] = [0, 1]
|
||||
ray_proto.variable_values.values[:] = [1.0, 1.0]
|
||||
|
||||
# Test proto -> model
|
||||
parsed_ray = solution.parse_primal_ray(ray_proto, mod)
|
||||
self.assertDictEqual({x: 1.0, y: 1.0}, parsed_ray.variable_values)
|
||||
|
||||
# Test model -> proto
|
||||
exported_ray = ray.to_proto()
|
||||
self.assert_protos_equiv(exported_ray, ray_proto)
|
||||
|
||||
|
||||
class ParseDualSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
@@ -151,22 +158,33 @@ class ParseDualSolutionTest(compare_proto.MathOptProtoAssertions, absltest.TestC
|
||||
solution.parse_dual_solution(proto, mod)
|
||||
|
||||
|
||||
class ParseDualRayTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
class DualRayTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
def test_parse(self) -> None:
|
||||
def test_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")
|
||||
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)
|
||||
|
||||
dual_ray = solution.DualRay(
|
||||
dual_values={c: 0.0, d: 1.0}, reduced_costs={x: 10.0, y: 0.0}
|
||||
)
|
||||
|
||||
dual_ray_proto = solution_pb2.DualRayProto()
|
||||
dual_ray_proto.dual_values.ids[:] = [0, 1]
|
||||
dual_ray_proto.dual_values.values[:] = [0.0, 1.0]
|
||||
dual_ray_proto.reduced_costs.ids[:] = [0, 1]
|
||||
dual_ray_proto.reduced_costs.values[:] = [10.0, 0.0]
|
||||
|
||||
# Test proto -> dual ray
|
||||
parsed_ray = solution.parse_dual_ray(dual_ray_proto, mod)
|
||||
self.assertDictEqual(dual_ray.reduced_costs, parsed_ray.reduced_costs)
|
||||
self.assertDictEqual(dual_ray.dual_values, parsed_ray.dual_values)
|
||||
|
||||
# Test dual ray -> proto
|
||||
exported_proto = dual_ray.to_proto()
|
||||
self.assert_protos_equiv(exported_proto, dual_ray_proto)
|
||||
|
||||
|
||||
class BasisTest(compare_proto.MathOptProtoAssertions, absltest.TestCase):
|
||||
|
||||
@@ -21,6 +21,7 @@ from ortools.math_opt.core.python import solver
|
||||
from ortools.math_opt.python import callback
|
||||
from ortools.math_opt.python import compute_infeasible_subsystem_result
|
||||
from ortools.math_opt.python import errors
|
||||
from ortools.math_opt.python import init_arguments
|
||||
from ortools.math_opt.python import message_callback
|
||||
from ortools.math_opt.python import model
|
||||
from ortools.math_opt.python import model_parameters
|
||||
@@ -40,6 +41,7 @@ def solve(
|
||||
msg_cb: Optional[message_callback.SolveMessageCallback] = None,
|
||||
callback_reg: Optional[callback.CallbackRegistration] = None,
|
||||
cb: Optional[SolveCallback] = None,
|
||||
streamable_init_args: Optional[init_arguments.StreamableSolverInitArguments] = None,
|
||||
) -> result.SolveResult:
|
||||
"""Solves an optimization model.
|
||||
|
||||
@@ -56,6 +58,7 @@ def solve(
|
||||
callback_reg: Configures when the callback will be invoked (if provided) and
|
||||
what data will be collected to access in the callback.
|
||||
cb: A callback that will be called periodically as the solver runs.
|
||||
streamable_init_args: Configuration for initializing the underlying solver.
|
||||
|
||||
Returns:
|
||||
A SolveResult containing the termination reason, solution(s) and stats.
|
||||
@@ -68,6 +71,9 @@ def solve(
|
||||
params = params or parameters.SolveParameters()
|
||||
model_params = model_params or model_parameters.ModelSolveParameters()
|
||||
callback_reg = callback_reg or callback.CallbackRegistration()
|
||||
streamable_init_args = (
|
||||
streamable_init_args or init_arguments.StreamableSolverInitArguments()
|
||||
)
|
||||
model_proto = opt_model.export_model()
|
||||
proto_cb = None
|
||||
if cb is not None:
|
||||
@@ -79,7 +85,7 @@ def solve(
|
||||
proto_result = solver.solve(
|
||||
model_proto,
|
||||
solver_type.value,
|
||||
parameters_pb2.SolverInitializerProto(),
|
||||
streamable_init_args.to_proto(),
|
||||
params.to_proto(),
|
||||
model_params.to_proto(),
|
||||
msg_cb,
|
||||
@@ -98,6 +104,7 @@ def compute_infeasible_subsystem(
|
||||
*,
|
||||
params: Optional[parameters.SolveParameters] = None,
|
||||
msg_cb: Optional[message_callback.SolveMessageCallback] = None,
|
||||
streamable_init_args: Optional[init_arguments.StreamableSolverInitArguments] = None,
|
||||
) -> compute_infeasible_subsystem_result.ComputeInfeasibleSubsystemResult:
|
||||
"""Computes an infeasible subsystem of the input model.
|
||||
|
||||
@@ -107,6 +114,7 @@ def compute_infeasible_subsystem(
|
||||
August 2023, the only supported solver is Gurobi.
|
||||
params: Configuration of the underlying solver.
|
||||
msg_cb: A callback that gives back the underlying solver's logs by the line.
|
||||
streamable_init_args: Configuration for initializing the underlying solver.
|
||||
|
||||
Returns:
|
||||
An `ComputeInfeasibleSubsystemResult` where `feasibility` indicates if the
|
||||
@@ -116,13 +124,16 @@ def compute_infeasible_subsystem(
|
||||
RuntimeError: on invalid inputs or an internal solver error.
|
||||
"""
|
||||
params = params or parameters.SolveParameters()
|
||||
streamable_init_args = (
|
||||
streamable_init_args or init_arguments.StreamableSolverInitArguments()
|
||||
)
|
||||
model_proto = opt_model.export_model()
|
||||
# Solve
|
||||
try:
|
||||
proto_result = solver.compute_infeasible_subsystem(
|
||||
model_proto,
|
||||
solver_type.value,
|
||||
parameters_pb2.SolverInitializerProto(),
|
||||
streamable_init_args.to_proto(),
|
||||
params.to_proto(),
|
||||
msg_cb,
|
||||
None,
|
||||
@@ -163,7 +174,18 @@ class IncrementalSolver:
|
||||
When it is not possible to use `with`, the close() method can be called.
|
||||
"""
|
||||
|
||||
def __init__(self, opt_model: model.Model, solver_type: parameters.SolverType):
|
||||
def __init__(
|
||||
self,
|
||||
opt_model: model.Model,
|
||||
solver_type: parameters.SolverType,
|
||||
*,
|
||||
streamable_init_args: Optional[
|
||||
init_arguments.StreamableSolverInitArguments
|
||||
] = None,
|
||||
):
|
||||
streamable_init_args = (
|
||||
streamable_init_args or init_arguments.StreamableSolverInitArguments()
|
||||
)
|
||||
self._model = opt_model
|
||||
self._solver_type = solver_type
|
||||
self._update_tracker = self._model.add_update_tracker()
|
||||
@@ -171,7 +193,7 @@ class IncrementalSolver:
|
||||
self._proto_solver = solver.new(
|
||||
solver_type.value,
|
||||
self._model.export_model(),
|
||||
parameters_pb2.SolverInitializerProto(),
|
||||
streamable_init_args.to_proto(),
|
||||
)
|
||||
except StatusNotOk as e:
|
||||
raise _status_not_ok_to_exception(e) from None
|
||||
|
||||
@@ -19,8 +19,10 @@ machine.
|
||||
"""
|
||||
|
||||
from absl.testing import absltest
|
||||
from ortools.gurobi.isv.secret import gurobi_test_isv_key
|
||||
from ortools.math_opt.python import callback
|
||||
from ortools.math_opt.python import compute_infeasible_subsystem_result
|
||||
from ortools.math_opt.python import init_arguments
|
||||
from ortools.math_opt.python import model
|
||||
from ortools.math_opt.python import parameters
|
||||
from ortools.math_opt.python import result
|
||||
@@ -29,6 +31,18 @@ from ortools.math_opt.python import solve
|
||||
|
||||
_Bounds = compute_infeasible_subsystem_result.ModelSubsetBounds
|
||||
|
||||
_bad_isv_key = init_arguments.GurobiISVKey(
|
||||
name="cat", application_name="hat", expiration=10, key="bat"
|
||||
)
|
||||
|
||||
|
||||
def _init_args(
|
||||
gurobi_key: init_arguments.GurobiISVKey,
|
||||
) -> init_arguments.StreamableSolverInitArguments:
|
||||
return init_arguments.StreamableSolverInitArguments(
|
||||
gurobi=init_arguments.StreamableGurobiInitArguments(isv_key=gurobi_key)
|
||||
)
|
||||
|
||||
|
||||
class SolveTest(absltest.TestCase):
|
||||
|
||||
@@ -89,6 +103,95 @@ class SolveTest(absltest.TestCase):
|
||||
)
|
||||
self.assertEmpty(iis.infeasible_subsystem.variable_integrality)
|
||||
|
||||
def test_solve_valid_isv_success(self):
|
||||
mod = model.Model()
|
||||
x = mod.add_binary_variable()
|
||||
mod.maximize(x)
|
||||
res = solve.solve(
|
||||
mod,
|
||||
parameters.SolverType.GUROBI,
|
||||
streamable_init_args=_init_args(
|
||||
gurobi_test_isv_key.google_test_isv_key_placeholder()
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
res.termination.reason,
|
||||
result.TerminationReason.OPTIMAL,
|
||||
msg=res.termination,
|
||||
)
|
||||
self.assertAlmostEqual(1.0, res.termination.objective_bounds.primal_bound)
|
||||
|
||||
def test_solve_wrong_isv_error(self):
|
||||
mod = model.Model()
|
||||
x = mod.add_binary_variable()
|
||||
mod.maximize(x)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "failed to create Gurobi primary environment with ISV key"
|
||||
):
|
||||
solve.solve(
|
||||
mod,
|
||||
parameters.SolverType.GUROBI,
|
||||
streamable_init_args=_init_args(_bad_isv_key),
|
||||
)
|
||||
|
||||
def test_incremental_solver_valid_isv_success(self):
|
||||
mod = model.Model()
|
||||
x = mod.add_binary_variable()
|
||||
mod.maximize(x)
|
||||
s = solve.IncrementalSolver(
|
||||
mod,
|
||||
parameters.SolverType.GUROBI,
|
||||
streamable_init_args=_init_args(
|
||||
gurobi_test_isv_key.google_test_isv_key_placeholder()
|
||||
),
|
||||
)
|
||||
res = s.solve()
|
||||
self.assertEqual(
|
||||
res.termination.reason,
|
||||
result.TerminationReason.OPTIMAL,
|
||||
msg=res.termination,
|
||||
)
|
||||
self.assertAlmostEqual(1.0, res.termination.objective_bounds.primal_bound)
|
||||
|
||||
def test_incremental_solver_wrong_isv_error(self):
|
||||
mod = model.Model()
|
||||
x = mod.add_binary_variable()
|
||||
mod.maximize(x)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "failed to create Gurobi primary environment with ISV key"
|
||||
):
|
||||
solve.IncrementalSolver(
|
||||
mod,
|
||||
parameters.SolverType.GUROBI,
|
||||
streamable_init_args=_init_args(_bad_isv_key),
|
||||
)
|
||||
|
||||
def test_compute_infeasible_subsystem_valid_isv_success(self):
|
||||
mod = model.Model()
|
||||
x = mod.add_binary_variable()
|
||||
mod.add_linear_constraint(x >= 3.0)
|
||||
res = solve.compute_infeasible_subsystem(
|
||||
mod,
|
||||
parameters.SolverType.GUROBI,
|
||||
streamable_init_args=_init_args(
|
||||
gurobi_test_isv_key.google_test_isv_key_placeholder()
|
||||
),
|
||||
)
|
||||
self.assertEqual(res.feasibility, result.FeasibilityStatus.INFEASIBLE)
|
||||
|
||||
def test_compute_infeasible_subsystem_wrong_isv_error(self):
|
||||
mod = model.Model()
|
||||
x = mod.add_binary_variable()
|
||||
mod.add_linear_constraint(x >= 3.0)
|
||||
with self.assertRaisesRegex(
|
||||
ValueError, "failed to create Gurobi primary environment with ISV key"
|
||||
):
|
||||
solve.compute_infeasible_subsystem(
|
||||
mod,
|
||||
parameters.SolverType.GUROBI,
|
||||
streamable_init_args=_init_args(_bad_isv_key),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
absltest.main()
|
||||
|
||||
Reference in New Issue
Block a user