more improvements to diffn
This commit is contained in:
@@ -133,6 +133,26 @@ Neighborhood NeighborhoodGeneratorHelper::RelaxGivenVariables(
|
||||
return FixGivenVariables(initial_solution, fixed_variables);
|
||||
}
|
||||
|
||||
double NeighborhoodGenerator::GetUCBScore(int64 total_num_calls) const {
|
||||
DCHECK_GE(total_num_calls, num_calls_);
|
||||
if (num_calls_ <= 10) return std::numeric_limits<double>::infinity();
|
||||
return current_average_ + sqrt((2 * log(total_num_calls)) / num_calls_);
|
||||
}
|
||||
|
||||
void NeighborhoodGenerator::AddSolveData(double objective_diff,
|
||||
double deterministic_time) {
|
||||
double gain_per_time_unit = objective_diff / (1.0 + deterministic_time);
|
||||
// TODO(user): Add more data.
|
||||
// TODO(user): Weight more recent data.
|
||||
num_calls_++;
|
||||
// degrade the current average to forget old learnings.
|
||||
if (num_calls_ <= 100) {
|
||||
current_average_ += (gain_per_time_unit - current_average_) / num_calls_;
|
||||
} else {
|
||||
current_average_ = 0.9 * current_average_ + 0.1 * gain_per_time_unit;
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void GetRandomSubset(int seed, double relative_size, std::vector<int>* base) {
|
||||
|
||||
@@ -133,9 +133,28 @@ class NeighborhoodGenerator {
|
||||
// Returns a short description of the generator.
|
||||
std::string name() const { return name_; }
|
||||
|
||||
// Uses UCB1 algorithm to compute the score (Multi armed bandit problem).
|
||||
// Details are at
|
||||
// https://lilianweng.github.io/lil-log/2018/01/23/the-multi-armed-bandit-problem-and-its-solutions.html.
|
||||
// 'total_num_calls' should be the sum of calls across all generators part of
|
||||
// the multi armed bandit problem.
|
||||
// If the generator is called less than 10 times then the method returns
|
||||
// inifinity as score in order to get more data about the generator
|
||||
// performance.
|
||||
double GetUCBScore(int64 total_num_calls) const;
|
||||
|
||||
// Updates the records using the current improvement in objective for the
|
||||
// generator.
|
||||
void AddSolveData(double objective_diff, double deterministic_time);
|
||||
|
||||
// Number of times this generator is called.
|
||||
int64 num_calls() const { return num_calls_; }
|
||||
|
||||
protected:
|
||||
const NeighborhoodGeneratorHelper& helper_;
|
||||
const std::string name_;
|
||||
int64 num_calls_ = 0;
|
||||
double current_average_ = 0.0;
|
||||
};
|
||||
|
||||
// Pick a random subset of variables.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
@@ -1964,6 +1965,7 @@ CpSolverResponse SolveCpModelWithLNS(
|
||||
int num_no_progress = 0;
|
||||
|
||||
const int num_threads = std::max(1, parameters->lns_num_threads());
|
||||
int64 total_num_calls = 0;
|
||||
OptimizeWithLNS(
|
||||
num_threads,
|
||||
[&]() {
|
||||
@@ -2019,10 +2021,9 @@ CpSolverResponse SolveCpModelWithLNS(
|
||||
}
|
||||
}
|
||||
}
|
||||
AdaptiveParameterValue& difficulty =
|
||||
difficulties[seed % generators.size()];
|
||||
const double saved_difficulty = difficulty.value();
|
||||
const int selected_generator = seed % generators.size();
|
||||
AdaptiveParameterValue& difficulty = difficulties[selected_generator];
|
||||
const double saved_difficulty = difficulty.value();
|
||||
Neighborhood neighborhood = generators[selected_generator]->Generate(
|
||||
response, num_workers * seed + worker_id, saved_difficulty);
|
||||
CpModelProto& local_problem = neighborhood.cp_model;
|
||||
@@ -2071,63 +2072,82 @@ CpSolverResponse SolveCpModelWithLNS(
|
||||
}
|
||||
|
||||
const bool neighborhood_is_reduced = neighborhood.is_reduced;
|
||||
return
|
||||
[neighborhood_is_reduced, &num_no_progress, &model_proto, &response,
|
||||
&difficulty, local_response, &observer, limit, solution_info]() {
|
||||
// TODO(user): This is not ideal in multithread because even
|
||||
// though the saved_difficulty will be the same for all thread, we
|
||||
// will Increase()/Decrease() the difficuty sequentially more than
|
||||
// once.
|
||||
if (local_response.status() == CpSolverStatus::OPTIMAL ||
|
||||
local_response.status() == CpSolverStatus::INFEASIBLE) {
|
||||
if (neighborhood_is_reduced) {
|
||||
difficulty.Increase();
|
||||
} else {
|
||||
// We solved the full model here.
|
||||
response = local_response;
|
||||
}
|
||||
} else {
|
||||
difficulty.Decrease();
|
||||
return [neighborhood_is_reduced, &num_no_progress, &model_proto,
|
||||
&response, &difficulty, local_response, &observer, limit,
|
||||
solution_info, &generators, selected_generator,
|
||||
&total_num_calls]() {
|
||||
// TODO(user): This is not ideal in multithread because even though
|
||||
// the saved_difficulty will be the same for all thread, we will
|
||||
// Increase()/Decrease() the difficuty sequentially more than once.
|
||||
if (local_response.status() == CpSolverStatus::OPTIMAL ||
|
||||
local_response.status() == CpSolverStatus::INFEASIBLE) {
|
||||
if (neighborhood_is_reduced) {
|
||||
difficulty.Increase();
|
||||
} else {
|
||||
// We solved the full model here.
|
||||
response = local_response;
|
||||
}
|
||||
} else {
|
||||
difficulty.Decrease();
|
||||
}
|
||||
// Update the generator record.
|
||||
double objective_diff = 0.0;
|
||||
if (local_response.status() == CpSolverStatus::OPTIMAL ||
|
||||
local_response.status() == CpSolverStatus::FEASIBLE) {
|
||||
objective_diff = std::abs(local_response.objective_value() -
|
||||
response.objective_value());
|
||||
}
|
||||
total_num_calls++;
|
||||
generators[selected_generator]->AddSolveData(
|
||||
objective_diff, local_response.deterministic_time());
|
||||
VLOG(2)
|
||||
<< generators[selected_generator]->name()
|
||||
<< ": [difficulty: " << difficulty.value()
|
||||
<< ", deterministic time: " << local_response.deterministic_time()
|
||||
<< ", status: " << CpSolverStatus_Name(local_response.status())
|
||||
<< ", num calls: " << generators[selected_generator]->num_calls()
|
||||
<< ", UCB1 Score: "
|
||||
<< generators[selected_generator]->GetUCBScore(total_num_calls)
|
||||
<< "]";
|
||||
if (local_response.status() == CpSolverStatus::FEASIBLE ||
|
||||
local_response.status() == CpSolverStatus::OPTIMAL) {
|
||||
// If the objective are the same, we override the solution,
|
||||
// otherwise we just ignore this local solution and increment
|
||||
// num_no_progress.
|
||||
double coeff = model_proto.objective().scaling_factor();
|
||||
if (coeff == 0.0) coeff = 1.0;
|
||||
if (local_response.objective_value() * coeff >=
|
||||
response.objective_value() * coeff) {
|
||||
if (local_response.objective_value() * coeff >
|
||||
response.objective_value() * coeff) {
|
||||
return;
|
||||
}
|
||||
if (local_response.status() == CpSolverStatus::FEASIBLE ||
|
||||
local_response.status() == CpSolverStatus::OPTIMAL) {
|
||||
// If the objective are the same, we override the solution,
|
||||
// otherwise we just ignore this local solution and increment
|
||||
// num_no_progress.
|
||||
double coeff = model_proto.objective().scaling_factor();
|
||||
if (coeff == 0.0) coeff = 1.0;
|
||||
if (local_response.objective_value() * coeff >=
|
||||
response.objective_value() * coeff) {
|
||||
if (local_response.objective_value() * coeff >
|
||||
response.objective_value() * coeff) {
|
||||
return;
|
||||
}
|
||||
++num_no_progress;
|
||||
} else {
|
||||
num_no_progress = 0;
|
||||
}
|
||||
++num_no_progress;
|
||||
} else {
|
||||
num_no_progress = 0;
|
||||
}
|
||||
|
||||
// Update the global response.
|
||||
*(response.mutable_solution()) = local_response.solution();
|
||||
response.set_objective_value(local_response.objective_value());
|
||||
response.set_wall_time(limit->GetElapsedTime());
|
||||
response.set_user_time(response.user_time() +
|
||||
local_response.user_time());
|
||||
response.set_deterministic_time(
|
||||
response.deterministic_time() +
|
||||
local_response.deterministic_time());
|
||||
if (DEBUG_MODE || FLAGS_cp_model_check_intermediate_solutions) {
|
||||
CHECK(SolutionIsFeasible(
|
||||
model_proto,
|
||||
std::vector<int64>(local_response.solution().begin(),
|
||||
local_response.solution().end())));
|
||||
}
|
||||
if (num_no_progress == 0) { // Improving solution.
|
||||
response.set_solution_info(solution_info);
|
||||
observer(response);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Update the global response.
|
||||
*(response.mutable_solution()) = local_response.solution();
|
||||
response.set_objective_value(local_response.objective_value());
|
||||
response.set_wall_time(limit->GetElapsedTime());
|
||||
response.set_user_time(response.user_time() +
|
||||
local_response.user_time());
|
||||
response.set_deterministic_time(
|
||||
response.deterministic_time() +
|
||||
local_response.deterministic_time());
|
||||
if (DEBUG_MODE || FLAGS_cp_model_check_intermediate_solutions) {
|
||||
CHECK(SolutionIsFeasible(
|
||||
model_proto,
|
||||
std::vector<int64>(local_response.solution().begin(),
|
||||
local_response.solution().end())));
|
||||
}
|
||||
if (num_no_progress == 0) { // Improving solution.
|
||||
response.set_solution_info(solution_info);
|
||||
observer(response);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (response.status() == CpSolverStatus::FEASIBLE) {
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace sat {
|
||||
std::function<void(Model*)> Cumulative(
|
||||
const std::vector<IntervalVariable>& vars,
|
||||
const std::vector<IntegerVariable>& demand_vars,
|
||||
const IntegerVariable& capacity) {
|
||||
return [=](Model* model) {
|
||||
const IntegerVariable& capacity, SchedulingConstraintHelper* helper) {
|
||||
return [=](Model* model) mutable {
|
||||
if (vars.empty()) return;
|
||||
|
||||
IntervalsRepository* intervals = model->GetOrCreate<IntervalsRepository>();
|
||||
@@ -123,9 +123,10 @@ std::function<void(Model*)> Cumulative(
|
||||
Trail* trail = model->GetOrCreate<Trail>();
|
||||
IntegerTrail* integer_trail = model->GetOrCreate<IntegerTrail>();
|
||||
|
||||
SchedulingConstraintHelper* helper =
|
||||
new SchedulingConstraintHelper(vars, model);
|
||||
model->TakeOwnership(helper);
|
||||
if (helper == nullptr) {
|
||||
helper = new SchedulingConstraintHelper(vars, model);
|
||||
model->TakeOwnership(helper);
|
||||
}
|
||||
|
||||
// Propagator responsible for applying Timetabling filtering rule. It
|
||||
// increases the minimum of the start variables, decrease the maximum of the
|
||||
|
||||
@@ -39,10 +39,14 @@ namespace sat {
|
||||
//
|
||||
// This constraint assumes that an interval can be optional or have a duration
|
||||
// of zero. The demands and the capacity can be any non-negative number.
|
||||
//
|
||||
// Optimization: If one already have an helper constructed from the interval
|
||||
// variable, it can be passed as last argument.
|
||||
std::function<void(Model*)> Cumulative(
|
||||
const std::vector<IntervalVariable>& vars,
|
||||
const std::vector<IntegerVariable>& demand_vars,
|
||||
const IntegerVariable& capacity_var);
|
||||
const IntegerVariable& capacity_var,
|
||||
SchedulingConstraintHelper* helper = nullptr);
|
||||
|
||||
// Adds a simple cumulative constraint on the given intervals, the associated
|
||||
// demands and the capacity variables. See the comment of Cumulative() above for
|
||||
|
||||
@@ -30,37 +30,29 @@
|
||||
namespace operations_research {
|
||||
namespace sat {
|
||||
|
||||
void AddCumulativeRelaxation(const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y,
|
||||
Model* model) {
|
||||
IntervalsRepository* const repository =
|
||||
model->GetOrCreate<IntervalsRepository>();
|
||||
std::vector<IntegerVariable> starts;
|
||||
void AddCumulativeRelaxation(SchedulingConstraintHelper* x,
|
||||
SchedulingConstraintHelper* y, Model* model) {
|
||||
std::vector<IntegerVariable> sizes;
|
||||
std::vector<IntegerVariable> ends;
|
||||
|
||||
int64 min_starts = kint64max;
|
||||
int64 max_ends = kint64min;
|
||||
|
||||
for (const IntervalVariable& interval : y) {
|
||||
starts.push_back(repository->StartVar(interval));
|
||||
IntegerVariable s_var = repository->SizeVar(interval);
|
||||
for (int box = 0; box < y->NumTasks(); ++box) {
|
||||
IntegerVariable s_var = y->DurationVars()[box];
|
||||
if (s_var == kNoIntegerVariable) {
|
||||
s_var = model->Add(
|
||||
ConstantIntegerVariable(repository->MinSize(interval).value()));
|
||||
s_var = model->Add(ConstantIntegerVariable(y->DurationMin(box).value()));
|
||||
}
|
||||
sizes.push_back(s_var);
|
||||
ends.push_back(repository->EndVar(interval));
|
||||
min_starts = std::min(min_starts, model->Get(LowerBound(starts.back())));
|
||||
max_ends = std::max(max_ends, model->Get(UpperBound(ends.back())));
|
||||
min_starts = std::min(min_starts, y->StartMin(box).value());
|
||||
max_ends = std::max(max_ends, y->EndMax(box).value());
|
||||
}
|
||||
|
||||
const IntegerVariable min_start_var =
|
||||
model->Add(NewIntegerVariable(min_starts, max_ends));
|
||||
model->Add(IsEqualToMinOf(min_start_var, starts));
|
||||
model->Add(IsEqualToMinOf(min_start_var, y->StartVars()));
|
||||
|
||||
const IntegerVariable max_end_var =
|
||||
model->Add(NewIntegerVariable(min_starts, max_ends));
|
||||
model->Add(IsEqualToMaxOf(max_end_var, ends));
|
||||
model->Add(IsEqualToMaxOf(max_end_var, y->EndVars()));
|
||||
|
||||
const IntegerVariable capacity =
|
||||
model->Add(NewIntegerVariable(0, CapSub(max_ends, min_starts)));
|
||||
@@ -68,7 +60,7 @@ void AddCumulativeRelaxation(const std::vector<IntervalVariable>& x,
|
||||
model->Add(WeightedSumGreaterOrEqual({capacity, min_start_var, max_end_var},
|
||||
coeffs, 0));
|
||||
|
||||
model->Add(Cumulative(x, sizes, capacity));
|
||||
model->Add(Cumulative(x->Intervals(), sizes, capacity, x));
|
||||
}
|
||||
|
||||
namespace {
|
||||
@@ -135,22 +127,28 @@ std::vector<absl::Span<int>> SplitDisjointBoxes(
|
||||
#define RETURN_IF_FALSE(f) \
|
||||
if (!(f)) return false;
|
||||
|
||||
NonOverlappingRectanglesEnergyPropagator::
|
||||
NonOverlappingRectanglesEnergyPropagator(
|
||||
const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y, Model* model)
|
||||
: x_(x, model), y_(y, model) {}
|
||||
|
||||
NonOverlappingRectanglesEnergyPropagator::
|
||||
~NonOverlappingRectanglesEnergyPropagator() {}
|
||||
|
||||
bool NonOverlappingRectanglesEnergyPropagator::Propagate() {
|
||||
cached_areas_.resize(x_.NumTasks());
|
||||
const int num_boxes = x_.NumTasks();
|
||||
x_.SetTimeDirection(true);
|
||||
y_.SetTimeDirection(true);
|
||||
|
||||
active_boxes_.clear();
|
||||
for (int box = 0; box < x_.NumTasks(); ++box) {
|
||||
cached_areas_.resize(num_boxes);
|
||||
cached_dimensions_.resize(num_boxes);
|
||||
for (int box = 0; box < num_boxes; ++box) {
|
||||
cached_areas_[box] = x_.DurationMin(box) * y_.DurationMin(box);
|
||||
if (cached_areas_[box] == 0) continue;
|
||||
|
||||
// TODO(user): Also consider shifted end max.
|
||||
Dimension& dimension = cached_dimensions_[box];
|
||||
dimension.x_min = x_.ShiftedStartMin(box);
|
||||
dimension.x_max = x_.EndMax(box);
|
||||
dimension.y_min = y_.ShiftedStartMin(box);
|
||||
dimension.y_max = y_.EndMax(box);
|
||||
|
||||
active_boxes_.push_back(box);
|
||||
}
|
||||
if (active_boxes_.size() <= 1) return true;
|
||||
@@ -182,24 +180,16 @@ int NonOverlappingRectanglesEnergyPropagator::RegisterWith(
|
||||
|
||||
void NonOverlappingRectanglesEnergyPropagator::SortBoxesIntoNeighbors(
|
||||
int box, absl::Span<const int> local_boxes) {
|
||||
const IntegerValue box_x_min = x_.StartMin(box);
|
||||
const IntegerValue box_x_max = x_.EndMax(box);
|
||||
const IntegerValue box_y_min = y_.StartMin(box);
|
||||
const IntegerValue box_y_max = y_.EndMax(box);
|
||||
const Dimension& box_dim = cached_dimensions_[box];
|
||||
|
||||
neighbors_.clear();
|
||||
for (const int other_box : local_boxes) {
|
||||
if (other_box == box) continue;
|
||||
|
||||
const IntegerValue other_x_min = x_.StartMin(other_box);
|
||||
const IntegerValue other_x_max = x_.EndMax(other_box);
|
||||
const IntegerValue other_y_min = y_.StartMin(other_box);
|
||||
const IntegerValue other_y_max = y_.EndMax(other_box);
|
||||
|
||||
const IntegerValue span_x =
|
||||
std::max(box_x_max, other_x_max) - std::min(box_x_min, other_x_min) + 1;
|
||||
const IntegerValue span_y =
|
||||
std::max(box_y_max, other_y_max) - std::min(box_y_min, other_y_min) + 1;
|
||||
const Dimension& other_dim = cached_dimensions_[other_box];
|
||||
const IntegerValue span_x = std::max(box_dim.x_max, other_dim.x_max) -
|
||||
std::min(box_dim.x_min, other_dim.x_min) + 1;
|
||||
const IntegerValue span_y = std::max(box_dim.y_max, other_dim.y_max) -
|
||||
std::min(box_dim.y_min, other_dim.y_min) + 1;
|
||||
neighbors_.push_back({other_box, span_x * span_y});
|
||||
}
|
||||
std::sort(neighbors_.begin(), neighbors_.end());
|
||||
@@ -210,11 +200,7 @@ bool NonOverlappingRectanglesEnergyPropagator::FailWhenEnergyIsTooLarge(
|
||||
// Note that we only consider the smallest dimension of each boxes here.
|
||||
SortBoxesIntoNeighbors(box, local_boxes);
|
||||
|
||||
IntegerValue area_min_x = x_.StartMin(box);
|
||||
IntegerValue area_max_x = x_.EndMax(box);
|
||||
IntegerValue area_min_y = y_.StartMin(box);
|
||||
IntegerValue area_max_y = y_.EndMax(box);
|
||||
|
||||
Dimension area = cached_dimensions_[box];
|
||||
IntegerValue sum_of_areas = cached_areas_[box];
|
||||
|
||||
IntegerValue total_sum_of_areas = sum_of_areas;
|
||||
@@ -223,12 +209,10 @@ bool NonOverlappingRectanglesEnergyPropagator::FailWhenEnergyIsTooLarge(
|
||||
}
|
||||
|
||||
const auto add_box_energy_in_rectangle_reason = [&](int b) {
|
||||
x_.AddStartMinReason(b, area_min_x);
|
||||
x_.AddDurationMinReason(b, x_.DurationMin(b));
|
||||
x_.AddEndMaxReason(b, area_max_x);
|
||||
y_.AddStartMinReason(b, area_min_y);
|
||||
y_.AddDurationMinReason(b, y_.DurationMin(b));
|
||||
y_.AddEndMaxReason(b, area_max_y);
|
||||
x_.AddEnergyAfterReason(b, x_.DurationMin(b), area.x_min);
|
||||
x_.AddEndMaxReason(b, area.x_max);
|
||||
y_.AddEnergyAfterReason(b, y_.DurationMin(b), area.y_min);
|
||||
y_.AddEndMaxReason(b, area.y_max);
|
||||
};
|
||||
|
||||
for (int i = 0; i < neighbors_.size(); ++i) {
|
||||
@@ -236,15 +220,12 @@ bool NonOverlappingRectanglesEnergyPropagator::FailWhenEnergyIsTooLarge(
|
||||
CHECK_GT(cached_areas_[other_box], 0);
|
||||
|
||||
// Update Bounding box.
|
||||
area_min_x = std::min(area_min_x, x_.StartMin(other_box));
|
||||
area_max_x = std::max(area_max_x, x_.EndMax(other_box));
|
||||
area_min_y = std::min(area_min_y, y_.StartMin(other_box));
|
||||
area_max_y = std::max(area_max_y, y_.EndMax(other_box));
|
||||
area.TakeUnionWith(cached_dimensions_[other_box]);
|
||||
|
||||
// Update sum of areas.
|
||||
sum_of_areas += cached_areas_[other_box];
|
||||
const IntegerValue bounding_area =
|
||||
(area_max_x - area_min_x) * (area_max_y - area_min_y);
|
||||
(area.x_max - area.x_min) * (area.y_max - area.y_min);
|
||||
if (bounding_area >= total_sum_of_areas) {
|
||||
// Nothing will be deduced. Exiting.
|
||||
return true;
|
||||
@@ -267,13 +248,14 @@ bool NonOverlappingRectanglesEnergyPropagator::FailWhenEnergyIsTooLarge(
|
||||
// Note that x_ and y_ must be initialized with enough intervals when passed
|
||||
// to the disjunctive propagators.
|
||||
NonOverlappingRectanglesDisjunctivePropagator::
|
||||
NonOverlappingRectanglesDisjunctivePropagator(
|
||||
const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y, bool strict, Model* model)
|
||||
: global_x_(x, model),
|
||||
global_y_(y, model),
|
||||
x_(x, model),
|
||||
y_(y, model),
|
||||
NonOverlappingRectanglesDisjunctivePropagator(bool strict,
|
||||
SchedulingConstraintHelper* x,
|
||||
SchedulingConstraintHelper* y,
|
||||
Model* model)
|
||||
: global_x_(*x),
|
||||
global_y_(*y),
|
||||
x_(x->Intervals(), model),
|
||||
y_(y->Intervals(), model),
|
||||
strict_(strict),
|
||||
watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
|
||||
overload_checker_(true, &x_),
|
||||
@@ -287,17 +269,17 @@ NonOverlappingRectanglesDisjunctivePropagator::
|
||||
NonOverlappingRectanglesDisjunctivePropagator::
|
||||
~NonOverlappingRectanglesDisjunctivePropagator() {}
|
||||
|
||||
void NonOverlappingRectanglesDisjunctivePropagator::RegisterWith(
|
||||
GenericLiteralWatcher* watcher, int fast_priority, int slow_priority) {
|
||||
fast_id_ = watcher->Register(this);
|
||||
watcher->SetPropagatorPriority(fast_id_, fast_priority);
|
||||
global_x_.WatchAllTasks(fast_id_, watcher);
|
||||
global_y_.WatchAllTasks(fast_id_, watcher);
|
||||
void NonOverlappingRectanglesDisjunctivePropagator::Register(
|
||||
int fast_priority, int slow_priority) {
|
||||
fast_id_ = watcher_->Register(this);
|
||||
watcher_->SetPropagatorPriority(fast_id_, fast_priority);
|
||||
global_x_.WatchAllTasks(fast_id_, watcher_);
|
||||
global_y_.WatchAllTasks(fast_id_, watcher_);
|
||||
|
||||
const int slow_id = watcher->Register(this);
|
||||
watcher->SetPropagatorPriority(slow_id, slow_priority);
|
||||
global_x_.WatchAllTasks(slow_id, watcher);
|
||||
global_y_.WatchAllTasks(slow_id, watcher);
|
||||
const int slow_id = watcher_->Register(this);
|
||||
watcher_->SetPropagatorPriority(slow_id, slow_priority);
|
||||
global_x_.WatchAllTasks(slow_id, watcher_);
|
||||
global_y_.WatchAllTasks(slow_id, watcher_);
|
||||
}
|
||||
|
||||
bool NonOverlappingRectanglesDisjunctivePropagator::
|
||||
@@ -425,6 +407,9 @@ bool NonOverlappingRectanglesDisjunctivePropagator::
|
||||
}
|
||||
|
||||
bool NonOverlappingRectanglesDisjunctivePropagator::Propagate() {
|
||||
global_x_.SetTimeDirection(true);
|
||||
global_y_.SetTimeDirection(true);
|
||||
|
||||
std::function<bool()> inner_propagate;
|
||||
if (watcher_->GetCurrentId() == fast_id_) {
|
||||
inner_propagate = [this]() {
|
||||
|
||||
@@ -35,9 +35,9 @@ class NonOverlappingRectanglesEnergyPropagator : public PropagatorInterface {
|
||||
// The strict parameters indicates how to place zero width or zero height
|
||||
// boxes. If strict is true, these boxes must not 'cross' another box, and are
|
||||
// pushed by the other boxes.
|
||||
NonOverlappingRectanglesEnergyPropagator(
|
||||
const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y, Model* model);
|
||||
NonOverlappingRectanglesEnergyPropagator(SchedulingConstraintHelper* x,
|
||||
SchedulingConstraintHelper* y)
|
||||
: x_(*x), y_(*y) {}
|
||||
~NonOverlappingRectanglesEnergyPropagator() override;
|
||||
|
||||
bool Propagate() final;
|
||||
@@ -47,12 +47,27 @@ class NonOverlappingRectanglesEnergyPropagator : public PropagatorInterface {
|
||||
void SortBoxesIntoNeighbors(int box, absl::Span<const int> local_boxes);
|
||||
bool FailWhenEnergyIsTooLarge(int box, absl::Span<const int> local_boxes);
|
||||
|
||||
SchedulingConstraintHelper x_;
|
||||
SchedulingConstraintHelper y_;
|
||||
SchedulingConstraintHelper& x_;
|
||||
SchedulingConstraintHelper& y_;
|
||||
|
||||
std::vector<int> active_boxes_;
|
||||
std::vector<IntegerValue> cached_areas_;
|
||||
|
||||
struct Dimension {
|
||||
IntegerValue x_min;
|
||||
IntegerValue x_max;
|
||||
IntegerValue y_min;
|
||||
IntegerValue y_max;
|
||||
|
||||
void TakeUnionWith(const Dimension& other) {
|
||||
x_min = std::min(x_min, other.x_min);
|
||||
y_min = std::min(y_min, other.y_min);
|
||||
x_max = std::max(x_max, other.x_max);
|
||||
y_max = std::max(y_max, other.y_max);
|
||||
}
|
||||
};
|
||||
std::vector<Dimension> cached_dimensions_;
|
||||
|
||||
struct Neighbor {
|
||||
int box;
|
||||
IntegerValue distance_to_bounding_box;
|
||||
@@ -76,14 +91,14 @@ class NonOverlappingRectanglesDisjunctivePropagator
|
||||
// boxes. If strict is true, these boxes must not 'cross' another box, and are
|
||||
// pushed by the other boxes.
|
||||
// The slow_propagators select which disjunctive algorithms to propagate.
|
||||
NonOverlappingRectanglesDisjunctivePropagator(
|
||||
const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y, bool strict, Model* model);
|
||||
NonOverlappingRectanglesDisjunctivePropagator(bool strict,
|
||||
SchedulingConstraintHelper* x,
|
||||
SchedulingConstraintHelper* y,
|
||||
Model* model);
|
||||
~NonOverlappingRectanglesDisjunctivePropagator() override;
|
||||
|
||||
bool Propagate() final;
|
||||
void RegisterWith(GenericLiteralWatcher* watcher, int fast_priority,
|
||||
int slow_priority);
|
||||
void Register(int fast_priority, int slow_priority);
|
||||
|
||||
private:
|
||||
bool PropagateTwoBoxes();
|
||||
@@ -91,8 +106,8 @@ class NonOverlappingRectanglesDisjunctivePropagator
|
||||
const SchedulingConstraintHelper& x, const SchedulingConstraintHelper& y,
|
||||
std::function<bool()> inner_propagate);
|
||||
|
||||
SchedulingConstraintHelper global_x_;
|
||||
SchedulingConstraintHelper global_y_;
|
||||
SchedulingConstraintHelper& global_x_;
|
||||
SchedulingConstraintHelper& global_y_;
|
||||
SchedulingConstraintHelper x_;
|
||||
SchedulingConstraintHelper y_;
|
||||
const bool strict_;
|
||||
@@ -127,9 +142,8 @@ class NonOverlappingRectanglesDisjunctivePropagator
|
||||
|
||||
// Add a cumulative relaxation. That is, on one direction, it does not enforce
|
||||
// the rectangle aspect, allowing vertical slices to move freely.
|
||||
void AddCumulativeRelaxation(const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y,
|
||||
Model* model);
|
||||
void AddCumulativeRelaxation(SchedulingConstraintHelper* x,
|
||||
SchedulingConstraintHelper* y, Model* model);
|
||||
|
||||
// Enforces that the boxes with corners in (x, y), (x + dx, y), (x, y + dy)
|
||||
// and (x + dx, y + dy) do not overlap.
|
||||
@@ -139,22 +153,28 @@ inline std::function<void(Model*)> NonOverlappingRectangles(
|
||||
const std::vector<IntervalVariable>& x,
|
||||
const std::vector<IntervalVariable>& y, bool is_strict) {
|
||||
return [=](Model* model) {
|
||||
GenericLiteralWatcher* const watcher =
|
||||
model->GetOrCreate<GenericLiteralWatcher>();
|
||||
SchedulingConstraintHelper* x_helper =
|
||||
new SchedulingConstraintHelper(x, model);
|
||||
SchedulingConstraintHelper* y_helper =
|
||||
new SchedulingConstraintHelper(y, model);
|
||||
model->TakeOwnership(x_helper);
|
||||
model->TakeOwnership(y_helper);
|
||||
|
||||
NonOverlappingRectanglesEnergyPropagator* energy_constraint =
|
||||
new NonOverlappingRectanglesEnergyPropagator(x, y, model);
|
||||
new NonOverlappingRectanglesEnergyPropagator(x_helper, y_helper);
|
||||
GenericLiteralWatcher* const watcher =
|
||||
model->GetOrCreate<GenericLiteralWatcher>();
|
||||
watcher->SetPropagatorPriority(energy_constraint->RegisterWith(watcher), 3);
|
||||
model->TakeOwnership(energy_constraint);
|
||||
|
||||
NonOverlappingRectanglesDisjunctivePropagator* constraint =
|
||||
new NonOverlappingRectanglesDisjunctivePropagator(x, y, is_strict,
|
||||
model);
|
||||
constraint->RegisterWith(watcher, /*fast_priority=*/3, /*slow_priority=*/4);
|
||||
new NonOverlappingRectanglesDisjunctivePropagator(is_strict, x_helper,
|
||||
y_helper, model);
|
||||
constraint->Register(/*fast_priority=*/3, /*slow_priority=*/4);
|
||||
model->TakeOwnership(constraint);
|
||||
|
||||
AddCumulativeRelaxation(x, y, model);
|
||||
AddCumulativeRelaxation(y, x, model);
|
||||
AddCumulativeRelaxation(x_helper, y_helper, model);
|
||||
AddCumulativeRelaxation(y_helper, x_helper, model);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,9 @@ class SchedulingConstraintHelper {
|
||||
// Returns the underlying integer variables.
|
||||
const std::vector<IntegerVariable>& StartVars() const { return start_vars_; }
|
||||
const std::vector<IntegerVariable>& EndVars() const { return end_vars_; }
|
||||
const std::vector<IntegerVariable>& DurationVars() const {
|
||||
return duration_vars_;
|
||||
}
|
||||
const std::vector<IntervalVariable>& Intervals() const { return intervals_; }
|
||||
|
||||
// Registers the given propagator id to be called if any of the tasks
|
||||
|
||||
Reference in New Issue
Block a user