Google OR-Tools v9.14
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
scheduling.h
Go to the documentation of this file.
1// Copyright 2010-2025 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
14#ifndef OR_TOOLS_UTIL_SCHEDULING_H_
15#define OR_TOOLS_UTIL_SCHEDULING_H_
16
17#include <algorithm>
18#include <limits>
19#include <vector>
20
21#include "absl/log/check.h"
22
23namespace operations_research {
24
25// The Theta-Lambda tree can be used to implement several scheduling algorithms.
26//
27// This template class is instantiated only for IntegerValue and int64_t.
28//
29// The tree structure itself is a binary tree coded in a vector, where node 0 is
30// unused, node 1 is the root, node 2 is the left child of the root, node 3 its
31// right child, etc.
32//
33// The API gives access to rightmost events that realize a given envelope.
34//
35// See:
36// _ (0) Petr Vilim's PhD thesis "Global Constraints in Scheduling".
37// _ (1) Petr Vilim "Edge Finding Filtering Algorithm for Discrete Cumulative
38// Resources in O(kn log n)"
39// _ (2) Petr Vilim "Max energy filtering algorithm for discrete cumulative
40// resources".
41// _ (3) Wolf & Schrader "O(n log n) Overload Checking for the Cumulative
42// Constraint and Its Application".
43// _ (4) Kameugne & Fotso "A cumulative not-first/not-last filtering algorithm
44// in O(n^2 log n)".
45// _ (5) Ouellet & Quimper "Time-table extended-edge-finding for the cumulative
46// constraint".
47//
48// Instead of providing one declination of the theta-tree per possible filtering
49// algorithm, this generalization intends to provide a data structure that can
50// fit several algorithms.
51// This tree is based around the notion of events. It has events at its leaves
52// that can be present or absent, and present events come with an
53// initial_envelope, a minimal and a maximal energy.
54// All nodes maintain values on the set of present events under them:
55// _ sum_energy_min(node) = sum_{leaf \in leaves(node)} energy_min(leaf)
56// _ envelope(node) =
57// max_{leaf \in leaves(node)}
58// initial_envelope(leaf) +
59// sum_{leaf' \in leaves(node), leaf' >= leaf} energy_min(leaf').
60//
61// Thus, the envelope of a leaf representing an event, when present, is
62// initial_envelope(event) + sum_energy_min(event).
63//
64// We also maintain envelope_opt with is the maximum envelope a node could take
65// if at most one of the events were at its maximum energy.
66// _ energy_delta(leaf) = energy_max(leaf) - energy_min(leaf)
67// _ max_energy_delta(node) = max_{leaf \in leaves(node)} energy_delta(leaf)
68// _ envelope_opt(node) =
69// max_{leaf \in leaves(node)}
70// initial_envelope(leaf) +
71// sum_{leaf' \in leaves(node), leaf' >= leaf} energy_min(leaf') +
72// max_{leaf' \in leaves(node), leaf' >= leaf} energy_delta(leaf');
73//
74// Most articles using theta-tree variants hack Vilim's original theta tree
75// for the disjunctive resource constraint by manipulating envelope and
76// energy:
77// _ in (0), initial_envelope = start_min, energy = duration
78// _ in (3), initial_envelope = C * start_min, energy = demand * duration
79// _ in (5), there are several trees in parallel:
80// initial_envelope = C * start_min or (C - h) * start_min
81// energy = demand * duration, h * (Horizon - start_min),
82// or h * (end_min).
83// _ in (2), same as (3), but putting the max energy instead of min in lambda.
84// _ in OscaR's TimeTableOverloadChecker,
85// initial_envelope = C * start_min -
86// energy of mandatory profile before start_min,
87// energy = demand * duration
88//
89// There is hope to unify the variants of these algorithms by abstracting the
90// tasks away to reason only on events.
91
92template <typename IntegerType>
94 public:
95 // Builds a reusable tree. Initialization is done with Reset().
96 ThetaLambdaTree() = default;
97
98 // Initializes this class for events in [0, num_events) and makes all of them
99 // absent. Instead of allocating and de-allocating trees at every usage, i.e.
100 // at every Propagate() of the scheduling algorithms that uses it, this class
101 // allows to keep the same memory for each call.
102 void Reset(int num_events);
103
104 // Makes event present and updates its initial envelope and min/max energies.
105 // This updates the tree in O(log n).
106 void AddOrUpdateEvent(int event, IntegerType initial_envelope,
107 IntegerType energy_min, IntegerType energy_max) {
108 DCHECK_LE(0, energy_min);
109 DCHECK_LE(energy_min, energy_max);
110 const int node = GetLeafFromEvent(event);
111 tree_[node] = {.envelope = initial_envelope + energy_min,
112 .envelope_opt = initial_envelope + energy_max,
113 .sum_of_energy_min = energy_min,
114 .max_of_energy_delta = energy_max - energy_min};
115 RefreshNode(node);
116 }
117
118 // Adds event to the lambda part of the tree only.
119 // This will leave GetEnvelope() unchanged, only GetOptionalEnvelope() can
120 // be affected, by setting envelope to std::numeric_limits<>::min(),
121 // energy_min to 0, and initial_envelope_opt and energy_max to the parameters.
122 // This updates the tree in O(log n).
123 void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt,
124 IntegerType energy_max) {
125 DCHECK_LE(0, energy_max);
126 const int node = GetLeafFromEvent(event);
127 tree_[node] = {.envelope = std::numeric_limits<IntegerType>::min(),
128 .envelope_opt = initial_envelope_opt + energy_max,
129 .sum_of_energy_min = IntegerType{0},
130 .max_of_energy_delta = energy_max};
131 RefreshNode(node);
132 }
133
134 // Makes event absent, compute the new envelope in O(log n).
135 void RemoveEvent(int event) {
136 const int node = GetLeafFromEvent(event);
137 tree_[node] = {.envelope = std::numeric_limits<IntegerType>::min(),
138 .envelope_opt = std::numeric_limits<IntegerType>::min(),
139 .sum_of_energy_min = IntegerType{0},
140 .max_of_energy_delta = IntegerType{0}};
141 RefreshNode(node);
142 }
143
144 // Returns the maximum envelope using all the energy_min in O(1).
145 // If theta is empty, returns std::numeric_limits<>::min().
146 IntegerType GetEnvelope() const { return tree_[1].envelope; }
147
148 // Returns the maximum envelope using the energy min of all task but
149 // one and the energy max of the last one in O(1).
150 // If theta and lambda are empty, returns std::numeric_limits<>::min().
151 IntegerType GetOptionalEnvelope() const { return tree_[1].envelope_opt; }
152
153 // Computes the maximum event s.t. GetEnvelopeOf(event) > envelope_max.
154 // There must be such an event, i.e. GetEnvelope() > envelope_max.
155 // This finds the maximum event e such that
156 // initial_envelope(e) + sum_{e' >= e} energy_min(e') > target_envelope.
157 // This operation is O(log n).
158 int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const {
159 DCHECK_LT(target_envelope, tree_[1].envelope);
160 IntegerType unused;
161 return GetEventFromLeaf(
162 GetMaxLeafWithEnvelopeGreaterThan(1, target_envelope, &unused));
163 }
164
165 // Returns initial_envelope(event) + sum_{event' >= event} energy_min(event'),
166 // in time O(log n).
167 IntegerType GetEnvelopeOf(int event) const;
168
169 // Computes a pair of events (critical_event, optional_event) such that
170 // if optional_event was at its maximum energy, the envelope of critical_event
171 // would be greater than target_envelope.
172 //
173 // This assumes that such a pair exists, i.e. GetOptionalEnvelope() should be
174 // greater than target_envelope. More formally, this finds events such that:
175 // initial_envelope(critical_event) +
176 // sum_{event' >= critical_event} energy_min(event') +
177 // max_{optional_event >= critical_event} energy_delta(optional_event)
178 // > target envelope.
179 //
180 // For efficiency reasons, this also fills available_energy with the maximum
181 // energy the optional task can take such that the optional envelope of the
182 // pair would be target_envelope, i.e.
183 // target_envelope - GetEnvelopeOf(event) + energy_min(optional_event).
184 //
185 // This operation is O(log n).
187 IntegerType target_envelope, int* critical_event, int* optional_event,
188 IntegerType* available_energy) const {
189 int critical_leaf;
190 int optional_leaf;
191 GetLeavesWithOptionalEnvelopeGreaterThan(target_envelope, &critical_leaf,
192 &optional_leaf, available_energy);
193 *critical_event = GetEventFromLeaf(critical_leaf);
194 *optional_event = GetEventFromLeaf(optional_leaf);
195 }
196
197 // Getters.
198 IntegerType EnergyMin(int event) const {
199 return tree_[GetLeafFromEvent(event)].sum_of_energy_min;
200 }
201
202 private:
203 struct TreeNode {
204 IntegerType envelope;
205 IntegerType envelope_opt;
206 IntegerType sum_of_energy_min;
207 IntegerType max_of_energy_delta;
208 };
209
210 TreeNode ComposeTreeNodes(const TreeNode& left, const TreeNode& right) {
211 return {
212 .envelope =
213 std::max(right.envelope, left.envelope + right.sum_of_energy_min),
214 .envelope_opt =
215 std::max(right.envelope_opt,
216 right.sum_of_energy_min +
217 std::max(left.envelope_opt,
218 left.envelope + right.max_of_energy_delta)),
219 .sum_of_energy_min = left.sum_of_energy_min + right.sum_of_energy_min,
220 .max_of_energy_delta =
221 std::max(right.max_of_energy_delta, left.max_of_energy_delta)};
222 }
223
224 int GetLeafFromEvent(int event) const {
225 DCHECK_LE(0, event);
226 DCHECK_LT(event, num_events_);
227 // Keeping the ordering of events is important, so the first set of events
228 // must be mapped to the set of leaves at depth d, and the second set of
229 // events must be mapped to the set of leaves at depth d-1.
230 const int r = power_of_two_ + event;
231 return r < 2 * num_leaves_ ? r : r - num_leaves_;
232 }
233
234 int GetEventFromLeaf(int leaf) const {
235 DCHECK_GE(leaf, num_leaves_);
236 DCHECK_LT(leaf, 2 * num_leaves_);
237 const int r = leaf - power_of_two_;
238 return r >= 0 ? r : r + num_leaves_;
239 }
240
241 // Propagates the change of leaf energies and envelopes towards the root.
242 void RefreshNode(int node);
243
244 // Finds the maximum leaf under node such that
245 // initial_envelope(leaf) + sum_{leaf' >= leaf} energy_min(leaf')
246 // > target_envelope.
247 // Fills extra with the difference.
248 int GetMaxLeafWithEnvelopeGreaterThan(int node, IntegerType target_envelope,
249 IntegerType* extra) const;
250
251 // Returns the leaf with maximum energy delta under node.
252 int GetLeafWithMaxEnergyDelta(int node) const;
253
254 // Finds the leaves and energy relevant for
255 // GetEventsWithOptionalEnvelopeGreaterThan().
256 void GetLeavesWithOptionalEnvelopeGreaterThan(
257 IntegerType target_envelope, int* critical_leaf, int* optional_leaf,
258 IntegerType* available_energy) const;
259
260 // Number of events of the last Reset().
261 int num_events_;
262 int num_leaves_;
263 int power_of_two_;
264
265 // Envelopes and energies of nodes.
266 std::vector<TreeNode> tree_;
267};
268
269template <typename IntegerType>
271 // Because the algorithm needs to access a node sibling (i.e. node_index ^ 1),
272 // our tree will always have an even number of leaves, just large enough to
273 // fit our number of events. And at least 2 for the empty tree case.
274 num_events_ = num_events;
275 num_leaves_ = std::max(2, num_events + (num_events & 1));
276
277 const int num_nodes = 2 * num_leaves_;
278 tree_.assign(num_nodes,
279 TreeNode{.envelope = std::numeric_limits<IntegerType>::min(),
280 .envelope_opt = std::numeric_limits<IntegerType>::min(),
281 .sum_of_energy_min = IntegerType{0},
282 .max_of_energy_delta = IntegerType{0}});
283
284 // If num_leaves is not a power or two, the last depth of the tree will not be
285 // full, and the array will look like:
286 // [( num_leafs parents)(leaves at depth d - 1)(leaves at depth d)
287 // The first leaves at depth p will have power_of_two_ as index.
288 for (power_of_two_ = 2; power_of_two_ < num_leaves_; power_of_two_ <<= 1) {
289 }
290}
291
292template <typename IntegerType>
294 const int leaf = GetLeafFromEvent(event);
295 IntegerType envelope = tree_[leaf].envelope;
296 for (int node = leaf; node > 1; node >>= 1) {
297 const int right = node | 1;
298 if (node != right) envelope += tree_[right].sum_of_energy_min;
299 }
300 return envelope;
301}
302
303template <typename IntegerType>
304void ThetaLambdaTree<IntegerType>::RefreshNode(int node) {
305 TreeNode* tree = tree_.data();
306 do {
307 const int right = node | 1;
308 const int left = right ^ 1;
309 node >>= 1;
310 tree[node] = ComposeTreeNodes(tree[left], tree[right]);
311 } while (node > 1);
312}
313
314template <typename IntegerType>
315int ThetaLambdaTree<IntegerType>::GetMaxLeafWithEnvelopeGreaterThan(
316 int node, IntegerType target_envelope, IntegerType* extra) const {
317 DCHECK_LT(target_envelope, tree_[node].envelope);
318 while (node < num_leaves_) {
319 const int left = node << 1;
320 const int right = left | 1;
321 DCHECK_LT(right, tree_.size());
322
323 if (target_envelope < tree_[right].envelope) {
324 node = right;
325 } else {
326 target_envelope -= tree_[right].sum_of_energy_min;
327 node = left;
328 }
329 }
330 *extra = tree_[node].envelope - target_envelope;
331 return node;
332}
333
334template <typename IntegerType>
335int ThetaLambdaTree<IntegerType>::GetLeafWithMaxEnergyDelta(int node) const {
336 const IntegerType delta_node = tree_[node].max_of_energy_delta;
337 while (node < num_leaves_) {
338 const int left = node << 1;
339 const int right = left | 1;
340 DCHECK_LT(right, tree_.size());
341 if (tree_[right].max_of_energy_delta == delta_node) {
342 node = right;
343 } else {
344 DCHECK_EQ(tree_[left].max_of_energy_delta, delta_node);
345 node = left;
346 }
347 }
348 return node;
349}
350
351template <typename IntegerType>
352void ThetaLambdaTree<IntegerType>::GetLeavesWithOptionalEnvelopeGreaterThan(
353 IntegerType target_envelope, int* critical_leaf, int* optional_leaf,
354 IntegerType* available_energy) const {
355 DCHECK_LT(target_envelope, tree_[1].envelope_opt);
356 int node = 1;
357 while (node < num_leaves_) {
358 const int left = node << 1;
359 const int right = left | 1;
360 DCHECK_LT(right, tree_.size());
361
362 if (target_envelope < tree_[right].envelope_opt) {
363 node = right;
364 } else {
365 const IntegerType opt_energy_right =
366 tree_[right].sum_of_energy_min + tree_[right].max_of_energy_delta;
367 if (target_envelope < tree_[left].envelope + opt_energy_right) {
368 *optional_leaf = GetLeafWithMaxEnergyDelta(right);
369 IntegerType extra;
370 *critical_leaf = GetMaxLeafWithEnvelopeGreaterThan(
371 left, target_envelope - opt_energy_right, &extra);
372 *available_energy = tree_[*optional_leaf].sum_of_energy_min +
373 tree_[*optional_leaf].max_of_energy_delta - extra;
374 return;
375 } else { // < tree_[left].envelope_opt + tree_[right].sum_of_energy_min
376 target_envelope -= tree_[right].sum_of_energy_min;
377 node = left;
378 }
379 }
380 }
381 *critical_leaf = node;
382 *optional_leaf = node;
383 *available_energy = target_envelope - (tree_[node].envelope_opt -
384 tree_[node].sum_of_energy_min -
385 tree_[node].max_of_energy_delta);
386}
387
388} // namespace operations_research
389
390#endif // OR_TOOLS_UTIL_SCHEDULING_H_
void AddOrUpdateEvent(int event, IntegerType initial_envelope, IntegerType energy_min, IntegerType energy_max)
Definition scheduling.h:106
void AddOrUpdateOptionalEvent(int event, IntegerType initial_envelope_opt, IntegerType energy_max)
Definition scheduling.h:123
void RemoveEvent(int event)
Makes event absent, compute the new envelope in O(log n).
Definition scheduling.h:135
int GetMaxEventWithEnvelopeGreaterThan(IntegerType target_envelope) const
Definition scheduling.h:158
ThetaLambdaTree()=default
Builds a reusable tree. Initialization is done with Reset().
IntegerType EnergyMin(int event) const
Getters.
Definition scheduling.h:198
IntegerType GetEnvelopeOf(int event) const
Definition scheduling.h:293
IntegerType GetOptionalEnvelope() const
Definition scheduling.h:151
void GetEventsWithOptionalEnvelopeGreaterThan(IntegerType target_envelope, int *critical_event, int *optional_event, IntegerType *available_energy) const
Definition scheduling.h:186
In SWIG mode, we don't want anything besides these top-level includes.