2025-01-10 11:33:35 +01:00
|
|
|
# Copyright 2010-2025 Google LLC
|
2023-11-17 16:25:02 +01:00
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
|
#
|
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
#
|
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
|
|
"""Model specific solver configuration (e.g. starting basis)."""
|
|
|
|
|
import dataclasses
|
|
|
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
|
|
|
|
from ortools.math_opt import model_parameters_pb2
|
|
|
|
|
from ortools.math_opt.python import model
|
|
|
|
|
from ortools.math_opt.python import solution
|
|
|
|
|
from ortools.math_opt.python import sparse_containers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
|
class SolutionHint:
|
|
|
|
|
"""A suggested starting solution for the solver.
|
|
|
|
|
|
|
|
|
|
MIP solvers generally only want primal information (`variable_values`),
|
|
|
|
|
while LP solvers want both primal and dual information (`dual_values`).
|
|
|
|
|
|
|
|
|
|
Many MIP solvers can work with: (1) partial solutions that do not specify all
|
|
|
|
|
variables or (2) infeasible solutions. In these cases, solvers typically solve
|
|
|
|
|
a sub-MIP to complete/correct the hint.
|
|
|
|
|
|
|
|
|
|
How the hint is used by the solver, if at all, is highly dependent on the
|
|
|
|
|
solver, the problem type, and the algorithm used. The most reliable way to
|
|
|
|
|
ensure your hint has an effect is to read the underlying solvers logs with
|
|
|
|
|
and without the hint.
|
|
|
|
|
|
|
|
|
|
Simplex-based LP solvers typically prefer an initial basis to a solution
|
|
|
|
|
hint (they need to crossover to convert the hint to a basic feasible
|
|
|
|
|
solution otherwise).
|
|
|
|
|
|
|
|
|
|
Floating point values should be finite and not NaN, they are validated by
|
|
|
|
|
MathOpt at Solve() time (resulting in an exception).
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
variable_values: a potentially partial assignment from the model's primal
|
|
|
|
|
variables to finite (and not NaN) double values.
|
|
|
|
|
dual_values: a potentially partial assignment from the model's linear
|
|
|
|
|
constraints to finite (and not NaN) double values.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
variable_values: Dict[model.Variable, float] = dataclasses.field(
|
|
|
|
|
default_factory=dict
|
|
|
|
|
)
|
|
|
|
|
dual_values: Dict[model.LinearConstraint, float] = dataclasses.field(
|
|
|
|
|
default_factory=dict
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def to_proto(self) -> model_parameters_pb2.SolutionHintProto:
|
|
|
|
|
"""Returns an equivalent protocol buffer to this."""
|
|
|
|
|
return model_parameters_pb2.SolutionHintProto(
|
|
|
|
|
variable_values=sparse_containers.to_sparse_double_vector_proto(
|
|
|
|
|
self.variable_values
|
|
|
|
|
),
|
|
|
|
|
dual_values=sparse_containers.to_sparse_double_vector_proto(
|
|
|
|
|
self.dual_values
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_solution_hint(
|
|
|
|
|
hint_proto: model_parameters_pb2.SolutionHintProto, mod: model.Model
|
|
|
|
|
) -> SolutionHint:
|
|
|
|
|
"""Returns an equivalent SolutionHint to `hint_proto`.
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
hint_proto: The solution, as encoded by the ids of the variables and
|
|
|
|
|
constraints.
|
|
|
|
|
mod: A MathOpt Model that must contain variables and linear constraints with
|
|
|
|
|
the ids from hint_proto.
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
A SolutionHint equivalent.
|
|
|
|
|
|
|
|
|
|
Raises:
|
|
|
|
|
ValueError if hint_proto is invalid or refers to variables or constraints
|
|
|
|
|
not in mod.
|
|
|
|
|
"""
|
|
|
|
|
return SolutionHint(
|
|
|
|
|
variable_values=sparse_containers.parse_variable_map(
|
|
|
|
|
hint_proto.variable_values, mod
|
|
|
|
|
),
|
|
|
|
|
dual_values=sparse_containers.parse_linear_constraint_map(
|
|
|
|
|
hint_proto.dual_values, mod
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
|
|
class ModelSolveParameters:
|
|
|
|
|
"""Model specific solver configuration, for example, an initial basis.
|
|
|
|
|
|
|
|
|
|
This class mirrors (and can generate) the related proto
|
|
|
|
|
model_parameters_pb2.ModelSolveParametersProto.
|
|
|
|
|
|
|
|
|
|
Attributes:
|
|
|
|
|
variable_values_filter: Only return solution and primal ray values for
|
|
|
|
|
variables accepted by this filter (default accepts all variables).
|
|
|
|
|
dual_values_filter: Only return dual variable values and dual ray values for
|
|
|
|
|
linear constraints accepted by thei filter (default accepts all linear
|
|
|
|
|
constraints).
|
|
|
|
|
reduced_costs_filter: Only return reduced cost and dual ray values for
|
|
|
|
|
variables accepted by this filter (default accepts all variables).
|
|
|
|
|
initial_basis: If set, provides a warm start for simplex based solvers.
|
|
|
|
|
solution_hints: Optional solution hints. If the underlying solver only
|
|
|
|
|
accepts a single hint, the first hint is used.
|
|
|
|
|
branching_priorities: Optional branching priorities. Variables with higher
|
|
|
|
|
values will be branched on first. Variables for which priorities are not
|
|
|
|
|
set get the solver's default priority (usually zero).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
variable_values_filter: sparse_containers.VariableFilter = (
|
|
|
|
|
sparse_containers.VariableFilter()
|
|
|
|
|
)
|
|
|
|
|
dual_values_filter: sparse_containers.LinearConstraintFilter = (
|
|
|
|
|
sparse_containers.LinearConstraintFilter()
|
|
|
|
|
)
|
|
|
|
|
reduced_costs_filter: sparse_containers.VariableFilter = (
|
|
|
|
|
sparse_containers.VariableFilter()
|
|
|
|
|
)
|
|
|
|
|
initial_basis: Optional[solution.Basis] = None
|
|
|
|
|
solution_hints: List[SolutionHint] = dataclasses.field(default_factory=list)
|
|
|
|
|
branching_priorities: Dict[model.Variable, int] = dataclasses.field(
|
|
|
|
|
default_factory=dict
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def to_proto(self) -> model_parameters_pb2.ModelSolveParametersProto:
|
|
|
|
|
"""Returns an equivalent protocol buffer."""
|
|
|
|
|
# TODO(b/236289022): these methods should check that the variables are from
|
|
|
|
|
# the correct model.
|
|
|
|
|
result = model_parameters_pb2.ModelSolveParametersProto(
|
|
|
|
|
variable_values_filter=self.variable_values_filter.to_proto(),
|
|
|
|
|
dual_values_filter=self.dual_values_filter.to_proto(),
|
|
|
|
|
reduced_costs_filter=self.reduced_costs_filter.to_proto(),
|
|
|
|
|
branching_priorities=sparse_containers.to_sparse_int32_vector_proto(
|
|
|
|
|
self.branching_priorities
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
if self.initial_basis:
|
|
|
|
|
result.initial_basis.CopyFrom(self.initial_basis.to_proto())
|
|
|
|
|
for hint in self.solution_hints:
|
|
|
|
|
result.solution_hints.append(hint.to_proto())
|
|
|
|
|
return result
|