ortools.math_opt.python.solution

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

  1#!/usr/bin/env python3
  2# Copyright 2010-2025 Google LLC
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#     http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14
 15"""The solution to an optimization problem defined by Model in model.py."""
 16import dataclasses
 17import enum
 18from typing import Dict, Optional, TypeVar
 19
 20from ortools.math_opt import solution_pb2
 21from ortools.math_opt.python import linear_constraints
 22from ortools.math_opt.python import model
 23from ortools.math_opt.python import objectives
 24from ortools.math_opt.python import quadratic_constraints
 25from ortools.math_opt.python import sparse_containers
 26from ortools.math_opt.python import variables
 27
 28
 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
 49
 50
 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
 64
 65
 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    )
 75
 76
 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
 82
 83
 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        )
132
133
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
155
156
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        )
196
197
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
210
211
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        )
267
268
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
294
295
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        )
344
345
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
358
359
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        )
427
428
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
452
453
454T = TypeVar("T", variables.Variable, linear_constraints.LinearConstraint)
455
456
457def _to_sparse_basis_status_vector_proto(
458    terms: Dict[T, BasisStatus],
459) -> solution_pb2.SparseBasisStatusVector:
460    """Converts a basis vector from a python Dict to a protocol buffer."""
461    result = solution_pb2.SparseBasisStatusVector()
462    if terms:
463        id_and_status = sorted(
464            (key.id, status.value) for (key, status) in terms.items()
465        )
466        ids, values = zip(*id_and_status)
467        result.ids[:] = ids
468        result.values[:] = values
469    return result
470
471
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        )
495
496
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
@enum.unique
class BasisStatus(enum.Enum):
30@enum.unique
31class BasisStatus(enum.Enum):
32    """Status of a variable/constraint in a LP basis.
33
34    Attributes:
35      FREE: The variable/constraint is free (it has no finite bounds).
36      AT_LOWER_BOUND: The variable/constraint is at its lower bound (which must be
37        finite).
38      AT_UPPER_BOUND: The variable/constraint is at its upper bound (which must be
39        finite).
40      FIXED_VALUE: The variable/constraint has identical finite lower and upper
41        bounds.
42      BASIC: The variable/constraint is basic.
43    """
44
45    FREE = solution_pb2.BASIS_STATUS_FREE
46    AT_LOWER_BOUND = solution_pb2.BASIS_STATUS_AT_LOWER_BOUND
47    AT_UPPER_BOUND = solution_pb2.BASIS_STATUS_AT_UPPER_BOUND
48    FIXED_VALUE = solution_pb2.BASIS_STATUS_FIXED_VALUE
49    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):
52@enum.unique
53class SolutionStatus(enum.Enum):
54    """Feasibility of a primal or dual solution as claimed by the solver.
55
56    Attributes:
57      UNDETERMINED: Solver does not claim a feasibility status.
58      FEASIBLE: Solver claims the solution is feasible.
59      INFEASIBLE: Solver claims the solution is infeasible.
60    """
61
62    UNDETERMINED = solution_pb2.SOLUTION_STATUS_UNDETERMINED
63    FEASIBLE = solution_pb2.SOLUTION_STATUS_FEASIBLE
64    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>) -> SolutionStatus | None:
67def parse_optional_solution_status(
68    proto: solution_pb2.SolutionStatusProto,
69) -> Optional[SolutionStatus]:
70    """Converts a proto SolutionStatus to an optional Python SolutionStatus."""
71    return (
72        None
73        if proto == solution_pb2.SOLUTION_STATUS_UNSPECIFIED
74        else SolutionStatus(proto)
75    )

Converts a proto SolutionStatus to an optional Python SolutionStatus.

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

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:
120    def to_proto(self) -> solution_pb2.PrimalSolutionProto:
121        """Returns an equivalent proto for a primal solution."""
122        return solution_pb2.PrimalSolutionProto(
123            variable_values=sparse_containers.to_sparse_double_vector_proto(
124                self.variable_values
125            ),
126            objective_value=self.objective_value,
127            auxiliary_objective_values={
128                obj.id: obj_value
129                for obj, obj_value in self.auxiliary_objective_values.items()
130            },
131            feasibility_status=self.feasibility_status.value,
132        )

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

Returns an equivalent PrimalSolution from the input proto.

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

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:
190    def to_proto(self) -> solution_pb2.PrimalRayProto:
191        """Returns an equivalent proto to this PrimalRay."""
192        return solution_pb2.PrimalRayProto(
193            variable_values=sparse_containers.to_sparse_double_vector_proto(
194                self.variable_values
195            )
196        )

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

Returns an equivalent PrimalRay from the input proto.

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

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: float | None = None, feasibility_status: SolutionStatus = <SolutionStatus.UNDETERMINED: 1>)
reduced_costs: Dict[ortools.math_opt.python.variables.Variable, float]
objective_value: float | None = None
feasibility_status: SolutionStatus = <SolutionStatus.UNDETERMINED: 1>
def to_proto(self) -> ortools.math_opt.solution_pb2.DualSolutionProto:
253    def to_proto(self) -> solution_pb2.DualSolutionProto:
254        """Returns an equivalent proto for a dual solution."""
255        return solution_pb2.DualSolutionProto(
256            dual_values=sparse_containers.to_sparse_double_vector_proto(
257                self.dual_values
258            ),
259            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
260                self.reduced_costs
261            ),
262            quadratic_dual_values=sparse_containers.to_sparse_double_vector_proto(
263                self.quadratic_dual_values
264            ),
265            objective_value=self.objective_value,
266            feasibility_status=self.feasibility_status.value,
267        )

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

Returns an equivalent DualSolution from the input proto.

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

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:
335    def to_proto(self) -> solution_pb2.DualRayProto:
336        """Returns an equivalent proto to this PrimalRay."""
337        return solution_pb2.DualRayProto(
338            dual_values=sparse_containers.to_sparse_double_vector_proto(
339                self.dual_values
340            ),
341            reduced_costs=sparse_containers.to_sparse_double_vector_proto(
342                self.reduced_costs
343            ),
344        )

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

Returns an equivalent DualRay from the input proto.

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

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: SolutionStatus | None = None)
basic_dual_feasibility: SolutionStatus | None = None
def to_proto(self) -> ortools.math_opt.solution_pb2.BasisProto:
417    def to_proto(self) -> solution_pb2.BasisProto:
418        """Returns an equivalent proto for the basis."""
419        return solution_pb2.BasisProto(
420            variable_status=_to_sparse_basis_status_vector_proto(self.variable_status),
421            constraint_status=_to_sparse_basis_status_vector_proto(
422                self.constraint_status
423            ),
424            basic_dual_feasibility=optional_solution_status_to_proto(
425                self.basic_dual_feasibility
426            ),
427        )

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

Returns an equivalent Basis to the input proto.

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

A solution to the optimization problem in a Model.

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

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

Returns a Solution equivalent to the input proto.