21#include "absl/strings/str_format.h"
33class Deviation :
public Constraint {
35 Deviation(Solver*
const solver,
const std::vector<IntVar*>& vars,
36 IntVar*
const deviation_var, int64_t total_sum)
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_]),
47 maximum_(new int64_t[size_]),
48 overlaps_sup_(new int64_t[size_]),
50 active_sum_rounded_down_(0),
51 active_sum_rounded_up_(0),
52 active_sum_nearest_(0) {
53 CHECK(deviation_var !=
nullptr);
56 ~Deviation()
override {}
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);
64 deviation_var_->WhenRange(demon);
65 s->AddConstraint(s->MakeSumEquality(vars_, total_sum_));
68 void InitialPropagate()
override {
69 const int64_t delta_min = BuildMinimalDeviationAssignment();
70 deviation_var_->SetMin(delta_min);
71 PropagateBounds(delta_min);
74 std::string DebugString()
const override {
75 return absl::StrFormat(
"Deviation([%s], deviation_var = %s, sum = %d)",
77 deviation_var_->DebugString(), total_sum_);
80 void Accept(ModelVisitor*
const visitor)
const override {
94 int64_t BuildMinimalDeviationAssignment() {
95 RepairGreedySum(BuildGreedySum(
true));
96 int64_t minimal_deviation = 0;
97 for (
int i = 0;
i < size_; ++
i) {
99 std::abs(scaled_vars_assigned_value_[i] - total_sum_);
101 return minimal_deviation;
109 void PropagateBounds(int64_t min_delta) {
110 PropagateBounds(min_delta,
true);
111 PropagateBounds(min_delta,
false);
119 void PropagateBounds(int64_t min_delta,
bool upper_bound) {
121 const int64_t greedy_sum = BuildGreedySum(
upper_bound);
123 RepairSumAndComputeInfo(greedy_sum);
132 for (
int i = 0;
i < size_; ++
i) {
133 scaled_vars_max_[
i] =
135 scaled_vars_min_[
i] =
137 scaled_sum_max_ += scaled_vars_max_[
i];
138 scaled_sum_min_ += scaled_vars_min_[
i];
140 active_sum_ = (!
upper_bound ? -total_sum_ : total_sum_);
142 active_sum_rounded_down_ =
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_;
158 DCHECK_GE(size_ * active_sum_, scaled_sum_min_);
159 DCHECK_LE(size_ * active_sum_, scaled_sum_max_);
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];
172 scaled_vars_assigned_value_[
i] = active_sum_nearest_;
173 if (active_sum_ % size_ != 0) {
174 overlaps_.push_back(i);
177 sum += scaled_vars_assigned_value_[
i];
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_);
187 return scaled_vars_min_[
var_index] < active_sum_ &&
188 scaled_vars_max_[
var_index] > active_sum_;
192 void RepairGreedySum(int64_t greedy_sum) {
194 const int64_t scaled_total_sum = size_ * active_sum_;
196 const int64_t
delta = greedy_sum > scaled_total_sum ? -size_ : size_;
201 for (
int j = 0; j < overlaps_.size() && greedy_sum != scaled_total_sum;
203 scaled_vars_assigned_value_[overlaps_[j]] +=
delta;
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) {
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]);
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]);
223 greedy_sum += scaled_vars_assigned_value_[
i] - old_scaled_vars_i;
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();
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;
240 overlaps_sup_[
i] = num_overlap_sum_rounded_up;
248 int ComputeNumOverlapsVariableRoundedUp() {
249 if (active_sum_ % size_ == 0) {
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++;
259 return num_overlap_sum_rounded_up;
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_);
275 void RepairSumAndComputeInfo(int64_t greedy_sum) {
276 const int64_t scaled_total_sum = size_ * active_sum_;
280 if (greedy_sum == scaled_total_sum) {
281 ComputeMaxWhenNoRepair();
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;
288 scaled_vars_assigned_value_[overlaps_[j]] +=
delta;
293 const int num_overlap_sum_rounded_up =
294 ComputeNumOverlapsVariableRoundedUp();
296 if (greedy_sum == scaled_total_sum) {
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;
303 maximum_[
i] = scaled_vars_assigned_value_[
i];
304 overlaps_sup_[
i] = num_overlap_sum_rounded_up;
307 }
else if (greedy_sum > scaled_total_sum) {
311 for (
int i = 0;
i < size_; ++
i) {
312 maximum_[
i] = scaled_vars_assigned_value_[
i];
313 overlaps_sup_[
i] = 0;
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;
320 overlaps_sup_[
i] = num_overlap_sum_rounded_up;
323 if (scaled_vars_assigned_value_[i] < scaled_vars_max_[i]) {
325 scaled_vars_assigned_value_[
i] + scaled_total_sum - greedy_sum;
327 maximum_[
i] = scaled_vars_assigned_value_[
i];
335 void PruneVars(int64_t min_delta,
bool upper_bound) {
337 const int64_t increase_down_up = (active_sum_rounded_up_ - active_sum_) -
338 (active_sum_ - active_sum_rounded_down_);
343 const int64_t new_max =
344 ComputeNewMax(
var_index, min_delta, increase_down_up);
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;
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);
364 if (maximum_value == active_sum_rounded_down_ &&
365 active_sum_rounded_down_ < active_sum_) {
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_;
372 maximum_value += size_;
375 overlaps_sup_[
var_index] * (size_ - increase_down_up);
376 maximum_value += size_ * overlaps_sup_[
var_index];
378 const int64_t
delta = deviation_var_->Max() - current_min_delta;
379 maximum_value +=
delta / 2;
393 std::vector<IntVar*>
vars_;
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_;
403 std::vector<int> overlaps_;
404 std::unique_ptr<int64_t[]> maximum_;
405 std::unique_ptr<int64_t[]> overlaps_sup_;
408 int64_t active_sum_rounded_down_;
409 int64_t active_sum_rounded_up_;
410 int64_t active_sum_nearest_;
415 IntVar*
const deviation_var,
417 return RevAlloc(
new Deviation(
this, vars, deviation_var, total_sum));
const std::vector< IntVar * > vars_
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
static const char kDeviation[]
static const char kTargetArgument[]
static const char kValueArgument[]
static const char kVarsArgument[]
Constraint * MakeDeviation(const std::vector< IntVar * > &vars, IntVar *deviation_var, int64_t total_sum)
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().