Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
scheduling_cuts.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 ORTOOLS_SAT_SCHEDULING_CUTS_H_
15#define ORTOOLS_SAT_SCHEDULING_CUTS_H_
16
17#include <memory>
18#include <optional>
19#include <string>
20#include <utility>
21#include <vector>
22
23#include "absl/types/span.h"
24#include "ortools/sat/cuts.h"
25#include "ortools/sat/integer.h"
27#include "ortools/sat/model.h"
29#include "ortools/sat/util.h"
30
31namespace operations_research {
32namespace sat {
33
34// For a given set of intervals and demands, we compute the energy of
35// each task and make sure their sum fits in the span of the intervals * its
36// capacity.
37//
38// If an interval is optional, it contributes
39// min_demand * min_size * presence_literal
40// amount of total energy.
41//
42// If an interval is performed, we use the linear energy formulation (if
43// defined, that is if different from a constant -1), or the McCormick
44// relaxation of the product size * demand if not defined.
45//
46// The maximum energy is capacity * span of intervals at level 0.
49 const AffineExpression& capacity,
50 const std::optional<AffineExpression>& makespan, Model* model);
51
52// For a given set of intervals and demands, we first compute the mandatory part
53// of the interval as [start_max , end_min]. We use this to calculate mandatory
54// demands for each start_max time points for eligible intervals.
55// Since the sum of these mandatory demands must be smaller or equal to the
56// capacity, we create a cut representing that.
57//
58// If an interval is optional, it contributes min_demand * presence_literal
59// amount of demand to the mandatory demands sum. So the final cut is generated
60// as follows:
61// sum(demands of always present intervals)
62// + sum(presence_literal * min_of_demand) <= capacity.
65 const AffineExpression& capacity, Model* model);
66
67// Completion time cuts for the cumulative constraint. It is a simple relaxation
68// where we replace a cumulative task with demand k and duration d by a
69// no_overlap task with duration d * k / capacity_max.
72 const AffineExpression& capacity, Model* model);
73
74// For a given set of intervals in a cumulative constraint, we detect violated
75// mandatory precedences and create a cut for these.
78 const AffineExpression& capacity, Model* model);
79
80// For a given set of intervals, we first compute the min and max of all
81// intervals. Then we create a cut that indicates that all intervals must fit
82// in that span.
83//
84// If an interval is optional, it contributes min_size * presence_literal
85// amount of demand to the mandatory demands sum. So the final cut is generated
86// as follows:
87// sum(sizes of always present intervals)
88// + sum(presence_literal * min_of_size) <= span of all intervals.
91 const std::optional<AffineExpression>& makespan, Model* model);
92
93// For a given set of intervals in a no_overlap constraint, we detect violated
94// mandatory precedences and create a cut for these.
96 SchedulingConstraintHelper* helper, Model* model);
97
98// For a given set of intervals in a no_overlap constraint, we detect violated
99// area based cuts from Queyranne 93 [see note in the code] and create a cut for
100// these.
102 SchedulingConstraintHelper* helper, Model* model);
103
104// Internal methods and data structures, useful for testing.
105
106// Stores the event for a task (interval, demand).
107// For a no_overlap constraint, demand is always between 0 and 1.
108// For a cumulative constraint, demand must be between 0 and capacity_max.
111 SchedulingDemandHelper* demands_helper);
112
113 // The index of the task in the helper.
115
116 // Cache of the bounds of the interval.
117 IntegerValue start_min;
118 IntegerValue start_max;
119 IntegerValue end_min;
120 IntegerValue end_max;
121 IntegerValue size_min;
122
123 // Start and end affine expressions and lp value of the end of the interval.
126 double lp_end = 0.0;
127
128 // Cache of the bounds of the demand.
129 IntegerValue demand_min;
130
131 // If we know that the size on y is fixed, we can use some heuristic to
132 // compute the maximum subset sums under the capacity and use that instead
133 // of the full capacity.
134 bool demand_is_fixed = false;
135
136 // The energy min of this event.
137 IntegerValue energy_min;
138
139 // If non empty, a decomposed view of the energy of this event.
140 // First value in each pair is x_size, second is y_size.
141 std::vector<LiteralValueValue> decomposed_energy;
142
143 // Indicates if the events used the optional energy information from the
144 // model.
146
147 // Indicates if the cut is lifted, that is if it includes tasks that are not
148 // strictly contained in the current time window.
149 bool lifted = false;
150
151 std::string DebugString() const;
152};
153
155 public:
157
158 int max_task_index() const { return max_task_index_; }
159 const CompactVectorVector<int>& predecessors() const { return predecessors_; }
160
161 // Temporary data.
162 std::vector<std::pair<IntegerValue, IntegerValue>> profile_;
163 std::vector<std::pair<IntegerValue, IntegerValue>> new_profile_;
164 std::vector<IntegerValue> assigned_ends_;
165 std::vector<int> task_to_index_;
167 std::vector<CompletionTimeEvent*> residual_events_;
168
169 // Collect precedences, set max_task_index.
170 // TODO(user): Do some transitive closure.
171 void Init(absl::Span<std::unique_ptr<CompletionTimeEvent>> events,
172 Model* model);
173
175 absl::Span<std::unique_ptr<CompletionTimeEvent>> events,
176 absl::Span<const int> permutation);
177
178 private:
179 void BuildPredecessors(
180 absl::Span<std::unique_ptr<CompletionTimeEvent>> events, Model* model);
181
182 CompactVectorVector<int> predecessors_;
183 int max_task_index_ = 0;
184 std::vector<bool> visited_;
185};
186
192
193template <typename Sink>
194void AbslStringify(Sink& sink, const CompletionTimeExplorationStatus& status) {
195 switch (status) {
197 sink.Append("FINISHED");
198 break;
200 sink.Append("ABORTED");
201 break;
203 sink.Append("NO_VALID_PERMUTATION");
204 break;
205 }
206}
207
208// Computes the minimum sum of the end min and the minimum sum of the end min
209// weighted by weight of all events. It returns false if no permutation is
210// valid w.r.t. the range of starts.
211//
212// Note that this is an exhaustive algorithm, so the number of events should be
213// small, like <= 10. They should also starts in index order.
214//
215// Optim: If both sums are proven <= to the corresponding threshold, we abort.
217 absl::Span<CompletionTimeEvent*> events, IntegerValue capacity_max,
218 double unweighted_threshold, double weighted_threshold,
219 CtExhaustiveHelper& helper, double& min_sum_of_ends,
220 double& min_sum_of_weighted_ends, bool& cut_use_precedences,
221 int& exploration_credit);
222
223// Split the list of events in connected components. Two intervals are connected
224// if they overlap. It expects the events to have the start_min and end_max
225// fields. Note that events are semi-open intervals [start_min, end_max). This
226// will filter out components of size one.
227template <class E>
228std::vector<absl::Span<std::unique_ptr<E>>> SplitEventsInIndendentSets(
229 std::vector<std::unique_ptr<E>>& events) {
230 if (events.empty()) return {};
231
232 std::sort(events.begin(), events.end(),
233 [](const std::unique_ptr<E>& a, const std::unique_ptr<E>& b) {
234 return std::tie(a->start_min, a->end_max) <
235 std::tie(b->start_min, b->end_max);
236 });
237 const int size = events.size();
238 std::vector<absl::Span<std::unique_ptr<E>>> result;
239 IntegerValue max_end_max = events[0]->end_max;
240 int start = 0;
241 for (int i = 1; i < size; ++i) {
242 const E& event = *events[i];
243 if (event.start_min >= max_end_max) {
244 if (i - start > 1) {
245 result.push_back(absl::MakeSpan(events.data() + start, i - start));
246 }
247 start = i;
248 }
249 max_end_max = std::max(max_end_max, event.end_max);
250 }
251 if (size - start > 1) {
252 result.push_back(absl::MakeSpan(events.data() + start, size - start));
253 }
254 return result;
255}
256
257} // namespace sat
258} // namespace operations_research
259
260#endif // ORTOOLS_SAT_SCHEDULING_CUTS_H_
const CompactVectorVector< int > & predecessors() const
std::vector< std::pair< IntegerValue, IntegerValue > > profile_
void Init(absl::Span< std::unique_ptr< CompletionTimeEvent > > events, Model *model)
bool PermutationIsCompatibleWithPrecedences(absl::Span< std::unique_ptr< CompletionTimeEvent > > events, absl::Span< const int > permutation)
std::vector< CompletionTimeEvent * > residual_events_
DagTopologicalSortIterator valid_permutation_iterator_
std::vector< std::pair< IntegerValue, IntegerValue > > new_profile_
CutGenerator CreateCumulativeCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
CompletionTimeExplorationStatus ComputeMinSumOfWeightedEndMins(absl::Span< CompletionTimeEvent * > events, IntegerValue capacity_max, double unweighted_threshold, double weighted_threshold, CtExhaustiveHelper &helper, double &min_sum_of_ends, double &min_sum_of_weighted_ends, bool &cut_use_precedences, int &exploration_credit)
CutGenerator CreateCumulativePrecedenceCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
std::vector< absl::Span< std::unique_ptr< E > > > SplitEventsInIndendentSets(std::vector< std::unique_ptr< E > > &events)
CutGenerator CreateCumulativeEnergyCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, const std::optional< AffineExpression > &makespan, Model *model)
CutGenerator CreateCumulativeTimeTableCutGenerator(SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands_helper, const AffineExpression &capacity, Model *model)
CutGenerator CreateNoOverlapCompletionTimeCutGenerator(SchedulingConstraintHelper *helper, Model *model)
CutGenerator CreateNoOverlapEnergyCutGenerator(SchedulingConstraintHelper *helper, const std::optional< AffineExpression > &makespan, Model *model)
CutGenerator CreateNoOverlapPrecedenceCutGenerator(SchedulingConstraintHelper *helper, Model *model)
void AbslStringify(Sink &sink, EdgePosition e)
OR-Tools root namespace.
std::vector< LiteralValueValue > decomposed_energy
CompletionTimeEvent(int t, SchedulingConstraintHelper *x_helper, SchedulingDemandHelper *demands_helper)