diff --git a/Makefile b/Makefile index 0ba0f3e423..708cfb661b 100644 --- a/Makefile +++ b/Makefile @@ -370,6 +370,12 @@ objs/cvrptw.o: examples/cvrptw.cc cvrptw: $(CPLIBS) $(BASE_LIBS) objs/cvrptw.o $(CCC) $(CFLAGS) $(LDFLAGS) objs/cvrptw.o $(CPLIBS) $(BASE_LIBS) -o cvrptw +objs/dobble_ls.o:examples/dobble_ls.cc + $(CCC) $(CFLAGS) -c examples/dobble_ls.cc -o objs/dobble_ls.o + +dobble_ls: $(CPLIBS) $(BASE_LIBS) objs/dobble_ls.o + $(CCC) $(CFLAGS) $(LDFLAGS) objs/dobble_ls.o $(CPLIBS) $(BASE_LIBS) -o dobble_ls + objs/flow_example.o:examples/flow_example.cc $(CCC) $(CFLAGS) -c examples/flow_example.cc -o objs/flow_example.o diff --git a/examples/dobble_ls.cc b/examples/dobble_ls.cc new file mode 100644 index 0000000000..7b131b817c --- /dev/null +++ b/examples/dobble_ls.cc @@ -0,0 +1,864 @@ +// 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. + +// Dobble Generation problem: +// - We have 57 cards +// - 57 symbols +// - 8 symbols per card +// We want to assign symbols per card such that any two cards have exactly +// one symbol in common. + +#include "base/commandlineflags.h" +#include "base/integral_types.h" +#include "base/logging.h" +#include "base/concise_iterator.h" +#include "base/scoped_ptr.h" +#include "base/stringprintf.h" +#include "constraint_solver/constraint_solveri.h" +#include "util/bitset.h" +#include "base/random.h" + +DEFINE_int32(lns_size, 10, "Size of the lns fragment."); +DEFINE_int32(lns_limit, 5, "Limit the number of failures of the lns loop."); +DEFINE_int32(lns_seed, 1, "Seed for the LNS random number generator."); +DEFINE_int32(fail_limit, 50000, "Fail limit for the global search."); + +namespace operations_research { +class IntersectionCount : public Constraint { + public: + IntersectionCount(Solver* const solver, + const vector& vars1, + const vector& vars2, + IntVar* const count_var, + int count) + : Constraint(solver), + vars1_(new IntVar*[vars1.size()]), + vars2_(new IntVar*[vars2.size()]), + size_(vars1.size()), + count_var_(count_var), + count_(count) { + CHECK_EQ(vars1.size(), vars2.size()); + memcpy(vars1_.get(), vars1.data(), vars1.size() * sizeof(vars1.data())); + memcpy(vars2_.get(), vars2.data(), vars2.size() * sizeof(vars2.data())); + for (int i = 0; i < size_; ++i) { + CHECK_GE(vars1_[i]->Min(), 0); + CHECK_GE(vars2_[i]->Min(), 0); + CHECK_LE(vars1_[i]->Max(), 1); + CHECK_LE(vars2_[i]->Max(), 1); + } + } + + virtual void Post() { + Demon* const delayed = + solver()->MakeDelayedConstraintInitialPropagateCallback(this); + for (int i = 0; i < size_; ++i) { + vars1_[i]->WhenBound(delayed); + vars2_[i]->WhenBound(delayed); + } + } + + virtual void InitialPropagate() { + int possible = 0; + int sure = 0; + int unbounded = 0; + for (int i = 0; i < size_; ++i) { + if (vars1_[i]->Min() == 1 && vars2_[i]->Min() == 1) { + sure++; + } + if (vars1_[i]->Max() == 1 && vars2_[i]->Max() == 1) { + possible++; + } + if (!vars1_[i]->Bound() || !vars2_[i]->Bound()) { + unbounded++; + } + } + count_var_->SetRange(sure, possible); + if (unbounded > 0) { + if (count_var_->Max() == sure) { + for (int i = 0; i < size_; ++i) { + if (vars1_[i]->Min() == 1 && vars2_[i]->Min() == 0) { + vars2_[i]->SetValue(0); + } else if (vars2_[i]->Min() == 1 && vars1_[i]->Min() == 0) { + vars1_[i]->SetValue(0); + } + } + } else if (count_var_->Min() == possible) { + for (int i = 0; i < size_; ++i) { + if (vars1_[i]->Max() == 1 && vars2_[i]->Max() == 1) { + vars1_[i]->SetValue(1); + vars2_[i]->SetValue(1); + } + } + } + } + } + private: + scoped_array vars1_; + scoped_array vars2_; + const int size_; + IntVar* const count_var_; + const int count_; +}; + +IntVar* AddIntersectionVar(Solver* const solver, + const vector& vars1, + const vector& vars2, + int count) { + IntVar* const cardinality = solver->MakeIntVar(0, count); + solver->AddConstraint(solver->RevAlloc(new IntersectionCount(solver, + vars1, + vars2, + cardinality, + count))); + return solver->MakeAbs(solver->MakeSum(cardinality, -1))->Var(); +} + +class CardLns : public BaseLNS { + public: + CardLns(const IntVar* const* vars, + int size, + int fragment_size, + int num_cards, + int num_symbols) + : BaseLNS(vars, size), + rand_(FLAGS_lns_seed), + fragment_size_(fragment_size), + num_cards_(num_cards), + num_symbols_(num_symbols) {} + + virtual ~CardLns() {} + + virtual bool NextFragment(vector* fragment) { + if (rand_.Uniform(2)) { // Release Cards. + for (int i = 0; i < fragment_size_; ++i) { + const int card_index = rand_.Uniform(num_cards_); + for (int symbol_index = 0; + symbol_index < num_symbols_; + ++symbol_index) { + fragment->push_back(card_index * num_symbols_ + symbol_index); + } + } + } else { // Release Symbols. + for (int i = 0; i < fragment_size_; ++i) { + const int symbol_index = rand_.Uniform(num_symbols_); + for (int card_index = 0; card_index < num_cards_; ++card_index) { + fragment->push_back(card_index * num_symbols_ + symbol_index); + } + } + } + return true; + } + private: + ACMRandom rand_; + const int fragment_size_; + const int num_cards_; + const int num_symbols_; +}; + +class CrossLns : public BaseLNS { + public: + CrossLns(const IntVar* const* vars, + int size, + int fragment_size, + int num_cards, + int num_symbols, + int num_symbols_per_card) + : BaseLNS(vars, size), + rand_(FLAGS_lns_seed), + fragment_size_(fragment_size), + num_cards_(num_cards), + num_symbols_(num_symbols), + num_symbols_per_card_(num_symbols_per_card), + symbols_per_card_(num_cards) { + for (int card = 0; card < num_cards_; ++card) { + symbols_per_card_[card].resize(num_symbols_per_card_, -1); + } + } + + virtual ~CrossLns() {} + + virtual void InitFragments() { + for (int card = 0; card < num_cards_; ++card) { + int found = 0; + for (int symbol = 0; symbol < num_symbols_; ++symbol) { + if (Value(Index(card, symbol)) == 1) { + symbols_per_card_[card][found++] = symbol; + } + } + DCHECK_EQ(num_symbols_per_card_, found); + } + } + + virtual bool NextFragment(vector* fragment) { + hash_set cards_to_release; + const int max_cards = max(fragment_size_, num_cards_ / 2); + hash_set symbols_to_release; + while (cards_to_release.size() < max_cards) { + const int card = rand_.Uniform(num_cards_); + const int symbol = + symbols_per_card_[card][rand_.Uniform(num_symbols_per_card_)]; + cards_to_release.insert(card); + symbols_to_release.insert(symbol); + } + + for (ConstIter > card_it(cards_to_release); + !card_it.at_end(); + ++card_it) { + for (ConstIter > symbol_it(symbols_to_release); + !symbol_it.at_end(); + ++symbol_it) { + fragment->push_back(Index(*card_it, *symbol_it)); + } + } + return true; + } + private: + int Index(int card, int symbol) { + return card * num_symbols_ + symbol; + } + + ACMRandom rand_; + const int fragment_size_; + const int num_cards_; + const int num_symbols_; + const int num_symbols_per_card_; + vector > symbols_per_card_; +}; + +class SwitchSymbols : public IntVarLocalSearchOperator { + public: + SwitchSymbols(const IntVar* const* vars, + int size, + int num_cards, + int num_symbols, + int num_symbols_per_card) + : IntVarLocalSearchOperator(vars, size), + num_cards_(num_cards), + num_symbols_(num_symbols), + num_symbols_per_card_(num_symbols_per_card), + current_card1_(-1), + current_card2_(-1), + current_symbol1_(-1), + current_symbol2_(-1), + symbols_per_card_(num_cards) { + for (int card = 0; card < num_cards; ++card) { + symbols_per_card_[card].resize(num_symbols_per_card, -1); + } + } + + virtual ~SwitchSymbols() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (!Increment()) { + VLOG(1) << "finished nhood"; + return false; + } + const int symbol1 = symbols_per_card_[current_card1_][current_symbol1_]; + const int symbol2 = symbols_per_card_[current_card2_][current_symbol2_]; + DCHECK(Value(Index(current_card1_, symbol1))); + DCHECK(Value(Index(current_card2_, symbol2))); + if (Value(Index(current_card1_, symbol2)) || + Value(Index(current_card2_, symbol1))) { + VLOG(1) << "Will overwrite symbol, continuing"; + continue; + } + SetValue(Index(current_card1_, symbol1), 0); + SetValue(Index(current_card2_, symbol2), 0); + SetValue(Index(current_card1_, symbol2), 1); + SetValue(Index(current_card2_, symbol1), 1); + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + protected: + virtual void OnStart() { + VLOG(1) << "start nhood"; + for (int card = 0; card < num_cards_; ++card) { + int found = 0; + for (int symbol = 0; symbol < num_symbols_; ++symbol) { + if (Value(Index(card, symbol)) == 1) { + symbols_per_card_[card][found++] = symbol; + } + } + DCHECK_EQ(num_symbols_per_card_, found); + } + current_card1_ = 0; + current_card2_ = 1; + current_symbol1_ = 0; + current_symbol2_ = -1; + } + private: + int Index(int card, int symbol) { + return card * num_symbols_ + symbol; + } + + bool Increment() { + current_symbol2_++; + if (current_symbol2_ == num_symbols_per_card_) { + current_symbol2_ = 0; + current_symbol1_++; + if (current_symbol1_ == num_symbols_per_card_) { + current_symbol1_ = 0; + current_card2_++; + if (current_card2_ == num_cards_) { + current_card1_++; + current_card2_ = current_card1_ + 1; + } + } + } + return current_card1_ < num_cards_ - 1; + } + + const int num_cards_; + const int num_symbols_; + const int num_symbols_per_card_; + int current_card1_; + int current_card2_; + int current_symbol1_; + int current_symbol2_; + vector > symbols_per_card_; +}; + +class CycleSymbols : public IntVarLocalSearchOperator { + public: + CycleSymbols(const IntVar* const* vars, + int size, + int num_cards, + int num_symbols, + int num_symbols_per_card) + : IntVarLocalSearchOperator(vars, size), + num_cards_(num_cards), + num_symbols_(num_symbols), + num_symbols_per_card_(num_symbols_per_card), + current_card1_(-1), + current_card2_(-1), + current_symbol1_(-1), + current_symbol2_(-1), + symbols_per_card_(num_cards) { + for (int card = 0; card < num_cards; ++card) { + symbols_per_card_[card].resize(num_symbols_per_card, -1); + } + InitTriples(); + } + + virtual ~CycleSymbols() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + if (!Increment()) { + VLOG(1) << "finished nhood"; + return false; + } + const int symbol1 = symbols_per_card_[current_card1_][current_symbol1_]; + const int symbol2 = symbols_per_card_[current_card2_][current_symbol2_]; + const int symbol3 = symbols_per_card_[current_card3_][current_symbol3_]; + DCHECK(Value(Index(current_card1_, symbol1))); + DCHECK(Value(Index(current_card2_, symbol2))); + DCHECK(Value(Index(current_card3_, symbol3))); + if (Value(Index(current_card1_, symbol2)) || + Value(Index(current_card2_, symbol3)) || + Value(Index(current_card3_, symbol1))) { + VLOG(1) << "Will overwrite symbol, continuing"; + continue; + } + SetValue(Index(current_card1_, symbol1), 0); + SetValue(Index(current_card2_, symbol2), 0); + SetValue(Index(current_card3_, symbol3), 0); + SetValue(Index(current_card1_, symbol2), 1); + SetValue(Index(current_card2_, symbol3), 1); + SetValue(Index(current_card3_, symbol1), 1); + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + protected: + virtual void OnStart() { + VLOG(1) << "start nhood"; + for (int card = 0; card < num_cards_; ++card) { + int found = 0; + for (int symbol = 0; symbol < num_symbols_; ++symbol) { + if (Value(Index(card, symbol)) == 1) { + symbols_per_card_[card][found++] = symbol; + } + } + DCHECK_EQ(num_symbols_per_card_, found); + } + card_triple_index_ = 0; + current_card1_ = card_triples_[0].card1; + current_card2_ = card_triples_[0].card2; + current_card3_ = card_triples_[0].card3; + current_symbol1_ = 0; + current_symbol2_ = 0; + current_symbol3_ = -1; + } + private: + struct Triple { + Triple(int c1, int c2, int c3) : card1(c1), card2(c2), card3(c3) {} + int card1; + int card2; + int card3; + }; + + void InitTriples() { + for (int c1 = 0; c1 < num_cards_; ++c1) { + for (int c2 = 0; c2 < num_cards_; ++c2) { + for (int c3 = 0; c3 < num_cards_; ++c3) { + if (c1 != c2 && c1 != c3 && c2 != c3) { + card_triples_.push_back(Triple(c1, c2, c3)); + } + } + } + } + } + + int Index(int card, int symbol) { + return card * num_symbols_ + symbol; + } + + void NextCardTriple() { + card_triple_index_++; + if (card_triple_index_ < card_triples_.size()) { + const Triple& triple = card_triples_[card_triple_index_]; + current_card1_ = triple.card1; + current_card2_ = triple.card2; + current_card3_ = triple.card3; + } + } + + bool Increment() { + current_symbol3_++; + if (current_symbol3_ == num_symbols_per_card_) { + current_symbol3_ = 0; + current_symbol2_++; + if (current_symbol2_ == num_symbols_per_card_) { + current_symbol2_ = 0; + current_symbol1_++; + if (current_symbol1_ == num_symbols_per_card_) { + current_symbol1_ = 0; + NextCardTriple(); + } + } + } + return card_triple_index_ < card_triples_.size(); + } + + const int num_cards_; + const int num_symbols_; + const int num_symbols_per_card_; + int current_card1_; + int current_card2_; + int current_card3_; + int current_symbol1_; + int current_symbol2_; + int current_symbol3_; + vector > symbols_per_card_; + int card_triple_index_; + vector card_triples_; +}; + +class CycleNeighborhood : public IntVarLocalSearchOperator { + public: + CycleNeighborhood(const IntVar* const* vars, + int size, + int max_size, + int num_cards, + int num_symbols, + int num_symbols_per_card) + : IntVarLocalSearchOperator(vars, size), + rand_(FLAGS_lns_seed), + max_size_(max_size), + num_cards_(num_cards), + num_symbols_(num_symbols), + num_symbols_per_card_(num_symbols_per_card), + symbols_per_card_(num_cards) { + for (int card = 0; card < num_cards_; ++card) { + symbols_per_card_[card].resize(num_symbols_per_card_, -1); + } + } + + virtual ~CycleNeighborhood() {} + + virtual bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) { + CHECK_NOTNULL(delta); + while (true) { + RevertChanges(true); + + const int num_cards_to_release = rand_.Uniform(max_size_ - 3) + 3; + hash_set cards_set; + vector to_swap; + hash_set symbols_to_release; + while (cards_set.size() < num_cards_to_release) { + const int card = rand_.Uniform(num_cards_); + if (ContainsKey(cards_set, card)) { + continue; + } + while (true) { + const int symbol = + symbols_per_card_[card][rand_.Uniform(num_symbols_per_card_)]; + if (!ContainsKey(symbols_to_release, symbol)) { + to_swap.push_back(ToSwap(card, symbol)); + symbols_to_release.insert(symbol); + break; + } + } + } + + std::random_shuffle(to_swap.begin(), to_swap.end()); + for (int i = 0; i < num_cards_to_release; ++i) { + SetValue(Index(to_swap[i].card, to_swap[i].symbol), 0); + SetValue(Index(to_swap[i].card, + to_swap[(i + 1) % num_cards_to_release].symbol), 1); + } + + if (ApplyChanges(delta, deltadelta)) { + VLOG(1) << "Delta = " << delta->DebugString(); + return true; + } + } + return false; + } + protected: + virtual void OnStart() { + for (int card = 0; card < num_cards_; ++card) { + int found = 0; + for (int symbol = 0; symbol < num_symbols_; ++symbol) { + if (Value(Index(card, symbol)) == 1) { + symbols_per_card_[card][found++] = symbol; + } + } + DCHECK_EQ(num_symbols_per_card_, found); + } + } + + private: + struct ToSwap { + ToSwap(int c, int s) : card(c), symbol(s) {} + int card; + int symbol; + }; + + int Index(int card, int symbol) { + return card * num_symbols_ + symbol; + } + + ACMRandom rand_; + const int max_size_; + const int num_cards_; + const int num_symbols_; + const int num_symbols_per_card_; + vector > symbols_per_card_; +}; + + +class DobbleFilter : public IntVarLocalSearchFilter { + public: + DobbleFilter(const IntVar* const* vars, + int size, + int num_cards, + int num_symbols, + int num_symbols_per_card) + : IntVarLocalSearchFilter(vars, size), + num_cards_(num_cards), + num_symbols_(num_symbols), + num_symbols_per_card_(num_symbols_per_card), + cards_(num_cards, 0ULL), + costs_(num_cards_) { + for (int card = 0; card < num_cards_; ++card) { + costs_[card].resize(num_cards_, 0); + } + } + + virtual void OnSynchronize() { + memset(cards_.data(), 0, num_cards_ * sizeof(*cards_.data())); + for (int card = 0; card < num_cards_; ++card) { + for (int symbol = 0; symbol < num_symbols_; ++symbol) { + if (Value(card * num_symbols_ + symbol)) { + SetBit64(&cards_[card], symbol); + } + } + } + for (int card1 = 0; card1 < num_cards_; ++card1) { + for (int card2 = 0; card2 < num_cards_; ++card2) { + costs_[card1][card2] = + Cost(BitCount64(cards_[card1] & cards_[card2])); + } + } + DCHECK(CheckCards()); + } + + virtual bool Accept(const Assignment* delta, const Assignment* deltadelta) { + const Assignment::IntContainer& solution_delta = delta->IntVarContainer(); + const int solution_delta_size = solution_delta.Size(); + + // First we check we are not using LNS. + for (int i = 0; i < solution_delta_size; ++i) { + const IntVarElement& element = solution_delta.Element(i); + if (!element.Activated()) { + // Doodle filters are not robust to LNS, so skip filters when + // some elements are not activated. + VLOG(1) << "LNS"; + return true; + } + } + VLOG(1) << "No LNS, size = " << solution_delta_size; + backtrack_.clear(); + hash_set touched_cards; + for (int index = 0; index < solution_delta_size; ++index) { + int64 touched_var = -1; + FindIndex(solution_delta.Element(index).Var(), &touched_var); + const int card = touched_var / num_symbols_; + const int symbol = touched_var % num_symbols_; + const int new_value = solution_delta.Element(index).Value(); + if (!ContainsKey(touched_cards, card)) { + Save(card); + touched_cards.insert(card); + } + if (new_value) { + SetBit64(&cards_[card], symbol); + } else { + ClearBit64(&cards_[card], symbol); + } + } + + if (!CheckCards()) { + Backtrack(); + DCHECK(CheckCards()); + VLOG(1) << "reject by size"; + return false; + } + + hash_set treated_cards; + int cost_delta = 0; + for (ConstIter > card_it(touched_cards); + !card_it.at_end(); + ++card_it) { + treated_cards.insert(*card_it); + const uint64 bitset = cards_[*card_it]; + const vector& row_cost = costs_[*card_it]; + for (int card = 0; card < num_cards_; ++card) { + if (!ContainsKey(treated_cards, card)) { + cost_delta += + Cost(BitCount64(bitset & cards_[card])) - row_cost[card]; + } + } + } + Backtrack(); + if (cost_delta >= 0) { + VLOG(1) << "reject"; + } + return cost_delta < 0; + } + private: + struct Undo { + Undo(int c, uint64 b) : card(c), bitset(b) {} + int card; + uint64 bitset; + }; + + int Cost(int intersection_size) { + return abs(intersection_size - 1); + } + + void Backtrack() { + for (int i = 0; i < backtrack_.size(); ++i) { + cards_[backtrack_[i].card] = backtrack_[i].bitset; + } + } + + void Save(int card) { + backtrack_.push_back(Undo(card, cards_[card])); + } + + bool CheckCards() { + for (int i = 0; i < num_cards_; ++i) { + if (num_symbols_per_card_ != BitCount64(cards_[i])) { + VLOG(1) << "card " << i << " has bitset of size " + << BitCount64(cards_[i]); + return false; + } + } + return true; + } + + const int num_cards_; + const int num_symbols_; + const int num_symbols_per_card_; + vector cards_; + vector > costs_; + vector backtrack_; +}; + +void SolveDobble(int num_cards, int num_symbols, int num_symbols_per_card) { + LOG(INFO) << "Solving dobble assignment problem:"; + LOG(INFO) << " - " << num_cards << " cards"; + LOG(INFO) << " - " << num_symbols << " symbols"; + LOG(INFO) << " - " << num_symbols_per_card << " symbols per card"; + + Solver solver("dobble"); + vector > vars(num_cards); + vector all_vars; + for (int card_index = 0; card_index < num_cards; ++card_index) { + solver.MakeBoolVarArray(num_symbols, + StringPrintf("card_%i_", card_index), + &vars[card_index]); + for (int symbol_index = 0; + symbol_index < num_symbols; + ++symbol_index) { + all_vars.push_back(vars[card_index][symbol_index]); + } + } + vector slack_vars; + for (int card1 = 0; card1 < num_cards; ++card1) { + for (int card2 = 0; card2 < num_cards; ++card2) { + if (card1 != card2) { + slack_vars.push_back(AddIntersectionVar(&solver, + vars[card1], + vars[card2], + num_symbols_per_card)); + } + } + } + + for (int card = 0; card < num_cards; ++card) { + solver.AddConstraint(solver.MakeSumEquality(vars[card], + num_symbols_per_card)); + } + + for (int symbol_index = 0; symbol_index < num_symbols; ++symbol_index) { + vector tmp; + for (int card_index = 0; card_index < num_cards; ++card_index) { + tmp.push_back(vars[card_index][symbol_index]); + } + solver.AddConstraint(solver.MakeSumEquality(tmp, num_symbols_per_card)); + } + + LOG(INFO) << "Solving with LNS"; + LOG(INFO) << " - lns_size = " << FLAGS_lns_size; + LOG(INFO) << " - lns_limit = " << FLAGS_lns_limit; + LOG(INFO) << " - fail_limit = " << FLAGS_fail_limit; + + DecisionBuilder* const build_db = solver.MakePhase(all_vars, + Solver::CHOOSE_RANDOM, + Solver::ASSIGN_MAX_VALUE); + + const int kNhoodLimit = 1000; + LocalSearchOperator* const switch_operator = + solver.RevAlloc(new SwitchSymbols(all_vars.data(), + all_vars.size(), + num_cards, + num_symbols, + num_symbols_per_card)); + LocalSearchOperator* const cycle_operator = + solver.RevAlloc(new CycleSymbols(all_vars.data(), + all_vars.size(), + num_cards, + num_symbols, + num_symbols_per_card)); + LocalSearchOperator* const long_cycle_operator_limited = + solver.MakeNeighborhoodLimit( + solver.RevAlloc(new CycleNeighborhood(all_vars.data(), + all_vars.size(), + FLAGS_lns_size, + num_cards, + num_symbols, + num_symbols_per_card)), + kNhoodLimit); + LocalSearchOperator* const long_cycle_operator_unlimited = + solver.RevAlloc(new CycleNeighborhood(all_vars.data(), + all_vars.size(), + FLAGS_lns_size, + num_cards, + num_symbols, + num_symbols_per_card)); + // LocalSearchOperator* const cross_lns_operator_limited = + // solver.MakeNeighborhoodLimit( + // solver.RevAlloc(new CrossLns(all_vars.data(), + // all_vars.size(), + // FLAGS_lns_size, + // num_cards, + // num_symbols, + // num_symbols_per_card)), + // kNhoodLimit); + LocalSearchOperator* const card_lns_operator_limited = + solver.MakeNeighborhoodLimit( + solver.RevAlloc(new CardLns(all_vars.data(), + all_vars.size(), + FLAGS_lns_size, + num_cards, + num_symbols)), + kNhoodLimit); + LocalSearchOperator* const card_lns_operator_unlimited = + solver.RevAlloc(new CardLns(all_vars.data(), + all_vars.size(), + FLAGS_lns_size, + num_cards, + num_symbols)); + + vector operators; + operators.push_back(switch_operator); + operators.push_back(card_lns_operator_limited); + operators.push_back(long_cycle_operator_limited); + operators.push_back(cycle_operator); + // operators.push_back(card_lns_operator_unlimited); + operators.push_back(long_cycle_operator_unlimited); + LocalSearchOperator* const concat = + solver.ConcatenateOperators(operators, true); + SearchLimit* const lns_limit = + solver.MakeLimit(kint64max, kint64max, FLAGS_lns_limit, kint64max); + DecisionBuilder* const ls_db = solver.MakeSolveOnce(build_db, lns_limit); + vector filters; + filters.push_back(solver.RevAlloc(new DobbleFilter(all_vars.data(), + all_vars.size(), + num_cards, + num_symbols, + num_symbols_per_card))); + LocalSearchPhaseParameters* const parameters = + solver.MakeLocalSearchPhaseParameters(concat, ls_db, NULL, filters); + DecisionBuilder* const final_db = + solver.MakeLocalSearchPhase(all_vars, build_db, parameters); + + IntVar* const objective_var = solver.MakeSum(slack_vars)->Var(); + + vector monitors; + OptimizeVar* const optimize = solver.MakeMinimize(objective_var, 1); + monitors.push_back(optimize); + SearchMonitor* const log = solver.MakeSearchLog(100000, optimize); + monitors.push_back(log); + SearchLimit* const fail_limit = + solver.MakeLimit(kint64max, kint64max, FLAGS_fail_limit, kint64max); + monitors.push_back(fail_limit); + solver.Solve(final_db, monitors); +} +} + +int main(int argc, char **argv) { + google::ParseCommandLineFlags(&argc, &argv, true); + const int kCards = 57; + const int kSymbols = 57; + const int kSymbolsPerCard = 8; + operations_research::SolveDobble(kCards, + kSymbols, + kSymbolsPerCard); + return 0; +}