From 03824e8fa9ef2b3ad93ce60b7a9d41f76123ed7c Mon Sep 17 00:00:00 2001 From: "lperron@google.com" Date: Fri, 4 Jul 2014 11:03:57 +0000 Subject: [PATCH] cumulative with variable demands, experimental code --- src/constraint_solver/constraint_solver.h | 22 ++ src/constraint_solver/resource.cc | 455 +++++++++++++++++++--- 2 files changed, 414 insertions(+), 63 deletions(-) diff --git a/src/constraint_solver/constraint_solver.h b/src/constraint_solver/constraint_solver.h index 215bbe7a2a..cabf557f46 100644 --- a/src/constraint_solver/constraint_solver.h +++ b/src/constraint_solver/constraint_solver.h @@ -2008,6 +2008,28 @@ class Solver { const std::vector& demands, IntVar* const capacity, const std::string& name); + // This constraint forces that, for any integer t, the sum of the demands + // corresponding to an interval containing t does not exceed the given + // capacity. + // + // Intervals and demands should be vectors of equal size. + // + // Demands should be positive. + Constraint* MakeCumulative(const std::vector& intervals, + const std::vector& demands, + int64 capacity, const std::string& name); + + // This constraint forces that, for any integer t, the sum of the demands + // corresponding to an interval containing t does not exceed the given + // capacity. + // + // Intervals and demands should be vectors of equal size. + // + // Demands should be positive. + Constraint* MakeCumulative(const std::vector& intervals, + const std::vector& demands, + IntVar* const capacity, const std::string& name); + // This constraint states that the target_var is the convex hull of // the intervals. If none of the interval variables is performed, // then the target var is unperformed too. Also, if the target diff --git a/src/constraint_solver/resource.cc b/src/constraint_solver/resource.cc index ff95a67bae..7044b63d24 100644 --- a/src/constraint_solver/resource.cc +++ b/src/constraint_solver/resource.cc @@ -110,6 +110,12 @@ struct CumulativeTask { int64 EnergyMin() const { return interval->DurationMin() * demand; } + int64 DemandMin() const { return demand; } + + void WhenAnything(Demon* const demon) { + interval->WhenAnything(demon); + } + std::string DebugString() const { return StringPrintf("Task{ %s, demand: %" GG_LL_FORMAT "d }", interval->DebugString().c_str(), demand); @@ -120,6 +126,35 @@ struct CumulativeTask { int index; }; +// A VariableCumulativeTask is a non-preemptive task sharing a +// cumulative resource. That is, it corresponds to an interval and a +// demand. The sum of demands of all cumulative tasks +// VariableCumulativeTasks sharing a resource of capacity c those +// intervals contain any integer t cannot exceed c. It is indexed, +// that is it is aware of its position in a reference array. +struct VariableCumulativeTask { + VariableCumulativeTask(IntervalVar* const interval_, IntVar* demand_) + : interval(interval_), demand(demand_), index(-1) {} + + int64 EnergyMin() const { return interval->DurationMin() * demand->Min(); } + + int64 DemandMin() const { return demand->Min(); } + + void WhenAnything(Demon* const demon) { + interval->WhenAnything(demon); + demand->WhenRange(demon); + } + + std::string DebugString() const { + return StringPrintf("Task{ %s, demand: %" GG_LL_FORMAT "d }", + interval->DebugString().c_str(), demand); + } + + IntervalVar* interval; + IntVar* demand; + int index; +}; + // ---------- Theta-Trees ---------- // This is based on Petr Vilim (public) PhD work. @@ -137,7 +172,8 @@ struct ThetaNode { void Compute(const ThetaNode& left, const ThetaNode& right) { total_processing = left.total_processing + right.total_processing; - total_ect = std::max(left.total_ect + right.total_processing, right.total_ect); + total_ect = + std::max(left.total_ect + right.total_processing, right.total_ect); } bool IsIdentity() const { @@ -218,6 +254,27 @@ struct LambdaThetaNode { DCHECK_GE(index, 0); } + // Constructor for a single cumulative task in the Theta set + LambdaThetaNode(int64 capacity, const VariableCumulativeTask& task) + : energy(task.EnergyMin()), + energetic_end_min(capacity * task.interval->StartMin() + energy), + energy_opt(energy), + argmax_energy_opt(kNone), + energetic_end_min_opt(energetic_end_min), + argmax_energetic_end_min_opt(kNone) {} + + // Constructor for a single cumulative task in the Lambda set + LambdaThetaNode(int64 capacity, const VariableCumulativeTask& task, int index) + : energy(0LL), + energetic_end_min(kint64min), + energy_opt(task.EnergyMin()), + argmax_energy_opt(index), + energetic_end_min_opt(capacity * task.interval->StartMin() + + energy_opt), + argmax_energetic_end_min_opt(index) { + DCHECK_GE(index, 0); + } + // Constructor for a single interval in the Theta set explicit LambdaThetaNode(const IntervalVar* const interval) : energy(interval->DurationMin()), @@ -248,8 +305,8 @@ struct LambdaThetaNode { // associated with such sets. void Compute(const LambdaThetaNode& left, const LambdaThetaNode& right) { energy = left.energy + right.energy; - energetic_end_min = - std::max(right.energetic_end_min, left.energetic_end_min + right.energy); + energetic_end_min = std::max(right.energetic_end_min, + left.energetic_end_min + right.energy); const int64 energy_left_opt = left.energy_opt + right.energy; const int64 energy_right_opt = left.energy + right.energy_opt; if (energy_left_opt > energy_right_opt) { @@ -341,6 +398,15 @@ class CumulativeLambdaThetaTree : public MonoidOperationTree { Set(index, LambdaThetaNode(capacity_, task, index)); } + void Insert(const VariableCumulativeTask& task) { + Set(task.index, LambdaThetaNode(capacity_, task)); + } + + void Grey(const VariableCumulativeTask& task) { + const int index = task.index; + Set(index, LambdaThetaNode(capacity_, task, index)); + } + int64 energetic_end_min() const { return result().energetic_end_min; } int64 energetic_end_min_opt() const { return result().energetic_end_min_opt; } int64 Ect() const { @@ -378,8 +444,8 @@ class NotLast { std::vector new_lct_; }; -NotLast::NotLast(Solver* const solver, const std::vector& intervals, - bool mirror) +NotLast::NotLast(Solver* const solver, + const std::vector& intervals, bool mirror) : theta_tree_(intervals.size()), by_start_min_(intervals.size()), by_end_max_(intervals.size()), @@ -399,11 +465,12 @@ NotLast::NotLast(Solver* const solver, const std::vector& interval bool NotLast::Propagate() { // ---- Init ---- std::sort(by_start_max_.begin(), by_start_max_.end(), - StartMaxLessThan); - std::sort(by_end_max_.begin(), by_end_max_.end(), EndMaxLessThan); + StartMaxLessThan); + std::sort(by_end_max_.begin(), by_end_max_.end(), + EndMaxLessThan); // Update start min positions std::sort(by_start_min_.begin(), by_start_min_.end(), - StartMinLessThan); + StartMinLessThan); for (int i = 0; i < by_start_min_.size(); ++i) { by_start_min_[i]->index = i; } @@ -495,7 +562,8 @@ class EdgeFinderAndDetectablePrecedences { }; EdgeFinderAndDetectablePrecedences::EdgeFinderAndDetectablePrecedences( - Solver* const solver, const std::vector& intervals, bool mirror) + Solver* const solver, const std::vector& intervals, + bool mirror) : solver_(solver), theta_tree_(intervals.size()), lt_tree_(intervals.size()) { @@ -515,7 +583,7 @@ EdgeFinderAndDetectablePrecedences::EdgeFinderAndDetectablePrecedences( void EdgeFinderAndDetectablePrecedences::UpdateEst() { std::sort(by_start_min_.begin(), by_start_min_.end(), - StartMinLessThan); + StartMinLessThan); for (int i = 0; i < size(); ++i) { by_start_min_[i]->index = i; } @@ -524,7 +592,8 @@ void EdgeFinderAndDetectablePrecedences::UpdateEst() { void EdgeFinderAndDetectablePrecedences::OverloadChecking() { // Initialization. UpdateEst(); - std::sort(by_end_max_.begin(), by_end_max_.end(), EndMaxLessThan); + std::sort(by_end_max_.begin(), by_end_max_.end(), + EndMaxLessThan); theta_tree_.Clear(); for (int i = 0; i < size(); ++i) { @@ -544,9 +613,10 @@ bool EdgeFinderAndDetectablePrecedences::DetectablePrecedences() { } // Propagate in one direction - std::sort(by_end_min_.begin(), by_end_min_.end(), EndMinLessThan); + std::sort(by_end_min_.begin(), by_end_min_.end(), + EndMinLessThan); std::sort(by_start_max_.begin(), by_start_max_.end(), - StartMaxLessThan); + StartMaxLessThan); theta_tree_.Clear(); int j = 0; for (int i = 0; i < size(); ++i) { @@ -595,7 +665,8 @@ bool EdgeFinderAndDetectablePrecedences::EdgeFinder() { } // Push in one direction. - std::sort(by_end_max_.begin(), by_end_max_.end(), EndMaxLessThan); + std::sort(by_end_max_.begin(), by_end_max_.end(), + EndMaxLessThan); lt_tree_.Clear(); for (int i = 0; i < size(); ++i) { lt_tree_.Insert(*by_start_min_[i]); @@ -995,7 +1066,8 @@ class FullDisjunctiveConstraint : public DisjunctiveConstraint { nexts_, actives_, time_cumuls_, time_slacks_, NewPermanentCallback(this, &FullDisjunctiveConstraint::Distance))); - std::vector short_slacks(time_slacks_.begin() + 1, time_slacks_.end()); + std::vector short_slacks(time_slacks_.begin() + 1, + time_slacks_.end()); s->AddConstraint(s->RevAlloc( new RankedPropagator(s, nexts_, intervals_, short_slacks, this))); } @@ -1036,6 +1108,14 @@ struct DualCapacityThetaNode { residual_energetic_end_min( residual_capacity * task.interval->StartMin() + energy) {} + // Constructor for a single cumulative task in the Theta set + DualCapacityThetaNode(int64 capacity, int64 residual_capacity, + const VariableCumulativeTask& task) + : energy(task.EnergyMin()), + energetic_end_min(capacity * task.interval->StartMin() + energy), + residual_energetic_end_min( + residual_capacity * task.interval->StartMin() + energy) {} + // Sets this DualCapacityThetaNode to the result of the natural binary // operation over the two given operands, corresponding to the following set // operation: Theta = left.Theta union right.Theta @@ -1045,11 +1125,11 @@ struct DualCapacityThetaNode { void Compute(const DualCapacityThetaNode& left, const DualCapacityThetaNode& right) { energy = left.energy + right.energy; - energetic_end_min = - std::max(left.energetic_end_min + right.energy, right.energetic_end_min); + energetic_end_min = std::max(left.energetic_end_min + right.energy, + right.energetic_end_min); residual_energetic_end_min = std::max(left.residual_energetic_end_min + right.energy, - right.residual_energetic_end_min); + right.residual_energetic_end_min); } // Amount of resource consumed by the Theta set, in units of demand X time. @@ -1083,6 +1163,11 @@ class DualCapacityThetaTree DualCapacityThetaNode(capacity_, residual_capacity_, *task)); } + void Insert(const VariableCumulativeTask* task) { + Set(task->index, + DualCapacityThetaNode(capacity_, residual_capacity_, *task)); + } + void SetResidualCapacity(int residual_capacity) { Clear(); DCHECK_LE(0, residual_capacity); @@ -1243,9 +1328,10 @@ int64 SafeProduct(int64 a, int64 b) { } // One-sided cumulative edge finder. +template class EdgeFinder : public Constraint { public: - EdgeFinder(Solver* const solver, const std::vector& tasks, + EdgeFinder(Solver* const solver, const std::vector& tasks, int64 capacity) : Constraint(solver), capacity_(capacity), @@ -1258,7 +1344,7 @@ class EdgeFinder : public Constraint { by_start_min_[i] = tasks[i]; by_end_max_[i] = tasks[i]; by_end_min_[i] = tasks[i]; - const int64 demand = tasks[i]->demand; + const int64 demand = tasks[i]->DemandMin(); // Create the UpdateForADemand if needed (the [] operator calls the // constructor). May rehash. if (update_map_[demand] == nullptr) { @@ -1275,12 +1361,11 @@ class EdgeFinder : public Constraint { virtual void Post() { // Add the demons for (int i = 0; i < by_start_min_.size(); ++i) { - IntervalVar* const interval = by_start_min_[i]->interval; // Delay propagation, as this constraint is not incremental: we pay // O(n log n) each time the constraint is awakened. Demon* const demon = MakeDelayedConstraintDemon0( solver(), this, &EdgeFinder::InitialPropagate, "RangeChanged"); - interval->WhenAnything(demon); + by_start_min_[i]->WhenAnything(demon); } } @@ -1307,16 +1392,14 @@ class EdgeFinder : public Constraint { new_start_min_.clear(); // sort y start min. std::sort(by_start_min_.begin(), by_start_min_.end(), - StartMinLessThan); + StartMinLessThan); for (int i = 0; i < by_start_min_.size(); ++i) { by_start_min_[i]->index = i; } // Sort by end max. - std::sort(by_end_max_.begin(), by_end_max_.end(), - EndMaxLessThan); + std::sort(by_end_max_.begin(), by_end_max_.end(), EndMaxLessThan); // Sort by end min. - std::sort(by_end_min_.begin(), by_end_min_.end(), - EndMinLessThan); + std::sort(by_end_min_.begin(), by_end_min_.end(), EndMinLessThan); // Clear tree. lt_tree_.Clear(); // Clear updates @@ -1363,9 +1446,8 @@ class EdgeFinder : public Constraint { // Returns the new start min that can be inferred for task_to_push if it is // proved that it cannot end before by_end_max[end_max_index] does. - int64 ConditionalStartMin(const CumulativeTask& task_to_push, - int end_max_index) { - const int64 demand = task_to_push.demand; + int64 ConditionalStartMin(const Task& task_to_push, int end_max_index) { + const int64 demand = task_to_push.DemandMin(); if (!update_map_[demand]->up_to_date()) { ComputeConditionalStartMins(demand); } @@ -1382,12 +1464,12 @@ class EdgeFinder : public Constraint { int end_max_index = 0; int64 max_start_min = kint64min; for (int i = 0; i < by_start_min_.size(); ++i) { - CumulativeTask* const task = by_end_min_[i]; + Task* const task = by_end_min_[i]; const int64 end_min = task->interval->EndMin(); while (end_max_index < by_start_min_.size() && by_end_max_[end_max_index]->interval->EndMax() <= end_min) { - max_start_min = std::max(max_start_min, - by_end_max_[end_max_index]->interval->StartMin()); + max_start_min = std::max( + max_start_min, by_end_max_[end_max_index]->interval->StartMin()); ++end_max_index; } if (end_max_index > 0 && task->interval->StartMin() <= max_start_min && @@ -1415,7 +1497,7 @@ class EdgeFinder : public Constraint { // Fill the theta-lambda-tree, and check for overloading. void FillInTree() { for (int i = 0; i < by_start_min_.size(); ++i) { - CumulativeTask* const task = by_end_max_[i]; + Task* const task = by_end_max_[i]; lt_tree_.Insert(*task); // Maximum energetic end min without overload. const int64 max_feasible = @@ -1431,7 +1513,7 @@ class EdgeFinder : public Constraint { void PropagateBasedOnEnergy() { for (int j = by_start_min_.size() - 2; j >= 0; --j) { lt_tree_.Grey(*by_end_max_[j + 1]); - CumulativeTask* const twj = by_end_max_[j]; + Task* const twj = by_end_max_[j]; // We should have checked for overload earlier. const int64 max_feasible = SafeProduct(capacity_, twj->interval->EndMax()); @@ -1448,7 +1530,7 @@ class EdgeFinder : public Constraint { // Takes into account the fact that the task of given index cannot end before // the given new end min. void PropagateTaskCannotEndBefore(int index, int end_max_index) { - CumulativeTask* const task_to_push = by_start_min_[index]; + Task* const task_to_push = by_start_min_[index]; const int64 update = ConditionalStartMin(*task_to_push, end_max_index); StartMinUpdater startMinUpdater(task_to_push->interval, update); new_start_min_.push_back(startMinUpdater); @@ -1465,13 +1547,13 @@ class EdgeFinder : public Constraint { const int64 capacity_; // Cumulative tasks, ordered by non-decreasing start min. - std::vector by_start_min_; + std::vector by_start_min_; // Cumulative tasks, ordered by non-decreasing end max. - std::vector by_end_max_; + std::vector by_end_max_; // Cumulative tasks, ordered by non-decreasing end min. - std::vector by_end_min_; + std::vector by_end_min_; // Cumulative theta-lamba tree. CumulativeLambdaThetaTree lt_tree_; @@ -1532,10 +1614,11 @@ bool TimeLessThan(const ProfileDelta& delta1, const ProfileDelta& delta2) { // // The implementation is quite naive, and could certainly be improved, for // example by maintaining the profile incrementally. +template class CumulativeTimeTable : public Constraint { public: - CumulativeTimeTable(Solver* const solver, - const std::vector& tasks, int64 capacity) + CumulativeTimeTable(Solver* const solver, const std::vector& tasks, + int64 capacity) : Constraint(solver), by_start_min_(tasks), capacity_(capacity) { // There may be up to 2 delta's per interval (one on each side), // plus two sentinels @@ -1556,7 +1639,7 @@ class CumulativeTimeTable : public Constraint { solver(), this, &CumulativeTimeTable::InitialPropagate, "InitialPropagate"); for (int i = 0; i < NumTasks(); ++i) { - by_start_min_[i]->interval->WhenAnything(d); + by_start_min_[i]->WhenAnything(d); } } @@ -1574,19 +1657,19 @@ class CumulativeTimeTable : public Constraint { // Build profile with non unique time profile_non_unique_time_.clear(); for (int i = 0; i < NumTasks(); ++i) { - const CumulativeTask* const task = by_start_min_[i]; + const Task* const task = by_start_min_[i]; const IntervalVar* const interval = task->interval; const int64 start_max = interval->StartMax(); const int64 end_min = interval->EndMin(); if (interval->MustBePerformed() && start_max < end_min) { - const int64 demand = task->demand; + const int64 demand = task->DemandMin(); profile_non_unique_time_.push_back(ProfileDelta(start_max, +demand)); profile_non_unique_time_.push_back(ProfileDelta(end_min, -demand)); } } // Sort std::sort(profile_non_unique_time_.begin(), profile_non_unique_time_.end(), - TimeLessThan); + TimeLessThan); // Build profile with unique times profile_unique_time_.clear(); profile_unique_time_.push_back(ProfileDelta(kint64min, 0)); @@ -1615,11 +1698,11 @@ class CumulativeTimeTable : public Constraint { // Update the start min for all tasks. Runs in O(n^2) and Omega(n). void PushTasks() { std::sort(by_start_min_.begin(), by_start_min_.end(), - StartMinLessThan); + StartMinLessThan); int64 usage = 0; int profile_index = 0; for (int task_index = 0; task_index < NumTasks(); ++task_index) { - const CumulativeTask* const task = by_start_min_[task_index]; + const Task* const task = by_start_min_[task_index]; while (task->interval->StartMin() > profile_unique_time_[profile_index].time) { DCHECK(profile_index < profile_unique_time_.size()); @@ -1634,11 +1717,10 @@ class CumulativeTimeTable : public Constraint { // that the profile usage for all tasks, excluding the current one, does not // exceed capacity_ - task->demand on the interval // [new_start_min, new_start_min + task->interval->DurationMin() ). - void PushTask(const CumulativeTask* const task, int profile_index, - int64 usage) { + void PushTask(const Task* const task, int profile_index, int64 usage) { // Init const IntervalVar* const interval = task->interval; - const int64 demand = task->demand; + const int64 demand = task->DemandMin(); const int64 residual_capacity = capacity_ - demand; const int64 duration = task->interval->DurationMin(); const ProfileDelta& first_prof_delta = profile_unique_time_[profile_index]; @@ -1698,7 +1780,7 @@ class CumulativeTimeTable : public Constraint { Profile profile_unique_time_; Profile profile_non_unique_time_; - std::vector by_start_min_; + std::vector by_start_min_; const int64 capacity_; DISALLOW_COPY_AND_ASSIGN(CumulativeTimeTable); @@ -1706,7 +1788,8 @@ class CumulativeTimeTable : public Constraint { class CumulativeConstraint : public Constraint { public: - CumulativeConstraint(Solver* const s, const std::vector& intervals, + CumulativeConstraint(Solver* const s, + const std::vector& intervals, const std::vector& demands, int64 capacity, const std::string& name) : Constraint(s), @@ -1810,8 +1893,8 @@ class CumulativeConstraint : public Constraint { // Populate the given vector with useful tasks, meaning the ones on which // some propagation can be done - void PopulateVectorUsefulTasks(bool mirror, - std::vector* const useful_tasks) { + void PopulateVectorUsefulTasks( + bool mirror, std::vector* const useful_tasks) { DCHECK(useful_tasks->empty()); for (int i = 0; i < tasks_.size(); ++i) { const CumulativeTask& original_task = tasks_[i]; @@ -1845,9 +1928,11 @@ class CumulativeConstraint : public Constraint { } else { Solver* const s = solver(); if (edge_finder) { - return s->RevAlloc(new EdgeFinder(s, useful_tasks, capacity_)); + return s->RevAlloc( + new EdgeFinder(s, useful_tasks, capacity_)); } else { - return s->RevAlloc(new CumulativeTimeTable(s, useful_tasks, capacity_)); + return s->RevAlloc(new CumulativeTimeTable( + s, useful_tasks, capacity_)); } } } @@ -1873,6 +1958,181 @@ class CumulativeConstraint : public Constraint { DISALLOW_COPY_AND_ASSIGN(CumulativeConstraint); }; + +class VariableDemandCumulativeConstraint : public Constraint { + public: + VariableDemandCumulativeConstraint(Solver* const s, + const std::vector& intervals, + const std::vector& demands, + int64 capacity, const std::string& name) + : Constraint(s), + capacity_(capacity), + intervals_(intervals), + demands_(demands) { + tasks_.reserve(intervals.size()); + for (int i = 0; i < intervals.size(); ++i) { + tasks_.push_back(VariableCumulativeTask(intervals[i], demands[i])); + } + } + + virtual void Post() { + // For the cumulative constraint, there are many propagators, and they + // don't dominate each other. So the strongest propagation is obtained + // by posting a bunch of different propagators. + if (FLAGS_cp_use_cumulative_time_table) { + PostOneSidedConstraint(false, false); + PostOneSidedConstraint(true, false); + } + if (FLAGS_cp_use_cumulative_edge_finder) { + PostOneSidedConstraint(false, true); + PostOneSidedConstraint(true, true); + } + if (FLAGS_cp_use_sequence_high_demand_tasks) { + PostHighDemandSequenceConstraint(); + } + if (FLAGS_cp_use_all_possible_disjunctions) { + PostAllDisjunctions(); + } + } + + virtual void InitialPropagate() { + // Nothing to do: this constraint delegates all the work to other classes + } + + void Accept(ModelVisitor* const visitor) const { + // TODO(user): Build arrays on demand? + visitor->BeginVisitConstraint(ModelVisitor::kCumulative, this); + visitor->VisitIntervalArrayArgument(ModelVisitor::kIntervalsArgument, + intervals_); + visitor->VisitIntegerVariableArrayArgument(ModelVisitor::kDemandsArgument, + demands_); + visitor->VisitIntegerArgument(ModelVisitor::kCapacityArgument, capacity_); + visitor->EndVisitConstraint(ModelVisitor::kCumulative, this); + } + + virtual std::string DebugString() const { + return "VariableDemandCumulativeConstraint"; + } + + private: + // Post temporal disjunctions for tasks that cannot overlap. + void PostAllDisjunctions() { + for (int i = 0; i < intervals_.size(); ++i) { + IntervalVar* const interval_i = intervals_[i]; + if (interval_i->MayBePerformed()) { + for (int j = i + 1; j < intervals_.size(); ++j) { + IntervalVar* const interval_j = intervals_[j]; + if (interval_j->MayBePerformed()) { + if (tasks_[i].demand->Min() + tasks_[j].demand->Min() > capacity_) { + Constraint* const constraint = + solver()->MakeTemporalDisjunction(interval_i, interval_j); + solver()->AddConstraint(constraint); + } + } + } + } + } + } + + // Post a Sequence constraint for tasks that requires strictly more than half + // of the resource + void PostHighDemandSequenceConstraint() { + Constraint* constraint = nullptr; + { // Need a block to avoid memory leaks in case the AddConstraint fails + std::vector high_demand_intervals; + high_demand_intervals.reserve(intervals_.size()); + for (int i = 0; i < demands_.size(); ++i) { + const int64 demand = tasks_[i].demand->Min(); + // Consider two tasks with demand d1 and d2 such that + // d1 * 2 > capacity_ and d2 * 2 > capacity_. + // Then d1 + d2 = 1/2 (d1 * 2 + d2 * 2) + // > 1/2 (capacity_ + capacity_) + // > capacity_. + // Therefore these two tasks cannot overlap. + if (demand * 2 > capacity_ && tasks_[i].interval->MayBePerformed()) { + high_demand_intervals.push_back(tasks_[i].interval); + } + } + if (high_demand_intervals.size() >= 2) { + // If there are less than 2 such intervals, the constraint would do + // nothing + std::string seq_name = StrCat(name(), "-HighDemandSequence"); + constraint = solver()->MakeDisjunctiveConstraint(high_demand_intervals, + seq_name); + } + } + if (constraint != nullptr) { + solver()->AddConstraint(constraint); + } + } + + // Populate the given vector with useful tasks, meaning the ones on which + // some propagation can be done + void PopulateVectorUsefulTasks( + bool mirror, std::vector* const useful_tasks) { + DCHECK(useful_tasks->empty()); + for (int i = 0; i < tasks_.size(); ++i) { + const VariableCumulativeTask& original_task = tasks_[i]; + IntervalVar* const interval = original_task.interval; + // Check if exceed capacity + if (original_task.demand->Min() > capacity_) { + interval->SetPerformed(false); + } + // Add to the useful_task vector if it may be performed and that it + // actually consumes some of the resource. + if (interval->MayBePerformed() && original_task.demand > 0) { + Solver* const s = solver(); + IntervalVar* const original_interval = original_task.interval; + IntervalVar* const interval = + mirror ? s->MakeMirrorInterval(original_interval) + : original_interval; + IntervalVar* const relaxed_max = s->MakeIntervalRelaxedMax(interval); + useful_tasks->push_back( + new VariableCumulativeTask(relaxed_max, original_task.demand)); + } + } + } + + // Makes and return an edge-finder or a time table, or nullptr if it is not + // necessary. + Constraint* MakeOneSidedConstraint(bool mirror, bool edge_finder) { + std::vector useful_tasks; + PopulateVectorUsefulTasks(mirror, &useful_tasks); + if (useful_tasks.empty()) { + return nullptr; + } else { + Solver* const s = solver(); + if (edge_finder) { + return s->RevAlloc( + new EdgeFinder(s, useful_tasks, capacity_)); + } else { + return s->RevAlloc(new CumulativeTimeTable( + s, useful_tasks, capacity_)); + } + } + } + + // Post a straight or mirrored edge-finder, if needed + void PostOneSidedConstraint(bool mirror, bool edge_finder) { + Constraint* const constraint = MakeOneSidedConstraint(mirror, edge_finder); + if (constraint != nullptr) { + solver()->AddConstraint(constraint); + } + } + + // Capacity of the cumulative resource + const int64 capacity_; + + // The tasks that share the cumulative resource + std::vector tasks_; + + // Array of intervals for the visitor. + const std::vector intervals_; + // Array of demands for the visitor. + const std::vector demands_; + + DISALLOW_COPY_AND_ASSIGN(VariableDemandCumulativeConstraint); +}; } // namespace // Sequence Constraint @@ -1880,7 +2140,8 @@ class CumulativeConstraint : public Constraint { // ----- Public class ----- DisjunctiveConstraint::DisjunctiveConstraint( - Solver* const s, const std::vector& intervals, const std::string& name) + Solver* const s, const std::vector& intervals, + const std::string& name) : Constraint(s), intervals_(intervals) { if (!name.empty()) { set_name(name); @@ -1896,9 +2157,11 @@ DisjunctiveConstraint* Solver::MakeDisjunctiveConstraint( return RevAlloc(new FullDisjunctiveConstraint(this, intervals, name)); } +// Demands are constant + Constraint* Solver::MakeCumulative(const std::vector& intervals, - const std::vector& demands, int64 capacity, - const std::string& name) { + const std::vector& demands, + int64 capacity, const std::string& name) { CHECK_EQ(intervals.size(), demands.size()); for (int i = 0; i < intervals.size(); ++i) { CHECK_GE(demands[i], 0); @@ -1911,14 +2174,15 @@ Constraint* Solver::MakeCumulative(const std::vector& intervals, } Constraint* Solver::MakeCumulative(const std::vector& intervals, - const std::vector& demands, int64 capacity, - const std::string& name) { + const std::vector& demands, + int64 capacity, const std::string& name) { return MakeCumulative(intervals, ToInt64Vector(demands), capacity, name); } Constraint* Solver::MakeCumulative(const std::vector& intervals, const std::vector& demands, - IntVar* const capacity, const std::string& name) { + IntVar* const capacity, + const std::string& name) { CHECK_EQ(intervals.size(), demands.size()); for (int i = 0; i < intervals.size(); ++i) { CHECK_GE(demands[i], 0); @@ -1963,7 +2227,72 @@ Constraint* Solver::MakeCumulative(const std::vector& intervals, Constraint* Solver::MakeCumulative(const std::vector& intervals, const std::vector& demands, - IntVar* const capacity, const std::string& name) { + IntVar* const capacity, + const std::string& name) { return MakeCumulative(intervals, ToInt64Vector(demands), capacity, name); } + +// Demands are variable + +Constraint* Solver::MakeCumulative(const std::vector& intervals, + const std::vector& demands, + int64 capacity, const std::string& name) { + CHECK_EQ(intervals.size(), demands.size()); + for (int i = 0; i < intervals.size(); ++i) { + CHECK_GE(demands[i]->Min(), 0); + } + if (AreAllBound(demands)) { + std::vector fixed_demands(demands.size()); + for (int i = 0; i < demands.size(); ++i) { + fixed_demands[i] = demands[i]->Value(); + } + return MakeCumulative(intervals, fixed_demands, capacity, name); + } + return RevAlloc(new VariableDemandCumulativeConstraint( + this, intervals, demands, capacity, name)); +} + +Constraint* Solver::MakeCumulative(const std::vector& intervals, + const std::vector& demands, + IntVar* const capacity, + const std::string& name) { + CHECK_EQ(intervals.size(), demands.size()); + for (int i = 0; i < intervals.size(); ++i) { + CHECK_GE(demands[i], 0); + } + if (capacity->Bound()) { + return MakeCumulative(intervals, demands, capacity->Min(), name); + } + if (AreAllBound(demands)) { + std::vector fixed_demands(demands.size()); + for (int i = 0; i < demands.size(); ++i) { + fixed_demands[i] = demands[i]->Value(); + } + return MakeCumulative(intervals, fixed_demands, capacity, name); + } + std::vector a_intervals; + std::vector a_demands; + int64 horizon_min = kint64max; + int64 horizon_max = kint64min; + for (int i = 0; i < demands.size(); ++i) { + if (demands[i]->Max() > 0) { + a_intervals.push_back(intervals[i]); + a_demands.push_back(demands[i]); + horizon_max = std::max(horizon_max, intervals[i]->EndMax()); + horizon_min = std::min(horizon_min, intervals[i]->StartMin()); + } + } + const int64 total_duration = horizon_max = horizon_max + 1; + const int64 capacity_max = capacity->Max(); + IntVar* const residual_capacity = + MakeDifference(capacity_max, capacity)->Var(); + const std::string residual_name = + StringPrintf("VariableCapacity<%s>", name.c_str()); + IntervalVar* const var = MakeFixedDurationIntervalVar( + horizon_min, horizon_min, total_duration, true, residual_name); + a_intervals.push_back(var); + a_demands.push_back(residual_capacity); + return RevAlloc(new VariableDemandCumulativeConstraint( + this, a_intervals, a_demands, capacity_max, name)); +} } // namespace operations_research