Google OR-Tools v9.11
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-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 <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 DenseRow& variable_lower_bounds,
51 const DenseRow& variable_upper_bounds,
52 const DenseColumn& constraint_lower_bounds,
53 const DenseColumn& constraint_upper_bounds) {
54 const ColIndex num_cols = matrix_.num_cols();
55 const ColIndex num_variables = variable_upper_bounds.size();
56 const RowIndex num_rows = constraint_lower_bounds.size();
57
58 bool is_unchanged = (num_cols == lower_bounds_.size());
59 DCHECK_EQ(num_cols, num_variables + RowToColIndex(num_rows));
60 lower_bounds_.resize(num_cols, 0.0);
61 upper_bounds_.resize(num_cols, 0.0);
62 variable_type_.resize(num_cols, VariableType::FIXED_VARIABLE);
63
64 // Copy bounds of the variables.
65 for (ColIndex col(0); col < num_variables; ++col) {
66 if (lower_bounds_[col] != variable_lower_bounds[col] ||
67 upper_bounds_[col] != variable_upper_bounds[col]) {
68 lower_bounds_[col] = variable_lower_bounds[col];
69 upper_bounds_[col] = variable_upper_bounds[col];
70 is_unchanged = false;
71 variable_type_[col] = ComputeVariableType(col);
72 }
73 }
74
75 // Copy bounds of the slack.
76 for (RowIndex row(0); row < num_rows; ++row) {
77 const ColIndex col = num_variables + RowToColIndex(row);
78 if (lower_bounds_[col] != -constraint_upper_bounds[row] ||
79 upper_bounds_[col] != -constraint_lower_bounds[row]) {
80 lower_bounds_[col] = -constraint_upper_bounds[row];
81 upper_bounds_[col] = -constraint_lower_bounds[row];
82 is_unchanged = false;
83 variable_type_[col] = ComputeVariableType(col);
84 }
85 }
86
87 return is_unchanged;
88}
89
90void VariablesInfo::ResetStatusInfo() {
91 const ColIndex num_cols = matrix_.num_cols();
92 DCHECK_EQ(num_cols, lower_bounds_.size());
93 DCHECK_EQ(num_cols, upper_bounds_.size());
94
95 // TODO(user): These could just be Resized() but there is a bug with the
96 // iteration and resize it seems. Investigate. I suspect the last bucket
97 // is not cleared so you can still iterate on the ones there even if it all
98 // positions before num_cols are set to zero.
99 variable_status_.resize(num_cols, VariableStatus::FREE);
100 can_increase_.ClearAndResize(num_cols);
101 can_decrease_.ClearAndResize(num_cols);
102 is_basic_.ClearAndResize(num_cols);
103 not_basic_.ClearAndResize(num_cols);
104 non_basic_boxed_variables_.ClearAndResize(num_cols);
105
106 // This one cannot just be resized.
107 boxed_variables_are_relevant_ = true;
108 num_entries_in_relevant_columns_ = 0;
109 relevance_.ClearAndResize(num_cols);
110}
111
112void VariablesInfo::InitializeFromBasisState(ColIndex first_slack_col,
113 ColIndex num_new_cols,
114 const BasisState& state) {
115 ResetStatusInfo();
116
117 const ColIndex num_cols = lower_bounds_.size();
118 DCHECK_LE(num_new_cols, first_slack_col);
119 const ColIndex first_new_col(first_slack_col - num_new_cols);
120
121 // Compute the status for all the columns (note that the slack variables are
122 // already added at the end of the matrix at this stage).
123 for (ColIndex col(0); col < num_cols; ++col) {
124 // Start with the given "warm" status from the BasisState if it exists.
126 if (col < first_new_col && col < state.statuses.size()) {
127 status = state.statuses[col];
128 } else if (col >= first_slack_col &&
129 col - num_new_cols < state.statuses.size()) {
130 status = state.statuses[col - num_new_cols];
131 } else {
132 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
133 continue;
134 }
135
136 // Remove incompatibilities between the warm status and the current state.
137 switch (status) {
139 // Because we just called ResetStatusInfo(), we optimize the call to
140 // UpdateToNonBasicStatus(col) here. In an incremental setting with
141 // almost no work per call, the update of all the DenseBitRow are
142 // visible.
143 variable_status_[col] = VariableStatus::BASIC;
144 is_basic_.Set(col, true);
145 break;
147 if (lower_bounds_[col] == upper_bounds_[col]) {
149 } else {
150 UpdateToNonBasicStatus(col, lower_bounds_[col] == -kInfinity
151 ? DefaultVariableStatus(col)
152 : status);
153 }
154 break;
156 if (lower_bounds_[col] == upper_bounds_[col]) {
158 } else {
159 UpdateToNonBasicStatus(col, upper_bounds_[col] == kInfinity
160 ? DefaultVariableStatus(col)
161 : status);
162 }
163 break;
164 default:
165 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
166 }
167 }
168}
169
171 const RowToColMapping& basis) {
172 const ColIndex num_cols = lower_bounds_.size();
173 is_basic_.ClearAndResize(num_cols);
174 for (const ColIndex col : basis) {
176 }
177 int num_no_longer_in_basis = 0;
178 for (ColIndex col(0); col < num_cols; ++col) {
179 if (!is_basic_[col] && variable_status_[col] == VariableStatus::BASIC) {
180 ++num_no_longer_in_basis;
181 if (variable_type_[col] == VariableType::FIXED_VARIABLE) {
183 } else {
185 }
186 }
187 }
188 return num_no_longer_in_basis;
189}
190
192 const DenseRow& starting_values) {
193 int num_changes = 0;
194 const ColIndex num_cols = lower_bounds_.size();
195 for (ColIndex col(0); col < num_cols; ++col) {
196 if (variable_status_[col] != VariableStatus::FREE) continue;
197 if (variable_type_[col] == VariableType::UNCONSTRAINED) continue;
198 const Fractional value =
199 col < starting_values.size() ? starting_values[col] : 0.0;
200 const Fractional diff_ub = upper_bounds_[col] - value;
201 const Fractional diff_lb = value - lower_bounds_[col];
202 if (diff_lb <= diff_ub) {
203 if (diff_lb <= distance) {
204 ++num_changes;
206 }
207 } else {
208 if (diff_ub <= distance) {
209 ++num_changes;
211 }
212 }
213 }
214 return num_changes;
215}
216
218 ResetStatusInfo();
219 const ColIndex num_cols = lower_bounds_.size();
220 for (ColIndex col(0); col < num_cols; ++col) {
221 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
222 }
223}
224
225VariableStatus VariablesInfo::DefaultVariableStatus(ColIndex col) const {
226 DCHECK_GE(col, 0);
227 DCHECK_LT(col, lower_bounds_.size());
228 if (lower_bounds_[col] == upper_bounds_[col]) {
230 }
231 if (lower_bounds_[col] == -kInfinity && upper_bounds_[col] == kInfinity) {
233 }
234
235 // Returns the bound with the lowest magnitude. Note that it must be finite
236 // because the VariableStatus::FREE case was tested earlier.
237 DCHECK(IsFinite(lower_bounds_[col]) || IsFinite(upper_bounds_[col]));
238 return std::abs(lower_bounds_[col]) <= std::abs(upper_bounds_[col])
241}
242
244 if (value == boxed_variables_are_relevant_) return;
245 boxed_variables_are_relevant_ = value;
246 if (value) {
247 for (const ColIndex col : non_basic_boxed_variables_) {
248 SetRelevance(col, variable_type_[col] != VariableType::FIXED_VARIABLE);
249 }
250 } else {
251 for (const ColIndex col : non_basic_boxed_variables_) {
252 SetRelevance(col, false);
253 }
254 }
255}
256
258 if (in_dual_phase_one_) {
259 // TODO(user): A bit annoying that we need to test this even if we
260 // don't use the dual. But the cost is minimal.
261 if (lower_bounds_[col] != 0.0) lower_bounds_[col] = -kInfinity;
262 if (upper_bounds_[col] != 0.0) upper_bounds_[col] = +kInfinity;
263 variable_type_[col] = ComputeVariableType(col);
264 }
265 variable_status_[col] = VariableStatus::BASIC;
266 is_basic_.Set(col, true);
267 not_basic_.Set(col, false);
268 can_increase_.Set(col, false);
269 can_decrease_.Set(col, false);
270 non_basic_boxed_variables_.Set(col, false);
271 SetRelevance(col, false);
272}
273
276 DCHECK_NE(status, VariableStatus::BASIC);
277 variable_status_[col] = status;
278 is_basic_.Set(col, false);
279 not_basic_.Set(col, true);
280 can_increase_.Set(col, status == VariableStatus::AT_LOWER_BOUND ||
282 can_decrease_.Set(col, status == VariableStatus::AT_UPPER_BOUND ||
284
285 const bool boxed =
287 non_basic_boxed_variables_.Set(col, boxed);
288 const bool relevance = status != VariableStatus::FIXED_VALUE &&
289 (boxed_variables_are_relevant_ || !boxed);
290 SetRelevance(col, relevance);
291}
292
294 return variable_type_;
295}
296
298 return variable_status_;
299}
300
302 return can_increase_;
303}
304
306 return can_decrease_;
307}
308
310 return relevance_;
311}
312
313const DenseBitRow& VariablesInfo::GetIsBasicBitRow() const { return is_basic_; }
314
316 return not_basic_;
317}
318
320 return non_basic_boxed_variables_;
321}
322
324 return num_entries_in_relevant_columns_;
325}
326
327VariableType VariablesInfo::ComputeVariableType(ColIndex col) const {
328 DCHECK_LE(lower_bounds_[col], upper_bounds_[col]);
329 if (lower_bounds_[col] == -kInfinity) {
330 if (upper_bounds_[col] == kInfinity) {
332 }
334 } else if (upper_bounds_[col] == kInfinity) {
336 } else if (lower_bounds_[col] == upper_bounds_[col]) {
338 } else {
340 }
341}
342
343void VariablesInfo::SetRelevance(ColIndex col, bool relevance) {
344 if (relevance_.IsSet(col) == relevance) return;
345 if (relevance) {
346 relevance_.Set(col);
347 num_entries_in_relevant_columns_ += matrix_.ColumnNumEntries(col);
348 } else {
349 relevance_.Clear(col);
350 num_entries_in_relevant_columns_ -= matrix_.ColumnNumEntries(col);
351 }
352}
353
354// This is really similar to InitializeFromBasisState() but there is less
355// cases to consider for TransformToDualPhaseIProblem()/EndDualPhaseI().
356void VariablesInfo::UpdateStatusForNewType(ColIndex col) {
357 switch (variable_status_[col]) {
360 break;
362 if (lower_bounds_[col] == upper_bounds_[col]) {
364 } else if (lower_bounds_[col] == -kInfinity) {
365 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
366 } else {
367 // TODO(user): This is only needed for boxed variable to update their
368 // relevance. It should probably be done with the type and not the
369 // status update.
370 UpdateToNonBasicStatus(col, variable_status_[col]);
371 }
372 break;
374 if (lower_bounds_[col] == upper_bounds_[col]) {
376 } else if (upper_bounds_[col] == kInfinity) {
377 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
378 } else {
379 // TODO(user): Same as in the AT_LOWER_BOUND branch above.
380 UpdateToNonBasicStatus(col, variable_status_[col]);
381 }
382 break;
383 default:
384 // TODO(user): boxed variable that become fixed in
385 // TransformToDualPhaseIProblem() will be changed status twice. Once here,
386 // and once when we make them dual feasible according to their reduced
387 // cost. We should probably just do all at once.
388 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
389 }
390}
391
393 Fractional dual_feasibility_tolerance, DenseRow::ConstView reduced_costs) {
394 DCHECK(!in_dual_phase_one_);
395 in_dual_phase_one_ = true;
396 saved_lower_bounds_ = lower_bounds_;
397 saved_upper_bounds_ = upper_bounds_;
398
399 // Transform the bound and type to get a new problem. If this problem has an
400 // optimal value of 0.0, then the problem is dual feasible. And more
401 // importantly, by keeping the same basis, we have a feasible solution of the
402 // original problem.
403 const ColIndex num_cols = matrix_.num_cols();
404 for (ColIndex col(0); col < num_cols; ++col) {
405 switch (variable_type_[col]) {
406 case VariableType::FIXED_VARIABLE: // ABSL_FALLTHROUGH_INTENDED
408 lower_bounds_[col] = 0.0;
409 upper_bounds_[col] = 0.0;
410 variable_type_[col] = VariableType::FIXED_VARIABLE;
411 break;
413 lower_bounds_[col] = 0.0;
414 upper_bounds_[col] = 1.0;
416 break;
418 lower_bounds_[col] = -1.0;
419 upper_bounds_[col] = 0.0;
421 break;
423 lower_bounds_[col] = -1000.0;
424 upper_bounds_[col] = 1000.0;
426 break;
427 }
428
429 // Make sure we start with a feasible dual solution.
430 // If the reduced cost is close to zero, we keep the "default" status.
431 if (variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) {
432 if (reduced_costs[col] > dual_feasibility_tolerance) {
433 variable_status_[col] = VariableStatus::AT_LOWER_BOUND;
434 } else if (reduced_costs[col] < -dual_feasibility_tolerance) {
435 variable_status_[col] = VariableStatus::AT_UPPER_BOUND;
436 }
437 }
438
439 UpdateStatusForNewType(col);
440 }
441}
442
443void VariablesInfo::EndDualPhaseI(Fractional dual_feasibility_tolerance,
444 DenseRow::ConstView reduced_costs) {
445 DCHECK(in_dual_phase_one_);
446 in_dual_phase_one_ = false;
447 std::swap(saved_lower_bounds_, lower_bounds_);
448 std::swap(saved_upper_bounds_, upper_bounds_);
449
450 // This is to clear the memory of the saved bounds since it is no longer
451 // needed.
452 DenseRow empty1, empty2;
453 std::swap(empty1, saved_lower_bounds_);
454 std::swap(empty1, saved_upper_bounds_);
455
456 // Restore the type and update all other fields.
457 const ColIndex num_cols = matrix_.num_cols();
458 for (ColIndex col(0); col < num_cols; ++col) {
459 variable_type_[col] = ComputeVariableType(col);
460
461 // We make sure that the old fixed variables that are now boxed are dual
462 // feasible.
463 //
464 // TODO(user): When there is a choice, use the previous status that might
465 // have been warm-started ? but then this is not high priority since
466 // warm-starting with a non-dual feasible basis seems unfrequent.
467 if (variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) {
468 if (reduced_costs[col] > dual_feasibility_tolerance) {
469 variable_status_[col] = VariableStatus::AT_LOWER_BOUND;
470 } else if (reduced_costs[col] < -dual_feasibility_tolerance) {
471 variable_status_[col] = VariableStatus::AT_UPPER_BOUND;
472 }
473 }
474
475 UpdateStatusForNewType(col);
476 }
477}
478
479} // namespace glop
480} // namespace operations_research
bool IsSet(IndexType i) const
Returns true if the bit at position i is set.
Definition bitset.h:538
void Clear(IndexType i)
Sets the bit at position i to 0.
Definition bitset.h:510
void ClearAndResize(IndexType size)
Changes the number of bits the Bitset64 can hold and set all of them to 0.
Definition bitset.h:493
void Set(IndexType i)
Sets the bit at position i to 1.
Definition bitset.h:548
EntryIndex ColumnNumEntries(ColIndex col) const
Returns the number of entries (i.e. degree) of the given column.
Definition sparse.h:396
StrictITISpan< ColIndex, const Fractional > ConstView
Definition lp_types.h:293
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.
int64_t value
absl::Status status
Definition g_gurobi.cc:44
ColIndex col
Definition markowitz.cc:187
RowIndex row
Definition markowitz.cc:186
constexpr double kInfinity
Infinity for type Fractional.
Definition lp_types.h:89
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:96
VariableType
Different types of variables.
Definition lp_types.h:180
In SWIG mode, we don't want anything besides these top-level includes.
double distance