ortools.math_opt.python.solution

The solution to an optimization problem defined by Model in model.py.

  1# Copyright 2010-2025 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"""The solution to an optimization problem defined by Model in model.py."""
 15import dataclasses
 16import enum
 17from typing import Dict, Optional, TypeVar
 18
 19from ortools.math_opt import solution_pb2
 20from ortools.math_opt.python import linear_constraints
 21from ortools.math_opt.python import model
 22from ortools.math_opt.python import objectives
 23from ortools.math_opt.python import quadratic_constraints
 24from ortools.math_opt.python import sparse_containers
 25from ortools.math_opt.python import variables
 26
 27
 28@enum.unique
 29class BasisStatus(enum.Enum):
 30    """Status of a variable/constraint in a LP basis.
 31
 32    Attributes:
 33      FREE: The variable/constraint is free (it has no finite bounds).
 34      AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be
 35        finite).
 36      AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be
 37        finite).
 38      FIXED_VALUE: The variable/constraint has identical finite lower and upper
 39        bounds.
 40      BASIC: The variable/constraint is basic.
 41    """
 42
 43    FREE = solution_pb2.BASIS_STATUS_FREE
 44    AT_LOWER_BOUND = solution_pb2.BASIS_STATUS_AT_LOWER_BOUND
 45    AT_UPPER_BOUND = solution_pb2.BASIS_STATUS_AT_UPPER_BOUND
 46    FIXED_VALUE = solution_pb2.BASIS_STATUS_FIXED_VALUE
 47    BASIC = solution_pb2.BASIS_STATUS_BASIC
 48
 49
 50@enum.unique
 51class SolutionStatus(enum.Enum):
 52    """Feasibility of a primal or dual solution as claimed by the solver.
 53
 54    Attributes:
 55      UNDETERMINED: Solver does not claim a feasibility status.
 56      FEASIBLE: Solver claims the solution is feasible.
 57      INFEASIBLE: Solver claims the solution is infeasible.
 58    """
 59
 60    UNDETERMINED = solution_pb2.SOLUTION_STATUS_UNDETERMINED
 61    FEASIBLE = solution_pb2.SOLUTION_STATUS_FEASIBLE
 62    INFEASIBLE = solution_pb2.SOLUTION_STATUS_INFEASIBLE
 63
 64
 65def parse_optional_solution_status(
 66    proto: solution_pb2.SolutionStatusProto,
 67) -> Optional[SolutionStatus]:
 68    """Converts a proto SolutionStatus to an optional Python SolutionStatus."""
 69    return (
 70        None
 71        if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED
 72        else SolutionStatus(proto)
 73    )
 74
 75
 76def optional_solution_status_to_proto(
 77    status: Optional[SolutionStatus],
 78) -> solution_pb2.SolutionStatusProto:
 79    """Converts an optional Python SolutionStatus to a proto SolutionStatus."""
 80    return solution_pb2.SOLUTION_STATUS_UNSPECIFIED if status is None else status.value
 81
 82
 83@dataclasses.dataclass
 84class PrimalSolution:
 85    """A solution to the optimization problem in a Model.
 86
 87    E.g. consider a simple linear program:
 88      min c * x
 89      s.t. A * x >= b
 90      x >= 0.
 91    A primal solution is assignment values to x. It is feasible if it satisfies
 92    A * x >= b and x >= 0 from above. In the class PrimalSolution variable_values
 93    is x and objective_value is c * x.
 94
 95    For the general case of a MathOpt optimization model, see go/mathopt-solutions
 96    for details.
 97
 98    Attributes:
 99      variable_values: The value assigned for each Variable in the model.
100      objective_value: The value of the objective value at this solution. This
101        value may not be always populated.
102      auxiliary_objective_values: Set only for multi objective problems, the
103        objective value for each auxiliary objective, as computed by the solver.
104        This value will not always be populated.
105      feasibility_status: The feasibility of the solution as claimed by the
106        solver.
107    """
108
109    variable_values: Dict[variables.Variable, float] = dataclasses.field(
110        default_factory=dict
111    )
112    objective_value: float = 0.0
113    auxiliary_objective_values: Dict[objectives.AuxiliaryObjective, float] = (
114        dataclasses.field(default_factory=dict)
115    )
116    feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED
117
118    def to_proto(self) -> solution_pb2.PrimalSolutionProto:
119        """Returns an equivalent proto for a primal solution."""
120        return solution_pb2.PrimalSolutionProto(
121            variable_values=sparse_containers.to_sparse_double_vector_proto(
122                self.variable_values
123            ),
124            objective_value=self.objective_value,
125            auxiliary_objective_values={
126                obj.id: obj_value
127                for obj, obj_value in self.auxiliary_objective_values.items()
128            },
129            feasibility_status=self.feasibility_status.value,
130        )
131
132
133def parse_primal_solution(
134    proto: solution_pb2.PrimalSolutionProto,
135    mod: model.Model,
136    *,
137    validate: bool = True,
138) -> PrimalSolution:
139    """Returns an equivalent PrimalSolution from the input proto."""
140    result = PrimalSolution()
141    result.objective_value = proto.objective_value
142    for aux_id, obj_value in proto.auxiliary_objective_values.items():
143        result.auxiliary_objective_values[
144            mod.get_auxiliary_objective(aux_id, validate=validate)
145        ] = obj_value
146    result.variable_values = sparse_containers.parse_variable_map(
147        proto.variable_values, mod, validate=validate
148    )
149    status_proto = proto.feasibility_status
150    if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
151        raise ValueError("Primal solution feasibility status should not be UNSPECIFIED")
152    result.feasibility_status = SolutionStatus(status_proto)
153    return result
154
155
156@dataclasses.dataclass
157class PrimalRay:
158    """A direction of unbounded objective improvement in an optimization Model.
159
160    Equivalently, a certificate of infeasibility for the dual of the optimization
161    problem.
162
163    E.g. consider a simple linear program:
164      min c * x
165      s.t. A * x >= b
166      x >= 0.
167    A primal ray is an x that satisfies:
168      c * x < 0
169      A * x >= 0
170      x >= 0.
171    Observe that given a feasible solution, any positive multiple of the primal
172    ray plus that solution is still feasible, and gives a better objective
173    value. A primal ray also proves the dual optimization problem infeasible.
174
175    In the class PrimalRay, variable_values is this x.
176
177    For the general case of a MathOpt optimization model, see
178    go/mathopt-solutions for details.
179
180    Attributes:
181      variable_values: The value assigned for each Variable in the model.
182    """
183
184    variable_values: Dict[variables.Variable, float] = dataclasses.field(
185        default_factory=dict
186    )
187
188    def to_proto(self) -> solution_pb2.PrimalRayProto:
189        """Returns an equivalent proto to this PrimalRay."""
190        return solution_pb2.PrimalRayProto(
191            variable_values=sparse_containers.to_sparse_double_vector_proto(
192                self.variable_values
193            )
194        )
195
196
197def parse_primal_ray(
198    proto: solution_pb2.PrimalRayProto,
199    mod: model.Model,
200    *,
201    validate: bool = True,
202) -> PrimalRay:
203    """Returns an equivalent PrimalRay from the input proto."""
204    result = PrimalRay()
205    result.variable_values = sparse_containers.parse_variable_map(
206        proto.variable_values, mod, validate=validate
207    )
208    return result
209
210
211@dataclasses.dataclass
212class DualSolution:
213    """A solution to the dual of the optimization problem given by a Model.
214
215    E.g. consider the primal dual pair linear program pair:
216      (Primal)              (Dual)
217      min c * x             max b * y
218      s.t. A * x >= b       s.t. y * A + r = c
219      x >= 0                y, r >= 0.
220    The dual solution is the pair (y, r). It is feasible if it satisfies the
221    constraints from (Dual) above.
222
223    Below, y is dual_values, r is reduced_costs, and b * y is objective_value.
224
225    For the general case, see go/mathopt-solutions and go/mathopt-dual (and note
226    that the dual objective depends on r in the general case).
227
228    Attributes:
229      dual_values: The value assigned for each LinearConstraint in the model.
230      quadratic_dual_values: The value assigned for each QuadraticConstraint in
231        the model.
232      reduced_costs: The value assigned for each Variable in the model.
233      objective_value: The value of the dual objective value at this solution.
234        This value may not be always populated.
235      feasibility_status: The feasibility of the solution as claimed by the
236        solver.
237    """
238
239    dual_values: Dict[linear_constraints.LinearConstraint, float] = dataclasses.field(
240        default_factory=dict
241    )
242    quadratic_dual_values: Dict[quadratic_constraints.QuadraticConstraint, float] = (
243        dataclasses.field(default_factory=dict)
244    )
245    reduced_costs: Dict[variables.Variable, float] = dataclasses.field(
246        default_factory=dict
247    )
248    objective_value: Optional[float] = None
249    feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED
250
251    def to_proto(self) -> solution_pb2.DualSolutionProto:
252        """Returns an equivalent proto for a dual solution."""
253        return solution_pb2.DualSolutionProto(
254            dual_values=sparse_containers.to_sparse_double_vector_proto(
255                self.dual_values
256            ),
257            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
258                self.reduced_costs
259            ),
260            quadratic_dual_values=sparse_containers.to_sparse_double_vector_proto(
261                self.quadratic_dual_values
262            ),
263            objective_value=self.objective_value,
264            feasibility_status=self.feasibility_status.value,
265        )
266
267
268def parse_dual_solution(
269    proto: solution_pb2.DualSolutionProto,
270    mod: model.Model,
271    *,
272    validate: bool = True,
273) -> DualSolution:
274    """Returns an equivalent DualSolution from the input proto."""
275    result = DualSolution()
276    result.objective_value = (
277        proto.objective_value if proto.HasField("objective_value") else None
278    )
279    result.dual_values = sparse_containers.parse_linear_constraint_map(
280        proto.dual_values, mod, validate=validate
281    )
282    result.quadratic_dual_values = sparse_containers.parse_quadratic_constraint_map(
283        proto.quadratic_dual_values, mod, validate=validate
284    )
285    result.reduced_costs = sparse_containers.parse_variable_map(
286        proto.reduced_costs, mod, validate=validate
287    )
288    status_proto = proto.feasibility_status
289    if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
290        raise ValueError("Dual solution feasibility status should not be UNSPECIFIED")
291    result.feasibility_status = SolutionStatus(status_proto)
292    return result
293
294
295@dataclasses.dataclass
296class DualRay:
297    """A direction of unbounded objective improvement in an optimization Model.
298
299    A direction of unbounded improvement to the dual of an optimization,
300    problem; equivalently, a certificate of primal infeasibility.
301
302    E.g. consider the primal dual pair linear program pair:
303      (Primal)              (Dual)
304      min c * x             max b * y
305      s.t. A * x >= b       s.t. y * A + r = c
306      x >= 0                y, r >= 0.
307
308    The dual ray is the pair (y, r) satisfying:
309      b * y > 0
310      y * A + r = 0
311      y, r >= 0.
312    Observe that adding a positive multiple of (y, r) to dual feasible solution
313    maintains dual feasibility and improves the objective (proving the dual is
314    unbounded). The dual ray also proves the primal problem is infeasible.
315
316    In the class DualRay below, y is dual_values and r is reduced_costs.
317
318    For the general case, see go/mathopt-solutions and go/mathopt-dual (and note
319    that the dual objective depends on r in the general case).
320
321    Attributes:
322      dual_values: The value assigned for each LinearConstraint in the model.
323      reduced_costs: The value assigned for each Variable in the model.
324    """
325
326    dual_values: Dict[linear_constraints.LinearConstraint, float] = dataclasses.field(
327        default_factory=dict
328    )
329    reduced_costs: Dict[variables.Variable, float] = dataclasses.field(
330        default_factory=dict
331    )
332
333    def to_proto(self) -> solution_pb2.DualRayProto:
334        """Returns an equivalent proto to this PrimalRay."""
335        return solution_pb2.DualRayProto(
336            dual_values=sparse_containers.to_sparse_double_vector_proto(
337                self.dual_values
338            ),
339            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
340                self.reduced_costs
341            ),
342        )
343
344
345def parse_dual_ray(
346    proto: solution_pb2.DualRayProto, mod: model.Model, *, validate: bool = True
347) -> DualRay:
348    """Returns an equivalent DualRay from the input proto."""
349    result = DualRay()
350    result.dual_values = sparse_containers.parse_linear_constraint_map(
351        proto.dual_values, mod, validate=validate
352    )
353    result.reduced_costs = sparse_containers.parse_variable_map(
354        proto.reduced_costs, mod, validate=validate
355    )
356    return result
357
358
359@dataclasses.dataclass
360class Basis:
361    """A combinatorial characterization for a solution to a linear program.
362
363    The simplex method for solving linear programs always returns a "basic
364    feasible solution" which can be described combinatorially as a Basis. A basis
365    assigns a BasisStatus for every variable and linear constraint.
366
367    E.g. consider a standard form LP:
368      min c * x
369      s.t. A * x = b
370      x >= 0
371    that has more variables than constraints and with full row rank A.
372
373    Let n be the number of variables and m the number of linear constraints. A
374    valid basis for this problem can be constructed as follows:
375     * All constraints will have basis status FIXED.
376     * Pick m variables such that the columns of A are linearly independent and
377       assign the status BASIC.
378     * Assign the status AT_LOWER for the remaining n - m variables.
379
380    The basic solution for this basis is the unique solution of A * x = b that has
381    all variables with status AT_LOWER fixed to their lower bounds (all zero). The
382    resulting solution is called a basic feasible solution if it also satisfies
383    x >= 0.
384
385    See go/mathopt-basis for treatment of the general case and an explanation of
386    how a dual solution is determined for a basis.
387
388    Attributes:
389      variable_status: The basis status for each variable in the model.
390      constraint_status: The basis status for each linear constraint in the model.
391      basic_dual_feasibility: This is an advanced feature used by MathOpt to
392        characterize feasibility of suboptimal LP solutions (optimal solutions
393        will always have status SolutionStatus.FEASIBLE). For single-sided LPs it
394        should be equal to the feasibility status of the associated dual solution.
395        For two-sided LPs it may be different in some edge cases (e.g. incomplete
396        solves with primal simplex). For more details see
397        go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting
398        basis via ModelSolveParameters.initial_basis, this value is ignored and
399        can be None. It is only relevant for the basis returned by Solution.basis,
400        and it is never None when returned from solve(). This is an advanced
401        status. For single-sided LPs it should be equal to the feasibility status
402        of the associated dual solution. For two-sided LPs it may be different in
403        some edge cases (e.g. incomplete solves with primal simplex). For more
404        details see go/mathopt-basis-advanced#dualfeasibility.
405    """
406
407    variable_status: Dict[variables.Variable, BasisStatus] = dataclasses.field(
408        default_factory=dict
409    )
410    constraint_status: Dict[linear_constraints.LinearConstraint, BasisStatus] = (
411        dataclasses.field(default_factory=dict)
412    )
413    basic_dual_feasibility: Optional[SolutionStatus] = None
414
415    def to_proto(self) -> solution_pb2.BasisProto:
416        """Returns an equivalent proto for the basis."""
417        return solution_pb2.BasisProto(
418            variable_status=_to_sparse_basis_status_vector_proto(self.variable_status),
419            constraint_status=_to_sparse_basis_status_vector_proto(
420                self.constraint_status
421            ),
422            basic_dual_feasibility=optional_solution_status_to_proto(
423                self.basic_dual_feasibility
424            ),
425        )
426
427
428def parse_basis(
429    proto: solution_pb2.BasisProto, mod: model.Model, *, validate: bool = True
430) -> Basis:
431    """Returns an equivalent Basis to the input proto."""
432    result = Basis()
433    for index, vid in enumerate(proto.variable_status.ids):
434        status_proto = proto.variable_status.values[index]
435        if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED:
436            raise ValueError("Variable basis status should not be UNSPECIFIED")
437        result.variable_status[mod.get_variable(vid, validate=validate)] = BasisStatus(
438            status_proto
439        )
440    for index, cid in enumerate(proto.constraint_status.ids):
441        status_proto = proto.constraint_status.values[index]
442        if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED:
443            raise ValueError("Constraint basis status should not be UNSPECIFIED")
444        result.constraint_status[mod.get_linear_constraint(cid, validate=validate)] = (
445            BasisStatus(status_proto)
446        )
447    result.basic_dual_feasibility = parse_optional_solution_status(
448        proto.basic_dual_feasibility
449    )
450    return result
451
452
453T = TypeVar("T", variables.Variable, linear_constraints.LinearConstraint)
454
455
456def _to_sparse_basis_status_vector_proto(
457    terms: Dict[T, BasisStatus],
458) -> solution_pb2.SparseBasisStatusVector:
459    """Converts a basis vector from a python Dict to a protocol buffer."""
460    result = solution_pb2.SparseBasisStatusVector()
461    if terms:
462        id_and_status = sorted(
463            (key.id, status.value) for (key, status) in terms.items()
464        )
465        ids, values = zip(*id_and_status)
466        result.ids[:] = ids
467        result.values[:] = values
468    return result
469
470
471@dataclasses.dataclass
472class Solution:
473    """A solution to the optimization problem in a Model."""
474
475    primal_solution: Optional[PrimalSolution] = None
476    dual_solution: Optional[DualSolution] = None
477    basis: Optional[Basis] = None
478
479    def to_proto(self) -> solution_pb2.SolutionProto:
480        """Returns an equivalent proto for a solution."""
481        return solution_pb2.SolutionProto(
482            primal_solution=(
483                self.primal_solution.to_proto()
484                if self.primal_solution is not None
485                else None
486            ),
487            dual_solution=(
488                self.dual_solution.to_proto()
489                if self.dual_solution is not None
490                else None
491            ),
492            basis=self.basis.to_proto() if self.basis is not None else None,
493        )
494
495
496def parse_solution(
497    proto: solution_pb2.SolutionProto,
498    mod: model.Model,
499    *,
500    validate: bool = True,
501) -> Solution:
502    """Returns a Solution equivalent to the input proto."""
503    result = Solution()
504    if proto.HasField("primal_solution"):
505        result.primal_solution = parse_primal_solution(
506            proto.primal_solution, mod, validate=validate
507        )
508    if proto.HasField("dual_solution"):
509        result.dual_solution = parse_dual_solution(
510            proto.dual_solution, mod, validate=validate
511        )
512    result.basis = (
513        parse_basis(proto.basis, mod, validate=validate)
514        if proto.HasField("basis")
515        else None
516    )
517    return result
@enum.unique
class BasisStatus(enum.Enum):
29@enum.unique
30class BasisStatus(enum.Enum):
31    """Status of a variable/constraint in a LP basis.
32
33    Attributes:
34      FREE: The variable/constraint is free (it has no finite bounds).
35      AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be
36        finite).
37      AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be
38        finite).
39      FIXED_VALUE: The variable/constraint has identical finite lower and upper
40        bounds.
41      BASIC: The variable/constraint is basic.
42    """
43
44    FREE = solution_pb2.BASIS_STATUS_FREE
45    AT_LOWER_BOUND = solution_pb2.BASIS_STATUS_AT_LOWER_BOUND
46    AT_UPPER_BOUND = solution_pb2.BASIS_STATUS_AT_UPPER_BOUND
47    FIXED_VALUE = solution_pb2.BASIS_STATUS_FIXED_VALUE
48    BASIC = solution_pb2.BASIS_STATUS_BASIC

Status of a variable/constraint in a LP basis.

Attributes:
  • FREE: The variable/constraint is free (it has no finite bounds).
  • AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be finite).
  • AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be finite).
  • FIXED_VALUE: The variable/constraint has identical finite lower and upper bounds.
  • BASIC: The variable/constraint is basic.
FREE = <BasisStatus.FREE: 1>
AT_LOWER_BOUND = <BasisStatus.AT_LOWER_BOUND: 2>
AT_UPPER_BOUND = <BasisStatus.AT_UPPER_BOUND: 3>
FIXED_VALUE = <BasisStatus.FIXED_VALUE: 4>
BASIC = <BasisStatus.BASIC: 5>
@enum.unique
class SolutionStatus(enum.Enum):
51@enum.unique
52class SolutionStatus(enum.Enum):
53    """Feasibility of a primal or dual solution as claimed by the solver.
54
55    Attributes:
56      UNDETERMINED: Solver does not claim a feasibility status.
57      FEASIBLE: Solver claims the solution is feasible.
58      INFEASIBLE: Solver claims the solution is infeasible.
59    """
60
61    UNDETERMINED = solution_pb2.SOLUTION_STATUS_UNDETERMINED
62    FEASIBLE = solution_pb2.SOLUTION_STATUS_FEASIBLE
63    INFEASIBLE = solution_pb2.SOLUTION_STATUS_INFEASIBLE

Feasibility of a primal or dual solution as claimed by the solver.

Attributes:
  • UNDETERMINED: Solver does not claim a feasibility status.
  • FEASIBLE: Solver claims the solution is feasible.
  • INFEASIBLE: Solver claims the solution is infeasible.
UNDETERMINED = <SolutionStatus.UNDETERMINED: 1>
FEASIBLE = <SolutionStatus.FEASIBLE: 2>
INFEASIBLE = <SolutionStatus.INFEASIBLE: 3>
def parse_optional_solution_status( proto: <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object>) -> Optional[SolutionStatus]:
66def parse_optional_solution_status(
67    proto: solution_pb2.SolutionStatusProto,
68) -> Optional[SolutionStatus]:
69    """Converts a proto SolutionStatus to an optional Python SolutionStatus."""
70    return (
71        None
72        if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED
73        else SolutionStatus(proto)
74    )

Converts a proto SolutionStatus to an optional Python SolutionStatus.

def optional_solution_status_to_proto( status: Optional[SolutionStatus]) -> <google.protobuf.internal.enum_type_wrapper.EnumTypeWrapper object at 0x7f200b6c3c50>:
77def optional_solution_status_to_proto(
78    status: Optional[SolutionStatus],
79) -> solution_pb2.SolutionStatusProto:
80    """Converts an optional Python SolutionStatus to a proto SolutionStatus."""
81    return solution_pb2.SOLUTION_STATUS_UNSPECIFIED if status is None else status.value

Converts an optional Python SolutionStatus to a proto SolutionStatus.

@dataclasses.dataclass
class PrimalSolution:
 84@dataclasses.dataclass
 85class PrimalSolution:
 86    """A solution to the optimization problem in a Model.
 87
 88    E.g. consider a simple linear program:
 89      min c * x
 90      s.t. A * x >= b
 91      x >= 0.
 92    A primal solution is assignment values to x. It is feasible if it satisfies
 93    A * x >= b and x >= 0 from above. In the class PrimalSolution variable_values
 94    is x and objective_value is c * x.
 95
 96    For the general case of a MathOpt optimization model, see go/mathopt-solutions
 97    for details.
 98
 99    Attributes:
100      variable_values: The value assigned for each Variable in the model.
101      objective_value: The value of the objective value at this solution. This
102        value may not be always populated.
103      auxiliary_objective_values: Set only for multi objective problems, the
104        objective value for each auxiliary objective, as computed by the solver.
105        This value will not always be populated.
106      feasibility_status: The feasibility of the solution as claimed by the
107        solver.
108    """
109
110    variable_values: Dict[variables.Variable, float] = dataclasses.field(
111        default_factory=dict
112    )
113    objective_value: float = 0.0
114    auxiliary_objective_values: Dict[objectives.AuxiliaryObjective, float] = (
115        dataclasses.field(default_factory=dict)
116    )
117    feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED
118
119    def to_proto(self) -> solution_pb2.PrimalSolutionProto:
120        """Returns an equivalent proto for a primal solution."""
121        return solution_pb2.PrimalSolutionProto(
122            variable_values=sparse_containers.to_sparse_double_vector_proto(
123                self.variable_values
124            ),
125            objective_value=self.objective_value,
126            auxiliary_objective_values={
127                obj.id: obj_value
128                for obj, obj_value in self.auxiliary_objective_values.items()
129            },
130            feasibility_status=self.feasibility_status.value,
131        )

A solution to the optimization problem in a Model.

E.g. consider a simple linear program: min c * x s.t. A * x >= b x >= 0. A primal solution is assignment values to x. It is feasible if it satisfies A * x >= b and x >= 0 from above. In the class PrimalSolution variable_values is x and objective_value is c * x.

For the general case of a MathOpt optimization model, see go/mathopt-solutions for details.

Attributes:
  • variable_values: The value assigned for each Variable in the model.
  • objective_value: The value of the objective value at this solution. This value may not be always populated.
  • auxiliary_objective_values: Set only for multi objective problems, the objective value for each auxiliary objective, as computed by the solver. This value will not always be populated.
  • feasibility_status: The feasibility of the solution as claimed by the solver.
PrimalSolution( variable_values: Dict[ortools.math_opt.python.variables.Variable, float] = <factory>, objective_value: float = 0.0, auxiliary_objective_values: Dict[ortools.math_opt.python.objectives.AuxiliaryObjective, float] = <factory>, feasibility_status: SolutionStatus = <SolutionStatus.UNDETERMINED: 1>)
variable_values: Dict[ortools.math_opt.python.variables.Variable, float]
objective_value: float = 0.0
auxiliary_objective_values: Dict[ortools.math_opt.python.objectives.AuxiliaryObjective, float]
feasibility_status: SolutionStatus = <SolutionStatus.UNDETERMINED: 1>
def to_proto(self) -> ortools.math_opt.solution_pb2.PrimalSolutionProto:
119    def to_proto(self) -> solution_pb2.PrimalSolutionProto:
120        """Returns an equivalent proto for a primal solution."""
121        return solution_pb2.PrimalSolutionProto(
122            variable_values=sparse_containers.to_sparse_double_vector_proto(
123                self.variable_values
124            ),
125            objective_value=self.objective_value,
126            auxiliary_objective_values={
127                obj.id: obj_value
128                for obj, obj_value in self.auxiliary_objective_values.items()
129            },
130            feasibility_status=self.feasibility_status.value,
131        )

Returns an equivalent proto for a primal solution.

def parse_primal_solution( proto: ortools.math_opt.solution_pb2.PrimalSolutionProto, mod: ortools.math_opt.python.model.Model, *, validate: bool = True) -> PrimalSolution:
134def parse_primal_solution(
135    proto: solution_pb2.PrimalSolutionProto,
136    mod: model.Model,
137    *,
138    validate: bool = True,
139) -> PrimalSolution:
140    """Returns an equivalent PrimalSolution from the input proto."""
141    result = PrimalSolution()
142    result.objective_value = proto.objective_value
143    for aux_id, obj_value in proto.auxiliary_objective_values.items():
144        result.auxiliary_objective_values[
145            mod.get_auxiliary_objective(aux_id, validate=validate)
146        ] = obj_value
147    result.variable_values = sparse_containers.parse_variable_map(
148        proto.variable_values, mod, validate=validate
149    )
150    status_proto = proto.feasibility_status
151    if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
152        raise ValueError("Primal solution feasibility status should not be UNSPECIFIED")
153    result.feasibility_status = SolutionStatus(status_proto)
154    return result

Returns an equivalent PrimalSolution from the input proto.

@dataclasses.dataclass
class PrimalRay:
157@dataclasses.dataclass
158class PrimalRay:
159    """A direction of unbounded objective improvement in an optimization Model.
160
161    Equivalently, a certificate of infeasibility for the dual of the optimization
162    problem.
163
164    E.g. consider a simple linear program:
165      min c * x
166      s.t. A * x >= b
167      x >= 0.
168    A primal ray is an x that satisfies:
169      c * x < 0
170      A * x >= 0
171      x >= 0.
172    Observe that given a feasible solution, any positive multiple of the primal
173    ray plus that solution is still feasible, and gives a better objective
174    value. A primal ray also proves the dual optimization problem infeasible.
175
176    In the class PrimalRay, variable_values is this x.
177
178    For the general case of a MathOpt optimization model, see
179    go/mathopt-solutions for details.
180
181    Attributes:
182      variable_values: The value assigned for each Variable in the model.
183    """
184
185    variable_values: Dict[variables.Variable, float] = dataclasses.field(
186        default_factory=dict
187    )
188
189    def to_proto(self) -> solution_pb2.PrimalRayProto:
190        """Returns an equivalent proto to this PrimalRay."""
191        return solution_pb2.PrimalRayProto(
192            variable_values=sparse_containers.to_sparse_double_vector_proto(
193                self.variable_values
194            )
195        )

A direction of unbounded objective improvement in an optimization Model.

Equivalently, a certificate of infeasibility for the dual of the optimization problem.

E.g. consider a simple linear program: min c * x s.t. A * x >= b x >= 0.

A primal ray is an x that satisfies:

c * x < 0 A * x >= 0 x >= 0.

Observe that given a feasible solution, any positive multiple of the primal ray plus that solution is still feasible, and gives a better objective value. A primal ray also proves the dual optimization problem infeasible.

In the class PrimalRay, variable_values is this x.

For the general case of a MathOpt optimization model, see go/mathopt-solutions for details.

Attributes:
  • variable_values: The value assigned for each Variable in the model.
PrimalRay( variable_values: Dict[ortools.math_opt.python.variables.Variable, float] = <factory>)
variable_values: Dict[ortools.math_opt.python.variables.Variable, float]
def to_proto(self) -> ortools.math_opt.solution_pb2.PrimalRayProto:
189    def to_proto(self) -> solution_pb2.PrimalRayProto:
190        """Returns an equivalent proto to this PrimalRay."""
191        return solution_pb2.PrimalRayProto(
192            variable_values=sparse_containers.to_sparse_double_vector_proto(
193                self.variable_values
194            )
195        )

Returns an equivalent proto to this PrimalRay.

def parse_primal_ray( proto: ortools.math_opt.solution_pb2.PrimalRayProto, mod: ortools.math_opt.python.model.Model, *, validate: bool = True) -> PrimalRay:
198def parse_primal_ray(
199    proto: solution_pb2.PrimalRayProto,
200    mod: model.Model,
201    *,
202    validate: bool = True,
203) -> PrimalRay:
204    """Returns an equivalent PrimalRay from the input proto."""
205    result = PrimalRay()
206    result.variable_values = sparse_containers.parse_variable_map(
207        proto.variable_values, mod, validate=validate
208    )
209    return result

Returns an equivalent PrimalRay from the input proto.

@dataclasses.dataclass
class DualSolution:
212@dataclasses.dataclass
213class DualSolution:
214    """A solution to the dual of the optimization problem given by a Model.
215
216    E.g. consider the primal dual pair linear program pair:
217      (Primal)              (Dual)
218      min c * x             max b * y
219      s.t. A * x >= b       s.t. y * A + r = c
220      x >= 0                y, r >= 0.
221    The dual solution is the pair (y, r). It is feasible if it satisfies the
222    constraints from (Dual) above.
223
224    Below, y is dual_values, r is reduced_costs, and b * y is objective_value.
225
226    For the general case, see go/mathopt-solutions and go/mathopt-dual (and note
227    that the dual objective depends on r in the general case).
228
229    Attributes:
230      dual_values: The value assigned for each LinearConstraint in the model.
231      quadratic_dual_values: The value assigned for each QuadraticConstraint in
232        the model.
233      reduced_costs: The value assigned for each Variable in the model.
234      objective_value: The value of the dual objective value at this solution.
235        This value may not be always populated.
236      feasibility_status: The feasibility of the solution as claimed by the
237        solver.
238    """
239
240    dual_values: Dict[linear_constraints.LinearConstraint, float] = dataclasses.field(
241        default_factory=dict
242    )
243    quadratic_dual_values: Dict[quadratic_constraints.QuadraticConstraint, float] = (
244        dataclasses.field(default_factory=dict)
245    )
246    reduced_costs: Dict[variables.Variable, float] = dataclasses.field(
247        default_factory=dict
248    )
249    objective_value: Optional[float] = None
250    feasibility_status: SolutionStatus = SolutionStatus.UNDETERMINED
251
252    def to_proto(self) -> solution_pb2.DualSolutionProto:
253        """Returns an equivalent proto for a dual solution."""
254        return solution_pb2.DualSolutionProto(
255            dual_values=sparse_containers.to_sparse_double_vector_proto(
256                self.dual_values
257            ),
258            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
259                self.reduced_costs
260            ),
261            quadratic_dual_values=sparse_containers.to_sparse_double_vector_proto(
262                self.quadratic_dual_values
263            ),
264            objective_value=self.objective_value,
265            feasibility_status=self.feasibility_status.value,
266        )

A solution to the dual of the optimization problem given by a Model.

E.g. consider the primal dual pair linear program pair: (Primal)            (Dual) min c * x             max b * y s.t. A * x >= b       s.t. y * A + r = c x >= 0              y, r >= 0. The dual solution is the pair (y, r). It is feasible if it satisfies the constraints from (Dual) above.

Below, y is dual_values, r is reduced_costs, and b * y is objective_value.

For the general case, see go/mathopt-solutions and go/mathopt-dual (and note that the dual objective depends on r in the general case).

Attributes:
  • dual_values: The value assigned for each LinearConstraint in the model.
  • quadratic_dual_values: The value assigned for each QuadraticConstraint in the model.
  • reduced_costs: The value assigned for each Variable in the model.
  • objective_value: The value of the dual objective value at this solution. This value may not be always populated.
  • feasibility_status: The feasibility of the solution as claimed by the solver.
DualSolution( dual_values: Dict[ortools.math_opt.python.linear_constraints.LinearConstraint, float] = <factory>, quadratic_dual_values: Dict[ortools.math_opt.python.quadratic_constraints.QuadraticConstraint, float] = <factory>, reduced_costs: Dict[ortools.math_opt.python.variables.Variable, float] = <factory>, objective_value: Optional[float] = None, feasibility_status: SolutionStatus = <SolutionStatus.UNDETERMINED: 1>)
reduced_costs: Dict[ortools.math_opt.python.variables.Variable, float]
objective_value: Optional[float] = None
feasibility_status: SolutionStatus = <SolutionStatus.UNDETERMINED: 1>
def to_proto(self) -> ortools.math_opt.solution_pb2.DualSolutionProto:
252    def to_proto(self) -> solution_pb2.DualSolutionProto:
253        """Returns an equivalent proto for a dual solution."""
254        return solution_pb2.DualSolutionProto(
255            dual_values=sparse_containers.to_sparse_double_vector_proto(
256                self.dual_values
257            ),
258            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
259                self.reduced_costs
260            ),
261            quadratic_dual_values=sparse_containers.to_sparse_double_vector_proto(
262                self.quadratic_dual_values
263            ),
264            objective_value=self.objective_value,
265            feasibility_status=self.feasibility_status.value,
266        )

Returns an equivalent proto for a dual solution.

def parse_dual_solution( proto: ortools.math_opt.solution_pb2.DualSolutionProto, mod: ortools.math_opt.python.model.Model, *, validate: bool = True) -> DualSolution:
269def parse_dual_solution(
270    proto: solution_pb2.DualSolutionProto,
271    mod: model.Model,
272    *,
273    validate: bool = True,
274) -> DualSolution:
275    """Returns an equivalent DualSolution from the input proto."""
276    result = DualSolution()
277    result.objective_value = (
278        proto.objective_value if proto.HasField("objective_value") else None
279    )
280    result.dual_values = sparse_containers.parse_linear_constraint_map(
281        proto.dual_values, mod, validate=validate
282    )
283    result.quadratic_dual_values = sparse_containers.parse_quadratic_constraint_map(
284        proto.quadratic_dual_values, mod, validate=validate
285    )
286    result.reduced_costs = sparse_containers.parse_variable_map(
287        proto.reduced_costs, mod, validate=validate
288    )
289    status_proto = proto.feasibility_status
290    if status_proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED:
291        raise ValueError("Dual solution feasibility status should not be UNSPECIFIED")
292    result.feasibility_status = SolutionStatus(status_proto)
293    return result

Returns an equivalent DualSolution from the input proto.

@dataclasses.dataclass
class DualRay:
296@dataclasses.dataclass
297class DualRay:
298    """A direction of unbounded objective improvement in an optimization Model.
299
300    A direction of unbounded improvement to the dual of an optimization,
301    problem; equivalently, a certificate of primal infeasibility.
302
303    E.g. consider the primal dual pair linear program pair:
304      (Primal)              (Dual)
305      min c * x             max b * y
306      s.t. A * x >= b       s.t. y * A + r = c
307      x >= 0                y, r >= 0.
308
309    The dual ray is the pair (y, r) satisfying:
310      b * y > 0
311      y * A + r = 0
312      y, r >= 0.
313    Observe that adding a positive multiple of (y, r) to dual feasible solution
314    maintains dual feasibility and improves the objective (proving the dual is
315    unbounded). The dual ray also proves the primal problem is infeasible.
316
317    In the class DualRay below, y is dual_values and r is reduced_costs.
318
319    For the general case, see go/mathopt-solutions and go/mathopt-dual (and note
320    that the dual objective depends on r in the general case).
321
322    Attributes:
323      dual_values: The value assigned for each LinearConstraint in the model.
324      reduced_costs: The value assigned for each Variable in the model.
325    """
326
327    dual_values: Dict[linear_constraints.LinearConstraint, float] = dataclasses.field(
328        default_factory=dict
329    )
330    reduced_costs: Dict[variables.Variable, float] = dataclasses.field(
331        default_factory=dict
332    )
333
334    def to_proto(self) -> solution_pb2.DualRayProto:
335        """Returns an equivalent proto to this PrimalRay."""
336        return solution_pb2.DualRayProto(
337            dual_values=sparse_containers.to_sparse_double_vector_proto(
338                self.dual_values
339            ),
340            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
341                self.reduced_costs
342            ),
343        )

A direction of unbounded objective improvement in an optimization Model.

A direction of unbounded improvement to the dual of an optimization, problem; equivalently, a certificate of primal infeasibility.

E.g. consider the primal dual pair linear program pair: (Primal)            (Dual) min c * x             max b * y s.t. A * x >= b       s.t. y * A + r = c x >= 0              y, r >= 0.

The dual ray is the pair (y, r) satisfying: b * y > 0 y * A + r = 0 y, r >= 0. Observe that adding a positive multiple of (y, r) to dual feasible solution maintains dual feasibility and improves the objective (proving the dual is unbounded). The dual ray also proves the primal problem is infeasible.

In the class DualRay below, y is dual_values and r is reduced_costs.

For the general case, see go/mathopt-solutions and go/mathopt-dual (and note that the dual objective depends on r in the general case).

Attributes:
  • dual_values: The value assigned for each LinearConstraint in the model.
  • reduced_costs: The value assigned for each Variable in the model.
DualRay( dual_values: Dict[ortools.math_opt.python.linear_constraints.LinearConstraint, float] = <factory>, reduced_costs: Dict[ortools.math_opt.python.variables.Variable, float] = <factory>)
reduced_costs: Dict[ortools.math_opt.python.variables.Variable, float]
def to_proto(self) -> ortools.math_opt.solution_pb2.DualRayProto:
334    def to_proto(self) -> solution_pb2.DualRayProto:
335        """Returns an equivalent proto to this PrimalRay."""
336        return solution_pb2.DualRayProto(
337            dual_values=sparse_containers.to_sparse_double_vector_proto(
338                self.dual_values
339            ),
340            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
341                self.reduced_costs
342            ),
343        )

Returns an equivalent proto to this PrimalRay.

def parse_dual_ray( proto: ortools.math_opt.solution_pb2.DualRayProto, mod: ortools.math_opt.python.model.Model, *, validate: bool = True) -> DualRay:
346def parse_dual_ray(
347    proto: solution_pb2.DualRayProto, mod: model.Model, *, validate: bool = True
348) -> DualRay:
349    """Returns an equivalent DualRay from the input proto."""
350    result = DualRay()
351    result.dual_values = sparse_containers.parse_linear_constraint_map(
352        proto.dual_values, mod, validate=validate
353    )
354    result.reduced_costs = sparse_containers.parse_variable_map(
355        proto.reduced_costs, mod, validate=validate
356    )
357    return result

Returns an equivalent DualRay from the input proto.

@dataclasses.dataclass
class Basis:
360@dataclasses.dataclass
361class Basis:
362    """A combinatorial characterization for a solution to a linear program.
363
364    The simplex method for solving linear programs always returns a "basic
365    feasible solution" which can be described combinatorially as a Basis. A basis
366    assigns a BasisStatus for every variable and linear constraint.
367
368    E.g. consider a standard form LP:
369      min c * x
370      s.t. A * x = b
371      x >= 0
372    that has more variables than constraints and with full row rank A.
373
374    Let n be the number of variables and m the number of linear constraints. A
375    valid basis for this problem can be constructed as follows:
376     * All constraints will have basis status FIXED.
377     * Pick m variables such that the columns of A are linearly independent and
378       assign the status BASIC.
379     * Assign the status AT_LOWER for the remaining n - m variables.
380
381    The basic solution for this basis is the unique solution of A * x = b that has
382    all variables with status AT_LOWER fixed to their lower bounds (all zero). The
383    resulting solution is called a basic feasible solution if it also satisfies
384    x >= 0.
385
386    See go/mathopt-basis for treatment of the general case and an explanation of
387    how a dual solution is determined for a basis.
388
389    Attributes:
390      variable_status: The basis status for each variable in the model.
391      constraint_status: The basis status for each linear constraint in the model.
392      basic_dual_feasibility: This is an advanced feature used by MathOpt to
393        characterize feasibility of suboptimal LP solutions (optimal solutions
394        will always have status SolutionStatus.FEASIBLE). For single-sided LPs it
395        should be equal to the feasibility status of the associated dual solution.
396        For two-sided LPs it may be different in some edge cases (e.g. incomplete
397        solves with primal simplex). For more details see
398        go/mathopt-basis-advanced#dualfeasibility. If you are providing a starting
399        basis via ModelSolveParameters.initial_basis, this value is ignored and
400        can be None. It is only relevant for the basis returned by Solution.basis,
401        and it is never None when returned from solve(). This is an advanced
402        status. For single-sided LPs it should be equal to the feasibility status
403        of the associated dual solution. For two-sided LPs it may be different in
404        some edge cases (e.g. incomplete solves with primal simplex). For more
405        details see go/mathopt-basis-advanced#dualfeasibility.
406    """
407
408    variable_status: Dict[variables.Variable, BasisStatus] = dataclasses.field(
409        default_factory=dict
410    )
411    constraint_status: Dict[linear_constraints.LinearConstraint, BasisStatus] = (
412        dataclasses.field(default_factory=dict)
413    )
414    basic_dual_feasibility: Optional[SolutionStatus] = None
415
416    def to_proto(self) -> solution_pb2.BasisProto:
417        """Returns an equivalent proto for the basis."""
418        return solution_pb2.BasisProto(
419            variable_status=_to_sparse_basis_status_vector_proto(self.variable_status),
420            constraint_status=_to_sparse_basis_status_vector_proto(
421                self.constraint_status
422            ),
423            basic_dual_feasibility=optional_solution_status_to_proto(
424                self.basic_dual_feasibility
425            ),
426        )

A combinatorial characterization for a solution to a linear program.

The simplex method for solving linear programs always returns a "basic feasible solution" which can be described combinatorially as a Basis. A basis assigns a BasisStatus for every variable and linear constraint.

E.g. consider a standard form LP: min c * x s.t. A * x = b x >= 0 that has more variables than constraints and with full row rank A.

Let n be the number of variables and m the number of linear constraints. A valid basis for this problem can be constructed as follows:

  • All constraints will have basis status FIXED.
  • Pick m variables such that the columns of A are linearly independent and assign the status BASIC.
  • Assign the status AT_LOWER for the remaining n - m variables.

The basic solution for this basis is the unique solution of A * x = b that has all variables with status AT_LOWER fixed to their lower bounds (all zero). The resulting solution is called a basic feasible solution if it also satisfies x >= 0.

See go/mathopt-basis for treatment of the general case and an explanation of how a dual solution is determined for a basis.

Attributes:
  • variable_status: The basis status for each variable in the model.
  • constraint_status: The basis status for each linear constraint in the model.
  • basic_dual_feasibility: This is an advanced feature used by MathOpt to characterize feasibility of suboptimal LP solutions (optimal solutions will always have status SolutionStatus.FEASIBLE). 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. If you are providing a starting 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.
Basis( variable_status: Dict[ortools.math_opt.python.variables.Variable, BasisStatus] = <factory>, constraint_status: Dict[ortools.math_opt.python.linear_constraints.LinearConstraint, BasisStatus] = <factory>, basic_dual_feasibility: Optional[SolutionStatus] = None)
basic_dual_feasibility: Optional[SolutionStatus] = None
def to_proto(self) -> ortools.math_opt.solution_pb2.BasisProto:
416    def to_proto(self) -> solution_pb2.BasisProto:
417        """Returns an equivalent proto for the basis."""
418        return solution_pb2.BasisProto(
419            variable_status=_to_sparse_basis_status_vector_proto(self.variable_status),
420            constraint_status=_to_sparse_basis_status_vector_proto(
421                self.constraint_status
422            ),
423            basic_dual_feasibility=optional_solution_status_to_proto(
424                self.basic_dual_feasibility
425            ),
426        )

Returns an equivalent proto for the basis.

def parse_basis( proto: ortools.math_opt.solution_pb2.BasisProto, mod: ortools.math_opt.python.model.Model, *, validate: bool = True) -> Basis:
429def parse_basis(
430    proto: solution_pb2.BasisProto, mod: model.Model, *, validate: bool = True
431) -> Basis:
432    """Returns an equivalent Basis to the input proto."""
433    result = Basis()
434    for index, vid in enumerate(proto.variable_status.ids):
435        status_proto = proto.variable_status.values[index]
436        if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED:
437            raise ValueError("Variable basis status should not be UNSPECIFIED")
438        result.variable_status[mod.get_variable(vid, validate=validate)] = BasisStatus(
439            status_proto
440        )
441    for index, cid in enumerate(proto.constraint_status.ids):
442        status_proto = proto.constraint_status.values[index]
443        if status_proto == solution_pb2.BASIS_STATUS_UNSPECIFIED:
444            raise ValueError("Constraint basis status should not be UNSPECIFIED")
445        result.constraint_status[mod.get_linear_constraint(cid, validate=validate)] = (
446            BasisStatus(status_proto)
447        )
448    result.basic_dual_feasibility = parse_optional_solution_status(
449        proto.basic_dual_feasibility
450    )
451    return result

Returns an equivalent Basis to the input proto.

@dataclasses.dataclass
class Solution:
472@dataclasses.dataclass
473class Solution:
474    """A solution to the optimization problem in a Model."""
475
476    primal_solution: Optional[PrimalSolution] = None
477    dual_solution: Optional[DualSolution] = None
478    basis: Optional[Basis] = None
479
480    def to_proto(self) -> solution_pb2.SolutionProto:
481        """Returns an equivalent proto for a solution."""
482        return solution_pb2.SolutionProto(
483            primal_solution=(
484                self.primal_solution.to_proto()
485                if self.primal_solution is not None
486                else None
487            ),
488            dual_solution=(
489                self.dual_solution.to_proto()
490                if self.dual_solution is not None
491                else None
492            ),
493            basis=self.basis.to_proto() if self.basis is not None else None,
494        )

A solution to the optimization problem in a Model.

Solution( primal_solution: Optional[PrimalSolution] = None, dual_solution: Optional[DualSolution] = None, basis: Optional[Basis] = None)
primal_solution: Optional[PrimalSolution] = None
dual_solution: Optional[DualSolution] = None
basis: Optional[Basis] = None
def to_proto(self) -> ortools.math_opt.solution_pb2.SolutionProto:
480    def to_proto(self) -> solution_pb2.SolutionProto:
481        """Returns an equivalent proto for a solution."""
482        return solution_pb2.SolutionProto(
483            primal_solution=(
484                self.primal_solution.to_proto()
485                if self.primal_solution is not None
486                else None
487            ),
488            dual_solution=(
489                self.dual_solution.to_proto()
490                if self.dual_solution is not None
491                else None
492            ),
493            basis=self.basis.to_proto() if self.basis is not None else None,
494        )

Returns an equivalent proto for a solution.

def parse_solution( proto: ortools.math_opt.solution_pb2.SolutionProto, mod: ortools.math_opt.python.model.Model, *, validate: bool = True) -> Solution:
497def parse_solution(
498    proto: solution_pb2.SolutionProto,
499    mod: model.Model,
500    *,
501    validate: bool = True,
502) -> Solution:
503    """Returns a Solution equivalent to the input proto."""
504    result = Solution()
505    if proto.HasField("primal_solution"):
506        result.primal_solution = parse_primal_solution(
507            proto.primal_solution, mod, validate=validate
508        )
509    if proto.HasField("dual_solution"):
510        result.dual_solution = parse_dual_solution(
511            proto.dual_solution, mod, validate=validate
512        )
513    result.basis = (
514        parse_basis(proto.basis, mod, validate=validate)
515        if proto.HasField("basis")
516        else None
517    )
518    return result

Returns a Solution equivalent to the input proto.