Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
primal_edge_norms.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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
15
16#include <algorithm>
17#include <cstdlib>
18
19#include "absl/log/check.h"
22#include "ortools/glop/parameters.pb.h"
29#include "ortools/util/stats.h"
30
31namespace operations_research {
32namespace glop {
33
35 const VariablesInfo& variables_info,
36 const BasisFactorization& basis_factorization)
37 : compact_matrix_(compact_matrix),
38 variables_info_(variables_info),
39 basis_factorization_(basis_factorization),
40 stats_(),
41 recompute_edge_squared_norms_(true),
42 reset_devex_weights_(true),
43 edge_squared_norms_(),
44 matrix_column_norms_(),
45 devex_weights_(),
46 direction_left_inverse_(),
47 num_operations_(0) {}
48
50 SCOPED_TIME_STAT(&stats_);
51 matrix_column_norms_.clear();
52 recompute_edge_squared_norms_ = true;
53 reset_devex_weights_ = true;
54 for (bool* watcher : watchers_) *watcher = true;
55}
56
58 if (pricing_rule_ != GlopParameters ::STEEPEST_EDGE) return false;
59 return recompute_edge_squared_norms_;
60}
61
63 switch (pricing_rule_) {
64 case GlopParameters::DANTZIG:
66 case GlopParameters::STEEPEST_EDGE:
68 case GlopParameters::DEVEX:
69 return GetDevexWeights().const_view();
70 }
71}
72
74 if (recompute_edge_squared_norms_) ComputeEdgeSquaredNorms();
75 return edge_squared_norms_;
76}
77
79 if (reset_devex_weights_) ResetDevexWeights();
80 return devex_weights_;
81}
82
84 if (matrix_column_norms_.empty()) ComputeMatrixColumnNorms();
85 return matrix_column_norms_;
86}
87
89 ColIndex entering_col, const ScatteredColumn& direction) {
90 if (!recompute_edge_squared_norms_) {
91 SCOPED_TIME_STAT(&stats_);
92 // Recompute the squared norm of the edge used during this
93 // iteration, i.e. the entering edge.
94 const Fractional old_squared_norm = edge_squared_norms_[entering_col];
95 const Fractional precise_squared_norm = 1.0 + SquaredNorm(direction);
96 edge_squared_norms_[entering_col] = precise_squared_norm;
97
98 const Fractional precise_norm = sqrt(precise_squared_norm);
99 const Fractional estimated_edges_norm_accuracy =
100 (precise_norm - sqrt(old_squared_norm)) / precise_norm;
101 stats_.edges_norm_accuracy.Add(estimated_edges_norm_accuracy);
102 if (std::abs(estimated_edges_norm_accuracy) >
103 parameters_.recompute_edges_norm_threshold()) {
104 VLOG(1) << "Recomputing edge norms: " << sqrt(precise_squared_norm)
105 << " vs " << sqrt(old_squared_norm);
106 recompute_edge_squared_norms_ = true;
107 for (bool* watcher : watchers_) *watcher = true;
108 }
109
110 if (old_squared_norm < 0.25 * precise_squared_norm) {
111 VLOG(1) << "Imprecise norm, reprice. old=" << old_squared_norm
112 << " new=" << precise_squared_norm;
113 return false;
114 }
115 }
116 return true;
117}
118
120 ColIndex leaving_col,
121 RowIndex leaving_row,
122 const ScatteredColumn& direction,
123 UpdateRow* update_row) {
124 SCOPED_TIME_STAT(&stats_);
125 DCHECK_NE(entering_col, leaving_col);
126 if (!recompute_edge_squared_norms_) {
127 update_row->ComputeUpdateRow(leaving_row);
128 ComputeDirectionLeftInverse(entering_col, direction);
129 UpdateEdgeSquaredNorms(entering_col, leaving_col, leaving_row,
130 direction.values, *update_row);
131 }
132 if (!reset_devex_weights_) {
133 // Resets devex weights once in a while. If so, no need to update them
134 // before.
135 ++num_devex_updates_since_reset_;
136 if (num_devex_updates_since_reset_ >
137 parameters_.devex_weights_reset_period()) {
138 reset_devex_weights_ = true;
139 } else {
140 update_row->ComputeUpdateRow(leaving_row);
141 UpdateDevexWeights(entering_col, leaving_col, leaving_row,
142 direction.values, *update_row);
143 }
144 }
145}
146
147void PrimalEdgeNorms::ComputeMatrixColumnNorms() {
148 SCOPED_TIME_STAT(&stats_);
149 matrix_column_norms_.resize(compact_matrix_.num_cols(), 0.0);
150 for (ColIndex col(0); col < compact_matrix_.num_cols(); ++col) {
151 matrix_column_norms_[col] = SquaredNorm(compact_matrix_.column(col));
152 num_operations_ += compact_matrix_.column(col).num_entries().value();
153 }
154}
155
156void PrimalEdgeNorms::ComputeEdgeSquaredNorms() {
157 SCOPED_TIME_STAT(&stats_);
158
159 // Since we will do a lot of inversions, it is better to be as efficient and
160 // precise as possible by refactorizing the basis.
161 DCHECK(basis_factorization_.IsRefactorized());
162 edge_squared_norms_.resize(compact_matrix_.num_cols(), 0.0);
163 for (const ColIndex col : variables_info_.GetIsRelevantBitRow()) {
164 // Note the +1.0 in the squared norm for the component of the edge on the
165 // 'entering_col'.
166 edge_squared_norms_[col] = 1.0 + basis_factorization_.RightSolveSquaredNorm(
167 compact_matrix_.column(col));
168 }
169 recompute_edge_squared_norms_ = false;
170}
171
172// TODO(user): It should be possible to reorganize the code and call this when
173// the value of direction is no longer needed. This will simplify the code and
174// avoid a copy here.
175void PrimalEdgeNorms::ComputeDirectionLeftInverse(
176 ColIndex entering_col, const ScatteredColumn& direction) {
177 SCOPED_TIME_STAT(&stats_);
178
179 // Initialize direction_left_inverse_ to direction. Note the special case when
180 // the non-zero vector is empty which means we don't know and need to use the
181 // dense version.
182 const ColIndex size = RowToColIndex(direction.values.size());
183 const double kThreshold = 0.05 * size.value();
184 if (!direction_left_inverse_.non_zeros.empty() &&
185 (direction_left_inverse_.non_zeros.size() + direction.non_zeros.size() <
186 2 * kThreshold)) {
187 ClearAndResizeVectorWithNonZeros(size, &direction_left_inverse_);
188 for (const auto e : direction) {
189 direction_left_inverse_[RowToColIndex(e.row())] = e.coefficient();
190 }
191 } else {
192 direction_left_inverse_.values = Transpose(direction.values);
193 direction_left_inverse_.non_zeros.clear();
194 }
195
196 if (direction.non_zeros.size() < kThreshold) {
197 direction_left_inverse_.non_zeros = TransposedView(direction).non_zeros;
198 }
199 basis_factorization_.LeftSolve(&direction_left_inverse_);
200
201 // TODO(user): Refactorize if estimated accuracy above a threshold.
202 IF_STATS_ENABLED(stats_.direction_left_inverse_accuracy.Add(
203 compact_matrix_.ColumnScalarProduct(entering_col,
204 direction_left_inverse_.values) -
205 SquaredNorm(direction.values)));
206 IF_STATS_ENABLED(stats_.direction_left_inverse_density.Add(
207 Density(direction_left_inverse_.values)));
208}
209
210// Let new_edge denote the edge of 'col' in the new basis. We want:
211// reduced_costs_[col] = ScalarProduct(new_edge, basic_objective_);
212// edge_squared_norms_[col] = SquaredNorm(new_edge);
213//
214// In order to compute this, we use the formulas:
215// new_leaving_edge = old_entering_edge / divisor.
216// new_edge = old_edge + update_coeff * new_leaving_edge.
217void PrimalEdgeNorms::UpdateEdgeSquaredNorms(ColIndex entering_col,
218 ColIndex leaving_col,
219 RowIndex leaving_row,
220 const DenseColumn& direction,
221 const UpdateRow& update_row) {
222 SCOPED_TIME_STAT(&stats_);
223
224 // 'pivot' is the value of the entering_edge at 'leaving_row'.
225 // The edge of the 'leaving_col' in the new basis is equal to
226 // entering_edge / 'pivot'.
227 const Fractional pivot = -direction[leaving_row];
228 DCHECK_NE(pivot, 0.0);
229
230 // Note that this should be precise because of the call to
231 // TestEnteringEdgeNormPrecision().
232 const Fractional entering_squared_norm = edge_squared_norms_[entering_col];
233 const Fractional leaving_squared_norm =
234 std::max(1.0, entering_squared_norm / Square(pivot));
235
236 int stat_lower_bounded_norms = 0;
237 const Fractional factor = 2.0 / pivot;
238 const auto view = compact_matrix_.view();
239 auto output = edge_squared_norms_.view();
240 const auto direction_left_inverse =
241 direction_left_inverse_.values.const_view();
242 for (const ColIndex col : update_row.GetNonZeroPositions()) {
243 const Fractional coeff = update_row.GetCoefficient(col);
244 const Fractional scalar_product =
245 view.ColumnScalarProduct(col, direction_left_inverse);
246 num_operations_ += view.ColumnNumEntries(col).value();
247
248 // Update the edge squared norm of this column. Note that the update
249 // formula used is important to maximize the precision. See an explanation
250 // in the dual context in Koberstein's PhD thesis, section 8.2.2.1.
251 output[col] +=
252 coeff * (coeff * leaving_squared_norm + factor * scalar_product);
253
254 // Make sure it doesn't go under a known lower bound (TODO(user): ref?).
255 // This way norms are always >= 1.0 .
256 // TODO(user): precompute 1 / Square(pivot) or 1 / pivot? it will be
257 // slightly faster, but may introduce numerical issues. More generally,
258 // this test is only needed in a few cases, so is it worth it?
259 const Fractional lower_bound = 1.0 + Square(coeff / pivot);
260 if (output[col] < lower_bound) {
261 output[col] = lower_bound;
262 ++stat_lower_bounded_norms;
263 }
264 }
265 output[leaving_col] = leaving_squared_norm;
266 stats_.lower_bounded_norms.Add(stat_lower_bounded_norms);
267}
268
269void PrimalEdgeNorms::UpdateDevexWeights(
270 ColIndex entering_col /* index q in the paper */,
271 ColIndex leaving_col /* index p in the paper */, RowIndex leaving_row,
272 const DenseColumn& direction, const UpdateRow& update_row) {
273 SCOPED_TIME_STAT(&stats_);
274
275 // Compared to steepest edge update, the DEVEX weight uses the largest of the
276 // norms of two vectors to approximate the norm of the sum.
277 const Fractional entering_norm = sqrt(PreciseSquaredNorm(direction));
278 const Fractional pivot_magnitude = std::abs(direction[leaving_row]);
279 const Fractional leaving_norm =
280 std::max(1.0, entering_norm / pivot_magnitude);
281 for (const ColIndex col : update_row.GetNonZeroPositions()) {
282 const Fractional coeff = update_row.GetCoefficient(col);
283 const Fractional update_vector_norm = std::abs(coeff) * leaving_norm;
284 devex_weights_[col] =
285 std::max(devex_weights_[col], Square(update_vector_norm));
286 }
287 devex_weights_[leaving_col] = Square(leaving_norm);
288}
289
290void PrimalEdgeNorms::ResetDevexWeights() {
291 SCOPED_TIME_STAT(&stats_);
292 if (parameters_.initialize_devex_with_column_norms()) {
293 devex_weights_ = GetMatrixColumnNorms();
294 } else {
295 devex_weights_.assign(compact_matrix_.num_cols(), 1.0);
296 }
297 num_devex_updates_since_reset_ = 0;
298 reset_devex_weights_ = false;
299}
300
301} // namespace glop
302} // namespace operations_research
IntegerValue size
bool IsRefactorized() const
Returns true if the factorization was just recomputed.
Fractional RightSolveSquaredNorm(const ColumnView &a) const
void LeftSolve(ScatteredRow *y) const
Left solves the system y.B = rhs, where y initially contains rhs.
Fractional ColumnScalarProduct(ColIndex col, const DenseRow &vector) const
Definition sparse.h:434
ColumnView column(ColIndex col) const
Definition sparse.h:416
void UpdateBeforeBasisPivot(ColIndex entering_col, ColIndex leaving_col, RowIndex leaving_row, const ScatteredColumn &direction, UpdateRow *update_row)
bool TestEnteringEdgeNormPrecision(ColIndex entering_col, const ScatteredColumn &direction)
PrimalEdgeNorms(const CompactSparseMatrix &compact_matrix, const VariablesInfo &variables_info, const BasisFactorization &basis_factorization)
void assign(IntType size, const T &v)
Definition lp_types.h:319
void ComputeUpdateRow(RowIndex leaving_row)
Definition update_row.cc:87
const DenseBitRow & GetIsRelevantBitRow() const
double lower_bound
ColIndex col
Definition markowitz.cc:187
double Density(const DenseRow &row)
Definition lp_utils.cc:176
Fractional Square(Fractional f)
Definition lp_utils.h:41
const DenseRow & Transpose(const DenseColumn &col)
Similar comment as the other Transpose() implementation above.
Definition lp_utils.h:199
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
Definition lp_types.h:54
void ClearAndResizeVectorWithNonZeros(IndexType size, ScatteredRowOrCol *v)
Sets a dense vector for which the non zeros are known to be non_zeros.
Definition lp_utils.h:285
Fractional PreciseSquaredNorm(const SparseColumn &v)
Definition lp_utils.cc:44
Fractional SquaredNorm(const SparseColumn &v)
Definition lp_utils.cc:36
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:382
const ScatteredRow & TransposedView(const ScatteredColumn &c)
In SWIG mode, we don't want anything besides these top-level includes.
#define IF_STATS_ENABLED(instructions)
Definition stats.h:417
#define SCOPED_TIME_STAT(stats)
Definition stats.h:418
StrictITIVector< Index, Fractional > values