Google OR-Tools v9.9
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cumulative_energy.cc
Go to the documentation of this file.
1// Copyright 2010-2024 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
20#include "absl/log/check.h"
22#include "ortools/sat/integer.h"
24#include "ortools/sat/model.h"
26#include "ortools/sat/util.h"
28
29namespace operations_research {
30namespace sat {
31
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 const auto by_decreasing_end_max = helper_->TaskByDecreasingEndMax();
91 for (const auto [current_task, current_end] :
92 ::gtl::reversed_view(by_decreasing_end_max)) {
93 if (task_to_start_event_[current_task] == -1) continue;
94
95 // Add the current task to the tree.
96 {
97 const int current_event = task_to_start_event_[current_task];
98 const IntegerValue start_min = start_event_task_time_[current_event].time;
99 const bool is_present = helper_->IsPresent(current_task);
100 start_event_is_present_[current_event] = is_present;
101 if (is_present) {
102 tree_has_mandatory_intervals = true;
103 theta_tree_.AddOrUpdateEvent(current_event, start_min * capacity_max,
104 demands_->EnergyMin(current_task),
105 demands_->EnergyMax(current_task));
106 } else {
107 theta_tree_.AddOrUpdateOptionalEvent(current_event,
108 start_min * capacity_max,
109 demands_->EnergyMax(current_task));
110 }
111 }
112
113 if (tree_has_mandatory_intervals) {
114 // Find the critical interval.
115 const IntegerValue envelope = theta_tree_.GetEnvelope();
116 const int critical_event =
117 theta_tree_.GetMaxEventWithEnvelopeGreaterThan(envelope - 1);
118 const IntegerValue window_start =
119 start_event_task_time_[critical_event].time;
120 const IntegerValue window_end = current_end;
121 const IntegerValue window_size = window_end - window_start;
122 if (window_size == 0) continue;
123 const IntegerValue new_capacity_min =
124 CeilRatio(envelope - window_start * capacity_max, window_size);
125
126 // Push the new capacity min, note that this can fail if it go above the
127 // maximum capacity.
128 //
129 // TODO(user): We do not need the capacity max in the reason, but by using
130 // a lower one, we could maybe have propagated more the minimum capacity.
131 // investigate.
132 if (new_capacity_min > integer_trail_->LowerBound(capacity_)) {
133 helper_->ClearReason();
134 for (int event = critical_event; event < num_events; event++) {
135 if (start_event_is_present_[event]) {
136 const int task = start_event_task_time_[event].task_index;
137 helper_->AddPresenceReason(task);
138 demands_->AddEnergyMinReason(task);
139 helper_->AddStartMinReason(task, window_start);
140 helper_->AddEndMaxReason(task, window_end);
141 }
142 }
143 if (capacity_.var == kNoIntegerVariable) {
144 return helper_->ReportConflict();
145 } else {
146 if (!helper_->PushIntegerLiteral(
147 capacity_.GreaterOrEqual(new_capacity_min))) {
148 return false;
149 }
150 }
151 }
152 }
153
154 // Reduce energy of all tasks whose max energy would exceed an interval
155 // ending at current_end.
156 while (theta_tree_.GetOptionalEnvelope() > current_end * capacity_max) {
157 // Some task's max energy is too high, reduce its maximal energy.
158 // Explain with tasks present in the critical interval.
159 // If it is optional, it might get excluded, in that case,
160 // remove it from the tree.
161 // TODO(user): This could be done lazily.
162 // TODO(user): the same required task can have its energy pruned
163 // several times, making this algorithm O(n^2 log n). Is there a way
164 // to get the best pruning in one go? This looks like edge-finding not
165 // being able to converge in one pass, so it might not be easy.
166 helper_->ClearReason();
167 int critical_event;
168 int event_with_new_energy_max;
169 IntegerValue new_energy_max;
171 current_end * capacity_max, &critical_event,
172 &event_with_new_energy_max, &new_energy_max);
173
174 const IntegerValue window_start =
175 start_event_task_time_[critical_event].time;
176
177 // TODO(user): Improve window_end using envelope of critical event.
178 const IntegerValue window_end = current_end;
179 for (int event = critical_event; event < num_events; event++) {
180 if (start_event_is_present_[event]) {
181 if (event == event_with_new_energy_max) continue;
182 const int task = start_event_task_time_[event].task_index;
183 helper_->AddPresenceReason(task);
184 helper_->AddStartMinReason(task, window_start);
185 helper_->AddEndMaxReason(task, window_end);
186 demands_->AddEnergyMinReason(task);
187 }
188 }
189 if (capacity_.var != kNoIntegerVariable) {
190 helper_->MutableIntegerReason()->push_back(
191 integer_trail_->UpperBoundAsLiteral(capacity_.var));
192 }
193
194 const int task_with_new_energy_max =
195 start_event_task_time_[event_with_new_energy_max].task_index;
196 helper_->AddStartMinReason(task_with_new_energy_max, window_start);
197 helper_->AddEndMaxReason(task_with_new_energy_max, window_end);
198 if (!demands_->DecreaseEnergyMax(task_with_new_energy_max,
199 new_energy_max)) {
200 return false;
201 }
202
203 if (helper_->IsPresent(task_with_new_energy_max)) {
204 theta_tree_.AddOrUpdateEvent(
205 task_to_start_event_[task_with_new_energy_max],
206 start_event_task_time_[event_with_new_energy_max].time *
207 capacity_max,
208 demands_->EnergyMin(task_with_new_energy_max), new_energy_max);
209 } else {
210 theta_tree_.RemoveEvent(event_with_new_energy_max);
211 }
212 }
213 }
214 return true;
215}
216
218 IntegerVariable var, AffineExpression capacity,
219 const std::vector<int>& subtasks, const std::vector<IntegerValue>& offsets,
221 Model* model)
222 : var_to_push_(var),
223 capacity_(capacity),
224 subtasks_(subtasks),
225 integer_trail_(model->GetOrCreate<IntegerTrail>()),
226 helper_(helper),
227 demands_(demands) {
228 is_in_subtasks_.assign(helper->NumTasks(), false);
229 task_offsets_.assign(helper->NumTasks(), 0);
230 for (int i = 0; i < subtasks.size(); ++i) {
231 is_in_subtasks_[subtasks[i]] = true;
232 task_offsets_[subtasks[i]] = offsets[i];
233 }
234}
235
237 if (!helper_->SynchronizeAndSetTimeDirection(true)) return false;
238
239 IntegerValue best_time = kMaxIntegerValue;
240 IntegerValue best_bound = kMinIntegerValue;
241
242 IntegerValue previous_time = kMaxIntegerValue;
243 IntegerValue energy_after_time(0);
244 IntegerValue profile_height(0);
245
246 // If the capacity_max is low enough, we compute the exact possible subset
247 // of reachable "sum of demands" of all tasks used in the energy. We will use
248 // the highest reachable as the capacity max.
249 const IntegerValue capacity_max = integer_trail_->UpperBound(capacity_);
250 dp_.Reset(capacity_max.value());
251
252 // We consider the energy after a given time.
253 // From that we derive a bound on the end_min of the subtasks.
254 const auto& profile = helper_->GetEnergyProfile();
255 IntegerValue min_offset = kMaxIntegerValue;
256 for (int i = profile.size() - 1; i >= 0;) {
257 // Skip tasks not relevant for this propagator.
258 {
259 const int t = profile[i].task;
260 if (!helper_->IsPresent(t) || !is_in_subtasks_[t]) {
261 --i;
262 continue;
263 }
264 }
265
266 const IntegerValue time = profile[i].time;
267 if (profile_height > 0) {
268 energy_after_time += profile_height * (previous_time - time);
269 }
270 previous_time = time;
271
272 // Any newly introduced tasks will only change the reachable capa max or
273 // the min_offset on the next time point.
274 const IntegerValue saved_capa_max = dp_.CurrentMax();
275 const IntegerValue saved_min_offset = min_offset;
276
277 for (; i >= 0 && profile[i].time == time; --i) {
278 // Skip tasks not relevant for this propagator.
279 const int t = profile[i].task;
280 if (!helper_->IsPresent(t) || !is_in_subtasks_[t]) continue;
281
282 min_offset = std::min(min_offset, task_offsets_[t]);
283 const IntegerValue demand_min = demands_->DemandMin(t);
284 if (profile[i].is_first) {
285 profile_height -= demand_min;
286 } else {
287 profile_height += demand_min;
288 if (demands_->Demands()[t].IsConstant()) {
289 dp_.Add(demand_min.value());
290 } else {
291 dp_.Add(capacity_max.value()); // Abort DP.
292 }
293 }
294 }
295
296 // We prefer higher time in case of ties since that should reduce the
297 // explanation size.
298 //
299 // Note that if the energy is zero, we don't push anything. Other propagator
300 // will make sure that the end_min is greater than the end_min of any of
301 // the task considered here. TODO(user): actually, we will push using the
302 // last task, and the reason will be non-optimal, fix.
303 if (energy_after_time == 0) continue;
304 DCHECK_GT(saved_capa_max, 0);
305 DCHECK_LT(saved_min_offset, kMaxIntegerValue);
306 const IntegerValue end_min_with_offset =
307 time + CeilRatio(energy_after_time, saved_capa_max) + saved_min_offset;
308 if (end_min_with_offset > best_bound) {
309 best_time = time;
310 best_bound = end_min_with_offset;
311 }
312 }
313 DCHECK_EQ(profile_height, 0);
314
315 if (best_bound == kMinIntegerValue) return true;
316 if (best_bound > integer_trail_->LowerBound(var_to_push_)) {
317 // Compute the reason.
318 // It is just the reason for the energy after time.
319 helper_->ClearReason();
320 for (int t = 0; t < helper_->NumTasks(); ++t) {
321 if (!is_in_subtasks_[t]) continue;
322 if (!helper_->IsPresent(t)) continue;
323
324 const IntegerValue size_min = helper_->SizeMin(t);
325 if (size_min == 0) continue;
326
327 const IntegerValue demand_min = demands_->DemandMin(t);
328 if (demand_min == 0) continue;
329
330 const IntegerValue end_min = helper_->EndMin(t);
331 if (end_min <= best_time) continue;
332
333 helper_->AddEndMinReason(t, std::min(best_time + size_min, end_min));
334 helper_->AddSizeMinReason(t);
335 helper_->AddPresenceReason(t);
336 demands_->AddDemandMinReason(t);
337 }
338 if (capacity_.var != kNoIntegerVariable) {
339 helper_->MutableIntegerReason()->push_back(
340 integer_trail_->UpperBoundAsLiteral(capacity_.var));
341 }
342
343 // Propagate.
344 if (!helper_->PushIntegerLiteral(
345 IntegerLiteral::GreaterOrEqual(var_to_push_, best_bound))) {
346 return false;
347 }
348 }
349
350 return true;
351}
352
354 GenericLiteralWatcher* watcher) {
355 helper_->SetTimeDirection(true);
356 const int id = watcher->Register(this);
357 watcher->SetPropagatorPriority(id, 2);
358 watcher->WatchUpperBound(capacity_, id);
359 for (const int t : subtasks_) {
360 watcher->WatchLowerBound(helper_->Starts()[t], id);
361 watcher->WatchLowerBound(helper_->Ends()[t], id);
362 watcher->WatchLowerBound(helper_->Sizes()[t], id);
363 watcher->WatchLowerBound(demands_->Demands()[t], id);
364 if (!helper_->IsPresent(t) && !helper_->IsAbsent(t)) {
365 watcher->WatchLiteral(helper_->PresenceLiteral(t), id);
366 }
367 }
368}
369
370} // namespace sat
371} // namespace operations_research
Implementation of AddCumulativeOverloadChecker().
void RegisterWith(GenericLiteralWatcher *watcher)
CumulativeEnergyConstraint(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
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:1744
void WatchUpperBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1770
void WatchLowerBound(IntegerVariable var, int id, int watch_index=-1)
Definition integer.h:1752
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2288
int Register(PropagatorInterface *propagator)
Registers a propagator and returns its unique ids.
Definition integer.cc:2265
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1617
IntegerLiteral UpperBoundAsLiteral(IntegerVariable i) const
Definition integer.h:1665
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1621
void Add(int64_t value)
Add a value to the base set for which subset sums will be taken.
Definition util.cc:496
absl::Span< const TaskTime > TaskByIncreasingStartMin()
Definition intervals.cc:542
std::vector< IntegerLiteral > * MutableIntegerReason()
Definition intervals.h:425
int NumTasks() const
Returns the number of task.
Definition intervals.h:282
absl::Span< const AffineExpression > Sizes() const
Definition intervals.h:455
const std::vector< ProfileEvent > & GetEnergyProfile()
Definition intervals.cc:612
absl::Span< const AffineExpression > Starts() const
Definition intervals.h:453
void AddEndMaxReason(int t, IntegerValue upper_bound)
Definition intervals.h:845
absl::Span< const AffineExpression > Ends() const
Definition intervals.h:454
void AddEndMinReason(int t, IntegerValue lower_bound)
Definition intervals.h:837
void ClearReason()
Functions to clear and then set the current reason.
Definition intervals.h:751
absl::Span< const TaskTime > TaskByDecreasingEndMax()
Definition intervals.cc:579
ABSL_MUST_USE_RESULT bool SynchronizeAndSetTimeDirection(bool is_forward)
Definition intervals.cc:478
void WatchAllTasks(int id, GenericLiteralWatcher *watcher, bool watch_start_max=true, bool watch_end_max=true) const
Definition intervals.cc:782
ABSL_MUST_USE_RESULT bool PushIntegerLiteral(IntegerLiteral lit)
Definition intervals.cc:689
void AddStartMinReason(int t, IntegerValue lower_bound)
Definition intervals.h:823
const std::vector< AffineExpression > & Demands() const
Definition intervals.h:614
ABSL_MUST_USE_RESULT bool DecreaseEnergyMax(int t, IntegerValue value)
Definition intervals.cc:993
void RemoveEvent(int event)
Makes event absent, compute the new envelope in O(log n).
void AddOrUpdateEvent(int event, IntegerType initial_envelope, IntegerType energy_min, IntegerType energy_max)
void GetEventsWithOptionalEnvelopeGreaterThan(IntegerType target_envelope, int *critical_event, int *optional_event, IntegerType *available_energy) const
int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const
void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt, IntegerType energy_max)
IntVar * var
GRBmodel * model
ReverseView< Container > reversed_view(const Container &c)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
IntegerValue CeilRatio(IntegerValue dividend, IntegerValue positive_divisor)
Definition integer.h:85
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
const IntegerVariable kNoIntegerVariable(-1)
void AddCumulativeOverloadChecker(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t time
Definition resource.cc:1708
Rev< int64_t > start_min
Rev< int64_t > end_min
IntegerLiteral GreaterOrEqual(IntegerValue bound) const
var * coeff + constant >= bound.
Definition integer.h:1596
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1567