OR-Tools  9.2
cumulative_energy.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 #include <memory>
17 #include <utility>
18 
19 #include "ortools/base/int_type.h"
21 #include "ortools/base/logging.h"
22 #include "ortools/sat/sat_base.h"
23 
24 namespace operations_research {
25 namespace sat {
26 
27 void AddCumulativeEnergyConstraint(std::vector<AffineExpression> energies,
30  Model* model) {
31  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
32  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
33 
35  std::move(energies), capacity, integer_trail, helper);
36  constraint->RegisterWith(watcher);
37  model->TakeOwnership(constraint);
38 }
39 
40 void AddCumulativeOverloadChecker(const std::vector<AffineExpression>& demands,
43  Model* model) {
44  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
45  auto* integer_trail = model->GetOrCreate<IntegerTrail>();
46 
47  std::vector<AffineExpression> energies;
48  const int num_tasks = helper->NumTasks();
49  CHECK_EQ(demands.size(), num_tasks);
50  for (int t = 0; t < num_tasks; ++t) {
51  const AffineExpression size = helper->Sizes()[t];
52  const AffineExpression demand = demands[t];
53 
54  if (demand.var == kNoIntegerVariable && size.var == kNoIntegerVariable) {
55  CHECK_GE(demand.constant, 0);
56  CHECK_GE(size.constant, 0);
57  energies.emplace_back(demand.constant * size.constant);
58  } else if (demand.var == kNoIntegerVariable) {
59  CHECK_GE(demand.constant, 0);
60  energies.push_back(size.MultipliedBy(demand.constant));
61  } else if (size.var == kNoIntegerVariable) {
62  CHECK_GE(size.constant, 0);
63  energies.push_back(demand.MultipliedBy(size.constant));
64  } else {
65  // The case where both demand and size are variable should be rare.
66  //
67  // TODO(user): Handle when needed by creating an intermediate product
68  // variable equal to demand * size. Note that because of the affine
69  // expression, we do need some custom code for this.
70  LOG(INFO) << "Overload checker with variable demand and variable size "
71  "is currently not implemented. Skipping.";
72  return;
73  }
74  }
75 
76  CumulativeEnergyConstraint* constraint =
77  new CumulativeEnergyConstraint(energies, capacity, integer_trail, helper);
78  constraint->RegisterWith(watcher);
79  model->TakeOwnership(constraint);
80 }
81 
83  std::vector<AffineExpression> energies, AffineExpression capacity,
84  IntegerTrail* integer_trail, SchedulingConstraintHelper* helper)
85  : energies_(std::move(energies)),
86  capacity_(capacity),
87  integer_trail_(integer_trail),
88  helper_(helper),
89  theta_tree_() {
90  const int num_tasks = helper_->NumTasks();
91  CHECK_EQ(energies_.size(), num_tasks);
92  task_to_start_event_.resize(num_tasks);
93 }
94 
96  const int id = watcher->Register(this);
97  helper_->WatchAllTasks(id, watcher);
99 }
100 
102  // This only uses one time direction, but the helper might be used elsewhere.
103  // TODO(user): just keep the current direction?
104  if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
105 
106  const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
107  // TODO(user): force capacity_max >= 0, fail/remove optionals when 0.
108  if (capacity_max <= 0) return true;
109 
110  // Set up theta tree.
111  start_event_task_time_.clear();
112  int num_events = 0;
113  for (const auto task_time : helper_->TaskByIncreasingStartMin()) {
114  const int task = task_time.task_index;
115  if (helper_->IsAbsent(task) ||
116  integer_trail_->UpperBound(energies_[task]) == 0) {
117  task_to_start_event_[task] = -1;
118  continue;
119  }
120  start_event_task_time_.emplace_back(task_time);
121  task_to_start_event_[task] = num_events;
122  num_events++;
123  }
124  start_event_is_present_.assign(num_events, false);
125  theta_tree_.Reset(num_events);
126 
127  bool tree_has_mandatory_intervals = false;
128 
129  // Main loop: insert tasks by increasing end_max, check for overloads.
130  for (const auto task_time :
132  const int current_task = task_time.task_index;
133  const IntegerValue current_end = task_time.time;
134  if (task_to_start_event_[current_task] == -1) continue;
135 
136  // Add the current task to the tree.
137  {
138  const int current_event = task_to_start_event_[current_task];
139  const IntegerValue start_min = start_event_task_time_[current_event].time;
140  const bool is_present = helper_->IsPresent(current_task);
141  start_event_is_present_[current_event] = is_present;
142  if (is_present) {
143  tree_has_mandatory_intervals = true;
144  theta_tree_.AddOrUpdateEvent(
145  current_event, start_min * capacity_max,
146  integer_trail_->LowerBound(energies_[current_task]),
147  integer_trail_->UpperBound(energies_[current_task]));
148  } else {
149  theta_tree_.AddOrUpdateOptionalEvent(
150  current_event, start_min * capacity_max,
151  integer_trail_->UpperBound(energies_[current_task]));
152  }
153  }
154 
155  if (tree_has_mandatory_intervals) {
156  // Find the critical interval.
157  const IntegerValue envelope = theta_tree_.GetEnvelope();
158  const int critical_event =
159  theta_tree_.GetMaxEventWithEnvelopeGreaterThan(envelope - 1);
160  const IntegerValue window_start =
161  start_event_task_time_[critical_event].time;
162  const IntegerValue window_end = current_end;
163  const IntegerValue window_size = window_end - window_start;
164  if (window_size == 0) continue;
165  const IntegerValue new_capacity_min =
166  CeilRatio(envelope - window_start * capacity_max, window_size);
167 
168  // Push the new capacity min, note that this can fail if it go above the
169  // maximum capacity.
170  //
171  // TODO(user): We do not need the capacity max in the reason, but by using
172  // a lower one, we could maybe have propagated more the minimum capacity.
173  // investigate.
174  if (new_capacity_min > integer_trail_->LowerBound(capacity_)) {
175  helper_->ClearReason();
176  for (int event = critical_event; event < num_events; event++) {
177  if (start_event_is_present_[event]) {
178  const int task = start_event_task_time_[event].task_index;
179  helper_->AddPresenceReason(task);
180  if (energies_[task].var != kNoIntegerVariable) {
181  helper_->MutableIntegerReason()->push_back(
182  integer_trail_->LowerBoundAsLiteral(energies_[task].var));
183  }
184  helper_->AddStartMinReason(task, window_start);
185  helper_->AddEndMaxReason(task, window_end);
186  }
187  }
188  if (capacity_.var == kNoIntegerVariable) {
189  return helper_->ReportConflict();
190  } else {
191  if (!helper_->PushIntegerLiteral(
192  capacity_.GreaterOrEqual(new_capacity_min))) {
193  return false;
194  }
195  }
196  }
197  }
198 
199  // Reduce energy of all tasks whose max energy would exceed an interval
200  // ending at current_end.
201  while (theta_tree_.GetOptionalEnvelope() > current_end * capacity_max) {
202  // Some task's max energy is too high, reduce its maximal energy.
203  // Explain with tasks present in the critical interval.
204  // If it is optional, it might get excluded, in that case,
205  // remove it from the tree.
206  // TODO(user): This could be done lazily.
207  // TODO(user): the same required task can have its energy pruned
208  // several times, making this algorithm O(n^2 log n). Is there a way
209  // to get the best pruning in one go? This looks like edge-finding not
210  // being able to converge in one pass, so it might not be easy.
211  helper_->ClearReason();
212  int critical_event;
213  int event_with_new_energy_max;
214  IntegerValue new_energy_max;
216  current_end * capacity_max, &critical_event,
217  &event_with_new_energy_max, &new_energy_max);
218 
219  const IntegerValue window_start =
220  start_event_task_time_[critical_event].time;
221 
222  // TODO(user): Improve window_end using envelope of critical event.
223  const IntegerValue window_end = current_end;
224  for (int event = critical_event; event < num_events; event++) {
225  if (start_event_is_present_[event]) {
226  if (event == event_with_new_energy_max) continue;
227  const int task = start_event_task_time_[event].task_index;
228  helper_->AddPresenceReason(task);
229  helper_->AddStartMinReason(task, window_start);
230  helper_->AddEndMaxReason(task, window_end);
231  if (energies_[task].var != kNoIntegerVariable) {
232  helper_->MutableIntegerReason()->push_back(
233  integer_trail_->LowerBoundAsLiteral(energies_[task].var));
234  }
235  }
236  }
237  if (capacity_.var != kNoIntegerVariable) {
238  helper_->MutableIntegerReason()->push_back(
239  integer_trail_->UpperBoundAsLiteral(capacity_.var));
240  }
241 
242  const int task_with_new_energy_max =
243  start_event_task_time_[event_with_new_energy_max].task_index;
244  helper_->AddStartMinReason(task_with_new_energy_max, window_start);
245  helper_->AddEndMaxReason(task_with_new_energy_max, window_end);
246 
247  if (new_energy_max <
248  integer_trail_->LowerBound(energies_[task_with_new_energy_max])) {
249  if (helper_->IsOptional(task_with_new_energy_max)) {
250  return helper_->PushTaskAbsence(task_with_new_energy_max);
251  } else {
252  return helper_->ReportConflict();
253  }
254  } else {
255  const IntegerLiteral deduction =
256  energies_[task_with_new_energy_max].LowerOrEqual(new_energy_max);
257  if (!helper_->PushIntegerLiteralIfTaskPresent(task_with_new_energy_max,
258  deduction)) {
259  return false;
260  }
261  }
262 
263  if (helper_->IsPresent(task_with_new_energy_max)) {
264  theta_tree_.AddOrUpdateEvent(
265  task_to_start_event_[task_with_new_energy_max],
266  start_event_task_time_[event_with_new_energy_max].time *
267  capacity_max,
268  integer_trail_->LowerBound(energies_[task_with_new_energy_max]),
269  new_energy_max);
270  } else {
271  theta_tree_.RemoveEvent(event_with_new_energy_max);
272  }
273  }
274  }
275  return true;
276 }
277 
279  IntegerVariable var, IntegerValue offset, AffineExpression capacity,
280  const std::vector<AffineExpression> demands,
281  const std::vector<int> subtasks, IntegerTrail* integer_trail,
283  : var_to_push_(var),
284  offset_(offset),
285  capacity_(capacity),
286  demands_(demands),
287  subtasks_(subtasks),
288  integer_trail_(integer_trail),
289  helper_(helper) {
290  is_in_subtasks_.assign(helper->NumTasks(), false);
291  for (const int t : subtasks) is_in_subtasks_[t] = true;
292 }
293 
295  const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
296 
297  if (!helper_->SynchronizeAndSetTimeDirection(true)) {
298  return false;
299  }
300 
301  // Compute the total energy.
302  // Compute the profile deltas in energy if all task are packed left.
303  IntegerValue energy_after_time(0);
304  std::vector<std::pair<IntegerValue, IntegerValue>> energy_changes;
305  for (int t = 0; t < helper_->NumTasks(); ++t) {
306  if (!is_in_subtasks_[t]) continue;
307  if (!helper_->IsPresent(t)) continue;
308  if (helper_->SizeMin(t) == 0) continue;
309 
310  const IntegerValue demand = integer_trail_->LowerBound(demands_[t]);
311  if (demand == 0) continue;
312 
313  const IntegerValue size_min = helper_->SizeMin(t);
314  const IntegerValue end_min = helper_->EndMin(t);
315  energy_changes.push_back({end_min - size_min, demand});
316  energy_changes.push_back({end_min, -demand});
317  energy_after_time += size_min * demand;
318  }
319 
320  IntegerValue best_time = kMinIntegerValue;
321  IntegerValue best_end_min = kMinIntegerValue;
322 
323  IntegerValue previous_time = kMinIntegerValue;
324  IntegerValue profile_height(0);
325 
326  // We consider the energy after a given time.
327  // From that we derive a bound on the end_min of the subtasks.
328  std::sort(energy_changes.begin(), energy_changes.end());
329  for (int i = 0; i < energy_changes.size();) {
330  const IntegerValue time = energy_changes[i].first;
331  if (profile_height > 0) {
332  energy_after_time -= profile_height * (time - previous_time);
333  }
334  previous_time = time;
335 
336  while (i < energy_changes.size() && energy_changes[i].first == time) {
337  profile_height += energy_changes[i].second;
338  ++i;
339  }
340 
341  // We prefer higher time in case of ties since that should reduce the
342  // explanation size.
343  const IntegerValue end_min =
344  time + CeilRatio(energy_after_time, capacity_max);
345  if (end_min >= best_end_min) {
346  best_time = time;
347  best_end_min = end_min;
348  }
349  }
350  CHECK_EQ(profile_height, 0);
351  CHECK_EQ(energy_after_time, 0);
352 
353  if (best_end_min + offset_ > integer_trail_->LowerBound(var_to_push_)) {
354  // Compute the reason.
355  // It is just the reason for the energy after time.
356  helper_->ClearReason();
357  for (int t = 0; t < helper_->NumTasks(); ++t) {
358  if (!is_in_subtasks_[t]) continue;
359  if (!helper_->IsPresent(t)) continue;
360  if (helper_->SizeMin(t) == 0) continue;
361 
362  const IntegerValue demand = integer_trail_->LowerBound(demands_[t]);
363  if (demand == 0) continue;
364 
365  const IntegerValue size_min = helper_->SizeMin(t);
366  const IntegerValue end_min = helper_->EndMin(t);
367  helper_->AddEndMinReason(t, std::min(best_time + size_min, end_min));
368  helper_->AddSizeMinReason(t);
369  helper_->AddPresenceReason(t);
370  if (demands_[t].var != kNoIntegerVariable) {
371  helper_->MutableIntegerReason()->push_back(
372  integer_trail_->LowerBoundAsLiteral(demands_[t].var));
373  }
374  }
375  if (capacity_.var != kNoIntegerVariable) {
376  helper_->MutableIntegerReason()->push_back(
377  integer_trail_->UpperBoundAsLiteral(capacity_.var));
378  }
379 
380  // Propagate.
382  var_to_push_, best_end_min + offset_))) {
383  return false;
384  }
385  }
386 
387  return true;
388 }
389 
391  GenericLiteralWatcher* watcher) {
392  helper_->SetTimeDirection(true);
393  const int id = watcher->Register(this);
394  watcher->WatchUpperBound(capacity_, id);
395  for (const int t : subtasks_) {
396  watcher->WatchLowerBound(helper_->Starts()[t], id);
397  watcher->WatchLowerBound(helper_->Ends()[t], id);
398  watcher->WatchLowerBound(helper_->Sizes()[t], id);
399  watcher->WatchLowerBound(demands_[t], id);
400  if (!helper_->IsPresent(t) && !helper_->IsAbsent(t)) {
401  watcher->WatchLiteral(helper_->PresenceLiteral(t), id);
402  }
403  }
404 }
405 
406 } // namespace sat
407 } // namespace operations_research
AffineExpression MultipliedBy(IntegerValue multiplier) const
Definition: integer.h:257
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:583
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
Definition: intervals.cc:431
void AddStartMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:561
int64_t min
Definition: alldiff_cst.cc:139
void AddCumulativeOverloadChecker(const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper, Model *model)
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
Definition: integer.h:1406
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:295
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition: intervals.cc:426
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
const std::vector< TaskTime > & TaskByDecreasingEndMax()
Definition: intervals.cc:350
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1472
void AddCumulativeEnergyConstraint(std::vector< AffineExpression > energies, AffineExpression capacity, SchedulingConstraintHelper *helper, Model *model)
void AddOrUpdateEvent(int event, IntegerType initial_envelope, IntegerType energy_min, IntegerType energy_max)
Definition: theta_tree.cc:112
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
#define LOG(severity)
Definition: base/logging.h:420
Rev< int64_t > start_min
GRBmodel * model
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:508
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1467
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:575
ReverseView< Container > reversed_view(const Container &c)
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1559
CumulativeIsAfterSubsetConstraint(IntegerVariable var, IntegerValue offset, AffineExpression capacity, const std::vector< AffineExpression > demands, const std::vector< int > subtasks, IntegerTrail *integer_trail, SchedulingConstraintHelper *helper)
ABSL_MUST_USE_RESULT bool PushTaskAbsence(int t)
Definition: intervals.cc:471
Rev< int64_t > end_min
int64_t demand
Definition: resource.cc:125
int64_t capacity
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition: integer.h:1551
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:337
void RegisterWith(GenericLiteralWatcher *watcher)
int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const
Definition: theta_tree.cc:180
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:83
const std::vector< TaskTime > & TaskByIncreasingStartMin()
Definition: intervals.cc:313
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:336
int Register(PropagatorInterface *propagator)
Definition: integer.cc:1995
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1439
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:313
Collection of objects used to extend the Constraint Solver library.
const IntegerVariable kNoIntegerVariable(-1)
int64_t time
Definition: resource.cc:1691
const int64_t offset_
Definition: interval.cc:2108
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1377
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1577
CumulativeEnergyConstraint(std::vector< AffineExpression > energies, AffineExpression capacity, IntegerTrail *integer_trail, SchedulingConstraintHelper *helper)
IntVar * var
Definition: expr_array.cc:1874
void GetEventsWithOptionalEnvelopeGreaterThan(IntegerType target_envelope, int *critical_event, int *optional_event, IntegerType *available_energy) const
Definition: theta_tree.cc:190
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:338
const int INFO
Definition: log_severity.h:31
void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt, IntegerType energy_max)
Definition: theta_tree.cc:125