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
23
24namespace operations_research {
25namespace sat {
26
27void 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
40void 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
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
#define LOG(severity)
Definition: base/logging.h:420
void RegisterWith(GenericLiteralWatcher *watcher)
CumulativeEnergyConstraint(std::vector< AffineExpression > energies, AffineExpression capacity, IntegerTrail *integer_trail, SchedulingConstraintHelper *helper)
CumulativeIsAfterSubsetConstraint(IntegerVariable var, IntegerValue offset, AffineExpression capacity, const std::vector< AffineExpression > demands, const std::vector< int > subtasks, IntegerTrail *integer_trail, SchedulingConstraintHelper *helper)
void WatchLiteral(Literal l, int id, int watch_index=-1)
Definition: integer.h:1551
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1559
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition: integer.h:1577
int Register(PropagatorInterface *propagator)
Definition: integer.cc:1995
IntegerLiteral LowerBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1467
IntegerValue UpperBound(IntegerVariable i) const
Definition: integer.h:1439
IntegerValue LowerBound(IntegerVariable i) const
Definition: integer.h:1435
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition: integer.h:1472
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition: intervals.cc:426
const std::vector< AffineExpression > & Starts() const
Definition: intervals.h:336
const std::vector< TaskTime > & TaskByDecreasingEndMax()
Definition: intervals.cc:350
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition: intervals.h:313
ABSL_MUST_USE_RESULT bool PushTaskAbsence(int t)
Definition: intervals.cc:471
const std::vector< TaskTime > & TaskByIncreasingStartMin()
Definition: intervals.cc:313
void AddStartMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:561
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition: intervals.cc:508
ABSL_MUST_USE_RESULT bool PushIntegerLiteralIfTaskPresent(int t, IntegerLiteral lit)
Definition: intervals.cc:431
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition: intervals.h:575
const std::vector< AffineExpression > & Sizes() const
Definition: intervals.h:338
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition: intervals.cc:295
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition: intervals.h:583
const std::vector< AffineExpression > & Ends() const
Definition: intervals.h:337
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
const int64_t offset_
Definition: interval.cc:2108
const int INFO
Definition: log_severity.h:31
ReverseView< Container > reversed_view(const Container &c)
void AddCumulativeOverloadChecker(const std::vector< AffineExpression > &demands, AffineExpression capacity, SchedulingConstraintHelper *helper, Model *model)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition: integer.h:83
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue)
const IntegerVariable kNoIntegerVariable(-1)
void AddCumulativeEnergyConstraint(std::vector< AffineExpression > energies, AffineExpression capacity, SchedulingConstraintHelper *helper, Model *model)
Collection of objects used to extend the Constraint Solver library.
STL namespace.
int64_t demand
Definition: resource.cc:125
int64_t time
Definition: resource.cc:1691
int64_t capacity
Rev< int64_t > start_min
Rev< int64_t > end_min
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
Definition: integer.h:1406
AffineExpression MultipliedBy(IntegerValue multiplier) const
Definition: integer.h:257
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition: integer.h:1377