#!/usr/bin/env python3 # Copyright 2010-2025 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Sparse vectors and matrices using variables and constraints from Model. Analogous to sparse_containers.proto, with bidirectional conversion. """ from typing import Dict, FrozenSet, Generic, Iterable, Mapping, Optional, Set, TypeVar from ortools.math_opt import sparse_containers_pb2 from ortools.math_opt.python import linear_constraints from ortools.math_opt.python import model from ortools.math_opt.python import quadratic_constraints from ortools.math_opt.python import variables VarOrConstraintType = TypeVar( "VarOrConstraintType", variables.Variable, linear_constraints.LinearConstraint, quadratic_constraints.QuadraticConstraint, ) def to_sparse_double_vector_proto( terms: Mapping[VarOrConstraintType, float], ) -> sparse_containers_pb2.SparseDoubleVectorProto: """Converts a sparse vector from proto to dict representation.""" result = sparse_containers_pb2.SparseDoubleVectorProto() if terms: id_and_values = [(key.id, value) for (key, value) in terms.items()] id_and_values.sort() ids, values = zip(*id_and_values) result.ids[:] = ids result.values[:] = values return result def to_sparse_int32_vector_proto( terms: Mapping[VarOrConstraintType, int], ) -> sparse_containers_pb2.SparseInt32VectorProto: """Converts a sparse vector from proto to dict representation.""" result = sparse_containers_pb2.SparseInt32VectorProto() if terms: id_and_values = [(key.id, value) for (key, value) in terms.items()] id_and_values.sort() ids, values = zip(*id_and_values) result.ids[:] = ids result.values[:] = values return result def parse_variable_map( proto: sparse_containers_pb2.SparseDoubleVectorProto, mod: model.Model, validate: bool = True, ) -> Dict[variables.Variable, float]: """Converts a sparse vector of variables from proto to dict representation.""" result = {} for index, var_id in enumerate(proto.ids): result[mod.get_variable(var_id, validate=validate)] = proto.values[index] return result def parse_linear_constraint_map( proto: sparse_containers_pb2.SparseDoubleVectorProto, mod: model.Model, validate: bool = True, ) -> Dict[linear_constraints.LinearConstraint, float]: """Converts a sparse vector of linear constraints from proto to dict representation.""" result = {} for index, lin_con_id in enumerate(proto.ids): result[mod.get_linear_constraint(lin_con_id, validate=validate)] = proto.values[ index ] return result def parse_quadratic_constraint_map( proto: sparse_containers_pb2.SparseDoubleVectorProto, mod: model.Model, validate: bool = True, ) -> Dict[quadratic_constraints.QuadraticConstraint, float]: """Converts a sparse vector of quadratic constraints from proto to dict representation.""" result = {} for index, quad_con_id in enumerate(proto.ids): result[mod.get_quadratic_constraint(quad_con_id, validate=validate)] = ( proto.values[index] ) return result class SparseVectorFilter(Generic[VarOrConstraintType]): """Restricts the variables or constraints returned in a sparse vector. The default behavior is to return entries for all variables/constraints. E.g. when requesting the solution to an optimization problem, use this class to restrict the variables that values are returned for. Attributes: skip_zero_values: Do not include key value pairs with value zero. filtered_items: If not None, include only key value pairs these keys. Note that the empty set is different (don't return any keys) from None (return all keys). """ def __init__( self, *, skip_zero_values: bool = False, filtered_items: Optional[Iterable[VarOrConstraintType]] = None, ): self._skip_zero_values: bool = skip_zero_values self._filtered_items: Optional[Set[VarOrConstraintType]] = ( None if filtered_items is None else frozenset(filtered_items) ) # pytype: disable=annotation-type-mismatch # attribute-variable-annotations @property def skip_zero_values(self) -> bool: return self._skip_zero_values @property def filtered_items(self) -> Optional[FrozenSet[VarOrConstraintType]]: return ( self._filtered_items ) # pytype: disable=bad-return-type # attribute-variable-annotations def to_proto(self) -> sparse_containers_pb2.SparseVectorFilterProto: """Returns an equivalent proto representation.""" result = sparse_containers_pb2.SparseVectorFilterProto() result.skip_zero_values = self._skip_zero_values if self._filtered_items is not None: result.filter_by_ids = True result.filtered_ids[:] = sorted(t.id for t in self._filtered_items) return result VariableFilter = SparseVectorFilter[variables.Variable] LinearConstraintFilter = SparseVectorFilter[linear_constraints.LinearConstraint] QuadraticConstraintFilter = SparseVectorFilter[ quadratic_constraints.QuadraticConstraint ]