OR-Tools  9.3
variables_info.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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
16namespace operations_research {
17namespace glop {
18
20 : matrix_(matrix) {}
21
23 const DenseRow& new_lower_bounds, const DenseRow& new_upper_bounds) {
24 const ColIndex num_cols = matrix_.num_cols();
25 DCHECK_EQ(num_cols, new_lower_bounds.size());
26 DCHECK_EQ(num_cols, new_upper_bounds.size());
27
28 // Optim if nothing changed.
29 if (lower_bounds_ == new_lower_bounds && upper_bounds_ == new_upper_bounds) {
30 return true;
31 }
32
33 lower_bounds_ = new_lower_bounds;
34 upper_bounds_ = new_upper_bounds;
35 variable_type_.resize(num_cols, VariableType::UNCONSTRAINED);
36 for (ColIndex col(0); col < num_cols; ++col) {
37 variable_type_[col] = ComputeVariableType(col);
38 }
39 return false;
40}
41
45 const DenseColumn& constraint_lower_bounds,
46 const DenseColumn& constraint_upper_bounds) {
47 const ColIndex num_cols = matrix_.num_cols();
48 const ColIndex num_variables = variable_upper_bounds.size();
49 const RowIndex num_rows = constraint_lower_bounds.size();
50
51 bool is_unchanged = (num_cols == lower_bounds_.size());
52 DCHECK_EQ(num_cols, num_variables + RowToColIndex(num_rows));
53 lower_bounds_.resize(num_cols, 0.0);
54 upper_bounds_.resize(num_cols, 0.0);
55 variable_type_.resize(num_cols, VariableType::FIXED_VARIABLE);
56
57 // Copy bounds of the variables.
58 for (ColIndex col(0); col < num_variables; ++col) {
59 if (lower_bounds_[col] != variable_lower_bounds[col] ||
60 upper_bounds_[col] != variable_upper_bounds[col]) {
61 lower_bounds_[col] = variable_lower_bounds[col];
62 upper_bounds_[col] = variable_upper_bounds[col];
63 is_unchanged = false;
64 variable_type_[col] = ComputeVariableType(col);
65 }
66 }
67
68 // Copy bounds of the slack.
69 for (RowIndex row(0); row < num_rows; ++row) {
70 const ColIndex col = num_variables + RowToColIndex(row);
71 if (lower_bounds_[col] != -constraint_upper_bounds[row] ||
72 upper_bounds_[col] != -constraint_lower_bounds[row]) {
73 lower_bounds_[col] = -constraint_upper_bounds[row];
74 upper_bounds_[col] = -constraint_lower_bounds[row];
75 is_unchanged = false;
76 variable_type_[col] = ComputeVariableType(col);
77 }
78 }
79
80 return is_unchanged;
81}
82
83void VariablesInfo::ResetStatusInfo() {
84 const ColIndex num_cols = matrix_.num_cols();
85 DCHECK_EQ(num_cols, lower_bounds_.size());
86 DCHECK_EQ(num_cols, upper_bounds_.size());
87
88 // TODO(user): These could just be Resized() but there is a bug with the
89 // iteration and resize it seems. Investigate. I suspect the last bucket
90 // is not cleared so you can still iterate on the ones there even if it all
91 // positions before num_cols are set to zero.
92 variable_status_.resize(num_cols, VariableStatus::FREE);
93 can_increase_.ClearAndResize(num_cols);
94 can_decrease_.ClearAndResize(num_cols);
95 is_basic_.ClearAndResize(num_cols);
96 not_basic_.ClearAndResize(num_cols);
97 non_basic_boxed_variables_.ClearAndResize(num_cols);
98
99 // This one cannot just be resized.
100 boxed_variables_are_relevant_ = true;
101 num_entries_in_relevant_columns_ = 0;
102 relevance_.ClearAndResize(num_cols);
103}
104
105void VariablesInfo::InitializeFromBasisState(ColIndex first_slack_col,
106 ColIndex num_new_cols,
107 const BasisState& state) {
108 ResetStatusInfo();
109
110 const ColIndex num_cols = lower_bounds_.size();
111 DCHECK_LE(num_new_cols, first_slack_col);
112 const ColIndex first_new_col(first_slack_col - num_new_cols);
113
114 // Compute the status for all the columns (note that the slack variables are
115 // already added at the end of the matrix at this stage).
116 for (ColIndex col(0); col < num_cols; ++col) {
117 // Start with the given "warm" status from the BasisState if it exists.
119 if (col < first_new_col && col < state.statuses.size()) {
120 status = state.statuses[col];
121 } else if (col >= first_slack_col &&
122 col - num_new_cols < state.statuses.size()) {
123 status = state.statuses[col - num_new_cols];
124 } else {
125 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
126 continue;
127 }
128
129 // Remove incompatibilities between the warm status and the current state.
130 switch (status) {
132 // Because we just called ResetStatusInfo(), we optimize the call to
133 // UpdateToNonBasicStatus(col) here. In an incremental setting with
134 // almost no work per call, the update of all the DenseBitRow are
135 // visible.
136 variable_status_[col] = VariableStatus::BASIC;
137 is_basic_.Set(col, true);
138 break;
140 if (lower_bounds_[col] == upper_bounds_[col]) {
142 } else {
143 UpdateToNonBasicStatus(col, lower_bounds_[col] == -kInfinity
144 ? DefaultVariableStatus(col)
145 : status);
146 }
147 break;
149 if (lower_bounds_[col] == upper_bounds_[col]) {
151 } else {
152 UpdateToNonBasicStatus(col, upper_bounds_[col] == kInfinity
153 ? DefaultVariableStatus(col)
154 : status);
155 }
156 break;
157 default:
158 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
159 }
160 }
161}
162
164 const RowToColMapping& basis) {
165 const ColIndex num_cols = lower_bounds_.size();
166 is_basic_.ClearAndResize(num_cols);
167 for (const ColIndex col : basis) {
169 }
170 int num_no_longer_in_basis = 0;
171 for (ColIndex col(0); col < num_cols; ++col) {
172 if (!is_basic_[col] && variable_status_[col] == VariableStatus::BASIC) {
173 ++num_no_longer_in_basis;
174 if (variable_type_[col] == VariableType::FIXED_VARIABLE) {
176 } else {
178 }
179 }
180 }
181 return num_no_longer_in_basis;
182}
183
185 const DenseRow& starting_values) {
186 int num_changes = 0;
187 const ColIndex num_cols = lower_bounds_.size();
188 for (ColIndex col(0); col < num_cols; ++col) {
189 if (variable_status_[col] != VariableStatus::FREE) continue;
190 if (variable_type_[col] == VariableType::UNCONSTRAINED) continue;
191 const Fractional value =
192 col < starting_values.size() ? starting_values[col] : 0.0;
193 const Fractional diff_ub = upper_bounds_[col] - value;
194 const Fractional diff_lb = value - lower_bounds_[col];
195 if (diff_lb <= diff_ub) {
196 if (diff_lb <= distance) {
197 ++num_changes;
199 }
200 } else {
201 if (diff_ub <= distance) {
202 ++num_changes;
204 }
205 }
206 }
207 return num_changes;
208}
209
211 ResetStatusInfo();
212 const ColIndex num_cols = lower_bounds_.size();
213 for (ColIndex col(0); col < num_cols; ++col) {
214 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
215 }
216}
217
218VariableStatus VariablesInfo::DefaultVariableStatus(ColIndex col) const {
219 DCHECK_GE(col, 0);
220 DCHECK_LT(col, lower_bounds_.size());
221 if (lower_bounds_[col] == upper_bounds_[col]) {
223 }
224 if (lower_bounds_[col] == -kInfinity && upper_bounds_[col] == kInfinity) {
226 }
227
228 // Returns the bound with the lowest magnitude. Note that it must be finite
229 // because the VariableStatus::FREE case was tested earlier.
230 DCHECK(IsFinite(lower_bounds_[col]) || IsFinite(upper_bounds_[col]));
231 return std::abs(lower_bounds_[col]) <= std::abs(upper_bounds_[col])
234}
235
237 if (value == boxed_variables_are_relevant_) return;
238 boxed_variables_are_relevant_ = value;
239 if (value) {
240 for (const ColIndex col : non_basic_boxed_variables_) {
241 SetRelevance(col, variable_type_[col] != VariableType::FIXED_VARIABLE);
242 }
243 } else {
244 for (const ColIndex col : non_basic_boxed_variables_) {
245 SetRelevance(col, false);
246 }
247 }
248}
249
251 if (in_dual_phase_one_) {
252 // TODO(user): A bit annoying that we need to test this even if we
253 // don't use the dual. But the cost is minimal.
254 if (lower_bounds_[col] != 0.0) lower_bounds_[col] = -kInfinity;
255 if (upper_bounds_[col] != 0.0) upper_bounds_[col] = +kInfinity;
256 variable_type_[col] = ComputeVariableType(col);
257 }
258 variable_status_[col] = VariableStatus::BASIC;
259 is_basic_.Set(col, true);
260 not_basic_.Set(col, false);
261 can_increase_.Set(col, false);
262 can_decrease_.Set(col, false);
263 non_basic_boxed_variables_.Set(col, false);
264 SetRelevance(col, false);
265}
266
270 variable_status_[col] = status;
271 is_basic_.Set(col, false);
272 not_basic_.Set(col, true);
273 can_increase_.Set(col, status == VariableStatus::AT_LOWER_BOUND ||
275 can_decrease_.Set(col, status == VariableStatus::AT_UPPER_BOUND ||
277
278 const bool boxed =
280 non_basic_boxed_variables_.Set(col, boxed);
281 const bool relevance = status != VariableStatus::FIXED_VALUE &&
282 (boxed_variables_are_relevant_ || !boxed);
283 SetRelevance(col, relevance);
284}
285
287 return variable_type_;
288}
289
291 return variable_status_;
292}
293
295 return can_increase_;
296}
297
299 return can_decrease_;
300}
301
303 return relevance_;
304}
305
306const DenseBitRow& VariablesInfo::GetIsBasicBitRow() const { return is_basic_; }
307
309 return not_basic_;
310}
311
313 return non_basic_boxed_variables_;
314}
315
317 return num_entries_in_relevant_columns_;
318}
319
320VariableType VariablesInfo::ComputeVariableType(ColIndex col) const {
321 DCHECK_LE(lower_bounds_[col], upper_bounds_[col]);
322 if (lower_bounds_[col] == -kInfinity) {
323 if (upper_bounds_[col] == kInfinity) {
325 }
327 } else if (upper_bounds_[col] == kInfinity) {
329 } else if (lower_bounds_[col] == upper_bounds_[col]) {
331 } else {
333 }
334}
335
336void VariablesInfo::SetRelevance(ColIndex col, bool relevance) {
337 if (relevance_.IsSet(col) == relevance) return;
338 if (relevance) {
339 relevance_.Set(col);
340 num_entries_in_relevant_columns_ += matrix_.ColumnNumEntries(col);
341 } else {
342 relevance_.Clear(col);
343 num_entries_in_relevant_columns_ -= matrix_.ColumnNumEntries(col);
344 }
345}
346
347// This is really similar to InitializeFromBasisState() but there is less
348// cases to consider for TransformToDualPhaseIProblem()/EndDualPhaseI().
349void VariablesInfo::UpdateStatusForNewType(ColIndex col) {
350 switch (variable_status_[col]) {
353 break;
355 if (lower_bounds_[col] == upper_bounds_[col]) {
357 } else if (lower_bounds_[col] == -kInfinity) {
358 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
359 } else {
360 // TODO(user): This is only needed for boxed variable to update their
361 // relevance. It should probably be done with the type and not the
362 // status update.
363 UpdateToNonBasicStatus(col, variable_status_[col]);
364 }
365 break;
367 if (lower_bounds_[col] == upper_bounds_[col]) {
369 } else if (upper_bounds_[col] == kInfinity) {
370 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
371 } else {
372 // TODO(user): Same as in the AT_LOWER_BOUND branch above.
373 UpdateToNonBasicStatus(col, variable_status_[col]);
374 }
375 break;
376 default:
377 // TODO(user): boxed variable that become fixed in
378 // TransformToDualPhaseIProblem() will be changed status twice. Once here,
379 // and once when we make them dual feasible according to their reduced
380 // cost. We should probably just do all at once.
381 UpdateToNonBasicStatus(col, DefaultVariableStatus(col));
382 }
383}
384
386 Fractional dual_feasibility_tolerance, const DenseRow& reduced_costs) {
387 DCHECK(!in_dual_phase_one_);
388 in_dual_phase_one_ = true;
389 saved_lower_bounds_ = lower_bounds_;
390 saved_upper_bounds_ = upper_bounds_;
391
392 // Transform the bound and type to get a new problem. If this problem has an
393 // optimal value of 0.0, then the problem is dual feasible. And more
394 // importantly, by keeping the same basis, we have a feasible solution of the
395 // original problem.
396 const ColIndex num_cols = matrix_.num_cols();
397 for (ColIndex col(0); col < num_cols; ++col) {
398 switch (variable_type_[col]) {
399 case VariableType::FIXED_VARIABLE: // ABSL_FALLTHROUGH_INTENDED
401 lower_bounds_[col] = 0.0;
402 upper_bounds_[col] = 0.0;
403 variable_type_[col] = VariableType::FIXED_VARIABLE;
404 break;
406 lower_bounds_[col] = 0.0;
407 upper_bounds_[col] = 1.0;
409 break;
411 lower_bounds_[col] = -1.0;
412 upper_bounds_[col] = 0.0;
414 break;
416 lower_bounds_[col] = -1000.0;
417 upper_bounds_[col] = 1000.0;
419 break;
420 }
421
422 // Make sure we start with a feasible dual solution.
423 // If the reduced cost is close to zero, we keep the "default" status.
424 if (variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) {
425 if (reduced_costs[col] > dual_feasibility_tolerance) {
426 variable_status_[col] = VariableStatus::AT_LOWER_BOUND;
427 } else if (reduced_costs[col] < -dual_feasibility_tolerance) {
428 variable_status_[col] = VariableStatus::AT_UPPER_BOUND;
429 }
430 }
431
432 UpdateStatusForNewType(col);
433 }
434}
435
436void VariablesInfo::EndDualPhaseI(Fractional dual_feasibility_tolerance,
437 const DenseRow& reduced_costs) {
438 DCHECK(in_dual_phase_one_);
439 in_dual_phase_one_ = false;
440 std::swap(saved_lower_bounds_, lower_bounds_);
441 std::swap(saved_upper_bounds_, upper_bounds_);
442
443 // This is to clear the memory of the saved bounds since it is no longer
444 // needed.
445 DenseRow empty1, empty2;
446 std::swap(empty1, saved_lower_bounds_);
447 std::swap(empty1, saved_upper_bounds_);
448
449 // Restore the type and update all other fields.
450 const ColIndex num_cols = matrix_.num_cols();
451 for (ColIndex col(0); col < num_cols; ++col) {
452 variable_type_[col] = ComputeVariableType(col);
453
454 // We make sure that the old fixed variables that are now boxed are dual
455 // feasible.
456 //
457 // TODO(user): When there is a choice, use the previous status that might
458 // have been warm-started ? but then this is not high priority since
459 // warm-starting with a non-dual feasible basis seems unfrequent.
460 if (variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED) {
461 if (reduced_costs[col] > dual_feasibility_tolerance) {
462 variable_status_[col] = VariableStatus::AT_LOWER_BOUND;
463 } else if (reduced_costs[col] < -dual_feasibility_tolerance) {
464 variable_status_[col] = VariableStatus::AT_UPPER_BOUND;
465 }
466 }
467
468 UpdateStatusForNewType(col);
469 }
470}
471
472} // namespace glop
473} // namespace operations_research
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:893
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:892
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:895
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:894
#define DCHECK(condition)
Definition: base/logging.h:890
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:891
void ClearAndResize(IndexType size)
Definition: bitset.h:440
void Clear(IndexType i)
Definition: bitset.h:457
void Set(IndexType i)
Definition: bitset.h:495
bool IsSet(IndexType i) const
Definition: bitset.h:485
EntryIndex ColumnNumEntries(ColIndex col) const
Definition: sparse.h:340
const DenseBitRow & GetIsBasicBitRow() const
int SnapFreeVariablesToBound(Fractional distance, const DenseRow &starting_values)
int ChangeUnusedBasicVariablesToFree(const RowToColMapping &basis)
const DenseBitRow & GetNonBasicBoxedVariables() const
const DenseBitRow & GetCanIncreaseBitRow() const
const DenseBitRow & GetCanDecreaseBitRow() const
const VariableTypeRow & GetTypeRow() const
void EndDualPhaseI(Fractional dual_feasibility_tolerance, const DenseRow &reduced_costs)
void UpdateToNonBasicStatus(ColIndex col, VariableStatus status)
const DenseBitRow & GetNotBasicBitRow() const
VariablesInfo(const CompactSparseMatrix &matrix)
const VariableStatusRow & GetStatusRow() const
const DenseBitRow & GetIsRelevantBitRow() const
void InitializeFromBasisState(ColIndex first_slack, ColIndex num_new_cols, const BasisState &state)
bool LoadBoundsAndReturnTrueIfUnchanged(const DenseRow &new_lower_bounds, const DenseRow &new_upper_bounds)
void TransformToDualPhaseIProblem(Fractional dual_feasibility_tolerance, const DenseRow &reduced_costs)
int64_t value
absl::Status status
Definition: g_gurobi.cc:35
ColIndex col
Definition: markowitz.cc:183
RowIndex row
Definition: markowitz.cc:182
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
bool IsFinite(Fractional value)
Definition: lp_types.h:91
const double kInfinity
Definition: lp_types.h:84
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:262
Collection of objects used to extend the Constraint Solver library.
double distance
VectorXd variable_lower_bounds
VectorXd variable_upper_bounds