Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
deviation.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
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"
24#include "ortools/base/types.h"
27
28namespace 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
32namespace {
33class 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
414Constraint* 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
IntegerValue size
const std::vector< IntVar * > vars_
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:54
Constraint * MakeDeviation(const std::vector< IntVar * > &vars, IntVar *deviation_var, int64_t total_sum)
Definition deviation.cc:414
double upper_bound
In SWIG mode, we don't want anything besides these top-level includes.
std::string JoinDebugStringPtr(const std::vector< T > &v, absl::string_view separator)
Join v[i]->DebugString().
int64_t delta
Definition resource.cc:1709
int64_t bound
int var_index
Definition search.cc:3268