OR-Tools  9.6
intervals.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 
14 #include "ortools/sat/intervals.h"
15 
16 #include <algorithm>
17 #include <string>
18 #include <utility>
19 #include <vector>
20 
21 #include "absl/strings/str_cat.h"
22 #include "absl/types/span.h"
23 #include "ortools/base/logging.h"
26 #include "ortools/sat/integer.h"
29 #include "ortools/sat/model.h"
31 #include "ortools/sat/sat_base.h"
32 #include "ortools/sat/sat_solver.h"
33 #include "ortools/util/sort.h"
35 
36 namespace operations_research {
37 namespace sat {
38 
39 IntervalVariable IntervalsRepository::CreateInterval(IntegerVariable start,
40  IntegerVariable end,
41  IntegerVariable size,
42  IntegerValue fixed_size,
43  LiteralIndex is_present) {
45  size == kNoIntegerVariable
46  ? AffineExpression(fixed_size)
47  : AffineExpression(size),
48  is_present, /*add_linear_relation=*/true);
49 }
50 
53  AffineExpression size,
54  LiteralIndex is_present,
55  bool add_linear_relation) {
56  // Create the interval.
57  const IntervalVariable i(starts_.size());
58  starts_.push_back(start);
59  ends_.push_back(end);
60  sizes_.push_back(size);
61  is_present_.push_back(is_present);
62 
63  std::vector<Literal> enforcement_literals;
64  if (is_present != kNoLiteralIndex) {
65  enforcement_literals.push_back(Literal(is_present));
66  }
67 
68  if (add_linear_relation) {
69  LinearConstraintBuilder builder(model_, IntegerValue(0), IntegerValue(0));
70  builder.AddTerm(Start(i), IntegerValue(1));
71  builder.AddTerm(Size(i), IntegerValue(1));
72  builder.AddTerm(End(i), IntegerValue(-1));
73  LoadConditionalLinearConstraint(enforcement_literals, builder.Build(),
74  model_);
75  }
76 
77  return i;
78 }
79 
81  const std::vector<IntervalVariable>& tasks, Model* model)
82  : trail_(model->GetOrCreate<Trail>()),
83  integer_trail_(model->GetOrCreate<IntegerTrail>()),
84  precedences_(model->GetOrCreate<PrecedencesPropagator>()) {
85  starts_.clear();
86  ends_.clear();
87  minus_ends_.clear();
88  minus_starts_.clear();
89  sizes_.clear();
90  reason_for_presence_.clear();
91 
92  auto* repository = model->GetOrCreate<IntervalsRepository>();
93  for (const IntervalVariable i : tasks) {
94  if (repository->IsOptional(i)) {
95  reason_for_presence_.push_back(repository->PresenceLiteral(i).Index());
96  } else {
97  reason_for_presence_.push_back(kNoLiteralIndex);
98  }
99  sizes_.push_back(repository->Size(i));
100  starts_.push_back(repository->Start(i));
101  ends_.push_back(repository->End(i));
102  minus_starts_.push_back(repository->Start(i).Negated());
103  minus_ends_.push_back(repository->End(i).Negated());
104  }
105 
106  RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
107  InitSortedVectors();
108  if (!SynchronizeAndSetTimeDirection(true)) {
109  model->GetOrCreate<SatSolver>()->NotifyThatModelIsUnsat();
110  }
111 }
112 
113 // TODO(user): Ideally we should sort the vector of variables, but right now
114 // we cannot since we often use this with a parallel vector of demands. So this
115 // "sorting" should happen in the presolver so we can share as much as possible.
117  const std::vector<IntervalVariable>& variables) {
118  const auto it = helper_repository_.find(variables);
119  if (it != helper_repository_.end()) return it->second;
120 
122  new SchedulingConstraintHelper(variables, model_);
123  helper_repository_[variables] = helper;
124  model_->TakeOwnership(helper);
125  return helper;
126 }
127 
129  Model* model)
130  : trail_(model->GetOrCreate<Trail>()),
131  integer_trail_(model->GetOrCreate<IntegerTrail>()),
132  precedences_(model->GetOrCreate<PrecedencesPropagator>()) {
133  starts_.resize(num_tasks);
134  CHECK_EQ(NumTasks(), num_tasks);
135 }
136 
138  recompute_all_cache_ = true;
139  return true;
140 }
141 
143  const std::vector<int>& watch_indices) {
144  for (const int t : watch_indices) recompute_cache_[t] = true;
145  return true;
146 }
147 
149  // If there was an Untrail before, we need to refresh the cache so that
150  // we never have value from lower in the search tree.
151  //
152  // TODO(user): We could be smarter here, but then this is not visible in our
153  // cpu_profile since we call many times IncrementalPropagate() for each new
154  // decision, but just call Propagate() once after each Untrail().
155  if (level < previous_level_) {
156  recompute_all_cache_ = true;
157  }
158  previous_level_ = level;
159 }
160 
162  const int id = watcher->Register(this);
163  const int num_tasks = starts_.size();
164  for (int t = 0; t < num_tasks; ++t) {
165  watcher->WatchIntegerVariable(sizes_[t].var, id, t);
166  watcher->WatchIntegerVariable(starts_[t].var, id, t);
167  watcher->WatchIntegerVariable(ends_[t].var, id, t);
168  }
169  watcher->SetPropagatorPriority(id, 0);
170 
171  // Note that it is important to register with the integer_trail_ so we are
172  // ALWAYS called before any propagator that depends on this helper.
173  integer_trail_->RegisterReversibleClass(this);
174 }
175 
176 bool SchedulingConstraintHelper::UpdateCachedValues(int t) {
177  recompute_cache_[t] = false;
178  if (IsAbsent(t)) return true;
179 
180  IntegerValue smin = integer_trail_->LowerBound(starts_[t]);
181  IntegerValue smax = integer_trail_->UpperBound(starts_[t]);
182  IntegerValue emin = integer_trail_->LowerBound(ends_[t]);
183  IntegerValue emax = integer_trail_->UpperBound(ends_[t]);
184 
185  // We take the max for the corner case where the size of an optional interval
186  // is used elsewhere and has a domain with negative value.
187  //
188  // TODO(user): maybe we should just disallow size with a negative domain, but
189  // is is harder to enforce if we have a linear expression for size.
190  IntegerValue dmin =
191  std::max(IntegerValue(0), integer_trail_->LowerBound(sizes_[t]));
192  IntegerValue dmax = integer_trail_->UpperBound(sizes_[t]);
193 
194  // Detect first if we have a conflict using the relation start + size = end.
195  if (dmax < 0) {
196  AddSizeMaxReason(t, dmax);
197  return PushTaskAbsence(t);
198  }
199  if (smin + dmin - emax > 0) {
200  ClearReason();
201  AddStartMinReason(t, smin);
202  AddSizeMinReason(t, dmin);
203  AddEndMaxReason(t, emax);
204  return PushTaskAbsence(t);
205  }
206  if (smax + dmax - emin < 0) {
207  ClearReason();
208  AddStartMaxReason(t, smax);
209  AddSizeMaxReason(t, dmax);
210  AddEndMinReason(t, emin);
211  return PushTaskAbsence(t);
212  }
213 
214  // Sometimes, for optional interval with non-optional bounds, this propagation
215  // give tighter bounds. We always consider the value assuming
216  // the interval is present.
217  //
218  // Note that this is also useful in case not everything was propagated. Note
219  // also that since there is no conflict, we reach the fix point in one pass.
220  smin = std::max(smin, emin - dmax);
221  smax = std::min(smax, emax - dmin);
222  dmin = std::max(dmin, emin - smax);
223  emin = std::max(emin, smin + dmin);
224  emax = std::min(emax, smax + dmax);
225 
226  if (emin != cached_end_min_[t]) {
227  recompute_energy_profile_ = true;
228  }
229 
230  cached_start_min_[t] = smin;
231  cached_end_min_[t] = emin;
232  cached_negated_start_max_[t] = -smax;
233  cached_negated_end_max_[t] = -emax;
234  cached_size_min_[t] = dmin;
235 
236  // Note that we use the cached value here for EndMin()/StartMax().
237  const IntegerValue new_shifted_start_min = EndMin(t) - dmin;
238  if (new_shifted_start_min != cached_shifted_start_min_[t]) {
239  recompute_energy_profile_ = true;
240  recompute_shifted_start_min_ = true;
241  cached_shifted_start_min_[t] = new_shifted_start_min;
242  }
243  const IntegerValue new_negated_shifted_end_max = -(StartMax(t) + dmin);
244  if (new_negated_shifted_end_max != cached_negated_shifted_end_max_[t]) {
245  recompute_negated_shifted_end_max_ = true;
246  cached_negated_shifted_end_max_[t] = new_negated_shifted_end_max;
247  }
248  return true;
249 }
250 
252  const SchedulingConstraintHelper& other, absl::Span<const int> tasks) {
253  current_time_direction_ = other.current_time_direction_;
254 
255  const int num_tasks = tasks.size();
256  starts_.resize(num_tasks);
257  ends_.resize(num_tasks);
258  minus_ends_.resize(num_tasks);
259  minus_starts_.resize(num_tasks);
260  sizes_.resize(num_tasks);
261  reason_for_presence_.resize(num_tasks);
262  for (int i = 0; i < num_tasks; ++i) {
263  const int t = tasks[i];
264  starts_[i] = other.starts_[t];
265  ends_[i] = other.ends_[t];
266  minus_ends_[i] = other.minus_ends_[t];
267  minus_starts_[i] = other.minus_starts_[t];
268  sizes_[i] = other.sizes_[t];
269  reason_for_presence_[i] = other.reason_for_presence_[t];
270  }
271 
272  InitSortedVectors();
273  return SynchronizeAndSetTimeDirection(true);
274 }
275 
276 void SchedulingConstraintHelper::InitSortedVectors() {
277  const int num_tasks = starts_.size();
278 
279  recompute_all_cache_ = true;
280  recompute_cache_.resize(num_tasks, true);
281 
282  cached_shifted_start_min_.resize(num_tasks);
283  cached_negated_shifted_end_max_.resize(num_tasks);
284  cached_size_min_.resize(num_tasks);
285  cached_start_min_.resize(num_tasks);
286  cached_end_min_.resize(num_tasks);
287  cached_negated_start_max_.resize(num_tasks);
288  cached_negated_end_max_.resize(num_tasks);
289 
290  task_by_increasing_start_min_.resize(num_tasks);
291  task_by_increasing_end_min_.resize(num_tasks);
292  task_by_decreasing_start_max_.resize(num_tasks);
293  task_by_decreasing_end_max_.resize(num_tasks);
294  task_by_increasing_shifted_start_min_.resize(num_tasks);
295  task_by_negated_shifted_end_max_.resize(num_tasks);
296  for (int t = 0; t < num_tasks; ++t) {
297  task_by_increasing_start_min_[t].task_index = t;
298  task_by_increasing_end_min_[t].task_index = t;
299  task_by_decreasing_start_max_[t].task_index = t;
300  task_by_decreasing_end_max_[t].task_index = t;
301  task_by_increasing_shifted_start_min_[t].task_index = t;
302  task_by_negated_shifted_end_max_[t].task_index = t;
303  }
304 
305  recompute_energy_profile_ = true;
306  recompute_shifted_start_min_ = true;
307  recompute_negated_shifted_end_max_ = true;
308 }
309 
311  if (current_time_direction_ != is_forward) {
312  current_time_direction_ = is_forward;
313 
314  std::swap(starts_, minus_ends_);
315  std::swap(ends_, minus_starts_);
316 
317  std::swap(task_by_increasing_start_min_, task_by_decreasing_end_max_);
318  std::swap(task_by_increasing_end_min_, task_by_decreasing_start_max_);
319  std::swap(task_by_increasing_shifted_start_min_,
320  task_by_negated_shifted_end_max_);
321 
322  recompute_energy_profile_ = true;
323  std::swap(cached_start_min_, cached_negated_end_max_);
324  std::swap(cached_end_min_, cached_negated_start_max_);
325  std::swap(cached_shifted_start_min_, cached_negated_shifted_end_max_);
326  std::swap(recompute_shifted_start_min_, recompute_negated_shifted_end_max_);
327  }
328 }
329 
331  bool is_forward) {
332  SetTimeDirection(is_forward);
333  if (recompute_all_cache_) {
334  for (int t = 0; t < recompute_cache_.size(); ++t) {
335  if (!UpdateCachedValues(t)) return false;
336  }
337  } else {
338  for (int t = 0; t < recompute_cache_.size(); ++t) {
339  if (recompute_cache_[t]) {
340  if (!UpdateCachedValues(t)) return false;
341  }
342  }
343  }
344  recompute_all_cache_ = false;
345  return true;
346 }
347 
348 const std::vector<TaskTime>&
350  const int num_tasks = NumTasks();
351  for (int i = 0; i < num_tasks; ++i) {
352  TaskTime& ref = task_by_increasing_start_min_[i];
353  ref.time = StartMin(ref.task_index);
354  }
355  IncrementalSort(task_by_increasing_start_min_.begin(),
356  task_by_increasing_start_min_.end());
357  return task_by_increasing_start_min_;
358 }
359 
360 const std::vector<TaskTime>&
362  const int num_tasks = NumTasks();
363  for (int i = 0; i < num_tasks; ++i) {
364  TaskTime& ref = task_by_increasing_end_min_[i];
365  ref.time = EndMin(ref.task_index);
366  }
367  IncrementalSort(task_by_increasing_end_min_.begin(),
368  task_by_increasing_end_min_.end());
369  return task_by_increasing_end_min_;
370 }
371 
372 const std::vector<TaskTime>&
374  const int num_tasks = NumTasks();
375  for (int i = 0; i < num_tasks; ++i) {
376  TaskTime& ref = task_by_decreasing_start_max_[i];
377  ref.time = StartMax(ref.task_index);
378  }
379  IncrementalSort(task_by_decreasing_start_max_.begin(),
380  task_by_decreasing_start_max_.end(),
381  std::greater<TaskTime>());
382  return task_by_decreasing_start_max_;
383 }
384 
385 const std::vector<TaskTime>&
387  const int num_tasks = NumTasks();
388  for (int i = 0; i < num_tasks; ++i) {
389  TaskTime& ref = task_by_decreasing_end_max_[i];
390  ref.time = EndMax(ref.task_index);
391  }
392  IncrementalSort(task_by_decreasing_end_max_.begin(),
393  task_by_decreasing_end_max_.end(), std::greater<TaskTime>());
394  return task_by_decreasing_end_max_;
395 }
396 
397 const std::vector<TaskTime>&
399  if (recompute_shifted_start_min_) {
400  recompute_shifted_start_min_ = false;
401  const int num_tasks = NumTasks();
402  bool is_sorted = true;
403  IntegerValue previous = kMinIntegerValue;
404  for (int i = 0; i < num_tasks; ++i) {
405  TaskTime& ref = task_by_increasing_shifted_start_min_[i];
406  ref.time = ShiftedStartMin(ref.task_index);
407  is_sorted = is_sorted && ref.time >= previous;
408  previous = ref.time;
409  }
410  if (is_sorted) return task_by_increasing_shifted_start_min_;
411  IncrementalSort(task_by_increasing_shifted_start_min_.begin(),
412  task_by_increasing_shifted_start_min_.end());
413  }
414  return task_by_increasing_shifted_start_min_;
415 }
416 
417 // TODO(user): Avoid recomputing it if nothing changed.
418 const std::vector<SchedulingConstraintHelper::ProfileEvent>&
420  if (energy_profile_.empty()) {
421  const int num_tasks = NumTasks();
422  for (int t = 0; t < num_tasks; ++t) {
423  energy_profile_.push_back(
424  {cached_shifted_start_min_[t], t, /*is_first=*/true});
425  energy_profile_.push_back({cached_end_min_[t], t, /*is_first=*/false});
426  }
427  } else {
428  if (!recompute_energy_profile_) return energy_profile_;
429  for (ProfileEvent& ref : energy_profile_) {
430  const int t = ref.task;
431  if (ref.is_first) {
432  ref.time = cached_shifted_start_min_[t];
433  } else {
434  ref.time = cached_end_min_[t];
435  }
436  }
437  }
438  IncrementalSort(energy_profile_.begin(), energy_profile_.end());
439  recompute_energy_profile_ = false;
440  return energy_profile_;
441 }
442 
443 // Produces a relaxed reason for StartMax(before) < EndMin(after).
445  int after) {
446  AddOtherReason(before);
447  AddOtherReason(after);
448 
449  // The reason will be a linear expression greater than a value. Note that all
450  // coeff must be positive, and we will use the variable lower bound.
451  std::vector<IntegerVariable> vars;
452  std::vector<IntegerValue> coeffs;
453 
454  // Reason for StartMax(before).
455  const IntegerValue smax_before = StartMax(before);
456  if (smax_before >= integer_trail_->UpperBound(starts_[before])) {
457  if (starts_[before].var != kNoIntegerVariable) {
458  vars.push_back(NegationOf(starts_[before].var));
459  coeffs.push_back(starts_[before].coeff);
460  }
461  } else {
462  if (ends_[before].var != kNoIntegerVariable) {
463  vars.push_back(NegationOf(ends_[before].var));
464  coeffs.push_back(ends_[before].coeff);
465  }
466  if (sizes_[before].var != kNoIntegerVariable) {
467  vars.push_back(sizes_[before].var);
468  coeffs.push_back(sizes_[before].coeff);
469  }
470  }
471 
472  // Reason for EndMin(after);
473  const IntegerValue emin_after = EndMin(after);
474  if (emin_after <= integer_trail_->LowerBound(ends_[after])) {
475  if (ends_[after].var != kNoIntegerVariable) {
476  vars.push_back(ends_[after].var);
477  coeffs.push_back(ends_[after].coeff);
478  }
479  } else {
480  if (starts_[after].var != kNoIntegerVariable) {
481  vars.push_back(starts_[after].var);
482  coeffs.push_back(starts_[after].coeff);
483  }
484  if (sizes_[after].var != kNoIntegerVariable) {
485  vars.push_back(sizes_[after].var);
486  coeffs.push_back(sizes_[after].coeff);
487  }
488  }
489 
490  DCHECK_LT(smax_before, emin_after);
491  const IntegerValue slack = emin_after - smax_before - 1;
492  integer_trail_->AppendRelaxedLinearReason(slack, coeffs, vars,
493  &integer_reason_);
494 }
495 
497  CHECK(other_helper_ == nullptr);
498  return integer_trail_->Enqueue(lit, literal_reason_, integer_reason_);
499 }
500 
502  int t, IntegerLiteral lit) {
503  if (IsAbsent(t)) return true;
504  AddOtherReason(t);
506  if (IsOptional(t)) {
507  return integer_trail_->ConditionalEnqueue(
508  PresenceLiteral(t), lit, &literal_reason_, &integer_reason_);
509  }
510  return integer_trail_->Enqueue(lit, literal_reason_, integer_reason_);
511 }
512 
513 // We also run directly the precedence propagator for this variable so that when
514 // we push an interval start for example, we have a chance to push its end.
515 bool SchedulingConstraintHelper::PushIntervalBound(int t, IntegerLiteral lit) {
516  if (!PushIntegerLiteralIfTaskPresent(t, lit)) return false;
517  if (IsAbsent(t)) return true;
518  if (!precedences_->PropagateOutgoingArcs(lit.var)) return false;
519  if (!UpdateCachedValues(t)) return false;
520  return true;
521 }
522 
524  if (starts_[t].var == kNoIntegerVariable) {
525  if (value > starts_[t].constant) return PushTaskAbsence(t);
526  return true;
527  }
528  return PushIntervalBound(t, starts_[t].GreaterOrEqual(value));
529 }
530 
532  if (ends_[t].var == kNoIntegerVariable) {
533  if (value > ends_[t].constant) return PushTaskAbsence(t);
534  return true;
535  }
536  return PushIntervalBound(t, ends_[t].GreaterOrEqual(value));
537 }
538 
540  if (ends_[t].var == kNoIntegerVariable) {
541  if (value < ends_[t].constant) return PushTaskAbsence(t);
542  return true;
543  }
544  return PushIntervalBound(t, ends_[t].LowerOrEqual(value));
545 }
546 
548  integer_trail_->EnqueueLiteral(l, literal_reason_, integer_reason_);
549  return true;
550 }
551 
553  if (IsAbsent(t)) return true;
554  if (!IsOptional(t)) return ReportConflict();
555 
556  AddOtherReason(t);
557 
558  if (IsPresent(t)) {
559  literal_reason_.push_back(Literal(reason_for_presence_[t]).Negated());
560  return ReportConflict();
561  }
563  integer_trail_->EnqueueLiteral(Literal(reason_for_presence_[t]).Negated(),
564  literal_reason_, integer_reason_);
565  return true;
566 }
567 
569  DCHECK_NE(reason_for_presence_[t], kNoLiteralIndex);
570  DCHECK(!IsPresent(t));
571 
572  AddOtherReason(t);
573 
574  if (IsAbsent(t)) {
575  literal_reason_.push_back(Literal(reason_for_presence_[t]));
576  return ReportConflict();
577  }
579  integer_trail_->EnqueueLiteral(Literal(reason_for_presence_[t]),
580  literal_reason_, integer_reason_);
581  return true;
582 }
583 
586  return integer_trail_->ReportConflict(literal_reason_, integer_reason_);
587 }
588 
590  GenericLiteralWatcher* watcher,
591  bool watch_start_max,
592  bool watch_end_max) const {
593  const int num_tasks = starts_.size();
594  for (int t = 0; t < num_tasks; ++t) {
595  watcher->WatchLowerBound(starts_[t], id);
596  watcher->WatchLowerBound(ends_[t], id);
597  watcher->WatchLowerBound(sizes_[t], id);
598  if (watch_start_max) {
599  watcher->WatchUpperBound(starts_[t], id);
600  }
601  if (watch_end_max) {
602  watcher->WatchUpperBound(ends_[t], id);
603  }
604  if (!IsPresent(t) && !IsAbsent(t)) {
605  watcher->WatchLiteral(Literal(reason_for_presence_[t]), id);
606  }
607  }
608 }
609 
610 void SchedulingConstraintHelper::AddOtherReason(int t) {
611  if (other_helper_ == nullptr || already_added_to_other_reasons_[t]) return;
612  already_added_to_other_reasons_[t] = true;
613  const int mapped_t = map_to_other_helper_[t];
614  other_helper_->AddStartMaxReason(mapped_t, event_for_other_helper_);
615  other_helper_->AddEndMinReason(mapped_t, event_for_other_helper_ + 1);
616 }
617 
618 void SchedulingConstraintHelper::ImportOtherReasons() {
619  if (other_helper_ != nullptr) ImportOtherReasons(*other_helper_);
620 }
621 
622 void SchedulingConstraintHelper::ImportOtherReasons(
623  const SchedulingConstraintHelper& other_helper) {
624  literal_reason_.insert(literal_reason_.end(),
625  other_helper.literal_reason_.begin(),
626  other_helper.literal_reason_.end());
627  integer_reason_.insert(integer_reason_.end(),
628  other_helper.integer_reason_.begin(),
629  other_helper.integer_reason_.end());
630 }
631 
633  return absl::StrCat("t=", t, " is_present=", IsPresent(t), " size=[",
634  SizeMin(t).value(), ",", SizeMax(t).value(), "]",
635  " start=[", StartMin(t).value(), ",", StartMax(t).value(),
636  "]", " end=[", EndMin(t).value(), ",", EndMax(t).value(),
637  "]");
638 }
639 
641  IntegerValue start,
642  IntegerValue end) const {
643  return std::min(std::min(end - start, SizeMin(t)),
644  std::min(EndMin(t) - start, end - StartMax(t)));
645 }
646 
648  IntegerValue start_min, IntegerValue start_max, IntegerValue end_min,
649  IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min,
650  const std::vector<LiteralValueValue>& filtered_energy,
651  IntegerValue window_start, IntegerValue window_end) {
652  if (window_end <= window_start) return IntegerValue(0);
653 
654  // Returns zero if the interval do not necessarily overlap.
655  if (end_min <= window_start) return IntegerValue(0);
656  if (start_max >= window_end) return IntegerValue(0);
657  const IntegerValue window_size = window_end - window_start;
658  const IntegerValue simple_energy_min =
659  demand_min * std::min({end_min - window_start, window_end - start_max,
660  size_min, window_size});
661  if (filtered_energy.empty()) return simple_energy_min;
662 
663  IntegerValue result = kMaxIntegerValue;
664  for (const auto [lit, fixed_size, fixed_demand] : filtered_energy) {
665  const IntegerValue alt_end_min = std::max(end_min, start_min + fixed_size);
666  const IntegerValue alt_start_max =
667  std::min(start_max, end_max - fixed_size);
668  const IntegerValue energy_min =
669  fixed_demand *
670  std::min({alt_end_min - window_start, window_end - alt_start_max,
671  fixed_size, window_size});
672  result = std::min(result, energy_min);
673  }
674  if (result == kMaxIntegerValue) return simple_energy_min;
675  return std::max(simple_energy_min, result);
676 }
677 
679  std::vector<AffineExpression> demands, SchedulingConstraintHelper* helper,
680  Model* model)
681  : integer_trail_(model->GetOrCreate<IntegerTrail>()),
682  sat_solver_(model->GetOrCreate<SatSolver>()),
683  assignment_(model->GetOrCreate<SatSolver>()->Assignment()),
684  demands_(std::move(demands)),
685  helper_(helper) {
686  const int num_tasks = helper->NumTasks();
687  linearized_energies_.resize(num_tasks);
688  decomposed_energies_.resize(num_tasks);
689  cached_energies_min_.resize(num_tasks, kMinIntegerValue);
690  cached_energies_max_.resize(num_tasks, kMaxIntegerValue);
691  energy_is_quadratic_.resize(num_tasks, false);
692 
693  // For the special case were demands is empty.
694  if (demands_.size() != num_tasks) return;
695  for (int t = 0; t < num_tasks; ++t) {
696  const AffineExpression size = helper->Sizes()[t];
697  const AffineExpression demand = demands_[t];
698  decomposed_energies_[t] = TryToDecomposeProduct(size, demand, model);
699  }
700 }
701 
702 IntegerValue SchedulingDemandHelper::SimpleEnergyMin(int t) const {
703  if (demands_.empty()) return kMinIntegerValue;
704  return DemandMin(t) * helper_->SizeMin(t);
705 }
706 
707 IntegerValue SchedulingDemandHelper::LinearEnergyMin(int t) const {
708  if (!linearized_energies_[t].has_value()) return kMinIntegerValue;
709  return linearized_energies_[t]->Min(*integer_trail_);
710 }
711 
712 IntegerValue SchedulingDemandHelper::DecomposedEnergyMin(int t) const {
713  if (decomposed_energies_[t].empty()) return kMinIntegerValue;
714  IntegerValue result = kMaxIntegerValue;
715  for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
716  if (assignment_.LiteralIsTrue(lit)) {
717  return fixed_size * fixed_demand;
718  }
719  if (assignment_.LiteralIsFalse(lit)) continue;
720  result = std::min(result, fixed_size * fixed_demand);
721  }
722  DCHECK_NE(result, kMaxIntegerValue);
723  return result;
724 }
725 
726 IntegerValue SchedulingDemandHelper::SimpleEnergyMax(int t) const {
727  if (demands_.empty()) return kMaxIntegerValue;
728  return DemandMax(t) * helper_->SizeMax(t);
729 }
730 
731 IntegerValue SchedulingDemandHelper::LinearEnergyMax(int t) const {
732  if (!linearized_energies_[t].has_value()) return kMaxIntegerValue;
733  return linearized_energies_[t]->Max(*integer_trail_);
734 }
735 
736 IntegerValue SchedulingDemandHelper::DecomposedEnergyMax(int t) const {
737  if (decomposed_energies_[t].empty()) return kMaxIntegerValue;
738  IntegerValue result = kMinIntegerValue;
739  for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
740  if (assignment_.LiteralIsTrue(lit)) {
741  return fixed_size * fixed_demand;
742  }
743  if (assignment_.LiteralIsFalse(lit)) continue;
744  result = std::max(result, fixed_size * fixed_demand);
745  }
746  DCHECK_NE(result, kMinIntegerValue);
747  return result;
748 }
749 
751  const int num_tasks = cached_energies_min_.size();
752  const bool is_at_level_zero = sat_solver_->CurrentDecisionLevel() == 0;
753  for (int t = 0; t < num_tasks; ++t) {
754  // Try to reduce the size of the decomposed energy vector.
755  if (is_at_level_zero) {
756  int new_size = 0;
757  for (int i = 0; i < decomposed_energies_[t].size(); ++i) {
758  if (assignment_.LiteralIsFalse(decomposed_energies_[t][i].literal)) {
759  continue;
760  }
761  decomposed_energies_[t][new_size++] = decomposed_energies_[t][i];
762  }
763  decomposed_energies_[t].resize(new_size);
764  }
765 
766  cached_energies_min_[t] = std::max(
767  {SimpleEnergyMin(t), LinearEnergyMin(t), DecomposedEnergyMin(t)});
768  CHECK_NE(cached_energies_min_[t], kMinIntegerValue);
769  energy_is_quadratic_[t] =
770  decomposed_energies_[t].empty() && !demands_.empty() &&
771  !integer_trail_->IsFixed(demands_[t]) && !helper_->SizeIsFixed(t);
772  cached_energies_max_[t] = std::min(
773  {SimpleEnergyMax(t), LinearEnergyMax(t), DecomposedEnergyMax(t)});
774  CHECK_NE(cached_energies_min_[t], kMaxIntegerValue);
775  }
776 }
777 
778 IntegerValue SchedulingDemandHelper::DemandMin(int t) const {
779  DCHECK_LT(t, demands_.size());
780  return integer_trail_->LowerBound(demands_[t]);
781 }
782 
783 IntegerValue SchedulingDemandHelper::DemandMax(int t) const {
784  DCHECK_LT(t, demands_.size());
785  return integer_trail_->UpperBound(demands_[t]);
786 }
787 
789  return integer_trail_->IsFixed(demands_[t]);
790 }
791 
793  if (value < EnergyMin(t)) {
794  if (helper_->IsOptional(t)) {
795  return helper_->PushTaskAbsence(t);
796  } else {
797  return helper_->ReportConflict();
798  }
799  } else if (!decomposed_energies_[t].empty()) {
800  for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
801  if (fixed_size * fixed_demand > value) {
802  if (assignment_.LiteralIsTrue(lit)) return helper_->ReportConflict();
803  if (assignment_.LiteralIsFalse(lit)) continue;
804  if (!helper_->PushLiteral(lit.Negated())) return false;
805  }
806  }
807  } else if (linearized_energies_[t].has_value() &&
808  linearized_energies_[t]->vars.size() == 1) {
809  const LinearExpression& e = linearized_energies_[t].value();
810  const AffineExpression affine_energy(e.vars[0], e.coeffs[0], e.offset);
811  const IntegerLiteral deduction = affine_energy.LowerOrEqual(value);
812  if (!helper_->PushIntegerLiteralIfTaskPresent(t, deduction)) {
813  return false;
814  }
815  } else {
816  // TODO(user): Propagate if possible.
817  VLOG(3) << "Cumulative energy missed propagation";
818  }
819  return true;
820 }
821 
823  DCHECK_LT(t, demands_.size());
824  if (demands_[t].var != kNoIntegerVariable) {
825  helper_->MutableIntegerReason()->push_back(
826  integer_trail_->LowerBoundAsLiteral(demands_[t].var));
827  }
828 }
829 
831  // We prefer these reason in order.
832  const IntegerValue value = cached_energies_min_[t];
833  if (DecomposedEnergyMin(t) >= value) {
834  auto* reason = helper_->MutableLiteralReason();
835  const int old_size = reason->size();
836  for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
837  if (assignment_.LiteralIsTrue(lit)) {
838  reason->resize(old_size);
839  reason->push_back(lit.Negated());
840  return;
841  } else if (fixed_size * fixed_demand < value &&
842  assignment_.LiteralIsFalse(lit)) {
843  reason->push_back(lit);
844  }
845  }
846  } else if (SimpleEnergyMin(t) >= value) {
848  helper_->AddSizeMinReason(t);
849  } else {
850  DCHECK_GE(LinearEnergyMin(t), value);
851  for (const IntegerVariable var : linearized_energies_[t]->vars) {
852  helper_->MutableIntegerReason()->push_back(
853  integer_trail_->LowerBoundAsLiteral(var));
854  }
855  }
856 }
857 
859  int t, LinearConstraintBuilder* builder) const {
860  if (helper_->IsPresent(t)) {
861  if (!decomposed_energies_[t].empty()) {
862  for (const LiteralValueValue& entry : decomposed_energies_[t]) {
863  if (!builder->AddLiteralTerm(entry.literal, entry.right_value)) {
864  return false;
865  }
866  }
867  } else {
868  builder->AddTerm(demands_[t], IntegerValue(1));
869  }
870  } else if (!helper_->IsAbsent(t)) {
871  return builder->AddLiteralTerm(helper_->PresenceLiteral(t), DemandMin(t));
872  }
873  return true;
874 }
875 
877  const std::vector<LinearExpression>& energies) {
878  const int num_tasks = energies.size();
879  DCHECK_EQ(num_tasks, helper_->NumTasks());
880  linearized_energies_.resize(num_tasks);
881  for (int t = 0; t < num_tasks; ++t) {
882  linearized_energies_[t] = energies[t];
883  if (DEBUG_MODE) {
884  for (const IntegerValue coeff : linearized_energies_[t]->coeffs) {
885  DCHECK_GE(coeff, 0);
886  }
887  }
888  }
889 }
890 
892  int index) {
893  if (decomposed_energies_[index].empty()) return {};
894  if (sat_solver_->CurrentDecisionLevel() == 0) {
895  // CacheAllEnergyValues has already filtered false literals.
896  return decomposed_energies_[index];
897  }
898 
899  // Scan and filter false literals.
900  std::vector<LiteralValueValue> result;
901  for (const auto& e : decomposed_energies_[index]) {
902  if (assignment_.LiteralIsFalse(e.literal)) continue;
903  result.push_back(e);
904  }
905  return result;
906 }
907 
909  const std::vector<std::vector<LiteralValueValue>>& energies) {
910  DCHECK_EQ(energies.size(), helper_->NumTasks());
911  decomposed_energies_ = energies;
912 }
913 
915  int t, IntegerValue window_start, IntegerValue window_end) {
917  helper_->StartMin(t), helper_->StartMax(t), helper_->EndMin(t),
918  helper_->EndMax(t), helper_->SizeMin(t), DemandMin(t),
919  FilteredDecomposedEnergy(t), window_start, window_end);
920 }
921 
922 // Since we usually ask way less often for the reason, we redo the computation
923 // here.
925  int t, IntegerValue window_start, IntegerValue window_end) {
926  const IntegerValue actual_energy_min =
927  EnergyMinInWindow(t, window_start, window_end);
928  if (actual_energy_min == 0) return;
929 
930  // Return simple reason right away if there is no decomposition or the simple
931  // energy is enough.
932  const IntegerValue start_max = helper_->StartMax(t);
933  const IntegerValue end_min = helper_->EndMin(t);
934  const IntegerValue min_overlap =
935  helper_->GetMinOverlap(t, window_start, window_end);
936  const IntegerValue simple_energy_min = DemandMin(t) * min_overlap;
937  if (simple_energy_min == actual_energy_min) {
939  helper_->AddSizeMinReason(t);
940  helper_->AddStartMaxReason(t, start_max);
941  helper_->AddEndMinReason(t, end_min);
942  return;
943  }
944 
945  // TODO(user): only include the one we need?
946  const IntegerValue start_min = helper_->StartMin(t);
947  const IntegerValue end_max = helper_->EndMax(t);
948  DCHECK(!decomposed_energies_[t].empty());
949  helper_->AddStartMinReason(t, start_min);
950  helper_->AddStartMaxReason(t, start_max);
951  helper_->AddEndMinReason(t, end_min);
952  helper_->AddEndMaxReason(t, end_max);
953 
954  auto* literal_reason = helper_->MutableLiteralReason();
955  const int old_size = literal_reason->size();
956 
957  DCHECK(!decomposed_energies_[t].empty());
958  for (const auto [lit, fixed_size, fixed_demand] : decomposed_energies_[t]) {
959  // Should be the same in most cases.
960  if (assignment_.LiteralIsTrue(lit)) {
961  literal_reason->resize(old_size);
962  literal_reason->push_back(lit.Negated());
963  return;
964  }
965  if (assignment_.LiteralIsFalse(lit)) {
966  const IntegerValue alt_em = std::max(end_min, start_min + fixed_size);
967  const IntegerValue alt_sm = std::min(start_max, end_max - fixed_size);
968  const IntegerValue energy_min =
969  fixed_demand *
970  std::min({alt_em - window_start, window_end - alt_sm, fixed_size});
971  if (energy_min >= actual_energy_min) continue;
972  literal_reason->push_back(lit);
973  }
974  }
975 }
976 
977 } // namespace sat
978 } // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
void push_back(const value_type &x)
An Assignment is a variable -> domains mapping, used to report solutions to the user.
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition: integer.h:1673
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1681
void WatchIntegerVariable(IntegerVariable i, int id, int watch_index=-1)
Definition: integer.h:1705
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1699
void SetPropagatorPriority(int id, int priority)
Definition: integer.cc:2309
int Register(PropagatorInterface *propagator)
Definition: integer.cc:2286
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1228
bool IsFixed(IntegerVariable i) const
Definition: integer.h:1565
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1589
bool ReportConflict(absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.h:1004
void EnqueueLiteral(Literal literal, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
Definition: integer.cc:1387
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
void AppendRelaxedLinearReason(IntegerValue slack, absl::Span< const IntegerValue > coeffs, absl::Span< const IntegerVariable > vars, std::vector< IntegerLiteral > *reason) const
Definition: integer.cc:1006
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
ABSL_MUST_USE_RESULT bool ConditionalEnqueue(Literal lit, IntegerLiteral i_lit, std::vector< Literal > *literal_reason, std::vector< IntegerLiteral > *integer_reason)
Definition: integer.cc:1235
void RegisterReversibleClass(ReversibleInterface *rev)
Definition: integer.h:1027
AffineExpression End(IntervalVariable i) const
Definition: intervals.h:101
AffineExpression Start(IntervalVariable i) const
Definition: intervals.h:100
AffineExpression Size(IntervalVariable i) const
Definition: intervals.h:99
SchedulingConstraintHelper * GetOrCreateHelper(const std::vector< IntervalVariable > &variables)
Definition: intervals.cc:116
IntervalVariable CreateInterval(IntegerVariable start, IntegerVariable end, IntegerVariable size, IntegerValue fixed_size, LiteralIndex is_present)
Definition: intervals.cc:39
ABSL_MUST_USE_RESULT bool AddLiteralTerm(Literal lit, IntegerValue coeff=IntegerValue(1))
void AddTerm(IntegerVariable var, IntegerValue coeff)
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
T * TakeOwnership(T *t)
Gives ownership of a pointer to this model.
Definition: sat/model.h:152
bool PropagateOutgoingArcs(IntegerVariable var)
Definition: precedences.cc:121
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition: intervals.cc:496
const std::vector< TaskTime > & TaskByDecreasingEndMax()
Definition: intervals.cc:386
ABSL_MUST_USE_RESULT bool PushTaskAbsence(int t)
Definition: intervals.cc:552
SchedulingConstraintHelper(const std::vector< IntervalVariable > &tasks, Model *model)
Definition: intervals.cc:80
ABSL_MUST_USE_RESULT bool IncreaseStartMin(int t, IntegerValue value)
Definition: intervals.cc:523
ABSL_MUST_USE_RESULT bool DecreaseEndMax(int t, IntegerValue value)
Definition: intervals.cc:539
const std::vector< TaskTime > & TaskByIncreasingStartMin()
Definition: intervals.cc:349
void AddStartMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:722
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:589
bool IncrementalPropagate(const std::vector< int > &watch_indices) final
Definition: intervals.cc:142
const std::vector< TaskTime > & TaskByIncreasingEndMin()
Definition: intervals.cc:361
ABSL_MUST_USE_RESULT bool IncreaseEndMin(int t, IntegerValue value)
Definition: intervals.cc:531
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:348
ABSL_MUST_USE_RESULT bool ResetFromSubset(const SchedulingConstraintHelper &other, absl::Span< const int > tasks)
Definition: intervals.cc:251
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
Definition: intervals.cc:501
void RegisterWith(GenericLiteralWatcher *watcher)
Definition: intervals.cc:161
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:736
IntegerValue GetMinOverlap(int t, IntegerValue start, IntegerValue end) const
Definition: intervals.cc:640
void AddSizeMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:715
ABSL_MUST_USE_RESULT bool PushLiteral(Literal l)
Definition: intervals.cc:547
void ImportOtherReasons(const SchedulingConstraintHelper &other_helper)
Definition: intervals.cc:622
const std::vector< ProfileEvent > & GetEnergyProfile()
Definition: intervals.cc:419
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:330
const std::vector< TaskTime > & TaskByDecreasingStartMax()
Definition: intervals.cc:373
ABSL_MUST_USE_RESULT bool PushTaskPresence(int t)
Definition: intervals.cc:568
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:744
const std::vector< TaskTime > & TaskByIncreasingShiftedStartMin()
Definition: intervals.cc:398
void AddReasonForBeingBefore(int before, int after)
Definition: intervals.cc:444
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:375
void AddStartMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:729
void OverrideLinearizedEnergies(const std::vector< LinearExpression > &energies)
Definition: intervals.cc:876
SchedulingDemandHelper(std::vector< AffineExpression > demands, SchedulingConstraintHelper *helper, Model *model)
Definition: intervals.cc:678
void AddEnergyMinInWindowReason(int t, IntegerValue window_start, IntegerValue window_end)
Definition: intervals.cc:924
ABSL_MUST_USE_RESULT bool AddLinearizedDemand(int t, LinearConstraintBuilder *builder) const
Definition: intervals.cc:858
std::vector< LiteralValueValue > FilteredDecomposedEnergy(int index)
Definition: intervals.cc:891
ABSL_MUST_USE_RESULT bool DecreaseEnergyMax(int t, IntegerValue value)
Definition: intervals.cc:792
void OverrideDecomposedEnergies(const std::vector< std::vector< LiteralValueValue >> &energies)
Definition: intervals.cc:908
IntegerValue EnergyMinInWindow(int t, IntegerValue window_start, IntegerValue window_end)
Definition: intervals.cc:914
bool LiteralIsTrue(Literal literal) const
Definition: sat_base.h:164
bool LiteralIsFalse(Literal literal) const
Definition: sat_base.h:161
int64_t value
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
int index
const bool DEBUG_MODE
Definition: macros.h:24
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
Definition: id_map.h:269
std::function< void(Model *)> GreaterOrEqual(IntegerVariable v, int64_t lb)
Definition: integer.h:1803
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
const LiteralIndex kNoLiteralIndex(-1)
void LoadConditionalLinearConstraint(const absl::Span< const Literal > enforcement_literals, const LinearConstraint &cst, Model *model)
Definition: integer_expr.h:606
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
std::function< void(Model *)> LowerOrEqual(IntegerVariable v, int64_t ub)
Definition: integer.h:1818
IntegerValue ComputeEnergyMinInWindow(IntegerValue start_min, IntegerValue start_max, IntegerValue end_min, IntegerValue end_max, IntegerValue size_min, IntegerValue demand_min, const std::vector< LiteralValueValue > &filtered_energy, IntegerValue window_start, IntegerValue window_end)
Definition: intervals.cc:647
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Definition: integer.cc:46
std::vector< LiteralValueValue > TryToDecomposeProduct(const AffineExpression &left, const AffineExpression &right, Model *model)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
Definition: integer.h:1775
Collection of objects used to extend the Constraint Solver library.
void IncrementalSort(int max_comparisons, Iterator begin, Iterator end, Compare comp=Compare{}, bool is_stable=false)
Definition: sort.h:46
int64_t demand
Definition: resource.cc:126
Rev< int64_t > start_max
Rev< int64_t > end_max
Rev< int64_t > start_min
Rev< int64_t > end_min
std::optional< int64_t > end
int64_t start
IntegerLiteral LowerOrEqual(IntegerValue bound) const
Definition: integer.h:1544
IntegerLiteral Negated() const
Definition: integer.h:1519
#define VLOG(verboselevel)
Definition: vlog.h:39