Files
ortools-clone/constraint_solver/timetabling.cc
lperron@google.com 1524c8f391 initial checking
2010-09-15 12:42:33 +00:00

1536 lines
44 KiB
C++

// Copyright 2010 Google
// 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.
#include "base/integral_types.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/scoped_ptr.h"
#include "base/stringprintf.h"
#include "base/stl_util-inl.h"
#include "constraint_solver/constraint_solveri.h"
namespace operations_research {
// ----- interval <unary relation> date -----
namespace {
const char* kUnaryNames[] = {
"ENDS_AFTER",
"ENDS_AT",
"ENDS_BEFORE",
"STARTS_AFTER",
"STARTS_AT",
"STARTS_BEFORE",
"CROSS_DATE",
"AVOID_DATE",
};
const char* kBinaryNames[] = {
"ENDS_AFTER_END",
"ENDS_AFTER_START",
"ENDS_AT_END",
"ENDS_AT_START",
"STARTS_AFTER_END",
"STARTS_AFTER_START",
"STARTS_AT_END",
"STARTS_AT_START"
};
} // namespace
class IntervalUnaryRelation : public Constraint {
public:
IntervalUnaryRelation(Solver* const s,
IntervalVar* const t,
int64 d,
Solver::UnaryIntervalRelation rel)
: Constraint(s), t_(t), d_(d), rel_(rel) {}
virtual ~IntervalUnaryRelation() {}
virtual void Post();
virtual void InitialPropagate();
virtual string DebugString() const {
return StringPrintf("(%s %s %" GG_LL_FORMAT "d)",
t_->DebugString().c_str(), kUnaryNames[rel_], d_);
}
private:
IntervalVar* const t_;
const int64 d_;
const Solver::UnaryIntervalRelation rel_;
};
void IntervalUnaryRelation::Post() {
if (t_->PerformedMax() != 0) {
Demon* d = solver()->MakeConstraintInitialPropagateCallback(this);
t_->WhenStartRange(d);
t_->WhenDurationRange(d);
t_->WhenEndRange(d);
t_->WhenPerformedBound(d);
}
}
void IntervalUnaryRelation::InitialPropagate() {
if (t_->PerformedMax() == 1) {
switch (rel_) {
case Solver::ENDS_AFTER:
t_->SetEndMin(d_);
break;
case Solver::ENDS_AT:
t_->SetEndRange(d_, d_);
break;
case Solver::ENDS_BEFORE:
t_->SetEndMax(d_);
break;
case Solver::STARTS_AFTER:
t_->SetStartMin(d_);
break;
case Solver::STARTS_AT:
t_->SetStartRange(d_, d_);
break;
case Solver::STARTS_BEFORE:
t_->SetStartMax(d_);
break;
case Solver::CROSS_DATE:
t_->SetStartMax(d_);
t_->SetEndMin(d_);
break;
case Solver::AVOID_DATE:
if (t_->EndMin() > d_) {
t_->SetStartMin(d_);
} else if (t_->StartMax() < d_) {
t_->SetEndMax(d_);
}
break;
}
}
}
Constraint* Solver::MakeIntervalVarRelation(IntervalVar* const t,
Solver::UnaryIntervalRelation r,
int64 d) {
return RevAlloc(new IntervalUnaryRelation(this, t, d, r));
}
// ----- interval <binary relation> interval -----
class IntervalBinaryRelation : public Constraint {
public:
IntervalBinaryRelation(Solver* const s,
IntervalVar* const t1,
IntervalVar* const t2,
Solver::BinaryIntervalRelation rel)
: Constraint(s), t1_(t1), t2_(t2), rel_(rel) {}
virtual ~IntervalBinaryRelation() {}
virtual void Post();
virtual void InitialPropagate();
virtual string DebugString() const {
return StringPrintf("(%s %s %s)",
t1_->DebugString().c_str(),
kBinaryNames[rel_],
t2_->DebugString().c_str());
}
private:
IntervalVar* const t1_;
IntervalVar* const t2_;
const Solver::BinaryIntervalRelation rel_;
};
void IntervalBinaryRelation::Post() {
if (t1_->PerformedMin() + t2_->PerformedMin() >= 1) {
Demon* d = solver()->MakeConstraintInitialPropagateCallback(this);
t1_->WhenStartRange(d);
t1_->WhenDurationRange(d);
t1_->WhenEndRange(d);
t1_->WhenPerformedBound(d);
t2_->WhenStartRange(d);
t2_->WhenDurationRange(d);
t2_->WhenEndRange(d);
t2_->WhenPerformedBound(d);
}
}
// TODO(user) : make code more compact, use function pointers?
void IntervalBinaryRelation::InitialPropagate() {
switch (rel_) {
case Solver::ENDS_AFTER_END:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetEndMin(t2_->EndMin());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetEndMax(t1_->EndMax());
}
break;
case Solver::ENDS_AFTER_START:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetEndMin(t2_->StartMin());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetStartMax(t1_->EndMax());
}
break;
case Solver::ENDS_AT_END:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetEndRange(t2_->EndMin(), t2_->EndMax());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetEndRange(t1_->EndMin(), t1_->EndMax());
}
break;
case Solver::ENDS_AT_START:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetEndRange(t2_->StartMin(), t2_->StartMax());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetStartRange(t1_->EndMin(), t1_->EndMax());
}
break;
case Solver::STARTS_AFTER_END:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetStartMin(t2_->EndMin());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetEndMax(t1_->StartMax());
}
break;
case Solver::STARTS_AFTER_START:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetStartMin(t2_->StartMin());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetEndMax(t1_->StartMax());
}
break;
case Solver::STARTS_AT_END:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetStartRange(t2_->EndMin(), t2_->EndMax());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetEndRange(t1_->StartMin(), t1_->StartMax());
}
break;
case Solver::STARTS_AT_START:
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetStartRange(t2_->StartMin(), t2_->StartMax());
}
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetStartRange(t1_->StartMin(), t1_->StartMax());
}
break;
}
}
Constraint* Solver::MakeIntervalVarRelation(IntervalVar* const t1,
Solver::BinaryIntervalRelation r,
IntervalVar* const t2) {
return RevAlloc(new IntervalBinaryRelation(this, t1, t2, r));
}
// ----- activity a before activity b or activity b before activity a -----
class TemporalDisjunction : public Constraint {
public:
enum State { ONE_BEFORE_TWO, TWO_BEFORE_ONE, UNDECIDED };
TemporalDisjunction(Solver* const s,
IntervalVar* const t1,
IntervalVar* const t2,
IntVar* const alt)
: Constraint(s), t1_(t1), t2_(t2), alt_(alt), state_(UNDECIDED) {}
virtual ~TemporalDisjunction() {}
virtual void Post();
virtual void InitialPropagate();
virtual string DebugString() const;
void RangeDemon1();
void RangeDemon2();
void RangeAlt();
void Decide(State s);
void TryToDecide();
private:
IntervalVar* const t1_;
IntervalVar* const t2_;
IntVar* const alt_;
State state_;
};
void TemporalDisjunction::Post() {
Solver* const s = solver();
Demon* d = MakeConstraintDemon0(s,
this,
&TemporalDisjunction::RangeDemon1,
"RangeDemon1");
t1_->WhenStartRange(d);
d = MakeConstraintDemon0(s,
this,
&TemporalDisjunction::RangeDemon2,
"RangeDemon2");
t2_->WhenStartRange(d);
if (alt_ != NULL) {
d = MakeConstraintDemon0(s,
this,
&TemporalDisjunction::RangeAlt,
"RangeAlt");
alt_->WhenRange(d);
}
}
void TemporalDisjunction::InitialPropagate() {
if (alt_ != NULL) {
alt_->SetRange(0, 1);
}
if (alt_ != NULL && alt_->Bound()) {
RangeAlt();
} else {
RangeDemon1();
RangeDemon2();
}
}
string TemporalDisjunction::DebugString() const {
string out;
SStringPrintf(&out, "TemporalDisjunction(%s, %s",
t1_->DebugString().c_str(), t2_->DebugString().c_str());
if (alt_ != NULL) {
StringAppendF(&out, " => %s", alt_->DebugString().c_str());
}
out += ") ";
return out;
}
void TemporalDisjunction::TryToDecide() {
DCHECK_EQ(UNDECIDED, state_);
if (t1_->PerformedMax() + t2_->PerformedMax() == 2 &&
t1_->PerformedMin() + t2_->PerformedMin() > 0) {
if (t1_->EndMin() > t2_->StartMax()) {
Decide(TWO_BEFORE_ONE);
} else if (t2_->EndMin() > t1_->StartMax()) {
Decide(ONE_BEFORE_TWO);
}
}
}
void TemporalDisjunction::RangeDemon1() {
switch (state_) {
case ONE_BEFORE_TWO: {
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetStartMin(t1_->EndMin());
}
break;
}
case TWO_BEFORE_ONE: {
if (t1_->PerformedMin() == 1 && t2_->PerformedMax() != 0) {
t2_->SetEndMax(t1_->StartMax());
}
break;
}
case UNDECIDED: {
TryToDecide();
}
}
}
void TemporalDisjunction::RangeDemon2() {
if (t1_->PerformedMax() == 1 || t2_->PerformedMax() == 1) {
switch (state_) {
case ONE_BEFORE_TWO: {
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetEndMax(t2_->StartMax());
}
break;
}
case TWO_BEFORE_ONE: {
if (t2_->PerformedMin() == 1 && t1_->PerformedMax() != 0) {
t1_->SetStartMin(t2_->EndMin());
}
break;
}
case UNDECIDED: {
TryToDecide();
}
}
}
}
void TemporalDisjunction::RangeAlt() {
DCHECK(alt_ != NULL);
if (alt_->Value() == 0) {
Decide(ONE_BEFORE_TWO);
} else {
Decide(TWO_BEFORE_ONE);
}
}
void TemporalDisjunction::Decide(State s) {
// Should Decide on a fixed state?
DCHECK_NE(s, UNDECIDED);
if (state_ != UNDECIDED && state_ != s) {
solver()->Fail();
}
solver()->SaveValue(reinterpret_cast<int*>(&state_));
state_ = s;
if (alt_ != NULL) {
if (s == ONE_BEFORE_TWO) {
alt_->SetValue(0);
} else {
alt_->SetValue(1);
}
}
RangeDemon1();
RangeDemon2();
}
Constraint* Solver::MakeTemporalDisjunction(IntervalVar* const t1,
IntervalVar* const t2,
IntVar* const alt) {
return RevAlloc(new TemporalDisjunction(this, t1, t2, alt));
}
Constraint* Solver::MakeTemporalDisjunction(IntervalVar* const t1,
IntervalVar* const t2) {
return RevAlloc(new TemporalDisjunction(this, t1, t2, NULL));
}
// ----- Sequence -----
Constraint* MakeSequenceConstraintOnPerformed(
Solver* const s, const IntervalVar* const * intervals, int size);
Sequence::Sequence(Solver* const s,
const IntervalVar* const * intervals,
int size,
const string& name)
: Constraint(s), intervals_(new IntervalVar*[size]),
size_(size), ranks_(new int[size]), current_rank_(0) {
memcpy(intervals_.get(), intervals, size_ * sizeof(*intervals));
states_.resize(size);
for (int i = 0; i < size_; ++i) {
ranks_[i] = 0;
states_[i].resize(size, UNDECIDED);
}
}
Sequence::~Sequence() {}
IntervalVar* Sequence::Interval(int index) const {
CHECK_GE(index, 0);
CHECK_LT(index, size_);
return intervals_[index];
}
void Sequence::Post() {
for (int i = 0; i < size_; ++i) {
IntervalVar* t = intervals_[i];
Demon* d = MakeConstraintDemon1(solver(),
this,
&Sequence::RangeChanged,
"RangeChanged",
i);
t->WhenStartRange(d);
t->WhenEndRange(d);
}
int all_performed = 0;
int all_decided = 0;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->PerformedMax() == 0) {
all_decided++;
} else if (intervals_[i]->PerformedMin() == 1) {
all_decided++;
all_performed++;
}
}
if (all_performed == size_) {
Constraint* ct = MakeSequenceConstraintOnPerformed(solver(),
intervals_.get(),
size_);
solver()->AddConstraint(ct);
} else if (all_decided == size_ && all_performed > 1) {
vector<IntervalVar*> performed;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->PerformedMin() == 1) {
performed.push_back(intervals_[i]);
}
}
Constraint* ct = MakeSequenceConstraintOnPerformed(solver(),
performed.data(),
performed.size());
solver()->AddConstraint(ct);
}
}
// TODO(user) : Post constraint when all is decided too
void Sequence::InitialPropagate() {
for (int i = 0; i < size_; ++i) {
RangeChanged(i);
}
}
void Sequence::RangeChanged(int index) {
for (int i = 0; i < index; ++i) {
Apply(i, index);
}
for (int i = index + 1; i < size_; ++i) {
Apply(index, i);
}
}
void Sequence::Apply(int i, int j) {
DCHECK_LT(i, j);
IntervalVar* t1 = intervals_[i];
IntervalVar* t2 = intervals_[j];
State s = states_[i][j];
if (s == UNDECIDED) {
TryToDecide(i, j);
}
if (s == ONE_BEFORE_TWO) {
if (t1->PerformedMin() == 1 && t2->PerformedMax() != 0) {
t2->SetStartMin(t1->EndMin());
}
if (t2->PerformedMin() == 1 && t1->PerformedMax() != 0) {
t1->SetEndMax(t2->StartMax());
}
} else if (s == TWO_BEFORE_ONE) {
if (t1->PerformedMin() == 1 && t2->PerformedMax() != 0) {
t2->SetEndMax(t1->StartMax());
}
if (t2->PerformedMin() == 1 && t1->PerformedMax() != 0) {
t1->SetStartMin(t2->EndMin());
}
}
}
void Sequence::TryToDecide(int i, int j) {
DCHECK_LT(i, j);
DCHECK_EQ(UNDECIDED, states_[i][j]);
IntervalVar* t1 = intervals_[i];
IntervalVar* t2 = intervals_[j];
if (t1->PerformedMax() + t2->PerformedMax() == 2 &&
t1->PerformedMin() + t2->PerformedMin() > 0) {
if (t1->EndMin() > t2->StartMax()) {
Decide(TWO_BEFORE_ONE, i, j);
} else if (t2->EndMin() > t1->StartMax()) {
Decide(ONE_BEFORE_TWO, i, j);
}
}
}
void Sequence::Decide(State s, int i, int j) {
DCHECK_LT(i, j);
// Should Decide on a fixed state?
DCHECK_NE(s, UNDECIDED);
if (states_[i][j] != UNDECIDED && states_[i][j] != s) {
solver()->Fail();
}
solver()->SaveValue(reinterpret_cast<int*>(&states_[i][j]));
states_[i][j] = s;
Apply(i, j);
}
string Sequence::DebugString() const {
int64 hmin, hmax, dmin, dmax;
HorizonRange(&hmin, &hmax);
DurationRange(&dmin, &dmax);
return StringPrintf("%s(horizon = %" GG_LL_FORMAT
"d..%" GG_LL_FORMAT
"d, duration = %" GG_LL_FORMAT
"d..%" GG_LL_FORMAT
"d, not ranked = %d, fixed = %d, ranked = %d)",
name().c_str(), hmin, hmax, dmin, dmax, NotRanked(),
Fixed(), Ranked());
}
void Sequence::DurationRange(int64* dmin, int64* dmax) const {
int64 dur_min = 0;
int64 dur_max = 0;
for (int i = 0; i < size_; ++i) {
IntervalVar* t = intervals_[i];
if (t->PerformedMax() == 1) {
if (t->PerformedMin() == 1) {
dur_min += t->DurationMin();
}
dur_max += t->DurationMax();
}
}
*dmin = dur_min;
*dmax = dur_max;
}
void Sequence::HorizonRange(int64* hmin, int64* hmax) const {
int64 hor_min = kint64max;
int64 hor_max = kint64min;
for (int i = 0; i < size_; ++i) {
IntervalVar* t = intervals_[i];
if (t->PerformedMax() == 1) {
const int64 tmin = t->StartMin();
const int64 tmax = t->EndMax();
if (tmin < hor_min) {
hor_min = tmin;
}
if (tmax > hor_max) {
hor_max = tmax;
}
}
}
*hmin = hor_min;
*hmax = hor_max;
}
void Sequence::ActiveHorizonRange(int64* hmin, int64* hmax) const {
int64 hor_min = kint64max;
int64 hor_max = kint64min;
for (int i = 0; i < size_; ++i) {
IntervalVar* t = intervals_[i];
if (t->PerformedMax() == 1 && ranks_[i] >= current_rank_) {
const int64 tmin = t->StartMin();
const int64 tmax = t->EndMax();
if (tmin < hor_min) {
hor_min = tmin;
}
if (tmax > hor_max) {
hor_max = tmax;
}
}
}
*hmin = hor_min;
*hmax = hor_max;
}
int Sequence::Ranked() const {
int count = 0;
for (int i = 0; i < size_; ++i) {
if (ranks_[i] < current_rank_ && intervals_[i]->PerformedMax() != 0) {
count++;
}
}
return count;
}
int Sequence::NotRanked() const {
int count = 0;
for (int i = 0; i < size_; ++i) {
if (ranks_[i] >= current_rank_ &&
intervals_[i]->PerformedMax() != 0) {
count++;
}
}
return count;
}
int Sequence::Active() const {
int count = 0;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->PerformedMax() != 0) {
count++;
}
}
return count;
}
int Sequence::Fixed() const {
int count = 0;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->PerformedMin() == 1 &&
intervals_[i]->StartMin() == intervals_[i]->StartMax()) {
count++;
}
}
return count;
}
void Sequence::ComputePossibleRanks() {
for (int i = 0; i < size_; ++i) {
if (ranks_[i] == current_rank_) {
int before = 0;
int after = 0;
for (int j = 0; j < i; ++j) {
if (intervals_[j]->PerformedMin() == 1) {
State s = states_[j][i];
if (s == ONE_BEFORE_TWO) {
before++;
} else if (s == TWO_BEFORE_ONE) {
after++;
}
}
}
for (int j = i + 1; j < size_; ++j) {
if (intervals_[j]->PerformedMin() == 1) {
State s = states_[i][j];
if (s == ONE_BEFORE_TWO) {
after++;
} else if (s == TWO_BEFORE_ONE) {
before++;
}
}
}
if (before > current_rank_) {
solver()->SaveAndSetValue(&ranks_[i], before);
}
}
}
}
bool Sequence::PossibleFirst(int index) {
return (ranks_[index] == current_rank_);
}
void Sequence::RankFirst(int index) {
IntervalVar* t = intervals_[index];
t->SetPerformed(true);
Solver* const s = solver();
for (int i = 0; i < size_; ++i) {
if (i != index &&
ranks_[i] >= current_rank_ &&
intervals_[i]->PerformedMax() != 0LL) {
s->SaveAndSetValue(&ranks_[i], current_rank_ + 1);
if (i < index) {
Decide(TWO_BEFORE_ONE, i, index);
} else {
Decide(ONE_BEFORE_TWO, index, i);
}
}
}
s->SaveAndSetValue(&ranks_[index], current_rank_);
s->SaveAndAdd(&current_rank_, 1);
}
void Sequence::RankNotFirst(int index) {
solver()->SaveAndSetValue(&ranks_[index], current_rank_ + 1);
int count = 0;
int support = -1;
for (int i = 0; i < size_; ++i) {
if (ranks_[i] == current_rank_ && intervals_[i]->PerformedMax() == 1) {
count++;
support = i;
}
}
if (count == 0) {
solver()->Fail();
}
if (count == 1 && intervals_[support]->PerformedMin() == 1) {
RankFirst(support);
}
}
Sequence* Solver::MakeSequence(const vector<IntervalVar*>& intervals,
const string& name) {
return RevAlloc(new Sequence(this,
intervals.data(), intervals.size(), name));
}
Sequence* Solver::MakeSequence(const IntervalVar* const * intervals, int size,
const string& name) {
return RevAlloc(new Sequence(this, intervals, size, name));
}
// ----- Additional constraint on Sequence -----
namespace {
class IntervalWrapper {
public:
explicit IntervalWrapper(int index, IntervalVar* const t)
: t_(t), index_(index), est_pos_(-1) {}
int index() const { return index_; }
IntervalVar* const interval() const { return t_; }
int est_pos() const { return est_pos_; }
void set_est_pos(int pos) { est_pos_ = pos; }
string DebugString() const {
return StringPrintf("Wrapper(%s, index = %d, est_pos = %d)",
t_->DebugString().c_str(), index_, est_pos_);
}
private:
IntervalVar* const t_;
const int index_;
int est_pos_;
DISALLOW_COPY_AND_ASSIGN(IntervalWrapper);
};
// This method is used by the STL sort.
bool CompareESTLT(const IntervalWrapper* const w1,
const IntervalWrapper* const w2) {
return (w1->interval()->StartMin() < w2->interval()->StartMin());
}
bool CompareLCTLT(const IntervalWrapper* const w1,
const IntervalWrapper* const w2) {
return (w1->interval()->EndMax() < w2->interval()->EndMax());
}
bool CompareLSTLT(const IntervalWrapper* const w1,
const IntervalWrapper* const w2) {
return (w1->interval()->StartMax() < w2->interval()->StartMax());
}
bool CompareECTLT(const IntervalWrapper* const w1,
const IntervalWrapper* const w2) {
return (w1->interval()->EndMin() < w2->interval()->EndMin());
}
// This is based on Petr Vilim (public) PhD work. All names comes from his work.
// see http://vilim.eu/petr
class ThetaTree : public BaseObject {
public:
struct Node {
Node() : interval(NULL), total_processing(0), total_ect(kint64min) {}
IntervalVar* interval;
int64 total_processing;
int64 total_ect;
};
explicit ThetaTree(int size);
virtual ~ThetaTree() {}
void Insert(IntervalVar* const t, int pos);
void Remove(int pos);
int64 ECT() { return ect(0); }
void Clear();
bool Inserted(int pos) {
return nodes_[pos + isize_].interval != NULL;
}
virtual string DebugString() const;
private:
void ReCompute(int pos);
void ReComputeAux(int pos);
int64 processing(int pos) { return nodes_[pos].total_processing; }
int64 ect(int pos) { return nodes_[pos].total_ect; }
int father(int pos) const { return (pos - 1) >> 1; }
int left(int pos) const { return (pos << 1) + 1; }
int right(int pos) const { return (pos + 1) << 1; }
const int size_;
int isize_;
vector<Node> nodes_;
};
ThetaTree::ThetaTree(int size) : size_(size) {
// Compute the number of internal nodes
isize_ = 1;
while (isize_ < size) {
isize_ <<= 1;
}
isize_--;
// Resize a bit bigger such that the bottom layer is full.
nodes_.resize((isize_ << 1) + 1);
}
void ThetaTree::Clear() {
for (vector<Node>::iterator it = nodes_.begin();
it != nodes_.end();
++it) {
(*it).interval = NULL;
(*it).total_processing = 0LL;
(*it).total_ect = kint64min;
}
}
void ThetaTree::Insert(IntervalVar* const t, int pos) {
const int curr_pos = isize_ + pos;
Node& n = nodes_[curr_pos];
DCHECK(n.interval == NULL);
n.interval = t;
n.total_ect = t->EndMin();
n.total_processing = t->DurationMin();
ReCompute(father(curr_pos));
}
void ThetaTree::Remove(int pos) {
const int curr_pos = isize_ + pos;
Node& n = nodes_[curr_pos];
DCHECK(n.interval != NULL);
n.interval = NULL;
n.total_ect = kint64min;
n.total_processing = 0LL;
ReCompute(father(curr_pos));
}
void ThetaTree::ReComputeAux(int pos) {
const int64 pl = processing(left(pos));
const int64 pr = processing(right(pos));
nodes_[pos].total_processing = pl + pr;
const int64 el = ect(left(pos));
const int64 er = ect(right(pos));
const int64 en = max(er, el + pr);
nodes_[pos].total_ect = en;
}
void ThetaTree::ReCompute(int pos) {
while (pos > 0) {
ReComputeAux(pos);
pos = father(pos);
}
// Fast recompute the top node. We do not need all info.
nodes_[0].total_ect = max(nodes_[2].total_ect,
nodes_[1].total_ect + nodes_[2].total_processing);
}
string ThetaTree::DebugString() const {
string out;
for (int i = 0; i < isize_ + size_; ++i) {
int64 p = nodes_[i].total_processing;
int64 e = nodes_[i].total_ect;
IntervalVar* t = nodes_[i].interval;
StringAppendF(&out, "(%d: p = %" GG_LL_FORMAT "d, e = %"
GG_LL_FORMAT "d", i, p, (e < 0 ? -1 : e));
if (t != NULL) {
StringAppendF(&out, ", t = %s", t->DebugString().c_str());
}
out += ") ";
}
return out;
}
// ----- Lambda Theta Tree -----
class LambdaThetaTree : public BaseObject {
public:
struct ENode {
ENode() : interval(NULL), processing(0LL), ect(kint64min),
processing_opt(0LL), ect_opt(kint64min),
responsible_ect(-1), responsible_processing(-1) {}
IntervalVar* interval;
int64 processing;
int64 ect;
int64 processing_opt;
int64 ect_opt;
int responsible_ect;
int responsible_processing;
};
explicit LambdaThetaTree(int size);
virtual ~LambdaThetaTree() {}
void Insert(IntervalVar* const t, int pos);
void Grey(int pos);
void Remove(int pos);
int64 ECT() { return ect(0); }
int64 ECT_opt() { return ect_opt(0); }
int Responsible_opt() { return responsible_ect(0); }
void Clear();
bool Inserted(int pos) {
return nodes_[pos + isize_].interval != NULL;
}
virtual string DebugString() const;
private:
void ReCompute(int pos);
void ReComputeAux(int pos);
void ReComputeTop();
int64 processing(int pos) { return nodes_[pos].processing; }
int64 ect(int pos) { return nodes_[pos].ect; }
int64 processing_opt(int pos) { return nodes_[pos].processing_opt; }
int64 ect_opt(int pos) { return nodes_[pos].ect_opt; }
int responsible_ect(int pos) { return nodes_[pos].responsible_ect; }
int responsible_processing(int pos) {
return nodes_[pos].responsible_processing;
}
int father(int pos) const { return (pos - 1) >> 1; }
int left(int pos) const { return (pos << 1) + 1; }
int right(int pos) const { return (pos + 1) << 1; }
const int size_;
int isize_;
vector<ENode> nodes_;
};
LambdaThetaTree::LambdaThetaTree(int size) : size_(size) {
// Compute the number of internal nodes
isize_ = 1;
while (isize_ < size) {
isize_ <<= 1;
}
isize_--;
// Resize a bit bigger such that the bottom layer is full.
nodes_.resize((isize_ << 1) + 1);
}
void LambdaThetaTree::Clear() {
for (vector<ENode>::iterator it = nodes_.begin();
it != nodes_.end();
++it) {
(*it).interval = NULL;
(*it).processing = 0LL;
(*it).ect = kint64min;
(*it).processing_opt = 0LL;
(*it).ect_opt = kint64min;
(*it).responsible_ect = -1;
(*it).responsible_processing = -1;
}
}
void LambdaThetaTree::Insert(IntervalVar* const t, int pos) {
const int curr_pos = isize_ + pos;
ENode& n = nodes_[curr_pos];
DCHECK(n.interval == NULL);
n.interval = t;
n.ect = t->EndMin();
n.processing = t->DurationMin();
n.ect_opt = t->EndMin();
n.processing_opt = t->DurationMin();
n.responsible_ect = -1;
n.responsible_processing = -1;
ReCompute(father(curr_pos));
}
void LambdaThetaTree::Grey(int pos) {
const int curr_pos = isize_ + pos;
ENode& n = nodes_[curr_pos];
DCHECK(n.interval != NULL);
n.ect = kint64min;
n.processing = 0LL;
n.responsible_ect = pos;
n.responsible_processing = pos;
ReCompute(father(curr_pos));
}
void LambdaThetaTree::Remove(int pos) {
const int curr_pos = isize_ + pos;
ENode& n = nodes_[curr_pos];
DCHECK(n.interval != NULL);
n.interval = NULL;
n.ect = kint64min;
n.processing = 0LL;
n.ect_opt = kint64min;
n.processing_opt = 0LL;
n.responsible_ect = -1;
n.responsible_processing = -1;
ReCompute(father(curr_pos));
}
void LambdaThetaTree::ReComputeAux(int pos) {
ENode& n = nodes_[pos];
const int64 pr = processing(right(pos));
n.processing = processing(left(pos)) + pr;
n.ect = max(ect(right(pos)), ect(left(pos)) + pr);
if (responsible_ect(left(pos)) == -1 && responsible_ect(right(pos)) == -1) {
n.processing_opt = n.processing;
n.ect_opt = n.ect;
n.responsible_ect = -1;
n.responsible_processing = -1;
} else {
const int64 lo = processing_opt(left(pos)) + processing(right(pos));
const int64 ro = processing(left(pos)) + processing_opt(right(pos));
if (lo > ro) {
n.processing_opt = lo;
n.responsible_processing = responsible_processing(left(pos));
} else {
n.processing_opt = ro;
n.responsible_processing = responsible_processing(right(pos));
}
const int64 ect1 = ect_opt(right(pos));
const int64 ect2 = ect(left(pos)) + processing_opt(right(pos));
const int64 ect3 = ect_opt(left(pos)) + processing(right(pos));
if (ect1 >= ect2 && ect1 >= ect3) { // ect1 max
n.ect_opt = ect1;
n.responsible_ect = responsible_ect(right(pos));
} else if (ect2 >= ect1 && ect2 >= ect3) { // ect2 max
n.ect_opt = ect2;
n.responsible_ect = responsible_processing(right(pos));
} else { // ect3 max
n.ect_opt = ect3;
n.responsible_ect = responsible_ect(left(pos));
}
DCHECK_NE(-1, n.responsible_processing);
}
}
void LambdaThetaTree::ReComputeTop() {
ENode& n = nodes_[0];
n.ect = max(ect(2), ect(1) + processing(2));
if (responsible_ect(1) == -1 && responsible_ect(2) == -1) {
n.processing_opt = n.processing;
n.ect_opt = n.ect;
n.responsible_ect = -1;
n.responsible_processing = -1;
} else {
const int64 ect1 = ect_opt(2);
const int64 ect2 = ect(1) + processing_opt(2);
const int64 ect3 = ect_opt(1) + processing(2);
if (ect1 >= ect2 && ect1 >= ect3) {
n.ect_opt = ect1;
n.responsible_ect = responsible_ect(2);
} else if (ect2 >= ect1 && ect2 >= ect3) {
n.ect_opt = ect2;
n.responsible_ect = responsible_processing(2);
} else { // ect3 >= ect1 && ect3 >= ect2
n.ect_opt = ect3;
n.responsible_ect = responsible_ect(1);
}
}
}
void LambdaThetaTree::ReCompute(int pos) {
DCHECK_LT(pos, isize_);
while (pos > 0) {
ReComputeAux(pos);
pos = father(pos);
}
ReComputeTop();
}
string LambdaThetaTree::DebugString() const {
string out;
for (int i = 0; i < isize_ + size_; ++i) {
int64 p = nodes_[i].processing;
int64 e = nodes_[i].ect;
int64 po = nodes_[i].processing_opt;
int64 eo = nodes_[i].ect_opt;
int re = nodes_[i].responsible_ect;
int rp = nodes_[i].responsible_processing;
IntervalVar* t = nodes_[i].interval;
StringAppendF(&out,
"(%d: p = %" GG_LL_FORMAT "d, e = %"
GG_LL_FORMAT "d, po = %" GG_LL_FORMAT "d, "
"eo = %" GG_LL_FORMAT "d, re = %d, rp = %d",
i, p, (e < 0 ? -1 : e), po, (eo < 0 ? -1 : eo), re, rp);
if (t != NULL) {
StringAppendF(&out, ", t = %s", t->DebugString().c_str());
}
out += ") ";
}
return out;
}
// ----- MirrorIntervalVar -----
class MirrorIntervalVar : public IntervalVar {
public:
MirrorIntervalVar(Solver* const s, IntervalVar* const t)
: IntervalVar(s, "Mirror<" + t->name() + ">"), t_(t) {}
virtual ~MirrorIntervalVar() {}
// These methods query, set and watch the start position of the
// interval var.
virtual int64 StartMin() const { return -t_->EndMax(); }
virtual int64 StartMax() const { return -t_->EndMin(); }
virtual void SetStartMin(int64 m) { t_->SetEndMax(-m); }
virtual void SetStartMax(int64 m) { t_->SetEndMin(-m); }
virtual void SetStartRange(int64 mi, int64 ma) { t_->SetEndRange(-ma, -mi); }
virtual void WhenStartRange(Demon* const d) { t_->WhenEndRange(d); }
virtual void WhenStartBound(Demon* const d) { t_->WhenEndBound(d); }
// These methods query, set and watch the duration of the interval var.
virtual int64 DurationMin() const { return t_->DurationMin(); }
virtual int64 DurationMax() const { return t_->DurationMax(); }
virtual void SetDurationMin(int64 m) { t_->SetDurationMin(m); }
virtual void SetDurationMax(int64 m) { t_->SetDurationMax(m); }
virtual void SetDurationRange(int64 mi, int64 ma) {
t_->SetDurationRange(mi, ma);
}
virtual void WhenDurationRange(Demon* const d) { t_->WhenDurationRange(d); }
virtual void WhenDurationBound(Demon* const d) { t_->WhenDurationBound(d); }
// These methods query, set and watch the end position of the interval var.
virtual int64 EndMin() const { return -t_->StartMax(); }
virtual int64 EndMax() const { return -t_->StartMin(); }
virtual void SetEndMin(int64 m) { t_->SetStartMax(-m); }
virtual void SetEndMax(int64 m) { t_->SetStartMin(-m); }
virtual void SetEndRange(int64 mi, int64 ma) { t_->SetStartRange(-ma, -mi); }
virtual void WhenEndRange(Demon* const d) { t_->WhenStartRange(d); }
virtual void WhenEndBound(Demon* const d) { t_->WhenStartBound(d); }
// These methods query, set and watches the performed status of the
// interval var.
virtual bool PerformedMin() const { return t_->PerformedMin(); }
virtual bool PerformedMax() const { return t_->PerformedMax(); }
virtual void SetPerformed(bool val) { t_->SetPerformed(val); }
virtual void WhenPerformedBound(Demon* const d) { t_->WhenPerformedBound(d); }
private:
IntervalVar* const t_;
DISALLOW_COPY_AND_ASSIGN(MirrorIntervalVar);
};
} // namespace
IntervalVar* MakeMirrorInterval(Solver* const s, IntervalVar* const t) {
return s->RevAlloc(new MirrorIntervalVar(s, t));
}
class SequenceConstraintOnPerformed : public Constraint {
public:
SequenceConstraintOnPerformed(Solver* const s,
const IntervalVar* const * intervals,
int size);
virtual ~SequenceConstraintOnPerformed();
virtual void Post() {
Demon* d = MakeDelayedConstraintDemon0(
solver(),
this,
&SequenceConstraintOnPerformed::InitialPropagate,
"InitialPropagate");
for (int32 i = 0; i < size_; ++i) {
intervals_[i]->WhenStartRange(d);
intervals_[i]->WhenDurationRange(d);
intervals_[i]->WhenEndRange(d);
}
}
virtual void InitialPropagate();
void UpdateEst();
void OverloadChecking();
bool DetectablePrecedences();
bool NotFirstNotLast();
bool EdgeFinder();
private:
scoped_array<IntervalVar*> intervals_;
const int size_;
ThetaTree theta_tree_;
vector<IntervalWrapper*> wrappers_;
vector<IntervalWrapper*> ect_;
vector<IntervalWrapper*> est_;
vector<IntervalWrapper*> lct_;
vector<IntervalWrapper*> lst_;
vector<int64> new_est_;
vector<IntervalWrapper*> mwrappers_;
vector<IntervalWrapper*> mect_;
vector<IntervalWrapper*> mest_;
vector<IntervalWrapper*> mlct_;
vector<IntervalWrapper*> mlst_;
vector<int64> new_lct_;
LambdaThetaTree lt_tree_;
};
SequenceConstraintOnPerformed::SequenceConstraintOnPerformed(
Solver* const s, const IntervalVar* const * intervals, int size)
: Constraint(s), intervals_(new IntervalVar*[size]),
size_(size), theta_tree_(size), lt_tree_(size) {
memcpy(intervals_.get(), intervals, size_ * sizeof(*intervals));
for (int i = 0; i < size; ++i) {
IntervalWrapper* w = new IntervalWrapper(i, intervals_[i]);
wrappers_.push_back(w);
ect_.push_back(w);
est_.push_back(w);
lct_.push_back(w);
lst_.push_back(w);
new_est_.push_back(kint64min);
IntervalVar* m = MakeMirrorInterval(s, intervals_[i]);
w = new IntervalWrapper(i, m);
mwrappers_.push_back(w);
mect_.push_back(w);
mest_.push_back(w);
mlct_.push_back(w);
mlst_.push_back(w);
new_lct_.push_back(kint64max);
}
}
SequenceConstraintOnPerformed::~SequenceConstraintOnPerformed() {
STLDeleteElements(&wrappers_);
STLDeleteElements(&mwrappers_);
}
void SequenceConstraintOnPerformed::UpdateEst() {
std::sort(est_.begin(), est_.end(), CompareESTLT);
for (int i = 0; i < size_; ++i) {
est_[i]->set_est_pos(i);
}
std::sort(mest_.begin(), mest_.end(), CompareESTLT);
for (int i = 0; i < size_; ++i) {
mest_[i]->set_est_pos(i);
}
}
void SequenceConstraintOnPerformed::InitialPropagate() {
do {
do {
do {
OverloadChecking();
} while (DetectablePrecedences());
} while (NotFirstNotLast());
} while (EdgeFinder());
}
void SequenceConstraintOnPerformed::OverloadChecking() {
// Init
UpdateEst();
// One direction
std::sort(lct_.begin(), lct_.end(), CompareLCTLT);
theta_tree_.Clear();
for (int i = 0; i < size_; ++i) {
IntervalWrapper* iw = lct_[i];
theta_tree_.Insert(iw->interval(), iw->est_pos());
if (theta_tree_.ECT() > iw->interval()->EndMax()) {
solver()->Fail();
}
}
// Other direction
std::sort(mlct_.begin(), mlct_.end(), CompareLCTLT);
theta_tree_.Clear();
for (int i = 0; i < size_; ++i) {
IntervalWrapper* iw = mlct_[i];
theta_tree_.Insert(iw->interval(), iw->est_pos());
if (theta_tree_.ECT() > iw->interval()->EndMax()) {
solver()->Fail();
}
}
}
bool SequenceConstraintOnPerformed::DetectablePrecedences() {
// Init
UpdateEst();
// Propagate in one direction
std::sort(ect_.begin(), ect_.end(), CompareECTLT);
std::sort(lst_.begin(), lst_.end(), CompareLSTLT);
theta_tree_.Clear();
int j = 0;
for (int i = 0; i < size_; ++i) {
IntervalWrapper* twi = ect_[i];
if (j < size_) {
IntervalWrapper* twj = lst_[j];
while (twi->interval()->EndMin() > twj->interval()->StartMax()) {
theta_tree_.Insert(twj->interval(), twj->est_pos());
j++;
if (j == size_)
break;
twj = lst_[j];
}
}
const int64 esti = twi->interval()->StartMin();
bool inserted = theta_tree_.Inserted(twi->est_pos());
if (inserted) {
theta_tree_.Remove(twi->est_pos());
}
const int64 oesti = theta_tree_.ECT();
if (inserted) {
theta_tree_.Insert(twi->interval(), twi->est_pos());
}
if (oesti > esti) {
new_est_[twi->index()] = oesti;
} else {
new_est_[twi->index()] = kint64min;
}
}
// Propagate in other direction
std::sort(mect_.begin(), mect_.end(), CompareECTLT);
std::sort(mlst_.begin(), mlst_.end(), CompareLSTLT);
theta_tree_.Clear();
j = 0;
for (int i = 0; i < size_; ++i) {
IntervalWrapper* twi = mect_[i];
if (j < size_) {
IntervalWrapper* twj = mlst_[j];
while (twi->interval()->EndMin() > twj->interval()->StartMax()) {
theta_tree_.Insert(twj->interval(), twj->est_pos());
j++;
if (j == size_)
break;
twj = mlst_[j];
}
}
const int64 lcti = twi->interval()->StartMin();
bool inserted = theta_tree_.Inserted(twi->est_pos());
if (inserted) {
theta_tree_.Remove(twi->est_pos());
}
const int64 olcti = theta_tree_.ECT();
if (inserted) {
theta_tree_.Insert(twi->interval(), twi->est_pos());
}
if (olcti > lcti) {
new_lct_[twi->index()] = -olcti;
} else {
new_lct_[twi->index()] = kint64max;
}
}
// Apply modifications
bool modified = false;
for (int i = 0; i < size_; ++i) {
if (new_est_[i] != kint64min) {
modified = true;
intervals_[i]->SetStartMin(new_est_[i]);
}
if (new_lct_[i] != kint64max) {
modified = true;
intervals_[i]->SetEndMax(new_lct_[i]);
}
}
return modified;
}
bool SequenceConstraintOnPerformed::NotFirstNotLast() {
// Init
UpdateEst();
for (int i = 0; i < size_; ++i) {
new_lct_[i] = intervals_[i]->EndMax();
new_est_[i] = intervals_[i]->StartMin();
}
// Push in one direction.
std::sort(lst_.begin(), lst_.end(), CompareLSTLT);
std::sort(lct_.begin(), lct_.end(), CompareLCTLT);
theta_tree_.Clear();
int j = 0;
for (int i = 0; i < size_; ++i) {
IntervalWrapper* twi = lct_[i];
while (j < size_ &&
twi->interval()->EndMax() > lst_[j]->interval()->StartMax()) {
if (j > 0 && theta_tree_.ECT() > lst_[j]->interval()->StartMax()) {
new_lct_[lst_[j]->index()] = lst_[j - 1]->interval()->StartMax();
}
theta_tree_.Insert(lst_[j]->interval(), lst_[j]->est_pos());
j++;
}
bool inserted = theta_tree_.Inserted(twi->est_pos());
if (inserted) {
theta_tree_.Remove(twi->est_pos());
}
const int64 ect_theta_less_i = theta_tree_.ECT();
if (inserted) {
theta_tree_.Insert(twi->interval(), twi->est_pos());
}
if (ect_theta_less_i > twi->interval()->EndMax() && j > 0) {
new_lct_[twi->index()] =
min(new_lct_[twi->index()], lst_[j - 1]->interval()->EndMax());
}
}
// Push in other direction.
std::sort(mlst_.begin(), mlst_.end(), CompareLSTLT);
std::sort(mlct_.begin(), mlct_.end(), CompareLCTLT);
theta_tree_.Clear();
j = 0;
for (int i = 0; i < size_; ++i) {
IntervalWrapper* twi = mlct_[i];
while (j < size_ &&
twi->interval()->EndMax() > mlst_[j]->interval()->StartMax()) {
if (j > 0 && theta_tree_.ECT() > mlst_[j]->interval()->StartMax()) {
new_est_[mlst_[j]->index()] = -mlst_[j - 1]->interval()->StartMax();
}
theta_tree_.Insert(mlst_[j]->interval(), mlst_[j]->est_pos());
j++;
}
bool inserted = theta_tree_.Inserted(twi->est_pos());
if (inserted) {
theta_tree_.Remove(twi->est_pos());
}
const int64 mect_theta_less_i = theta_tree_.ECT();
if (inserted) {
theta_tree_.Insert(twi->interval(), twi->est_pos());
}
if (mect_theta_less_i > twi->interval()->EndMax() && j > 0) {
new_est_[twi->index()] =
max(new_est_[twi->index()], -mlst_[j - 1]->interval()->EndMax());
}
}
// Apply modifications
bool modified = false;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->EndMax() > new_lct_[i] ||
intervals_[i]->StartMin() < new_est_[i]) {
modified = true;
intervals_[i]->SetStartMin(new_est_[i]);
intervals_[i]->SetEndMax(new_lct_[i]);
}
}
return modified;
}
bool SequenceConstraintOnPerformed::EdgeFinder() {
// Init
UpdateEst();
for (int i = 0; i < size_; ++i) {
new_est_[i] = intervals_[i]->StartMin();
new_lct_[i] = intervals_[i]->EndMax();
}
// Push in one direction.
std::sort(lct_.begin(), lct_.end(), CompareLCTLT);
lt_tree_.Clear();
for (int i = 0; i < size_; ++i) {
lt_tree_.Insert(est_[i]->interval(), i);
DCHECK_EQ(i, est_[i]->est_pos());
}
int j = size_ - 1;
IntervalWrapper* twj = lct_[j];
do {
lt_tree_.Grey(twj->est_pos());
if (--j < 0) {
break;
}
twj = lct_[j];
if (lt_tree_.ECT() > twj->interval()->EndMax()) {
solver()->Fail(); // Resource is overloaded
}
while (lt_tree_.ECT_opt() > twj->interval()->EndMax()) {
const int i = lt_tree_.Responsible_opt();
DCHECK_GE(i, 0);
const int act_i = est_[i]->index();
if (lt_tree_.ECT() > new_est_[act_i]) {
new_est_[act_i] = lt_tree_.ECT();
}
lt_tree_.Remove(i);
}
} while (j >= 0);
// Push in other direction.
std::sort(mlct_.begin(), mlct_.end(), CompareLCTLT);
lt_tree_.Clear();
for (int i = 0; i < size_; ++i) {
lt_tree_.Insert(mest_[i]->interval(), i);
DCHECK_EQ(i, mest_[i]->est_pos());
}
j = size_ - 1;
twj = mlct_[j];
do {
lt_tree_.Grey(twj->est_pos());
if (--j < 0) {
break;
}
twj = mlct_[j];
if (lt_tree_.ECT() > twj->interval()->EndMax()) {
solver()->Fail(); // Resource is overloaded
}
while (lt_tree_.ECT_opt() > twj->interval()->EndMax()) {
const int i = lt_tree_.Responsible_opt();
DCHECK_GE(i, 0);
const int act_i = mest_[i]->index();
if (-lt_tree_.ECT() < new_lct_[act_i]) {
new_lct_[act_i] = -lt_tree_.ECT();
}
lt_tree_.Remove(i);
}
} while (j >= 0);
// Apply modifications.
bool modified = false;
for (int i = 0; i < size_; ++i) {
if (intervals_[i]->EndMax() > new_lct_[i] ||
intervals_[i]->StartMin() < new_est_[i]) {
modified = true;
intervals_[i]->SetStartMin(new_est_[i]);
intervals_[i]->SetEndMax(new_lct_[i]);
}
}
return modified;
}
Constraint* MakeSequenceConstraintOnPerformed(Solver* const s,
const IntervalVar* const * intervals,
int size) {
return s->RevAlloc(new SequenceConstraintOnPerformed(s, intervals, size));
}
} // namespace operations_research