ortools.math_opt.python.solve
Solve optimization problems, as defined by Model in model.py.
1# Copyright 2010-2024 Google LLC 2# Licensed under the Apache License, Version 2.0 (the "License"); 3# you may not use this file except in compliance with the License. 4# You may obtain a copy of the License at 5# 6# http://www.apache.org/licenses/LICENSE-2.0 7# 8# Unless required by applicable law or agreed to in writing, software 9# distributed under the License is distributed on an "AS IS" BASIS, 10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11# See the License for the specific language governing permissions and 12# limitations under the License. 13 14"""Solve optimization problems, as defined by Model in model.py.""" 15import types 16from typing import Callable, Optional 17 18from ortools.math_opt import parameters_pb2 19from ortools.math_opt import rpc_pb2 20from ortools.math_opt.core.python import solver 21from ortools.math_opt.python import callback 22from ortools.math_opt.python import compute_infeasible_subsystem_result 23from ortools.math_opt.python import errors 24from ortools.math_opt.python import message_callback 25from ortools.math_opt.python import model 26from ortools.math_opt.python import model_parameters 27from ortools.math_opt.python import parameters 28from ortools.math_opt.python import result 29from pybind11_abseil.status import StatusNotOk 30 31SolveCallback = Callable[[callback.CallbackData], callback.CallbackResult] 32 33 34def solve( 35 opt_model: model.Model, 36 solver_type: parameters.SolverType, 37 *, 38 params: Optional[parameters.SolveParameters] = None, 39 model_params: Optional[model_parameters.ModelSolveParameters] = None, 40 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 41 callback_reg: Optional[callback.CallbackRegistration] = None, 42 cb: Optional[SolveCallback] = None, 43) -> result.SolveResult: 44 """Solves an optimization model. 45 46 Thread-safety: this function must not be called while modifying the Model 47 (adding variables...). Some solvers may add more restriction regarding 48 threading. Please see SolverType::XXX documentation for details. 49 50 Args: 51 opt_model: The optimization model. 52 solver_type: The underlying solver to use. 53 params: Configuration of the underlying solver. 54 model_params: Configuration of the solver that is model specific. 55 msg_cb: A callback that gives back the underlying solver's logs by the line. 56 callback_reg: Configures when the callback will be invoked (if provided) and 57 what data will be collected to access in the callback. 58 cb: A callback that will be called periodically as the solver runs. 59 60 Returns: 61 A SolveResult containing the termination reason, solution(s) and stats. 62 63 Raises: 64 RuntimeError: On a solve error. 65 """ 66 # First, initialize optional arguments that were not set to default values. 67 # Note that in python, default arguments must be immutable, and these are not. 68 params = params or parameters.SolveParameters() 69 model_params = model_params or model_parameters.ModelSolveParameters() 70 callback_reg = callback_reg or callback.CallbackRegistration() 71 model_proto = opt_model.export_model() 72 proto_cb = None 73 if cb is not None: 74 proto_cb = lambda x: cb( # pylint: disable=g-long-lambda 75 callback.parse_callback_data(x, opt_model) 76 ).to_proto() 77 # Solve 78 try: 79 proto_result = solver.solve( 80 model_proto, 81 solver_type.value, 82 parameters_pb2.SolverInitializerProto(), 83 params.to_proto(), 84 model_params.to_proto(), 85 msg_cb, 86 callback_reg.to_proto(), 87 proto_cb, 88 None, 89 ) 90 except StatusNotOk as e: 91 raise _status_not_ok_to_exception(e) from None 92 return result.parse_solve_result(proto_result, opt_model) 93 94 95def compute_infeasible_subsystem( 96 opt_model: model.Model, 97 solver_type: parameters.SolverType, 98 *, 99 params: Optional[parameters.SolveParameters] = None, 100 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 101) -> compute_infeasible_subsystem_result.ComputeInfeasibleSubsystemResult: 102 """Computes an infeasible subsystem of the input model. 103 104 Args: 105 opt_model: The optimization model to check for infeasibility. 106 solver_type: Which solver to use to compute the infeasible subsystem. As of 107 August 2023, the only supported solver is Gurobi. 108 params: Configuration of the underlying solver. 109 msg_cb: A callback that gives back the underlying solver's logs by the line. 110 111 Returns: 112 An `ComputeInfeasibleSubsystemResult` where `feasibility` indicates if the 113 problem was proven infeasible. 114 115 Throws: 116 RuntimeError: on invalid inputs or an internal solver error. 117 """ 118 params = params or parameters.SolveParameters() 119 model_proto = opt_model.export_model() 120 # Solve 121 try: 122 proto_result = solver.compute_infeasible_subsystem( 123 model_proto, 124 solver_type.value, 125 parameters_pb2.SolverInitializerProto(), 126 params.to_proto(), 127 msg_cb, 128 None, 129 ) 130 except StatusNotOk as e: 131 raise _status_not_ok_to_exception(e) from None 132 return ( 133 compute_infeasible_subsystem_result.parse_compute_infeasible_subsystem_result( 134 proto_result, opt_model 135 ) 136 ) 137 138 139class IncrementalSolver: 140 """Solve an optimization multiple times, with modifications between solves. 141 142 Prefer calling simply solve() above in most cases when incrementalism is not 143 needed. 144 145 Thread-safety: The __init__(), solve() methods must not be called while 146 modifying the Model (adding variables...). The user is expected to use proper 147 synchronization primitives to serialize changes to the model and the use of 148 this object. Note though that it is safe to call methods from different 149 IncrementalSolver instances on the same Model concurrently. The solve() method 150 must not be called concurrently on different threads for the same 151 IncrementalSolver. Some solvers may add more restriction regarding 152 threading. Please see to SolverType::XXX documentation for details. 153 154 This class references some resources that are freed when it is garbage 155 collected (which should usually happen when the last reference is lost). In 156 particular, it references some C++ objects. Although it is not mandatory, it 157 is recommended to free those as soon as possible. To do so it is possible to 158 use this class in the `with` statement: 159 160 with IncrementalSolver(model, SolverType.GLOP) as solver: 161 ... 162 163 When it is not possible to use `with`, the close() method can be called. 164 """ 165 166 def __init__(self, opt_model: model.Model, solver_type: parameters.SolverType): 167 self._model = opt_model 168 self._solver_type = solver_type 169 self._update_tracker = self._model.add_update_tracker() 170 try: 171 self._proto_solver = solver.new( 172 solver_type.value, 173 self._model.export_model(), 174 parameters_pb2.SolverInitializerProto(), 175 ) 176 except StatusNotOk as e: 177 raise _status_not_ok_to_exception(e) from None 178 self._closed = False 179 180 def solve( 181 self, 182 *, 183 params: Optional[parameters.SolveParameters] = None, 184 model_params: Optional[model_parameters.ModelSolveParameters] = None, 185 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 186 callback_reg: Optional[callback.CallbackRegistration] = None, 187 cb: Optional[SolveCallback] = None, 188 ) -> result.SolveResult: 189 """Solves the current optimization model. 190 191 Args: 192 params: The non-model specific solve parameters. 193 model_params: The model specific solve parameters. 194 msg_cb: An optional callback for solver messages. 195 callback_reg: The parameters controlling when cb is called. 196 cb: An optional callback for LP/MIP events. 197 198 Returns: 199 The result of the solve. 200 201 Raises: 202 RuntimeError: If called after being closed, or on a solve error. 203 """ 204 if self._closed: 205 raise RuntimeError("the solver is closed") 206 207 update = self._update_tracker.export_update() 208 if update is not None: 209 try: 210 if not self._proto_solver.update(update): 211 self._proto_solver = solver.new( 212 self._solver_type.value, 213 self._model.export_model(), 214 parameters_pb2.SolverInitializerProto(), 215 ) 216 except StatusNotOk as e: 217 raise _status_not_ok_to_exception(e) from None 218 self._update_tracker.advance_checkpoint() 219 params = params or parameters.SolveParameters() 220 model_params = model_params or model_parameters.ModelSolveParameters() 221 callback_reg = callback_reg or callback.CallbackRegistration() 222 proto_cb = None 223 if cb is not None: 224 proto_cb = lambda x: cb( # pylint: disable=g-long-lambda 225 callback.parse_callback_data(x, self._model) 226 ).to_proto() 227 try: 228 result_proto = self._proto_solver.solve( 229 params.to_proto(), 230 model_params.to_proto(), 231 msg_cb, 232 callback_reg.to_proto(), 233 proto_cb, 234 None, 235 ) 236 except StatusNotOk as e: 237 raise _status_not_ok_to_exception(e) from None 238 return result.parse_solve_result(result_proto, self._model) 239 240 def close(self) -> None: 241 """Closes this solver, freeing all its resources. 242 243 This is optional, the code is correct without calling this function. See the 244 class documentation for details. 245 246 After a solver has been closed, it can't be used anymore. Prefer using the 247 context manager API when possible instead of calling close() directly: 248 249 with IncrementalSolver(model, SolverType.GLOP) as solver: 250 ... 251 """ 252 if self._closed: 253 return 254 self._closed = True 255 256 del self._model 257 del self._solver_type 258 del self._update_tracker 259 del self._proto_solver 260 261 def __enter__(self) -> "IncrementalSolver": 262 """Returns the solver itself.""" 263 return self 264 265 def __exit__( 266 self, 267 exc_type: Optional[type[BaseException]], 268 exc_val: Optional[BaseException], 269 exc_tb: Optional[types.TracebackType], 270 ) -> None: 271 """Closes the solver.""" 272 self.close() 273 274 275def _status_not_ok_to_exception(err: StatusNotOk) -> Exception: 276 """Converts a StatusNotOk to the best matching Python exception. 277 278 Args: 279 err: The input errors. 280 281 Returns: 282 The corresponding exception. 283 """ 284 ret = errors.status_proto_to_exception( 285 rpc_pb2.StatusProto(code=err.canonical_code, message=err.message) 286 ) 287 # We never expect StatusNotOk to be OK. 288 assert ret is not None, err 289 return ret
35def solve( 36 opt_model: model.Model, 37 solver_type: parameters.SolverType, 38 *, 39 params: Optional[parameters.SolveParameters] = None, 40 model_params: Optional[model_parameters.ModelSolveParameters] = None, 41 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 42 callback_reg: Optional[callback.CallbackRegistration] = None, 43 cb: Optional[SolveCallback] = None, 44) -> result.SolveResult: 45 """Solves an optimization model. 46 47 Thread-safety: this function must not be called while modifying the Model 48 (adding variables...). Some solvers may add more restriction regarding 49 threading. Please see SolverType::XXX documentation for details. 50 51 Args: 52 opt_model: The optimization model. 53 solver_type: The underlying solver to use. 54 params: Configuration of the underlying solver. 55 model_params: Configuration of the solver that is model specific. 56 msg_cb: A callback that gives back the underlying solver's logs by the line. 57 callback_reg: Configures when the callback will be invoked (if provided) and 58 what data will be collected to access in the callback. 59 cb: A callback that will be called periodically as the solver runs. 60 61 Returns: 62 A SolveResult containing the termination reason, solution(s) and stats. 63 64 Raises: 65 RuntimeError: On a solve error. 66 """ 67 # First, initialize optional arguments that were not set to default values. 68 # Note that in python, default arguments must be immutable, and these are not. 69 params = params or parameters.SolveParameters() 70 model_params = model_params or model_parameters.ModelSolveParameters() 71 callback_reg = callback_reg or callback.CallbackRegistration() 72 model_proto = opt_model.export_model() 73 proto_cb = None 74 if cb is not None: 75 proto_cb = lambda x: cb( # pylint: disable=g-long-lambda 76 callback.parse_callback_data(x, opt_model) 77 ).to_proto() 78 # Solve 79 try: 80 proto_result = solver.solve( 81 model_proto, 82 solver_type.value, 83 parameters_pb2.SolverInitializerProto(), 84 params.to_proto(), 85 model_params.to_proto(), 86 msg_cb, 87 callback_reg.to_proto(), 88 proto_cb, 89 None, 90 ) 91 except StatusNotOk as e: 92 raise _status_not_ok_to_exception(e) from None 93 return result.parse_solve_result(proto_result, opt_model)
Solves an optimization model.
Thread-safety: this function must not be called while modifying the Model (adding variables...). Some solvers may add more restriction regarding threading. Please see SolverType::XXX documentation for details.
Arguments:
- opt_model: The optimization model.
- solver_type: The underlying solver to use.
- params: Configuration of the underlying solver.
- model_params: Configuration of the solver that is model specific.
- msg_cb: A callback that gives back the underlying solver's logs by the line.
- 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.
Returns:
A SolveResult containing the termination reason, solution(s) and stats.
Raises:
- RuntimeError: On a solve error.
96def compute_infeasible_subsystem( 97 opt_model: model.Model, 98 solver_type: parameters.SolverType, 99 *, 100 params: Optional[parameters.SolveParameters] = None, 101 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 102) -> compute_infeasible_subsystem_result.ComputeInfeasibleSubsystemResult: 103 """Computes an infeasible subsystem of the input model. 104 105 Args: 106 opt_model: The optimization model to check for infeasibility. 107 solver_type: Which solver to use to compute the infeasible subsystem. As of 108 August 2023, the only supported solver is Gurobi. 109 params: Configuration of the underlying solver. 110 msg_cb: A callback that gives back the underlying solver's logs by the line. 111 112 Returns: 113 An `ComputeInfeasibleSubsystemResult` where `feasibility` indicates if the 114 problem was proven infeasible. 115 116 Throws: 117 RuntimeError: on invalid inputs or an internal solver error. 118 """ 119 params = params or parameters.SolveParameters() 120 model_proto = opt_model.export_model() 121 # Solve 122 try: 123 proto_result = solver.compute_infeasible_subsystem( 124 model_proto, 125 solver_type.value, 126 parameters_pb2.SolverInitializerProto(), 127 params.to_proto(), 128 msg_cb, 129 None, 130 ) 131 except StatusNotOk as e: 132 raise _status_not_ok_to_exception(e) from None 133 return ( 134 compute_infeasible_subsystem_result.parse_compute_infeasible_subsystem_result( 135 proto_result, opt_model 136 ) 137 )
Computes an infeasible subsystem of the input model.
Arguments:
- opt_model: The optimization model to check for infeasibility.
- solver_type: Which solver to use to compute the infeasible subsystem. As of 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.
Returns:
An
ComputeInfeasibleSubsystemResult
wherefeasibility
indicates if the problem was proven infeasible.
Throws:
RuntimeError: on invalid inputs or an internal solver error.
140class IncrementalSolver: 141 """Solve an optimization multiple times, with modifications between solves. 142 143 Prefer calling simply solve() above in most cases when incrementalism is not 144 needed. 145 146 Thread-safety: The __init__(), solve() methods must not be called while 147 modifying the Model (adding variables...). The user is expected to use proper 148 synchronization primitives to serialize changes to the model and the use of 149 this object. Note though that it is safe to call methods from different 150 IncrementalSolver instances on the same Model concurrently. The solve() method 151 must not be called concurrently on different threads for the same 152 IncrementalSolver. Some solvers may add more restriction regarding 153 threading. Please see to SolverType::XXX documentation for details. 154 155 This class references some resources that are freed when it is garbage 156 collected (which should usually happen when the last reference is lost). In 157 particular, it references some C++ objects. Although it is not mandatory, it 158 is recommended to free those as soon as possible. To do so it is possible to 159 use this class in the `with` statement: 160 161 with IncrementalSolver(model, SolverType.GLOP) as solver: 162 ... 163 164 When it is not possible to use `with`, the close() method can be called. 165 """ 166 167 def __init__(self, opt_model: model.Model, solver_type: parameters.SolverType): 168 self._model = opt_model 169 self._solver_type = solver_type 170 self._update_tracker = self._model.add_update_tracker() 171 try: 172 self._proto_solver = solver.new( 173 solver_type.value, 174 self._model.export_model(), 175 parameters_pb2.SolverInitializerProto(), 176 ) 177 except StatusNotOk as e: 178 raise _status_not_ok_to_exception(e) from None 179 self._closed = False 180 181 def solve( 182 self, 183 *, 184 params: Optional[parameters.SolveParameters] = None, 185 model_params: Optional[model_parameters.ModelSolveParameters] = None, 186 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 187 callback_reg: Optional[callback.CallbackRegistration] = None, 188 cb: Optional[SolveCallback] = None, 189 ) -> result.SolveResult: 190 """Solves the current optimization model. 191 192 Args: 193 params: The non-model specific solve parameters. 194 model_params: The model specific solve parameters. 195 msg_cb: An optional callback for solver messages. 196 callback_reg: The parameters controlling when cb is called. 197 cb: An optional callback for LP/MIP events. 198 199 Returns: 200 The result of the solve. 201 202 Raises: 203 RuntimeError: If called after being closed, or on a solve error. 204 """ 205 if self._closed: 206 raise RuntimeError("the solver is closed") 207 208 update = self._update_tracker.export_update() 209 if update is not None: 210 try: 211 if not self._proto_solver.update(update): 212 self._proto_solver = solver.new( 213 self._solver_type.value, 214 self._model.export_model(), 215 parameters_pb2.SolverInitializerProto(), 216 ) 217 except StatusNotOk as e: 218 raise _status_not_ok_to_exception(e) from None 219 self._update_tracker.advance_checkpoint() 220 params = params or parameters.SolveParameters() 221 model_params = model_params or model_parameters.ModelSolveParameters() 222 callback_reg = callback_reg or callback.CallbackRegistration() 223 proto_cb = None 224 if cb is not None: 225 proto_cb = lambda x: cb( # pylint: disable=g-long-lambda 226 callback.parse_callback_data(x, self._model) 227 ).to_proto() 228 try: 229 result_proto = self._proto_solver.solve( 230 params.to_proto(), 231 model_params.to_proto(), 232 msg_cb, 233 callback_reg.to_proto(), 234 proto_cb, 235 None, 236 ) 237 except StatusNotOk as e: 238 raise _status_not_ok_to_exception(e) from None 239 return result.parse_solve_result(result_proto, self._model) 240 241 def close(self) -> None: 242 """Closes this solver, freeing all its resources. 243 244 This is optional, the code is correct without calling this function. See the 245 class documentation for details. 246 247 After a solver has been closed, it can't be used anymore. Prefer using the 248 context manager API when possible instead of calling close() directly: 249 250 with IncrementalSolver(model, SolverType.GLOP) as solver: 251 ... 252 """ 253 if self._closed: 254 return 255 self._closed = True 256 257 del self._model 258 del self._solver_type 259 del self._update_tracker 260 del self._proto_solver 261 262 def __enter__(self) -> "IncrementalSolver": 263 """Returns the solver itself.""" 264 return self 265 266 def __exit__( 267 self, 268 exc_type: Optional[type[BaseException]], 269 exc_val: Optional[BaseException], 270 exc_tb: Optional[types.TracebackType], 271 ) -> None: 272 """Closes the solver.""" 273 self.close()
Solve an optimization multiple times, with modifications between solves.
Prefer calling simply solve() above in most cases when incrementalism is not needed.
Thread-safety: The __init__(), solve() methods must not be called while modifying the Model (adding variables...). The user is expected to use proper synchronization primitives to serialize changes to the model and the use of this object. Note though that it is safe to call methods from different IncrementalSolver instances on the same Model concurrently. The solve() method must not be called concurrently on different threads for the same IncrementalSolver. Some solvers may add more restriction regarding threading. Please see to SolverType::XXX documentation for details.
This class references some resources that are freed when it is garbage
collected (which should usually happen when the last reference is lost). In
particular, it references some C++ objects. Although it is not mandatory, it
is recommended to free those as soon as possible. To do so it is possible to
use this class in the with
statement:
with IncrementalSolver(model, SolverType.GLOP) as solver: ...
When it is not possible to use with
, the close() method can be called.
167 def __init__(self, opt_model: model.Model, solver_type: parameters.SolverType): 168 self._model = opt_model 169 self._solver_type = solver_type 170 self._update_tracker = self._model.add_update_tracker() 171 try: 172 self._proto_solver = solver.new( 173 solver_type.value, 174 self._model.export_model(), 175 parameters_pb2.SolverInitializerProto(), 176 ) 177 except StatusNotOk as e: 178 raise _status_not_ok_to_exception(e) from None 179 self._closed = False
181 def solve( 182 self, 183 *, 184 params: Optional[parameters.SolveParameters] = None, 185 model_params: Optional[model_parameters.ModelSolveParameters] = None, 186 msg_cb: Optional[message_callback.SolveMessageCallback] = None, 187 callback_reg: Optional[callback.CallbackRegistration] = None, 188 cb: Optional[SolveCallback] = None, 189 ) -> result.SolveResult: 190 """Solves the current optimization model. 191 192 Args: 193 params: The non-model specific solve parameters. 194 model_params: The model specific solve parameters. 195 msg_cb: An optional callback for solver messages. 196 callback_reg: The parameters controlling when cb is called. 197 cb: An optional callback for LP/MIP events. 198 199 Returns: 200 The result of the solve. 201 202 Raises: 203 RuntimeError: If called after being closed, or on a solve error. 204 """ 205 if self._closed: 206 raise RuntimeError("the solver is closed") 207 208 update = self._update_tracker.export_update() 209 if update is not None: 210 try: 211 if not self._proto_solver.update(update): 212 self._proto_solver = solver.new( 213 self._solver_type.value, 214 self._model.export_model(), 215 parameters_pb2.SolverInitializerProto(), 216 ) 217 except StatusNotOk as e: 218 raise _status_not_ok_to_exception(e) from None 219 self._update_tracker.advance_checkpoint() 220 params = params or parameters.SolveParameters() 221 model_params = model_params or model_parameters.ModelSolveParameters() 222 callback_reg = callback_reg or callback.CallbackRegistration() 223 proto_cb = None 224 if cb is not None: 225 proto_cb = lambda x: cb( # pylint: disable=g-long-lambda 226 callback.parse_callback_data(x, self._model) 227 ).to_proto() 228 try: 229 result_proto = self._proto_solver.solve( 230 params.to_proto(), 231 model_params.to_proto(), 232 msg_cb, 233 callback_reg.to_proto(), 234 proto_cb, 235 None, 236 ) 237 except StatusNotOk as e: 238 raise _status_not_ok_to_exception(e) from None 239 return result.parse_solve_result(result_proto, self._model)
Solves the current optimization model.
Arguments:
- params: The non-model specific solve parameters.
- model_params: The model specific solve parameters.
- msg_cb: An optional callback for solver messages.
- callback_reg: The parameters controlling when cb is called.
- cb: An optional callback for LP/MIP events.
Returns:
The result of the solve.
Raises:
- RuntimeError: If called after being closed, or on a solve error.
241 def close(self) -> None: 242 """Closes this solver, freeing all its resources. 243 244 This is optional, the code is correct without calling this function. See the 245 class documentation for details. 246 247 After a solver has been closed, it can't be used anymore. Prefer using the 248 context manager API when possible instead of calling close() directly: 249 250 with IncrementalSolver(model, SolverType.GLOP) as solver: 251 ... 252 """ 253 if self._closed: 254 return 255 self._closed = True 256 257 del self._model 258 del self._solver_type 259 del self._update_tracker 260 del self._proto_solver
Closes this solver, freeing all its resources.
This is optional, the code is correct without calling this function. See the class documentation for details.
After a solver has been closed, it can't be used anymore. Prefer using the context manager API when possible instead of calling close() directly:
with IncrementalSolver(model, SolverType.GLOP) as solver: ...