ortools.math_opt.python.statistics
Statistics about MIP/LP models.
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"""Statistics about MIP/LP models.""" 16 17import dataclasses 18import io 19import math 20from typing import Iterable, Optional 21 22from ortools.math_opt.python import model 23 24 25@dataclasses.dataclass(frozen=True) 26class Range: 27 """A close range of values [min, max]. 28 29 Attributes: 30 minimum: The minimum value. 31 maximum: The maximum value. 32 """ 33 34 minimum: float 35 maximum: float 36 37 38def merge_optional_ranges( 39 lhs: Optional[Range], rhs: Optional[Range] 40) -> Optional[Range]: 41 """Merges the two optional ranges. 42 43 Args: 44 lhs: The left hand side range. 45 rhs: The right hand side range. 46 47 Returns: 48 A merged range (None if both lhs and rhs are None). 49 """ 50 if lhs is None: 51 return rhs 52 if rhs is None: 53 return lhs 54 return Range( 55 minimum=min(lhs.minimum, rhs.minimum), 56 maximum=max(lhs.maximum, rhs.maximum), 57 ) 58 59 60def absolute_finite_non_zeros_range(values: Iterable[float]) -> Optional[Range]: 61 """Returns the range of the absolute values of the finite non-zeros. 62 63 Args: 64 values: An iterable object of float values. 65 66 Returns: 67 The range of the absolute values of the finite non-zeros, None if no such 68 value is found. 69 """ 70 minimum: Optional[float] = None 71 maximum: Optional[float] = None 72 for v in values: 73 v = abs(v) 74 if math.isinf(v) or v == 0.0: 75 continue 76 if minimum is None: 77 minimum = v 78 maximum = v 79 else: 80 minimum = min(minimum, v) 81 maximum = max(maximum, v) 82 83 assert (maximum is None) == (minimum is None), (minimum, maximum) 84 85 if minimum is None: 86 return None 87 return Range(minimum=minimum, maximum=maximum) 88 89 90@dataclasses.dataclass(frozen=True) 91class ModelRanges: 92 """The ranges of the absolute values of the finite non-zero values in the model. 93 94 Each range is optional since there may be no finite non-zero values 95 (e.g. empty model, empty objective, all variables unbounded, ...). 96 97 Attributes: 98 objective_terms: The linear and quadratic objective terms (not including the 99 offset). 100 variable_bounds: The variables' lower and upper bounds. 101 linear_constraint_bounds: The linear constraints' lower and upper bounds. 102 linear_constraint_coefficients: The coefficients of the variables in linear 103 constraints. 104 """ 105 106 objective_terms: Optional[Range] 107 variable_bounds: Optional[Range] 108 linear_constraint_bounds: Optional[Range] 109 linear_constraint_coefficients: Optional[Range] 110 111 def __str__(self) -> str: 112 """Prints the ranges in scientific format with 2 digits (i.e. 113 114 f'{x:.2e}'). 115 116 It returns a multi-line table list of ranges. The last line does NOT end 117 with a new line. 118 119 Returns: 120 The ranges in multiline string. 121 """ 122 buf = io.StringIO() 123 124 def print_range(prefix: str, value: Optional[Range]) -> None: 125 buf.write(prefix) 126 if value is None: 127 buf.write("no finite values") 128 return 129 # Numbers are printed in scientific notation with a precision of 2. Since 130 # they are expected to be positive we can ignore the optional leading 131 # minus sign. We thus expects `d.dde[+-]dd(d)?` (the exponent is at least 132 # 2 digits but double can require 3 digits, with max +308 and min 133 # -308). Thus we can use a width of 9 to align the ranges properly. 134 buf.write(f"[{value.minimum:<9.2e}, {value.maximum:<9.2e}]") 135 136 print_range("Objective terms : ", self.objective_terms) 137 print_range("\nVariable bounds : ", self.variable_bounds) 138 print_range("\nLinear constraints bounds : ", self.linear_constraint_bounds) 139 print_range( 140 "\nLinear constraints coeffs : ", self.linear_constraint_coefficients 141 ) 142 return buf.getvalue() 143 144 145def compute_model_ranges(mdl: model.Model) -> ModelRanges: 146 """Returns the ranges of the finite non-zero values in the given model. 147 148 Args: 149 mdl: The input model. 150 151 Returns: 152 The ranges of the finite non-zero values in the model. 153 """ 154 return ModelRanges( 155 objective_terms=absolute_finite_non_zeros_range( 156 term.coefficient for term in mdl.objective.linear_terms() 157 ), 158 variable_bounds=merge_optional_ranges( 159 absolute_finite_non_zeros_range(v.lower_bound for v in mdl.variables()), 160 absolute_finite_non_zeros_range(v.upper_bound for v in mdl.variables()), 161 ), 162 linear_constraint_bounds=merge_optional_ranges( 163 absolute_finite_non_zeros_range( 164 c.lower_bound for c in mdl.linear_constraints() 165 ), 166 absolute_finite_non_zeros_range( 167 c.upper_bound for c in mdl.linear_constraints() 168 ), 169 ), 170 linear_constraint_coefficients=absolute_finite_non_zeros_range( 171 e.coefficient for e in mdl.linear_constraint_matrix_entries() 172 ), 173 )
@dataclasses.dataclass(frozen=True)
class
Range:
26@dataclasses.dataclass(frozen=True) 27class Range: 28 """A close range of values [min, max]. 29 30 Attributes: 31 minimum: The minimum value. 32 maximum: The maximum value. 33 """ 34 35 minimum: float 36 maximum: float
A close range of values [min, max].
Attributes:
- minimum: The minimum value.
- maximum: The maximum value.
39def merge_optional_ranges( 40 lhs: Optional[Range], rhs: Optional[Range] 41) -> Optional[Range]: 42 """Merges the two optional ranges. 43 44 Args: 45 lhs: The left hand side range. 46 rhs: The right hand side range. 47 48 Returns: 49 A merged range (None if both lhs and rhs are None). 50 """ 51 if lhs is None: 52 return rhs 53 if rhs is None: 54 return lhs 55 return Range( 56 minimum=min(lhs.minimum, rhs.minimum), 57 maximum=max(lhs.maximum, rhs.maximum), 58 )
Merges the two optional ranges.
Arguments:
- lhs: The left hand side range.
- rhs: The right hand side range.
Returns:
A merged range (None if both lhs and rhs are None).
61def absolute_finite_non_zeros_range(values: Iterable[float]) -> Optional[Range]: 62 """Returns the range of the absolute values of the finite non-zeros. 63 64 Args: 65 values: An iterable object of float values. 66 67 Returns: 68 The range of the absolute values of the finite non-zeros, None if no such 69 value is found. 70 """ 71 minimum: Optional[float] = None 72 maximum: Optional[float] = None 73 for v in values: 74 v = abs(v) 75 if math.isinf(v) or v == 0.0: 76 continue 77 if minimum is None: 78 minimum = v 79 maximum = v 80 else: 81 minimum = min(minimum, v) 82 maximum = max(maximum, v) 83 84 assert (maximum is None) == (minimum is None), (minimum, maximum) 85 86 if minimum is None: 87 return None 88 return Range(minimum=minimum, maximum=maximum)
Returns the range of the absolute values of the finite non-zeros.
Arguments:
- values: An iterable object of float values.
Returns:
The range of the absolute values of the finite non-zeros, None if no such value is found.
@dataclasses.dataclass(frozen=True)
class
ModelRanges:
91@dataclasses.dataclass(frozen=True) 92class ModelRanges: 93 """The ranges of the absolute values of the finite non-zero values in the model. 94 95 Each range is optional since there may be no finite non-zero values 96 (e.g. empty model, empty objective, all variables unbounded, ...). 97 98 Attributes: 99 objective_terms: The linear and quadratic objective terms (not including the 100 offset). 101 variable_bounds: The variables' lower and upper bounds. 102 linear_constraint_bounds: The linear constraints' lower and upper bounds. 103 linear_constraint_coefficients: The coefficients of the variables in linear 104 constraints. 105 """ 106 107 objective_terms: Optional[Range] 108 variable_bounds: Optional[Range] 109 linear_constraint_bounds: Optional[Range] 110 linear_constraint_coefficients: Optional[Range] 111 112 def __str__(self) -> str: 113 """Prints the ranges in scientific format with 2 digits (i.e. 114 115 f'{x:.2e}'). 116 117 It returns a multi-line table list of ranges. The last line does NOT end 118 with a new line. 119 120 Returns: 121 The ranges in multiline string. 122 """ 123 buf = io.StringIO() 124 125 def print_range(prefix: str, value: Optional[Range]) -> None: 126 buf.write(prefix) 127 if value is None: 128 buf.write("no finite values") 129 return 130 # Numbers are printed in scientific notation with a precision of 2. Since 131 # they are expected to be positive we can ignore the optional leading 132 # minus sign. We thus expects `d.dde[+-]dd(d)?` (the exponent is at least 133 # 2 digits but double can require 3 digits, with max +308 and min 134 # -308). Thus we can use a width of 9 to align the ranges properly. 135 buf.write(f"[{value.minimum:<9.2e}, {value.maximum:<9.2e}]") 136 137 print_range("Objective terms : ", self.objective_terms) 138 print_range("\nVariable bounds : ", self.variable_bounds) 139 print_range("\nLinear constraints bounds : ", self.linear_constraint_bounds) 140 print_range( 141 "\nLinear constraints coeffs : ", self.linear_constraint_coefficients 142 ) 143 return buf.getvalue()
The ranges of the absolute values of the finite non-zero values in the model.
Each range is optional since there may be no finite non-zero values (e.g. empty model, empty objective, all variables unbounded, ...).
Attributes:
- objective_terms: The linear and quadratic objective terms (not including the offset).
- variable_bounds: The variables' lower and upper bounds.
- linear_constraint_bounds: The linear constraints' lower and upper bounds.
- linear_constraint_coefficients: The coefficients of the variables in linear constraints.
ModelRanges( objective_terms: Range | None, variable_bounds: Range | None, linear_constraint_bounds: Range | None, linear_constraint_coefficients: Range | None)
objective_terms: Range | None
variable_bounds: Range | None
linear_constraint_bounds: Range | None
linear_constraint_coefficients: Range | None
146def compute_model_ranges(mdl: model.Model) -> ModelRanges: 147 """Returns the ranges of the finite non-zero values in the given model. 148 149 Args: 150 mdl: The input model. 151 152 Returns: 153 The ranges of the finite non-zero values in the model. 154 """ 155 return ModelRanges( 156 objective_terms=absolute_finite_non_zeros_range( 157 term.coefficient for term in mdl.objective.linear_terms() 158 ), 159 variable_bounds=merge_optional_ranges( 160 absolute_finite_non_zeros_range(v.lower_bound for v in mdl.variables()), 161 absolute_finite_non_zeros_range(v.upper_bound for v in mdl.variables()), 162 ), 163 linear_constraint_bounds=merge_optional_ranges( 164 absolute_finite_non_zeros_range( 165 c.lower_bound for c in mdl.linear_constraints() 166 ), 167 absolute_finite_non_zeros_range( 168 c.upper_bound for c in mdl.linear_constraints() 169 ), 170 ), 171 linear_constraint_coefficients=absolute_finite_non_zeros_range( 172 e.coefficient for e in mdl.linear_constraint_matrix_entries() 173 ), 174 )
Returns the ranges of the finite non-zero values in the given model.
Arguments:
- mdl: The input model.
Returns:
The ranges of the finite non-zero values in the model.