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