diff --git a/examples/cpp/jobshop_earlytardy.cc b/examples/cpp/jobshop_earlytardy.cc index b4a6fd8a81..36f0fea630 100644 --- a/examples/cpp/jobshop_earlytardy.cc +++ b/examples/cpp/jobshop_earlytardy.cc @@ -45,6 +45,7 @@ #include "linear_solver/linear_solver.h" #include "util/string_array.h" #include "cpp/jobshop_earlytardy.h" +#include "cpp/jobshop_ls.h" DEFINE_string( jet_file, @@ -66,6 +67,13 @@ DEFINE_int32(scale_factor, 130, "Scale factor (in percent)"); DEFINE_int32(seed, 1, "Random seed"); DEFINE_int32(time_limit_in_ms, 0, "Time limit in ms, 0 means no limit."); DEFINE_bool(time_placement, false, "Use MIP based time placement"); +DEFINE_int32(shuffle_length, 4, "Length of sub-sequences to shuffle LS."); +DEFINE_int32(sub_sequence_length, 4, + "Length of sub-sequences to relax in LNS."); +DEFINE_int32(lns_seed, 1, "Seed of the LNS random search"); +DEFINE_int32(lns_limit, 30, + "Limit the size of the search tree in a LNS fragment"); +DEFINE_bool(use_ls, false, "Use ls"); DECLARE_bool(log_prefix); namespace operations_research { @@ -294,7 +302,7 @@ void EtJobShop(const EtJobShopData& data) { // This decision builder will rank all tasks on all machines. DecisionBuilder* const sequence_phase = - solver.MakePhase(all_sequences, Solver::SEQUENCE_DEFAULT); + solver.MakePhase(all_sequences, Solver::CHOOSE_MIN_SLACK_RANK_FORWARD); // After the ranking of tasks, the schedule is still loose and any // task can be postponed at will. But, because the problem is now a PERT @@ -309,23 +317,102 @@ void EtJobShop(const EtJobShopData& data) { Solver::CHOOSE_FIRST_UNBOUND, Solver::ASSIGN_MIN_VALUE); - // The main decision builder (ranks all tasks, then fixes the - // objective_variable). - DecisionBuilder* const main_phase = - solver.Compose(sequence_phase, obj_phase); + if (FLAGS_use_ls) { + Assignment* const first_solution = solver.MakeAssignment(); + first_solution->Add(all_sequences); + first_solution->AddObjective(objective_var); + // Store the first solution in the 'solution' object. + DecisionBuilder* const store_db = + solver.MakeStoreAssignment(first_solution); - // Search log. - const int kLogFrequency = 1000000; - SearchMonitor* const search_log = - solver.MakeSearchLog(kLogFrequency, objective_monitor); + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const first_solution_phase = + solver.Compose(sequence_phase, obj_phase, store_db); - SearchLimit* limit = NULL; - if (FLAGS_time_limit_in_ms > 0) { - limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + LOG(INFO) << "Looking for the first solution"; + const bool first_solution_found = solver.Solve(first_solution_phase); + if (first_solution_found) { + LOG(INFO) << "Solution found with penalty cost of = " + << first_solution->ObjectiveValue(); + } else { + LOG(INFO) << "No initial solution found!"; + return; + } + + LOG(INFO) << "Switching to local search"; + std::vector operators; + LOG(INFO) << " - use swap operator"; + LocalSearchOperator* const swap_operator = + solver.RevAlloc(new SwapIntervals(all_sequences.data(), + all_sequences.size())); + operators.push_back(swap_operator); + LOG(INFO) << " - use shuffle operator with a max length of " + << FLAGS_shuffle_length; + LocalSearchOperator* const shuffle_operator = + solver.RevAlloc(new ShuffleIntervals(all_sequences.data(), + all_sequences.size(), + FLAGS_shuffle_length)); + operators.push_back(shuffle_operator); + LOG(INFO) << " - use free sub sequences of length " + << FLAGS_sub_sequence_length << " lns operator"; + LocalSearchOperator* const lns_operator = + solver.RevAlloc(new SequenceLns(all_sequences.data(), + all_sequences.size(), + FLAGS_lns_seed, + FLAGS_sub_sequence_length)); + operators.push_back(lns_operator); + + // Creates the local search decision builder. + LocalSearchOperator* const concat = + solver.ConcatenateOperators(operators, true); + + SearchLimit* const ls_limit = + solver.MakeLimit(kint64max, FLAGS_lns_limit, kint64max, kint64max); + DecisionBuilder* const random_sequence_phase = + solver.MakePhase(all_sequences, Solver::CHOOSE_RANDOM_RANK_FORWARD); + DecisionBuilder* const ls_db = + solver.MakeSolveOnce(solver.Compose(random_sequence_phase, obj_phase), + ls_limit); + + LocalSearchPhaseParameters* const parameters = + solver.MakeLocalSearchPhaseParameters(concat, ls_db); + DecisionBuilder* const final_db = + solver.MakeLocalSearchPhase(first_solution, parameters); + + OptimizeVar* const objective_monitor = + solver.MakeMinimize(objective_var, 1); + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_monitor); + + SearchLimit* const limit = FLAGS_time_limit_in_ms > 0 ? + solver.MakeTimeLimit(FLAGS_time_limit_in_ms) : + NULL; + + // Search. + solver.Solve(final_db, search_log, objective_monitor, limit); + } else { + // The main decision builder (ranks all tasks, then fixes the + // objective_variable). + DecisionBuilder* const main_phase = + solver.Compose(sequence_phase, obj_phase); + + // Search log. + const int kLogFrequency = 1000000; + SearchMonitor* const search_log = + solver.MakeSearchLog(kLogFrequency, objective_monitor); + + SearchLimit* limit = NULL; + if (FLAGS_time_limit_in_ms > 0) { + limit = solver.MakeTimeLimit(FLAGS_time_limit_in_ms); + } + + // Search. + solver.Solve(main_phase, search_log, objective_monitor, limit); } - - // Search. - solver.Solve(main_phase, search_log, objective_monitor, limit); } } // namespace operations_research diff --git a/examples/cpp/jobshop_ls.cc b/examples/cpp/jobshop_ls.cc index cc32b9849c..5c09d1c235 100644 --- a/examples/cpp/jobshop_ls.cc +++ b/examples/cpp/jobshop_ls.cc @@ -44,6 +44,7 @@ #include "constraint_solver/constraint_solver.h" #include "constraint_solver/constraint_solveri.h" #include "cpp/jobshop.h" +#include "cpp/jobshop_ls.h" DEFINE_string( data_file, @@ -65,210 +66,6 @@ DEFINE_int32(lns_limit, 30, namespace operations_research { -// ----- Exchange 2 intervals on a sequence variable ----- - -class SwapIntervals : public SequenceVarLocalSearchOperator { - public: - SwapIntervals(const SequenceVar* const* vars, int size) - : SequenceVarLocalSearchOperator(vars, size), - current_var_(-1), - current_first_(-1), - current_second_(-1) {} - - virtual ~SwapIntervals() {} - - virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { - CHECK_NOTNULL(delta); - while (true) { - RevertChanges(true); - if (!Increment()) { - VLOG(1) << "finished neighborhood"; - return false; - } - - std::vector sequence = Sequence(current_var_); - const int tmp = sequence[current_first_]; - sequence[current_first_] = sequence[current_second_]; - sequence[current_second_] = tmp; - SetForwardSequence(current_var_, sequence); - if (ApplyChanges(delta, deltadelta)) { - VLOG(1) << "Delta = " << delta->DebugString(); - return true; - } - } - return false; - } - - protected: - virtual void OnStart() { - VLOG(1) << "start neighborhood"; - current_var_ = 0; - current_first_ = 0; - current_second_ = 0; - } - - private: - bool Increment() { - const SequenceVar* const var = Var(current_var_); - if (++current_second_ >= var->size()) { - if (++current_first_ >= var->size() - 1) { - current_var_++; - current_first_ = 0; - } - current_second_ = current_first_ + 1; - } - return current_var_ < Size(); - } - - int current_var_; - int current_first_; - int current_second_; -}; - -// ----- Shuffle a fixed-length sub-sequence on one sequence variable ----- - -class ShuffleIntervals : public SequenceVarLocalSearchOperator { - public: - ShuffleIntervals(const SequenceVar* const* vars, int size, int max_length) - : SequenceVarLocalSearchOperator(vars, size), - max_length_(max_length), - current_var_(-1), - current_first_(-1), - current_index_(-1), - current_length_(-1) {} - - virtual ~ShuffleIntervals() {} - - virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { - CHECK_NOTNULL(delta); - while (true) { - RevertChanges(true); - if (!Increment()) { - VLOG(1) << "finished neighborhood"; - return false; - } - - std::vector sequence = Sequence(current_var_); - std::vector sequence_backup(current_length_); - for (int i = 0; i < current_length_; ++i) { - sequence_backup[i] = sequence[i + current_first_]; - } - for (int i = 0; i < current_length_; ++i) { - sequence[i + current_first_] = - sequence_backup[current_permutation_[i]]; - } - SetForwardSequence(current_var_, sequence); - if (ApplyChanges(delta, deltadelta)) { - VLOG(1) << "Delta = " << delta->DebugString(); - return true; - } - } - return false; - } - - protected: - virtual void OnStart() { - VLOG(1) << "start neighborhood"; - current_var_ = 0; - current_first_ = 0; - current_index_ = -1; - current_length_ = std::min(Var(current_var_)->size(), max_length_); - current_permutation_.resize(current_length_); - for (int i = 0; i < current_length_; ++i) { - current_permutation_[i] = i; - } - } - - private: - bool Increment() { - if (!std::next_permutation(current_permutation_.begin(), - current_permutation_.end())) { - if (++current_first_ >= Var(current_var_)->size() - current_length_) { - if (++current_var_ >= Size()) { - return false; - } - current_first_ = 0; - current_length_ = std::min(Var(current_var_)->size(), max_length_); - current_permutation_.resize(current_length_); - } - current_index_ = 0; - } - return true; - } - - const int max_length_; - int current_var_; - int current_first_; - int current_index_; - int current_length_; - std::vector current_permutation_; -}; - -// ----- LNS Operator ----- - -class SequenceLns : public SequenceVarLocalSearchOperator { - public: - SequenceLns(const SequenceVar* const* vars, - int size, - int seed, - int max_length) - : SequenceVarLocalSearchOperator(vars, size), - random_(seed), - max_length_(max_length) {} - - virtual ~SequenceLns() {} - - virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { - CHECK_NOTNULL(delta); - while (true) { - RevertChanges(true); - if (random_.Uniform(2) == 0) { - FreeTimeWindow(); - } else { - FreeTwoResources(); - } - if (ApplyChanges(delta, deltadelta)) { - VLOG(1) << "Delta = " << delta->DebugString(); - return true; - } - } - return false; - } - - private: - void FreeTimeWindow() { - for (int i = 0; i < Size(); ++i) { - std::vector sequence = Sequence(i); - const int current_length = - std::min(static_cast(sequence.size()), max_length_); - const int start_position = - random_.Uniform(sequence.size() - current_length); - std::vector forward; - for (int j = 0; j < start_position; ++j) { - forward.push_back(sequence[j]); - } - std::vector backward; - for (int j = sequence.size() - 1; - j >= start_position + current_length; - --j) { - backward.push_back(sequence[j]); - } - SetForwardSequence(i, forward); - SetBackwardSequence(i, backward); - } - } - - void FreeTwoResources() { - std::vector free_sequence; - SetForwardSequence(random_.Uniform(Size()), free_sequence); - SetForwardSequence(random_.Uniform(Size()), free_sequence); - } - - ACMRandom random_; - const int max_length_; -}; - - // ----- Model and Solve ----- void JobshopLs(const JobShopData& data) { diff --git a/examples/cpp/jobshop_ls.h b/examples/cpp/jobshop_ls.h new file mode 100644 index 0000000000..3716271b71 --- /dev/null +++ b/examples/cpp/jobshop_ls.h @@ -0,0 +1,253 @@ +// Copyright 2010-2012 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. +// +// This model implements a simple jobshop problem. +// +// A jobshop is a standard scheduling problem where you must schedule a +// set of jobs on a set of machines. Each job is a sequence of tasks +// (a task can only start when the preceding task finished), each of +// which occupies a single specific machine during a specific +// duration. Therefore, a job is simply given by a sequence of pairs +// (machine id, duration). + +// The objective is to minimize the 'makespan', which is the duration +// between the start of the first task (across all machines) and the +// completion of the last task (across all machines). +// +// This will be modelled by sets of intervals variables (see class +// IntervalVar in constraint_solver/constraint_solver.h), one per +// task, representing the [start_time, end_time] of the task. Tasks +// in the same job will be linked by precedence constraints. Tasks on +// the same machine will be covered by Sequence constraints. +// +// Search will be implemented as local search on the sequence variables. + +#ifndef OR_TOOLS_EXAMPLES_JOBSHOP_LS_H_ +#define OR_TOOLS_EXAMPLES_JOBSHOP_LS_H_ +#include +#include + +#include "base/commandlineflags.h" +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/bitmap.h" +#include "constraint_solver/constraint_solver.h" +#include "constraint_solver/constraint_solveri.h" + +namespace operations_research { +// ----- Exchange 2 intervals on a sequence variable ----- + +class SwapIntervals : public SequenceVarLocalSearchOperator { + public: + SwapIntervals(const SequenceVar* const* vars, int size) + : SequenceVarLocalSearchOperator(vars, size), + current_var_(-1), + current_first_(-1), + current_second_(-1) {} + + virtual ~SwapIntervals() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (!Increment()) { + VLOG(1) << "finished neighborhood"; + return false; + } + + std::vector sequence = Sequence(current_var_); + const int tmp = sequence[current_first_]; + sequence[current_first_] = sequence[current_second_]; + sequence[current_second_] = tmp; + SetForwardSequence(current_var_, sequence); + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + + protected: + virtual void OnStart() { + VLOG(1) << "start neighborhood"; + current_var_ = 0; + current_first_ = 0; + current_second_ = 0; + } + + private: + bool Increment() { + const SequenceVar* const var = Var(current_var_); + if (++current_second_ >= var->size()) { + if (++current_first_ >= var->size() - 1) { + current_var_++; + current_first_ = 0; + } + current_second_ = current_first_ + 1; + } + return current_var_ < Size(); + } + + int current_var_; + int current_first_; + int current_second_; +}; + +// ----- Shuffle a fixed-length sub-sequence on one sequence variable ----- + +class ShuffleIntervals : public SequenceVarLocalSearchOperator { + public: + ShuffleIntervals(const SequenceVar* const* vars, int size, int max_length) + : SequenceVarLocalSearchOperator(vars, size), + max_length_(max_length), + current_var_(-1), + current_first_(-1), + current_index_(-1), + current_length_(-1) {} + + virtual ~ShuffleIntervals() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (!Increment()) { + VLOG(1) << "finished neighborhood"; + return false; + } + + std::vector sequence = Sequence(current_var_); + std::vector sequence_backup(current_length_); + for (int i = 0; i < current_length_; ++i) { + sequence_backup[i] = sequence[i + current_first_]; + } + for (int i = 0; i < current_length_; ++i) { + sequence[i + current_first_] = + sequence_backup[current_permutation_[i]]; + } + SetForwardSequence(current_var_, sequence); + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + + protected: + virtual void OnStart() { + VLOG(1) << "start neighborhood"; + current_var_ = 0; + current_first_ = 0; + current_index_ = -1; + current_length_ = std::min(Var(current_var_)->size(), max_length_); + current_permutation_.resize(current_length_); + for (int i = 0; i < current_length_; ++i) { + current_permutation_[i] = i; + } + } + + private: + bool Increment() { + if (!std::next_permutation(current_permutation_.begin(), + current_permutation_.end())) { + if (++current_first_ >= Var(current_var_)->size() - current_length_) { + if (++current_var_ >= Size()) { + return false; + } + current_first_ = 0; + current_length_ = std::min(Var(current_var_)->size(), max_length_); + current_permutation_.resize(current_length_); + } + current_index_ = 0; + } + return true; + } + + const int max_length_; + int current_var_; + int current_first_; + int current_index_; + int current_length_; + std::vector current_permutation_; +}; + +// ----- LNS Operator ----- + +class SequenceLns : public SequenceVarLocalSearchOperator { + public: + SequenceLns(const SequenceVar* const* vars, + int size, + int seed, + int max_length) + : SequenceVarLocalSearchOperator(vars, size), + random_(seed), + max_length_(max_length) {} + + virtual ~SequenceLns() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (random_.Uniform(2) == 0) { + FreeTimeWindow(); + } else { + FreeTwoResources(); + } + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + + private: + void FreeTimeWindow() { + for (int i = 0; i < Size(); ++i) { + std::vector sequence = Sequence(i); + const int current_length = + std::min(static_cast(sequence.size()), max_length_); + const int start_position = + random_.Uniform(sequence.size() - current_length); + std::vector forward; + for (int j = 0; j < start_position; ++j) { + forward.push_back(sequence[j]); + } + std::vector backward; + for (int j = sequence.size() - 1; + j >= start_position + current_length; + --j) { + backward.push_back(sequence[j]); + } + SetForwardSequence(i, forward); + SetBackwardSequence(i, backward); + } + } + + void FreeTwoResources() { + std::vector free_sequence; + SetForwardSequence(random_.Uniform(Size()), free_sequence); + SetForwardSequence(random_.Uniform(Size()), free_sequence); + } + + ACMRandom random_; + const int max_length_; +}; +} // namespace operations_research +#endif // OR_TOOLS_EXAMPLES_JOBSHOP_LS_H_