Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
variables_info.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <cstdlib>
17#include <utility>
18
19#include "absl/log/check.h"
22
23namespace operations_research {
24namespace glop {
25
27 : matrix_(matrix) {}
28
30 const DenseRow& new_lower_bounds, const DenseRow& new_upper_bounds) {
31 const ColIndex num_cols = matrix_.num_cols();
32 DCHECK_EQ(num_cols, new_lower_bounds.size());
33 DCHECK_EQ(num_cols, new_upper_bounds.size());
34
35 // Optim if nothing changed.
36 if (lower_bounds_ == new_lower_bounds && upper_bounds_ == new_upper_bounds) {
37 return true;
38 }
39
40 lower_bounds_ = new_lower_bounds;
41 upper_bounds_ = new_upper_bounds;
42 variable_type_.resize(num_cols, VariableType::UNCONSTRAINED);
43 for (ColIndex col(0); col < num_cols; ++col) {
44 variable_type_[col] = ComputeVariableType(col);
45 }
46 return false;
47}
48
50 const ColIndex num_cols = matrix_.num_cols();
51 DCHECK_EQ(num_cols, lower_bounds_.size());
52 DCHECK_EQ(num_cols, upper_bounds_.size());
53 variable_type_.resize(num_cols, VariableType::UNCONSTRAINED);
54 for (ColIndex col(0); col < num_cols; ++col) {
55 variable_type_[col] = ComputeVariableType(col);
56 }
57}
58
60 const DenseRow& variable_lower_bounds,
61 const DenseRow& variable_upper_bounds,
62 const DenseColumn& constraint_lower_bounds,
63 const DenseColumn& constraint_upper_bounds) {
64 const ColIndex num_cols = matrix_.num_cols();
65 const ColIndex num_variables = variable_upper_bounds.size();
66 const RowIndex num_rows = constraint_lower_bounds.size();
67
68 bool is_unchanged = (num_cols == lower_bounds_.size());
69 DCHECK_EQ(num_cols, num_variables + RowToColIndex(num_rows));
70 lower_bounds_.resize(num_cols, 0.0);
71 upper_bounds_.resize(num_cols, 0.0);
72 variable_type_.resize(num_cols, VariableType::FIXED_VARIABLE);
73
74 // Copy bounds of the variables.
75 for (ColIndex col(0); col < num_variables; ++col) {
76 if (lower_bounds_[col] != variable_lower_bounds[col] ||
77 upper_bounds_[col] != variable_upper_bounds[col]) {
78 lower_bounds_[col] = variable_lower_bounds[col];
79 upper_bounds_[col] = variable_upper_bounds[col];
80 is_unchanged = false;
81 variable_type_[col] = ComputeVariableType(col);
82 }
83 }
84
85 // Copy bounds of the slack.
86 for (RowIndex row(0); row < num_rows; ++row) {
87 const ColIndex col = num_variables + RowToColIndex(row);
88 if (lower_bounds_[col] != -constraint_upper_bounds[row] ||
89 upper_bounds_[col] != -constraint_lower_bounds[row]) {
90 lower_bounds_[col] = -constraint_upper_bounds[row];
91 upper_bounds_[col] = -constraint_lower_bounds[row];
92 is_unchanged = false;
93 variable_type_[col] = ComputeVariableType(col);
94 }
95 }
96
97 return is_unchanged;
98}
99
100void VariablesInfo::ResetStatusInfo() {
101 const ColIndex num_cols = matrix_.num_cols();
102 DCHECK_EQ(num_cols, lower_bounds_.size());
103 DCHECK_EQ(num_cols, upper_bounds_.size());
104
105 // TODO(user): These could just be Resized() but there is a bug with the
106 // iteration and resize it seems. Investigate. I suspect the last bucket
107 // is not cleared so you can still iterate on the ones there even if it all
108 // positions before num_cols are set to zero.
109 variable_status_.resize(num_cols, VariableStatus::FREE);
110 can_increase_.ClearAndResize(num_cols);
111 can_decrease_.ClearAndResize(num_cols);
112 is_basic_.ClearAndResize(num_cols);
113 not_basic_.ClearAndResize(num_cols);
114 non_basic_boxed_variables_.ClearAndResize(num_cols);
115
116 // This one cannot just be resized.
117 boxed_variables_are_relevant_ = true;
118 num_entries_in_relevant_columns_ = 0;
119 relevance_.ClearAndResize(num_cols);
120}
121
122void VariablesInfo::InitializeFromBasisState(ColIndex first_slack_col,
123 ColIndex num_new_cols,
124 const BasisState& state) {
125 ResetStatusInfo();
126
127 const ColIndex num_cols = lower_bounds_.size();
128 DCHECK_LE(num_new_cols, first_slack_col);
129 const ColIndex first_new_col(first_slack_col - num_new_cols);
130
131 // Compute the status for all the columns (note that the slack variables are
132 // already added at the end of the matrix at this stage).
133 const int state_size = state.statuses.size().value();
134 for (ColIndex col(0); col < num_cols; ++col) {
135 // Start with the given "warm" status from the BasisState if it exists.
136 VariableStatus status;
137 if (col < first_new_col && col < state_size) {
138 status = state.statuses[col];
139 } else if (col >= first_slack_col && col - num_new_cols < state_size) {
140 status = state.statuses[col - num_new_cols];
141 } else {
142 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
143 continue;
144 }
145
146 // Remove incompatibilities between the warm status and the current state.
147 switch (status) {
149 // Because we just called ResetStatusInfo(), we optimize the call to
150 // UpdateToNonBasicStatus(col) here. In an incremental setting with
151 // almost no work per call, the update of all the DenseBitRow are
152 // visible.
153 variable_status_[col] = VariableStatus::BASIC;
154 is_basic_.Set(col, true);
155 break;
157 if (lower_bounds_[col] == upper_bounds_[col]) {
159 } else {
160 UpdateToNonBasicStatus(col, lower_bounds_[col] == -kInfinity
161 ? DefaultVariableStatus(col)
162 : status);
163 }
164 break;
166 if (lower_bounds_[col] == upper_bounds_[col]) {
168 } else {
169 UpdateToNonBasicStatus(col, upper_bounds_[col] == kInfinity
170 ? DefaultVariableStatus(col)
171 : status);
172 }
173 break;
174 default:
175 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
176 }
177 }
178}
179
181 const RowToColMapping& basis) {
182 const ColIndex num_cols = lower_bounds_.size();
183 is_basic_.ClearAndResize(num_cols);
184 for (const ColIndex col : basis) {
186 }
187 int num_no_longer_in_basis = 0;
188 for (ColIndex col(0); col < num_cols; ++col) {
189 if (!is_basic_[col] && variable_status_[col] == VariableStatus::BASIC) {
190 ++num_no_longer_in_basis;
191 if (variable_type_[col] == VariableType::FIXED_VARIABLE) {
193 } else {
195 }
196 }
197 }
198 return num_no_longer_in_basis;
199}
200
202 const DenseRow& starting_values) {
203 int num_changes = 0;
204 const ColIndex num_cols = lower_bounds_.size();
205 for (ColIndex col(0); col < num_cols; ++col) {
206 if (variable_status_[col] != VariableStatus::FREE) continue;
207 if (variable_type_[col] == VariableType::UNCONSTRAINED) continue;
208 const Fractional value =
209 col < starting_values.size() ? starting_values[col] : 0.0;
210 const Fractional diff_ub = upper_bounds_[col] - value;
211 const Fractional diff_lb = value - lower_bounds_[col];
212 if (diff_lb <= diff_ub) {
213 if (diff_lb <= distance) {
214 ++num_changes;
216 }
217 } else {
218 if (diff_ub <= distance) {
219 ++num_changes;
221 }
222 }
223 }
224 return num_changes;
225}
226
228 ResetStatusInfo();
229 const ColIndex num_cols = lower_bounds_.size();
230 for (ColIndex col(0); col < num_cols; ++col) {
231 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
232 }
233}
234
235VariableStatus VariablesInfo::DefaultVariableStatus(ColIndex col) const {
236 DCHECK_GE(col, 0);
237 DCHECK_LT(col, lower_bounds_.size());
238 if (lower_bounds_[col] == upper_bounds_[col]) {
240 }
241 if (lower_bounds_[col] == -kInfinity && upper_bounds_[col] == kInfinity) {
243 }
244
245 // Returns the bound with the lowest magnitude. Note that it must be finite
246 // because the VariableStatus::FREE case was tested earlier.
247 DCHECK(IsFinite(lower_bounds_[col]) || IsFinite(upper_bounds_[col]));
248 return std::abs(lower_bounds_[col]) <= std::abs(upper_bounds_[col])
251}
252
254 if (value == boxed_variables_are_relevant_) return;
255 boxed_variables_are_relevant_ = value;
256 if (value) {
257 for (const ColIndex col : non_basic_boxed_variables_) {
258 SetRelevance(col, variable_type_[col] != VariableType::FIXED_VARIABLE);
259 }
260 } else {
261 for (const ColIndex col : non_basic_boxed_variables_) {
262 SetRelevance(col, false);
263 }
264 }
265}
266
268 if (in_dual_phase_one_) {
269 // TODO(user): A bit annoying that we need to test this even if we
270 // don't use the dual. But the cost is minimal.
271 if (lower_bounds_[col] != 0.0) lower_bounds_[col] = -kInfinity;
272 if (upper_bounds_[col] != 0.0) upper_bounds_[col] = +kInfinity;
273 variable_type_[col] = ComputeVariableType(col);
274 }
275 variable_status_[col] = VariableStatus::BASIC;
276 is_basic_.Set(col, true);
277 not_basic_.Set(col, false);
278 can_increase_.Set(col, false);
279 can_decrease_.Set(col, false);
280 non_basic_boxed_variables_.Set(col, false);
281 SetRelevance(col, false);
282}
283
285 VariableStatus status) {
286 DCHECK_NE(status, VariableStatus::BASIC);
287 variable_status_[col] = status;
288 is_basic_.Set(col, false);
289 not_basic_.Set(col, true);
290 can_increase_.Set(col, status == VariableStatus::AT_LOWER_BOUND ||
291 status == VariableStatus::FREE);
292 can_decrease_.Set(col, status == VariableStatus::AT_UPPER_BOUND ||
293 status == VariableStatus::FREE);
294
295 const bool boxed =
296 variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED;
297 non_basic_boxed_variables_.Set(col, boxed);
298 const bool relevance = status != VariableStatus::FIXED_VALUE &&
299 (boxed_variables_are_relevant_ || !boxed);
300 SetRelevance(col, relevance);
301}
302
304 return variable_type_;
305}
306
308 return variable_status_;
309}
310
312 return can_increase_;
313}
314
316 return can_decrease_;
317}
318
320 return relevance_;
321}
322
323const DenseBitRow& VariablesInfo::GetIsBasicBitRow() const { return is_basic_; }
324
326 return not_basic_;
327}
328
330 return non_basic_boxed_variables_;
331}
332
334 return num_entries_in_relevant_columns_;
335}
336
337VariableType VariablesInfo::ComputeVariableType(ColIndex col) const {
338 DCHECK_LE(lower_bounds_[col], upper_bounds_[col]);
339 if (lower_bounds_[col] == -kInfinity) {
340 if (upper_bounds_[col] == kInfinity) {
342 }
344 } else if (upper_bounds_[col] == kInfinity) {
346 } else if (lower_bounds_[col] == upper_bounds_[col]) {
348 } else {
350 }
351}
352
353void VariablesInfo::SetRelevance(ColIndex col, bool relevance) {
354 if (relevance_.IsSet(col) == relevance) return;
355 if (relevance) {
356 relevance_.Set(col);
357 num_entries_in_relevant_columns_ += matrix_.ColumnNumEntries(col);
358 } else {
359 relevance_.Clear(col);
360 num_entries_in_relevant_columns_ -= matrix_.ColumnNumEntries(col);
361 }
362}
363
364// This is really similar to InitializeFromBasisState() but there is less
365// cases to consider for TransformToDualPhaseIProblem()/EndDualPhaseI().
366void VariablesInfo::UpdateStatusForNewType(ColIndex col) {
367 switch (variable_status_[col]) {
370 break;
372 if (lower_bounds_[col] == upper_bounds_[col]) {
374 } else if (lower_bounds_[col] == -kInfinity) {
375 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
376 } else {
377 // TODO(user): This is only needed for boxed variable to update their
378 // relevance. It should probably be done with the type and not the
379 // status update.
380 UpdateToNonBasicStatus(col, variable_status_[col]);
381 }
382 break;
384 if (lower_bounds_[col] == upper_bounds_[col]) {
386 } else if (upper_bounds_[col] == kInfinity) {
387 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
388 } else {
389 // TODO(user): Same as in the AT_LOWER_BOUND branch above.
390 UpdateToNonBasicStatus(col, variable_status_[col]);
391 }
392 break;
393 default:
394 // TODO(user): boxed variable that become fixed in
395 // TransformToDualPhaseIProblem() will be changed status twice. Once here,
396 // and once when we make them dual feasible according to their reduced
397 // cost. We should probably just do all at once.
398 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
399 }
400}
401
403 Fractional dual_feasibility_tolerance, DenseRow::ConstView reduced_costs) {
404 DCHECK(!in_dual_phase_one_);
405 in_dual_phase_one_ = true;
406 saved_lower_bounds_ = lower_bounds_;
407 saved_upper_bounds_ = upper_bounds_;
408
409 // Transform the bound and type to get a new problem. If this problem has an
410 // optimal value of 0.0, then the problem is dual feasible. And more
411 // importantly, by keeping the same basis, we have a feasible solution of the
412 // original problem.
413 const ColIndex num_cols = matrix_.num_cols();
414 for (ColIndex col(0); col < num_cols; ++col) {
415 switch (variable_type_[col]) {
416 case VariableType::FIXED_VARIABLE: // ABSL_FALLTHROUGH_INTENDED
418 lower_bounds_[col] = 0.0;
419 upper_bounds_[col] = 0.0;
420 variable_type_[col] = VariableType::FIXED_VARIABLE;
421 break;
423 lower_bounds_[col] = 0.0;
424 upper_bounds_[col] = 1.0;
425 variable_type_[col] = VariableType::UPPER_AND_LOWER_BOUNDED;
426 break;
428 lower_bounds_[col] = -1.0;
429 upper_bounds_[col] = 0.0;
430 variable_type_[col] = VariableType::UPPER_AND_LOWER_BOUNDED;
431 break;
433 lower_bounds_[col] = -1000.0;
434 upper_bounds_[col] = 1000.0;
435 variable_type_[col] = VariableType::UPPER_AND_LOWER_BOUNDED;
436 break;
437 }
438
439 // Make sure we start with a feasible dual solution.
440 // If the reduced cost is close to zero, we keep the "default" status.
441 if (variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) {
442 if (reduced_costs[col] > dual_feasibility_tolerance) {
443 variable_status_[col] = VariableStatus::AT_LOWER_BOUND;
444 } else if (reduced_costs[col] < -dual_feasibility_tolerance) {
445 variable_status_[col] = VariableStatus::AT_UPPER_BOUND;
446 }
447 }
448
449 UpdateStatusForNewType(col);
450 }
451}
452
453void VariablesInfo::EndDualPhaseI(Fractional dual_feasibility_tolerance,
454 DenseRow::ConstView reduced_costs) {
455 DCHECK(in_dual_phase_one_);
456 in_dual_phase_one_ = false;
457 std::swap(saved_lower_bounds_, lower_bounds_);
458 std::swap(saved_upper_bounds_, upper_bounds_);
459
460 // This is to clear the memory of the saved bounds since it is no longer
461 // needed.
462 DenseRow empty1, empty2;
463 std::swap(empty1, saved_lower_bounds_);
464 std::swap(empty1, saved_upper_bounds_);
465
466 // Restore the type and update all other fields.
467 const ColIndex num_cols = matrix_.num_cols();
468 for (ColIndex col(0); col < num_cols; ++col) {
469 variable_type_[col] = ComputeVariableType(col);
470
471 // We make sure that the old fixed variables that are now boxed are dual
472 // feasible.
473 //
474 // TODO(user): When there is a choice, use the previous status that might
475 // have been warm-started ? but then this is not high priority since
476 // warm-starting with a non-dual feasible basis seems unfrequent.
477 if (variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) {
478 if (reduced_costs[col] > dual_feasibility_tolerance) {
479 variable_status_[col] = VariableStatus::AT_LOWER_BOUND;
480 } else if (reduced_costs[col] < -dual_feasibility_tolerance) {
481 variable_status_[col] = VariableStatus::AT_UPPER_BOUND;
482 }
483 }
484
485 UpdateStatusForNewType(col);
486 }
487}
488
489} // namespace glop
490} // namespace operations_research
void ClearAndResize(IndexType size)
Changes the number of bits the Bitset64 can hold and set all of them to 0.
Definition bitset.h:489
StrictITISpan< ColIndex, const Fractional > ConstView
Definition lp_types.h:291
void TransformToDualPhaseIProblem(Fractional dual_feasibility_tolerance, DenseRow::ConstView reduced_costs)
const DenseBitRow & GetCanDecreaseBitRow() const
const DenseBitRow & GetCanIncreaseBitRow() const
const VariableStatusRow & GetStatusRow() const
const VariableTypeRow & GetTypeRow() const
const DenseBitRow & GetNotBasicBitRow() const
const DenseBitRow & GetIsRelevantBitRow() const
int ChangeUnusedBasicVariablesToFree(const RowToColMapping &basis)
const DenseBitRow & GetNonBasicBoxedVariables() const
void UpdateToNonBasicStatus(ColIndex col, VariableStatus status)
void EndDualPhaseI(Fractional dual_feasibility_tolerance, DenseRow::ConstView reduced_costs)
bool LoadBoundsAndReturnTrueIfUnchanged(const DenseRow &new_lower_bounds, const DenseRow &new_upper_bounds)
void InitializeFromBasisState(ColIndex first_slack, ColIndex num_new_cols, const BasisState &state)
const DenseBitRow & GetIsBasicBitRow() const
int SnapFreeVariablesToBound(Fractional distance, const DenseRow &starting_values)
VariablesInfo(const CompactSparseMatrix &matrix)
Takes references to the linear program data we need.
constexpr double kInfinity
Infinity for type Fractional.
Definition lp_types.h:87
StrictITIVector< RowIndex, ColIndex > RowToColMapping
Definition lp_types.h:394
Bitset64< ColIndex > DenseBitRow
Row of bits.
Definition lp_types.h:375
ColIndex RowToColIndex(RowIndex row)
Get the ColIndex corresponding to the column # row.
Definition lp_types.h:54
bool IsFinite(Fractional value)
Definition lp_types.h:94
VariableType
Different types of variables.
Definition lp_types.h:178
StrictITIVector< ColIndex, VariableType > VariableTypeRow
Row of variable types.
Definition lp_types.h:369
StrictITIVector< ColIndex, VariableStatus > VariableStatusRow
Row of variable statuses.
Definition lp_types.h:372
StrictITIVector< RowIndex, Fractional > DenseColumn
Column-vector types. Column-vector types are indexed by a row index.
Definition lp_types.h:380
StrictITIVector< ColIndex, Fractional > DenseRow
Row-vector types. Row-vector types are indexed by a column index.
Definition lp_types.h:351
In SWIG mode, we don't want anything besides these top-level includes.
static constexpr double kInfinity