2018-11-10 18:00:53 +01:00
|
|
|
// Copyright 2010-2018 Google LLC
|
2016-12-13 15:48:17 +01:00
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
|
//
|
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
//
|
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/sat/cumulative.h"
|
2017-03-28 16:11:06 +02:00
|
|
|
|
2016-12-13 15:48:17 +01:00
|
|
|
#include <algorithm>
|
2017-07-27 11:28:55 -07:00
|
|
|
#include <memory>
|
2016-12-13 15:48:17 +01:00
|
|
|
|
2017-07-27 11:28:55 -07:00
|
|
|
#include "ortools/base/int_type.h"
|
2018-06-08 16:40:43 +02:00
|
|
|
#include "ortools/base/logging.h"
|
2020-02-05 19:00:26 +01:00
|
|
|
#include "ortools/sat/cumulative_energy.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/sat/disjunctive.h"
|
2020-02-05 11:27:02 +01:00
|
|
|
#include "ortools/sat/linear_constraint.h"
|
2017-07-27 11:28:55 -07:00
|
|
|
#include "ortools/sat/pb_constraint.h"
|
|
|
|
|
#include "ortools/sat/precedences.h"
|
|
|
|
|
#include "ortools/sat/sat_base.h"
|
|
|
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
2017-04-26 17:30:25 +02:00
|
|
|
#include "ortools/sat/sat_solver.h"
|
|
|
|
|
#include "ortools/sat/timetable.h"
|
|
|
|
|
#include "ortools/sat/timetable_edgefinding.h"
|
2016-12-13 15:48:17 +01:00
|
|
|
|
|
|
|
|
namespace operations_research {
|
|
|
|
|
namespace sat {
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
std::function<void(Model *)> Cumulative(
|
|
|
|
|
const std::vector<IntervalVariable> &vars,
|
|
|
|
|
const std::vector<AffineExpression> &demands, AffineExpression capacity,
|
|
|
|
|
SchedulingConstraintHelper *helper) {
|
|
|
|
|
return [=](Model *model) mutable {
|
|
|
|
|
if (vars.empty()) return;
|
2020-10-21 00:21:54 +02:00
|
|
|
|
|
|
|
|
auto *intervals = model->GetOrCreate<IntervalsRepository>();
|
|
|
|
|
auto *encoder = model->GetOrCreate<IntegerEncoder>();
|
|
|
|
|
auto *integer_trail = model->GetOrCreate<IntegerTrail>();
|
|
|
|
|
auto *watcher = model->GetOrCreate<GenericLiteralWatcher>();
|
2016-12-13 15:48:17 +01:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// Redundant constraints to ensure that the resource capacity is high enough
|
|
|
|
|
// for each task. Also ensure that no task consumes more resource than what
|
|
|
|
|
// is available. This is useful because the subsequent propagators do not
|
|
|
|
|
// filter the capacity variable very well.
|
2020-02-05 11:27:02 +01:00
|
|
|
for (int i = 0; i < demands.size(); ++i) {
|
2020-10-22 23:36:58 +02:00
|
|
|
if (intervals->MaxSize(vars[i]) == 0) continue;
|
2017-03-28 16:11:06 +02:00
|
|
|
|
2020-02-05 11:27:02 +01:00
|
|
|
LinearConstraintBuilder builder(model, kMinIntegerValue, IntegerValue(0));
|
|
|
|
|
builder.AddTerm(demands[i], IntegerValue(1));
|
|
|
|
|
builder.AddTerm(capacity, IntegerValue(-1));
|
|
|
|
|
LinearConstraint ct = builder.Build();
|
|
|
|
|
|
|
|
|
|
std::vector<Literal> enforcement_literals;
|
|
|
|
|
if (intervals->IsOptional(vars[i])) {
|
|
|
|
|
enforcement_literals.push_back(intervals->IsPresentLiteral(vars[i]));
|
2016-12-13 15:48:17 +01:00
|
|
|
}
|
|
|
|
|
|
2020-02-05 11:27:02 +01:00
|
|
|
// If the interval can be of size zero, it currently do not count towards
|
|
|
|
|
// the capacity. TODO(user): Change that since we have optional interval
|
|
|
|
|
// for this.
|
|
|
|
|
if (intervals->MinSize(vars[i]) == 0) {
|
|
|
|
|
enforcement_literals.push_back(encoder->GetOrCreateAssociatedLiteral(
|
|
|
|
|
IntegerLiteral::GreaterOrEqual(intervals->SizeVar(vars[i]),
|
|
|
|
|
IntegerValue(1))));
|
|
|
|
|
}
|
2017-03-28 16:11:06 +02:00
|
|
|
|
2020-02-05 11:27:02 +01:00
|
|
|
if (enforcement_literals.empty()) {
|
|
|
|
|
LoadLinearConstraint(ct, model);
|
2017-03-28 16:11:06 +02:00
|
|
|
} else {
|
2020-02-05 11:27:02 +01:00
|
|
|
LoadConditionalLinearConstraint(enforcement_literals, ct, model);
|
2016-12-14 22:03:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
if (vars.size() == 1) return;
|
2017-03-28 16:11:06 +02:00
|
|
|
|
2020-10-21 00:21:54 +02:00
|
|
|
const SatParameters ¶meters = *(model->GetOrCreate<SatParameters>());
|
2017-03-28 16:11:06 +02:00
|
|
|
|
|
|
|
|
// Detect a subset of intervals that needs to be in disjunction and add a
|
|
|
|
|
// Disjunctive() constraint over them.
|
|
|
|
|
if (parameters.use_disjunctive_constraint_in_cumulative_constraint()) {
|
|
|
|
|
// TODO(user): We need to exclude intervals that can be of size zero
|
|
|
|
|
// because the disjunctive do not "ignore" them like the cumulative
|
|
|
|
|
// does. That is, the interval [2,2) will be assumed to be in
|
|
|
|
|
// disjunction with [1, 3) for instance. We need to uniformize the
|
|
|
|
|
// handling of interval with size zero.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): improve the condition (see CL147454185).
|
|
|
|
|
std::vector<IntervalVariable> in_disjunction;
|
2016-12-14 22:03:52 +01:00
|
|
|
for (int i = 0; i < vars.size(); ++i) {
|
2017-03-28 16:11:06 +02:00
|
|
|
if (intervals->MinSize(vars[i]) > 0 &&
|
2020-02-05 11:27:02 +01:00
|
|
|
2 * integer_trail->LowerBound(demands[i]) >
|
|
|
|
|
integer_trail->UpperBound(capacity)) {
|
2017-03-28 16:11:06 +02:00
|
|
|
in_disjunction.push_back(vars[i]);
|
2016-12-14 22:03:52 +01:00
|
|
|
}
|
|
|
|
|
}
|
2017-03-28 16:11:06 +02:00
|
|
|
|
|
|
|
|
// Add a disjunctive constraint on the intervals in in_disjunction. Do not
|
|
|
|
|
// create the cumulative at all when all intervals must be in disjunction.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): Do proper experiments to see how beneficial this is, the
|
|
|
|
|
// disjunctive will propagate more but is also using slower algorithms.
|
|
|
|
|
// That said, this is more a question of optimizing the disjunctive
|
|
|
|
|
// propagation code.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): Another "known" idea is to detect pair of tasks that must
|
|
|
|
|
// be in disjunction and to create a Boolean to indicate which one is
|
|
|
|
|
// before the other. It shouldn't change the propagation, but may result
|
|
|
|
|
// in a faster one with smaller explanations, and the solver can also take
|
|
|
|
|
// decision on such Boolean.
|
|
|
|
|
//
|
|
|
|
|
// TODO(user): A better place for stuff like this could be in the
|
|
|
|
|
// presolver so that it is easier to disable and play with alternatives.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (in_disjunction.size() > 1) model->Add(Disjunctive(in_disjunction));
|
|
|
|
|
if (in_disjunction.size() == vars.size()) return;
|
2016-12-14 22:03:52 +01:00
|
|
|
}
|
|
|
|
|
|
2019-03-28 21:23:28 +01:00
|
|
|
if (helper == nullptr) {
|
|
|
|
|
helper = new SchedulingConstraintHelper(vars, model);
|
|
|
|
|
model->TakeOwnership(helper);
|
|
|
|
|
}
|
2017-04-06 14:10:20 +02:00
|
|
|
|
2017-02-06 16:11:43 +01:00
|
|
|
// Propagator responsible for applying Timetabling filtering rule. It
|
|
|
|
|
// increases the minimum of the start variables, decrease the maximum of the
|
|
|
|
|
// end variables, and increase the minimum of the capacity variable.
|
2020-10-21 00:21:54 +02:00
|
|
|
TimeTablingPerTask *time_tabling =
|
2020-02-05 11:27:02 +01:00
|
|
|
new TimeTablingPerTask(demands, capacity, integer_trail, helper);
|
|
|
|
|
time_tabling->RegisterWith(watcher);
|
2016-12-13 15:48:17 +01:00
|
|
|
model->TakeOwnership(time_tabling);
|
2017-02-06 16:11:43 +01:00
|
|
|
|
|
|
|
|
// Propagator responsible for applying the Overload Checking filtering rule.
|
|
|
|
|
// It increases the minimum of the capacity variable.
|
|
|
|
|
if (parameters.use_overload_checker_in_cumulative_constraint()) {
|
2020-02-05 19:00:26 +01:00
|
|
|
AddCumulativeOverloadChecker(demands, capacity, helper, model);
|
2017-02-06 16:11:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Propagator responsible for applying the Timetable Edge finding filtering
|
|
|
|
|
// rule. It increases the minimum of the start variables and decreases the
|
|
|
|
|
// maximum of the end variables,
|
|
|
|
|
if (parameters.use_timetable_edge_finding_in_cumulative_constraint()) {
|
2020-10-21 00:21:54 +02:00
|
|
|
TimeTableEdgeFinding *time_table_edge_finding =
|
2020-02-05 11:27:02 +01:00
|
|
|
new TimeTableEdgeFinding(demands, capacity, helper, integer_trail);
|
|
|
|
|
time_table_edge_finding->RegisterWith(watcher);
|
2017-02-06 16:11:43 +01:00
|
|
|
model->TakeOwnership(time_table_edge_finding);
|
|
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
};
|
2016-12-13 15:48:17 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
std::function<void(Model *)> CumulativeTimeDecomposition(
|
|
|
|
|
const std::vector<IntervalVariable> &vars,
|
|
|
|
|
const std::vector<AffineExpression> &demands, AffineExpression capacity,
|
|
|
|
|
SchedulingConstraintHelper *helper) {
|
|
|
|
|
return [=](Model *model) {
|
|
|
|
|
if (vars.empty()) return;
|
2017-03-28 16:11:06 +02:00
|
|
|
|
2020-10-21 00:21:54 +02:00
|
|
|
IntegerTrail *integer_trail = model->GetOrCreate<IntegerTrail>();
|
2020-02-05 11:27:02 +01:00
|
|
|
CHECK(integer_trail->IsFixed(capacity));
|
2020-10-22 23:36:58 +02:00
|
|
|
const Coefficient fixed_capacity(
|
|
|
|
|
integer_trail->UpperBound(capacity).value());
|
2020-02-05 11:27:02 +01:00
|
|
|
|
|
|
|
|
const int num_tasks = vars.size();
|
2020-10-21 00:21:54 +02:00
|
|
|
SatSolver *sat_solver = model->GetOrCreate<SatSolver>();
|
|
|
|
|
IntegerEncoder *encoder = model->GetOrCreate<IntegerEncoder>();
|
|
|
|
|
IntervalsRepository *intervals = model->GetOrCreate<IntervalsRepository>();
|
2016-12-13 15:48:17 +01:00
|
|
|
|
|
|
|
|
std::vector<IntegerVariable> start_vars;
|
|
|
|
|
std::vector<IntegerVariable> end_vars;
|
2020-02-05 11:27:02 +01:00
|
|
|
std::vector<IntegerValue> fixed_demands;
|
2016-12-13 15:48:17 +01:00
|
|
|
|
|
|
|
|
for (int t = 0; t < num_tasks; ++t) {
|
2017-03-28 16:11:06 +02:00
|
|
|
start_vars.push_back(intervals->StartVar(vars[t]));
|
|
|
|
|
end_vars.push_back(intervals->EndVar(vars[t]));
|
2020-02-05 11:27:02 +01:00
|
|
|
CHECK(integer_trail->IsFixed(demands[t]));
|
|
|
|
|
fixed_demands.push_back(integer_trail->LowerBound(demands[t]));
|
2016-12-13 15:48:17 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compute time range.
|
|
|
|
|
IntegerValue min_start = kMaxIntegerValue;
|
|
|
|
|
IntegerValue max_end = kMinIntegerValue;
|
|
|
|
|
for (int t = 0; t < num_tasks; ++t) {
|
|
|
|
|
min_start = std::min(min_start, integer_trail->LowerBound(start_vars[t]));
|
|
|
|
|
max_end = std::max(max_end, integer_trail->UpperBound(end_vars[t]));
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
for (IntegerValue time = min_start; time < max_end; ++time) {
|
2016-12-13 15:48:17 +01:00
|
|
|
std::vector<LiteralWithCoeff> literals_with_coeff;
|
|
|
|
|
for (int t = 0; t < num_tasks; ++t) {
|
2017-09-29 15:08:48 +02:00
|
|
|
sat_solver->Propagate();
|
2016-12-13 15:48:17 +01:00
|
|
|
const IntegerValue start_min = integer_trail->LowerBound(start_vars[t]);
|
|
|
|
|
const IntegerValue end_max = integer_trail->UpperBound(end_vars[t]);
|
2020-02-05 11:27:02 +01:00
|
|
|
if (end_max <= time || time < start_min || fixed_demands[t] == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2016-12-13 15:48:17 +01:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// Task t consumes the resource at time if consume_condition is true.
|
|
|
|
|
std::vector<Literal> consume_condition;
|
|
|
|
|
const Literal consume = Literal(model->Add(NewBooleanVariable()), true);
|
|
|
|
|
|
|
|
|
|
// Task t consumes the resource at time if it is present.
|
|
|
|
|
if (intervals->IsOptional(vars[t])) {
|
|
|
|
|
consume_condition.push_back(intervals->IsPresentLiteral(vars[t]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Task t overlaps time.
|
2017-06-21 11:40:11 +02:00
|
|
|
consume_condition.push_back(encoder->GetOrCreateAssociatedLiteral(
|
2017-03-28 16:11:06 +02:00
|
|
|
IntegerLiteral::LowerOrEqual(start_vars[t], IntegerValue(time))));
|
2017-06-21 11:40:11 +02:00
|
|
|
consume_condition.push_back(encoder->GetOrCreateAssociatedLiteral(
|
|
|
|
|
IntegerLiteral::GreaterOrEqual(end_vars[t],
|
|
|
|
|
IntegerValue(time + 1))));
|
2016-12-13 15:48:17 +01:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
model->Add(ReifiedBoolAnd(consume_condition, consume));
|
2016-12-13 15:48:17 +01:00
|
|
|
|
2017-03-28 16:11:06 +02:00
|
|
|
// TODO(user): this is needed because we currently can't create a
|
|
|
|
|
// boolean variable if the model is unsat.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (sat_solver->IsModelUnsat()) return;
|
2016-12-13 15:48:17 +01:00
|
|
|
|
|
|
|
|
literals_with_coeff.push_back(
|
2020-02-05 11:27:02 +01:00
|
|
|
LiteralWithCoeff(consume, Coefficient(fixed_demands[t].value())));
|
2016-12-13 15:48:17 +01:00
|
|
|
}
|
|
|
|
|
// The profile cannot exceed the capacity at time.
|
|
|
|
|
sat_solver->AddLinearConstraint(false, Coefficient(0), true,
|
2020-02-05 11:27:02 +01:00
|
|
|
fixed_capacity, &literals_with_coeff);
|
2018-09-24 11:08:10 +02:00
|
|
|
|
|
|
|
|
// Abort if UNSAT.
|
2020-10-22 23:36:58 +02:00
|
|
|
if (sat_solver->IsModelUnsat()) return;
|
2016-12-13 15:48:17 +01:00
|
|
|
}
|
2020-10-22 23:36:58 +02:00
|
|
|
};
|
2016-12-13 15:48:17 +01:00
|
|
|
}
|
|
|
|
|
|
2020-10-22 23:36:58 +02:00
|
|
|
} // namespace sat
|
|
|
|
|
} // namespace operations_research
|