OR-Tools  9.1
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 
16 namespace operations_research {
17 namespace 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 
43  const DenseRow& variable_lower_bounds,
44  const DenseRow& variable_upper_bounds,
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 
83 void 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 
105 void 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.
118  VariableStatus status;
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 
218 VariableStatus 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) {
225  return VariableStatus::FREE;
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 
268  VariableStatus status) {
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 ||
274  status == VariableStatus::FREE);
275  can_decrease_.Set(col, status == VariableStatus::AT_UPPER_BOUND ||
276  status == VariableStatus::FREE);
277 
278  const bool boxed =
279  variable_type_[col] == VariableType::UPPER_AND_LOWER_BOUNDED;
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 
306 const 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 
320 VariableType 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 
336 void 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().
349 void 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;
408  variable_type_[col] = VariableType::UPPER_AND_LOWER_BOUNDED;
409  break;
411  lower_bounds_[col] = -1.0;
412  upper_bounds_[col] = 0.0;
413  variable_type_[col] = VariableType::UPPER_AND_LOWER_BOUNDED;
414  break;
416  lower_bounds_[col] = -1000.0;
417  upper_bounds_[col] = 1000.0;
418  variable_type_[col] = VariableType::UPPER_AND_LOWER_BOUNDED;
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 
436 void 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
ColIndex RowToColIndex(RowIndex row)
Definition: lp_types.h:49
void InitializeFromBasisState(ColIndex first_slack, ColIndex num_new_cols, const BasisState &state)
const DenseBitRow & GetIsBasicBitRow() const
int ChangeUnusedBasicVariablesToFree(const RowToColMapping &basis)
const DenseBitRow & GetCanDecreaseBitRow() const
const DenseBitRow & GetNotBasicBitRow() const
const DenseBitRow & GetCanIncreaseBitRow() const
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:263
ColIndex col
Definition: markowitz.cc:183
EntryIndex ColumnNumEntries(ColIndex col) const
Definition: sparse.h:340
int SnapFreeVariablesToBound(Fractional distance, const DenseRow &starting_values)
RowIndex row
Definition: markowitz.cc:182
bool IsFinite(Fractional value)
Definition: lp_types.h:91
void ClearAndResize(IndexType size)
Definition: bitset.h:440
#define DCHECK_NE(val1, val2)
Definition: base/logging.h:887
const double kInfinity
Definition: lp_types.h:84
const VariableTypeRow & GetTypeRow() const
const DenseBitRow & GetNonBasicBoxedVariables() const
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:890
#define DCHECK(condition)
Definition: base/logging.h:885
void EndDualPhaseI(Fractional dual_feasibility_tolerance, const DenseRow &reduced_costs)
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:886
bool IsSet(IndexType i) const
Definition: bitset.h:485
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:888
void TransformToDualPhaseIProblem(Fractional dual_feasibility_tolerance, const DenseRow &reduced_costs)
void Clear(IndexType i)
Definition: bitset.h:457
Collection of objects used to extend the Constraint Solver library.
const VariableStatusRow & GetStatusRow() const
int64_t value
VariablesInfo(const CompactSparseMatrix &matrix)
double distance
void Set(IndexType i)
Definition: bitset.h:495
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:889
const DenseBitRow & GetIsRelevantBitRow() const
bool LoadBoundsAndReturnTrueIfUnchanged(const DenseRow &new_lower_bounds, const DenseRow &new_upper_bounds)
void UpdateToNonBasicStatus(ColIndex col, VariableStatus status)