OR-Tools  9.0
deviation.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 
14 #include <algorithm>
15 #include <cstdint>
16 #include <cstdlib>
17 #include <memory>
18 #include <string>
19 #include <vector>
20 
21 #include "absl/strings/str_format.h"
23 #include "ortools/base/logging.h"
24 #include "ortools/base/mathutil.h"
27 
28 namespace operations_research {
29 // Deviation Constraint, a constraint for the average absolute
30 // deviation to the mean. See paper: Bound Consistent Deviation
31 // Constraint, Pierre Schaus et. al., CP07
32 namespace {
33 class Deviation : public Constraint {
34  public:
35  Deviation(Solver* const solver, const std::vector<IntVar*>& vars,
36  IntVar* const deviation_var, int64_t total_sum)
37  : Constraint(solver),
38  vars_(vars),
39  size_(vars.size()),
40  deviation_var_(deviation_var),
41  total_sum_(total_sum),
42  scaled_vars_assigned_value_(new int64_t[size_]),
43  scaled_vars_min_(new int64_t[size_]),
44  scaled_vars_max_(new int64_t[size_]),
45  scaled_sum_max_(0),
46  scaled_sum_min_(0),
47  maximum_(new int64_t[size_]),
48  overlaps_sup_(new int64_t[size_]),
49  active_sum_(0),
50  active_sum_rounded_down_(0),
51  active_sum_rounded_up_(0),
52  active_sum_nearest_(0) {
53  CHECK(deviation_var != nullptr);
54  }
55 
56  ~Deviation() override {}
57 
58  void Post() override {
59  Solver* const s = solver();
60  Demon* const demon = s->MakeConstraintInitialPropagateCallback(this);
61  for (int i = 0; i < size_; ++i) {
62  vars_[i]->WhenRange(demon);
63  }
64  deviation_var_->WhenRange(demon);
65  s->AddConstraint(s->MakeSumEquality(vars_, total_sum_));
66  }
67 
68  void InitialPropagate() override {
69  const int64_t delta_min = BuildMinimalDeviationAssignment();
70  deviation_var_->SetMin(delta_min);
71  PropagateBounds(delta_min);
72  }
73 
74  std::string DebugString() const override {
75  return absl::StrFormat("Deviation([%s], deviation_var = %s, sum = %d)",
76  JoinDebugStringPtr(vars_, ", "),
77  deviation_var_->DebugString(), total_sum_);
78  }
79 
80  void Accept(ModelVisitor* const visitor) const override {
81  visitor->BeginVisitConstraint(ModelVisitor::kDeviation, this);
82  visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kVarsArgument,
83  vars_);
84  visitor->VisitIntegerExpressionArgument(ModelVisitor::kTargetArgument,
85  deviation_var_);
86  visitor->VisitIntegerArgument(ModelVisitor::kValueArgument, total_sum_);
87  visitor->EndVisitConstraint(ModelVisitor::kDeviation, this);
88  }
89 
90  private:
91  // Builds an assignment with minimal deviation and assign it to
92  // scaled_vars_assigned_value_. It returns the minimal deviation:
93  // sum_i |scaled_vars_assigned_value_[i] - total_sum_|.
94  int64_t BuildMinimalDeviationAssignment() {
95  RepairGreedySum(BuildGreedySum(true));
96  int64_t minimal_deviation = 0;
97  for (int i = 0; i < size_; ++i) {
98  minimal_deviation +=
99  std::abs(scaled_vars_assigned_value_[i] - total_sum_);
100  }
101  return minimal_deviation;
102  }
103 
104  // Propagates the upper and lower bounds of x[i]'s.
105  // It assumes the constraint is consistent:
106  // - the sum constraint is consistent
107  // - min deviation smaller than max allowed deviation
108  // min_delta is the minimum possible deviation
109  void PropagateBounds(int64_t min_delta) {
110  PropagateBounds(min_delta, true); // Filter upper bounds.
111  PropagateBounds(min_delta, false); // Filter lower bounds.
112  }
113 
114  // Prunes the upper/lower-bound of vars. We apply a mirroing of the
115  // domains wrt 0 to prune the lower bounds such that we can use the
116  // same algo to prune both sides of the domains. upperBounds = true
117  // to prune the upper bounds of vars, false to prune the lower
118  // bounds.
119  void PropagateBounds(int64_t min_delta, bool upper_bound) {
120  // Builds greedy assignment.
121  const int64_t greedy_sum = BuildGreedySum(upper_bound);
122  // Repairs assignment and store information to be used when pruning.
123  RepairSumAndComputeInfo(greedy_sum);
124  // Does the actual pruning.
125  PruneVars(min_delta, upper_bound);
126  }
127 
128  // Cache min and max values of variables.
129  void ComputeData(bool upper_bound) {
130  scaled_sum_max_ = 0;
131  scaled_sum_min_ = 0;
132  for (int i = 0; i < size_; ++i) {
133  scaled_vars_max_[i] =
134  size_ * (upper_bound ? vars_[i]->Max() : -vars_[i]->Min());
135  scaled_vars_min_[i] =
136  size_ * (upper_bound ? vars_[i]->Min() : -vars_[i]->Max());
137  scaled_sum_max_ += scaled_vars_max_[i];
138  scaled_sum_min_ += scaled_vars_min_[i];
139  }
140  active_sum_ = (!upper_bound ? -total_sum_ : total_sum_);
141  // down is <= sum.
142  active_sum_rounded_down_ =
143  size_ * MathUtil::FloorOfRatio<int64_t>(active_sum_, size_);
144  // up is > sum, always.
145  active_sum_rounded_up_ = active_sum_rounded_down_ + size_;
146  active_sum_nearest_ = (active_sum_rounded_up_ - active_sum_ <=
147  active_sum_ - active_sum_rounded_down_)
148  ? active_sum_rounded_up_
149  : active_sum_rounded_down_;
150  }
151 
152  // Builds an approximate sum in a greedy way.
153  int64_t BuildGreedySum(bool upper_bound) {
154  // Update data structure.
155  ComputeData(upper_bound);
156 
157  // Number of constraint should be consistent.
158  DCHECK_GE(size_ * active_sum_, scaled_sum_min_);
159  DCHECK_LE(size_ * active_sum_, scaled_sum_max_);
160 
161  int64_t sum = 0;
162  // Greedily assign variable to nearest value to average.
163  overlaps_.clear();
164  for (int i = 0; i < size_; ++i) {
165  if (scaled_vars_min_[i] >= active_sum_) {
166  scaled_vars_assigned_value_[i] = scaled_vars_min_[i];
167  } else if (scaled_vars_max_[i] <= active_sum_) {
168  scaled_vars_assigned_value_[i] = scaled_vars_max_[i];
169  } else {
170  // Overlapping variable scaled_vars_min_[i] < active_sum_ <
171  // scaled_vars_max_[i].
172  scaled_vars_assigned_value_[i] = active_sum_nearest_;
173  if (active_sum_ % size_ != 0) {
174  overlaps_.push_back(i);
175  }
176  }
177  sum += scaled_vars_assigned_value_[i];
178  }
179  DCHECK_EQ(0, active_sum_rounded_down_ % size_);
180  DCHECK_LE(active_sum_rounded_down_, active_sum_);
181  DCHECK_LT(active_sum_ - active_sum_rounded_down_, size_);
182 
183  return sum;
184  }
185 
186  bool Overlap(int var_index) const {
187  return scaled_vars_min_[var_index] < active_sum_ &&
188  scaled_vars_max_[var_index] > active_sum_;
189  }
190 
191  // Repairs the greedy sum obtained above to get the correct sum.
192  void RepairGreedySum(int64_t greedy_sum) {
193  // Useful constant: scaled version of the sum.
194  const int64_t scaled_total_sum = size_ * active_sum_;
195  // Step used to make the repair.
196  const int64_t delta = greedy_sum > scaled_total_sum ? -size_ : size_;
197 
198  // Change overlapping variables as long as the sum is not
199  // satisfied and there are overlapping vars, we use that ones to
200  // repair.
201  for (int j = 0; j < overlaps_.size() && greedy_sum != scaled_total_sum;
202  j++) {
203  scaled_vars_assigned_value_[overlaps_[j]] += delta;
204  greedy_sum += delta;
205  }
206  // Change other variables if the sum is still not satisfied.
207  for (int i = 0; i < size_ && greedy_sum != scaled_total_sum; ++i) {
208  const int64_t old_scaled_vars_i = scaled_vars_assigned_value_[i];
209  if (greedy_sum < scaled_total_sum) {
210  // Increase scaled_vars_assigned_value_[i] as much as
211  // possible to fix the too low sum.
212  scaled_vars_assigned_value_[i] += scaled_total_sum - greedy_sum;
213  scaled_vars_assigned_value_[i] =
214  std::min(scaled_vars_assigned_value_[i], scaled_vars_max_[i]);
215  } else {
216  // Decrease scaled_vars_assigned_value_[i] as much as
217  // possible to fix the too high sum.
218  scaled_vars_assigned_value_[i] -= (greedy_sum - scaled_total_sum);
219  scaled_vars_assigned_value_[i] =
220  std::max(scaled_vars_assigned_value_[i], scaled_vars_min_[i]);
221  }
222  // Maintain the sum.
223  greedy_sum += scaled_vars_assigned_value_[i] - old_scaled_vars_i;
224  }
225  }
226 
227  // Computes the maximum values of variables in the case the repaired
228  // greedy sum is actually the active sum.
229  void ComputeMaxWhenNoRepair() {
230  int num_overlap_sum_rounded_up = 0;
231  if (active_sum_nearest_ == active_sum_rounded_up_) {
232  num_overlap_sum_rounded_up = overlaps_.size();
233  }
234  for (int i = 0; i < size_; ++i) {
235  maximum_[i] = scaled_vars_assigned_value_[i];
236  if (Overlap(i) && active_sum_nearest_ == active_sum_rounded_up_ &&
237  active_sum_ % size_ != 0) {
238  overlaps_sup_[i] = num_overlap_sum_rounded_up - 1;
239  } else {
240  overlaps_sup_[i] = num_overlap_sum_rounded_up;
241  }
242  }
243  }
244 
245  // Returns the number of variables overlapping the average value,
246  // assigned to // the average value rounded up that we can/need to
247  // move.
248  int ComputeNumOverlapsVariableRoundedUp() {
249  if (active_sum_ % size_ == 0) {
250  return 0;
251  }
252  int num_overlap_sum_rounded_up = 0;
253  for (int i = 0; i < size_; ++i) {
254  if (scaled_vars_assigned_value_[i] > scaled_vars_min_[i] &&
255  scaled_vars_assigned_value_[i] == active_sum_rounded_up_) {
256  num_overlap_sum_rounded_up++;
257  }
258  }
259  return num_overlap_sum_rounded_up;
260  }
261 
262  // Returns whether we can push the greedy sum across the scaled
263  // total sum in the same direction as going from the nearest rounded
264  // sum to the farthest one.
265  bool CanPushSumAcrossMean(int64_t greedy_sum,
266  int64_t scaled_total_sum) const {
267  return (greedy_sum > scaled_total_sum &&
268  active_sum_nearest_ == active_sum_rounded_up_) ||
269  (greedy_sum < scaled_total_sum &&
270  active_sum_nearest_ == active_sum_rounded_down_);
271  }
272 
273  // Repairs the sum and store intermediate information to be used
274  // during pruning.
275  void RepairSumAndComputeInfo(int64_t greedy_sum) {
276  const int64_t scaled_total_sum = size_ * active_sum_;
277  // Computation of key values for the pruning:
278  // - overlaps_sup_
279  // - maximum_[i]
280  if (greedy_sum == scaled_total_sum) { // No repair needed.
281  ComputeMaxWhenNoRepair();
282  } else { // Repair and compute maximums.
283  // Try to repair the sum greedily.
284  if (CanPushSumAcrossMean(greedy_sum, scaled_total_sum)) {
285  const int64_t delta = greedy_sum > scaled_total_sum ? -size_ : size_;
286  for (int j = 0; j < overlaps_.size() && greedy_sum != scaled_total_sum;
287  ++j) {
288  scaled_vars_assigned_value_[overlaps_[j]] += delta;
289  greedy_sum += delta;
290  }
291  }
292 
293  const int num_overlap_sum_rounded_up =
294  ComputeNumOverlapsVariableRoundedUp();
295 
296  if (greedy_sum == scaled_total_sum) {
297  // Greedy sum is repaired.
298  for (int i = 0; i < size_; ++i) {
299  if (Overlap(i) && num_overlap_sum_rounded_up > 0) {
300  maximum_[i] = active_sum_rounded_up_;
301  overlaps_sup_[i] = num_overlap_sum_rounded_up - 1;
302  } else {
303  maximum_[i] = scaled_vars_assigned_value_[i];
304  overlaps_sup_[i] = num_overlap_sum_rounded_up;
305  }
306  }
307  } else if (greedy_sum > scaled_total_sum) {
308  // scaled_vars_assigned_value_[i] = active_sum_rounded_down_ or
309  // scaled_vars_assigned_value_[i] <= total_sum
310  // (there is no more num_overlap_sum_rounded_up).
311  for (int i = 0; i < size_; ++i) {
312  maximum_[i] = scaled_vars_assigned_value_[i];
313  overlaps_sup_[i] = 0;
314  }
315  } else { // greedy_sum < scaled_total_sum.
316  for (int i = 0; i < size_; ++i) {
317  if (Overlap(i) && num_overlap_sum_rounded_up > 0) {
318  overlaps_sup_[i] = num_overlap_sum_rounded_up - 1;
319  } else {
320  overlaps_sup_[i] = num_overlap_sum_rounded_up;
321  }
322 
323  if (scaled_vars_assigned_value_[i] < scaled_vars_max_[i]) {
324  maximum_[i] =
325  scaled_vars_assigned_value_[i] + scaled_total_sum - greedy_sum;
326  } else {
327  maximum_[i] = scaled_vars_assigned_value_[i];
328  }
329  }
330  }
331  }
332  }
333 
334  // Propagates onto variables with all computed data.
335  void PruneVars(int64_t min_delta, bool upper_bound) {
336  // Pruning of upper bound of vars_[i] for var_index in [1..n].
337  const int64_t increase_down_up = (active_sum_rounded_up_ - active_sum_) -
338  (active_sum_ - active_sum_rounded_down_);
339  for (int var_index = 0; var_index < size_; ++var_index) {
340  // Not bound, and a compatible new max.
341  if (scaled_vars_max_[var_index] != scaled_vars_min_[var_index] &&
342  maximum_[var_index] < scaled_vars_max_[var_index]) {
343  const int64_t new_max =
344  ComputeNewMax(var_index, min_delta, increase_down_up);
345  PruneBound(var_index, new_max, upper_bound);
346  }
347  }
348  }
349 
350  // Computes new max for a variable.
351  int64_t ComputeNewMax(int var_index, int64_t min_delta,
352  int64_t increase_down_up) {
353  int64_t maximum_value = maximum_[var_index];
354  int64_t current_min_delta = min_delta;
355 
356  if (overlaps_sup_[var_index] > 0 &&
357  (current_min_delta +
358  overlaps_sup_[var_index] * (size_ - increase_down_up) >=
359  deviation_var_->Max())) {
360  const int64_t delta = deviation_var_->Max() - current_min_delta;
361  maximum_value += (size_ * delta) / (size_ - increase_down_up);
362  return MathUtil::FloorOfRatio<int64_t>(maximum_value, size_);
363  } else {
364  if (maximum_value == active_sum_rounded_down_ &&
365  active_sum_rounded_down_ < active_sum_) {
366  DCHECK_EQ(0, overlaps_sup_[var_index]);
367  current_min_delta += size_ + increase_down_up;
368  if (current_min_delta > deviation_var_->Max()) {
369  DCHECK_EQ(0, maximum_value % size_);
370  return maximum_value / size_;
371  }
372  maximum_value += size_;
373  }
374  current_min_delta +=
375  overlaps_sup_[var_index] * (size_ - increase_down_up);
376  maximum_value += size_ * overlaps_sup_[var_index];
377  // Slope of 2 x n.
378  const int64_t delta = deviation_var_->Max() - current_min_delta;
379  maximum_value += delta / 2; // n * delta / (2 * n);
380  return MathUtil::FloorOfRatio<int64_t>(maximum_value, size_);
381  }
382  }
383 
384  // Sets maximum on var or on its opposite.
385  void PruneBound(int var_index, int64_t bound, bool upper_bound) {
386  if (upper_bound) {
387  vars_[var_index]->SetMax(bound);
388  } else {
389  vars_[var_index]->SetMin(-bound);
390  }
391  }
392 
393  std::vector<IntVar*> vars_;
394  const int size_;
395  IntVar* const deviation_var_;
396  const int64_t total_sum_;
397  std::unique_ptr<int64_t[]> scaled_vars_assigned_value_;
398  std::unique_ptr<int64_t[]> scaled_vars_min_;
399  std::unique_ptr<int64_t[]> scaled_vars_max_;
400  int64_t scaled_sum_max_;
401  int64_t scaled_sum_min_;
402  // Stores the variables overlapping the mean value.
403  std::vector<int> overlaps_;
404  std::unique_ptr<int64_t[]> maximum_;
405  std::unique_ptr<int64_t[]> overlaps_sup_;
406  // These values are updated by ComputeData().
407  int64_t active_sum_;
408  int64_t active_sum_rounded_down_;
409  int64_t active_sum_rounded_up_;
410  int64_t active_sum_nearest_;
411 };
412 } // namespace
413 
414 Constraint* Solver::MakeDeviation(const std::vector<IntVar*>& vars,
415  IntVar* const deviation_var,
416  int64_t total_sum) {
417  return RevAlloc(new Deviation(this, vars, deviation_var, total_sum));
418 }
419 } // namespace operations_research
const std::vector< IntVar * > vars_
Definition: alldiff_cst.cc:44
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:498
#define DCHECK_LE(val1, val2)
Definition: base/logging.h:895
#define DCHECK_GE(val1, val2)
Definition: base/logging.h:897
#define DCHECK_LT(val1, val2)
Definition: base/logging.h:896
#define DCHECK_EQ(val1, val2)
Definition: base/logging.h:893
A constraint is the main modeling object.
The class IntVar is a subset of IntExpr.
T * RevAlloc(T *object)
Registers the given object as being reversible.
Constraint * MakeDeviation(const std::vector< IntVar * > &vars, IntVar *const deviation_var, int64_t total_sum)
Deviation constraint: sum_i |n * vars[i] - total_sum| <= deviation_var and sum_i vars[i] == total_sum...
Definition: deviation.cc:414
double upper_bound
Collection of objects used to extend the Constraint Solver library.
std::string JoinDebugStringPtr(const std::vector< T > &v, const std::string &separator)
Definition: string_array.h:45
int64_t delta
Definition: resource.cc:1692
int64_t bound