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