OR-Tools  9.6
cumulative_energy.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 
15 
16 #include <algorithm>
17 #include <utility>
18 #include <vector>
19 
21 #include "ortools/base/logging.h"
23 #include "ortools/sat/integer.h"
24 #include "ortools/sat/intervals.h"
25 #include "ortools/sat/model.h"
26 #include "ortools/sat/theta_tree.h"
28 
29 namespace operations_research {
30 namespace sat {
31 
34  SchedulingDemandHelper* demands,
35  Model* model) {
36  auto* watcher = model->GetOrCreate<GenericLiteralWatcher>();
37  CumulativeEnergyConstraint* constraint =
38  new CumulativeEnergyConstraint(capacity, helper, demands, model);
39  constraint->RegisterWith(watcher);
40  model->TakeOwnership(constraint);
41 }
42 
46  : capacity_(capacity),
47  integer_trail_(model->GetOrCreate<IntegerTrail>()),
48  helper_(helper),
49  demands_(demands) {
50  const int num_tasks = helper_->NumTasks();
51  task_to_start_event_.resize(num_tasks);
52 }
53 
55  const int id = watcher->Register(this);
56  helper_->WatchAllTasks(id, watcher);
57  watcher->SetPropagatorPriority(id, 2);
59 }
60 
62  // This only uses one time direction, but the helper might be used elsewhere.
63  // TODO(user): just keep the current direction?
64  if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
65  demands_->CacheAllEnergyValues();
66 
67  const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
68  // TODO(user): force capacity_max >= 0, fail/remove optionals when 0.
69  if (capacity_max <= 0) return true;
70 
71  // Set up theta tree.
72  start_event_task_time_.clear();
73  int num_events = 0;
74  for (const auto task_time : helper_->TaskByIncreasingStartMin()) {
75  const int task = task_time.task_index;
76  if (helper_->IsAbsent(task) || demands_->EnergyMax(task) == 0) {
77  task_to_start_event_[task] = -1;
78  continue;
79  }
80  start_event_task_time_.emplace_back(task_time);
81  task_to_start_event_[task] = num_events;
82  num_events++;
83  }
84  start_event_is_present_.assign(num_events, false);
85  theta_tree_.Reset(num_events);
86 
87  bool tree_has_mandatory_intervals = false;
88 
89  // Main loop: insert tasks by increasing end_max, check for overloads.
90  for (const auto task_time :
92  const int current_task = task_time.task_index;
93  const IntegerValue current_end = task_time.time;
94  if (task_to_start_event_[current_task] == -1) continue;
95 
96  // Add the current task to the tree.
97  {
98  const int current_event = task_to_start_event_[current_task];
99  const IntegerValue start_min = start_event_task_time_[current_event].time;
100  const bool is_present = helper_->IsPresent(current_task);
101  start_event_is_present_[current_event] = is_present;
102  if (is_present) {
103  tree_has_mandatory_intervals = true;
104  theta_tree_.AddOrUpdateEvent(current_event, start_min * capacity_max,
105  demands_->EnergyMin(current_task),
106  demands_->EnergyMax(current_task));
107  } else {
108  theta_tree_.AddOrUpdateOptionalEvent(current_event,
109  start_min * capacity_max,
110  demands_->EnergyMax(current_task));
111  }
112  }
113 
114  if (tree_has_mandatory_intervals) {
115  // Find the critical interval.
116  const IntegerValue envelope = theta_tree_.GetEnvelope();
117  const int critical_event =
118  theta_tree_.GetMaxEventWithEnvelopeGreaterThan(envelope - 1);
119  const IntegerValue window_start =
120  start_event_task_time_[critical_event].time;
121  const IntegerValue window_end = current_end;
122  const IntegerValue window_size = window_end - window_start;
123  if (window_size == 0) continue;
124  const IntegerValue new_capacity_min =
125  CeilRatio(envelope - window_start * capacity_max, window_size);
126 
127  // Push the new capacity min, note that this can fail if it go above the
128  // maximum capacity.
129  //
130  // TODO(user): We do not need the capacity max in the reason, but by using
131  // a lower one, we could maybe have propagated more the minimum capacity.
132  // investigate.
133  if (new_capacity_min > integer_trail_->LowerBound(capacity_)) {
134  helper_->ClearReason();
135  for (int event = critical_event; event < num_events; event++) {
136  if (start_event_is_present_[event]) {
137  const int task = start_event_task_time_[event].task_index;
138  helper_->AddPresenceReason(task);
139  demands_->AddEnergyMinReason(task);
140  helper_->AddStartMinReason(task, window_start);
141  helper_->AddEndMaxReason(task, window_end);
142  }
143  }
144  if (capacity_.var == kNoIntegerVariable) {
145  return helper_->ReportConflict();
146  } else {
147  if (!helper_->PushIntegerLiteral(
148  capacity_.GreaterOrEqual(new_capacity_min))) {
149  return false;
150  }
151  }
152  }
153  }
154 
155  // Reduce energy of all tasks whose max energy would exceed an interval
156  // ending at current_end.
157  while (theta_tree_.GetOptionalEnvelope() > current_end * capacity_max) {
158  // Some task's max energy is too high, reduce its maximal energy.
159  // Explain with tasks present in the critical interval.
160  // If it is optional, it might get excluded, in that case,
161  // remove it from the tree.
162  // TODO(user): This could be done lazily.
163  // TODO(user): the same required task can have its energy pruned
164  // several times, making this algorithm O(n^2 log n). Is there a way
165  // to get the best pruning in one go? This looks like edge-finding not
166  // being able to converge in one pass, so it might not be easy.
167  helper_->ClearReason();
168  int critical_event;
169  int event_with_new_energy_max;
170  IntegerValue new_energy_max;
172  current_end * capacity_max, &critical_event,
173  &event_with_new_energy_max, &new_energy_max);
174 
175  const IntegerValue window_start =
176  start_event_task_time_[critical_event].time;
177 
178  // TODO(user): Improve window_end using envelope of critical event.
179  const IntegerValue window_end = current_end;
180  for (int event = critical_event; event < num_events; event++) {
181  if (start_event_is_present_[event]) {
182  if (event == event_with_new_energy_max) continue;
183  const int task = start_event_task_time_[event].task_index;
184  helper_->AddPresenceReason(task);
185  helper_->AddStartMinReason(task, window_start);
186  helper_->AddEndMaxReason(task, window_end);
187  demands_->AddEnergyMinReason(task);
188  }
189  }
190  if (capacity_.var != kNoIntegerVariable) {
191  helper_->MutableIntegerReason()->push_back(
192  integer_trail_->UpperBoundAsLiteral(capacity_.var));
193  }
194 
195  const int task_with_new_energy_max =
196  start_event_task_time_[event_with_new_energy_max].task_index;
197  helper_->AddStartMinReason(task_with_new_energy_max, window_start);
198  helper_->AddEndMaxReason(task_with_new_energy_max, window_end);
199  if (!demands_->DecreaseEnergyMax(task_with_new_energy_max,
200  new_energy_max)) {
201  return false;
202  }
203 
204  if (helper_->IsPresent(task_with_new_energy_max)) {
205  theta_tree_.AddOrUpdateEvent(
206  task_to_start_event_[task_with_new_energy_max],
207  start_event_task_time_[event_with_new_energy_max].time *
208  capacity_max,
209  demands_->EnergyMin(task_with_new_energy_max), new_energy_max);
210  } else {
211  theta_tree_.RemoveEvent(event_with_new_energy_max);
212  }
213  }
214  }
215  return true;
216 }
217 
219  IntegerVariable var, AffineExpression capacity,
220  const std::vector<int>& subtasks, const std::vector<IntegerValue>& offsets,
222  Model* model)
223  : var_to_push_(var),
224  capacity_(capacity),
225  subtasks_(subtasks),
226  integer_trail_(model->GetOrCreate<IntegerTrail>()),
227  helper_(helper),
228  demands_(demands) {
229  is_in_subtasks_.assign(helper->NumTasks(), false);
230  task_offsets_.assign(helper->NumTasks(), 0);
231  for (int i = 0; i < subtasks.size(); ++i) {
232  is_in_subtasks_[subtasks[i]] = true;
233  task_offsets_[subtasks[i]] = offsets[i];
234  }
235 }
236 
238  if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
239 
240  IntegerValue best_time = kMaxIntegerValue;
241  IntegerValue best_bound = kMinIntegerValue;
242 
243  IntegerValue previous_time = kMaxIntegerValue;
244  IntegerValue energy_after_time(0);
245  IntegerValue profile_height(0);
246 
247  // If the capacity_max is low enough, we compute the exact possible subset
248  // of reachable "sum of demands" of all tasks used in the energy. We will use
249  // the highest reachable as the capacity max.
250  const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
251  dp_.Reset(capacity_max.value());
252 
253  // We consider the energy after a given time.
254  // From that we derive a bound on the end_min of the subtasks.
255  const auto& profile = helper_->GetEnergyProfile();
256  IntegerValue min_offset = kMaxIntegerValue;
257  for (int i = profile.size() - 1; i >= 0;) {
258  // Skip tasks not relevant for this propagator.
259  {
260  const int t = profile[i].task;
261  if (!helper_->IsPresent(t) || !is_in_subtasks_[t]) {
262  --i;
263  continue;
264  }
265  }
266 
267  const IntegerValue time = profile[i].time;
268  if (profile_height > 0) {
269  energy_after_time += profile_height * (previous_time - time);
270  }
271  previous_time = time;
272 
273  // Any newly introduced tasks will only change the reachable capa max or
274  // the min_offset on the next time point.
275  const IntegerValue saved_capa_max = dp_.CurrentMax();
276  const IntegerValue saved_min_offset = min_offset;
277 
278  for (; i >= 0 && profile[i].time == time; --i) {
279  // Skip tasks not relevant for this propagator.
280  const int t = profile[i].task;
281  if (!helper_->IsPresent(t) || !is_in_subtasks_[t]) continue;
282 
283  min_offset = std::min(min_offset, task_offsets_[t]);
284  const IntegerValue demand_min = demands_->DemandMin(t);
285  if (profile[i].is_first) {
286  profile_height -= demand_min;
287  } else {
288  profile_height += demand_min;
289  if (demands_->Demands()[t].IsConstant()) {
290  dp_.Add(demand_min.value());
291  } else {
292  dp_.Add(capacity_max.value()); // Abort DP.
293  }
294  }
295  }
296 
297  // We prefer higher time in case of ties since that should reduce the
298  // explanation size.
299  //
300  // Note that if the energy is zero, we don't push anything. Other propagator
301  // will make sure that the end_min is greater than the end_min of any of
302  // the task considered here. TODO(user): actually, we will push using the
303  // last task, and the reason will be non-optimal, fix.
304  if (energy_after_time == 0) continue;
305  DCHECK_GT(saved_capa_max, 0);
306  DCHECK_LT(saved_min_offset, kMaxIntegerValue);
307  const IntegerValue end_min_with_offset =
308  time + CeilRatio(energy_after_time, saved_capa_max) + saved_min_offset;
309  if (end_min_with_offset > best_bound) {
310  best_time = time;
311  best_bound = end_min_with_offset;
312  }
313  }
314  DCHECK_EQ(profile_height, 0);
315 
316  if (best_bound == kMinIntegerValue) return true;
317  if (best_bound > integer_trail_->LowerBound(var_to_push_)) {
318  // Compute the reason.
319  // It is just the reason for the energy after time.
320  helper_->ClearReason();
321  for (int t = 0; t < helper_->NumTasks(); ++t) {
322  if (!is_in_subtasks_[t]) continue;
323  if (!helper_->IsPresent(t)) continue;
324 
325  const IntegerValue size_min = helper_->SizeMin(t);
326  if (size_min == 0) continue;
327 
328  const IntegerValue demand_min = demands_->DemandMin(t);
329  if (demand_min == 0) continue;
330 
331  const IntegerValue end_min = helper_->EndMin(t);
332  if (end_min <= best_time) continue;
333 
334  helper_->AddEndMinReason(t, std::min(best_time + size_min, end_min));
335  helper_->AddSizeMinReason(t);
336  helper_->AddPresenceReason(t);
337  demands_->AddDemandMinReason(t);
338  }
339  if (capacity_.var != kNoIntegerVariable) {
340  helper_->MutableIntegerReason()->push_back(
341  integer_trail_->UpperBoundAsLiteral(capacity_.var));
342  }
343 
344  // Propagate.
345  if (!helper_->PushIntegerLiteral(
346  IntegerLiteral::GreaterOrEqual(var_to_push_, best_bound))) {
347  return false;
348  }
349  }
350 
351  return true;
352 }
353 
355  GenericLiteralWatcher* watcher) {
356  helper_->SetTimeDirection(true);
357  const int id = watcher->Register(this);
358  watcher->SetPropagatorPriority(id, 2);
359  watcher->WatchUpperBound(capacity_, id);
360  for (const int t : subtasks_) {
361  watcher->WatchLowerBound(helper_->Starts()[t], id);
362  watcher->WatchLowerBound(helper_->Ends()[t], id);
363  watcher->WatchLowerBound(helper_->Sizes()[t], id);
364  watcher->WatchLowerBound(demands_->Demands()[t], id);
365  if (!helper_->IsPresent(t) && !helper_->IsAbsent(t)) {
366  watcher->WatchLiteral(helper_->PresenceLiteral(t), id);
367  }
368  }
369 }
370 
371 } // namespace sat
372 } // namespace operations_research
int64_t min
Definition: alldiff_cst.cc:139
CumulativeEnergyConstraint(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
void RegisterWith(GenericLiteralWatcher *watcher)
CumulativeIsAfterSubsetConstraint(IntegerVariable var, AffineExpression capacity, const std::vector< int > &subtasks, const std::vector< IntegerValue > &offsets, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
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 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
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1561
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1557
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1594
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition: intervals.cc:496
const std::vector< TaskTime > & TaskByDecreasingEndMax()
Definition: intervals.cc:386
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
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:348
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:736
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:373
const std::vector< ProfileEvent > & GetEnergyProfile()
Definition: intervals.cc:419
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:330
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:744
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:375
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:374
ABSL_MUST_USE_RESULT bool DecreaseEnergyMax(int t, IntegerValue value)
Definition: intervals.cc:792
const std::vector< AffineExpression > & Demands() const
Definition: intervals.h:521
void GetEventsWithOptionalEnvelopeGreaterThan(IntegerType target_envelope, int *critical_event, int *optional_event, IntegerType *available_energy) const
Definition: theta_tree.cc:190
int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const
Definition: theta_tree.cc:180
void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt, IntegerType energy_max)
Definition: theta_tree.cc:125
void AddOrUpdateEvent(int event, IntegerType initial_envelope, IntegerType energy_min, IntegerType energy_max)
Definition: theta_tree.cc:112
IntVar * var
Definition: expr_array.cc:1874
GRBmodel * model
ReverseView< Container > reversed_view(const Container &c)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void AddCumulativeOverloadChecker(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:89
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
Collection of objects used to extend the Constraint Solver library.
int64_t time
Definition: resource.cc:1694
int64_t capacity
Rev< int64_t > start_min
Rev< int64_t > end_min
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
Definition: integer.h:1528
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1499