/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* * Main authors: * Christian Schulte * * Copyright: * Christian Schulte, 2019 * * This file is part of Gecode, the generic constraint * development environment: * http://www.gecode.org * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * */ #include #include #include #include #include #include #include using namespace Gecode; /* * The paper uses ideas from: D. Grimes and E. Hebrard, Solving Variants * of the Job Shop Scheduling Problem through Conflict-Directed Search, * INFORMS Journal of Computing, Volume 27, Issue 2, 2015. * * Warning: this solution is a sketch and not competitive as not all * techniques from the paper have been implemented. * */ /// Default configuration settings namespace JobShopConfig { /// Whether to print verbose information static const bool verbose = false; /// How many probes to perform static const unsigned int probes = 50U; /// How many failures maximal per probe static const unsigned int fail_probe = 10000U; /// How much time to spend on probing static const unsigned int time_probe = 30U * 1000U; /// How much time to spend on adjusting static const unsigned int time_adjust = 30U * 1000U; /// How much time to spend on solving static const unsigned int time_solve = 60U * 1000U; /// Restart scale factor static const double restart_scale = 5000.0; /// Restart base static const double restart_base = 1.3; } // Instance data namespace { // Instances extern const int* js[]; // Instance names extern const char* name[]; /// A wrapper class for instance data class Spec { protected: /// Raw instance data const int* data; /// Lower and upper bound int l, u; /// Name const char* n; public: /// Whether a valid specification has been found bool valid(void) const { return data != nullptr; } /// Return number of jobs int jobs(void) const { return data[0]; } /// Return number of machines int machines(void) const { return data[1]; } /// Return machine of step \a j in job \a i int machine(int i, int j) const { return data[2 + i*machines()*2 + j*2]; } /// Return duration of step \a j in job \a i int duration(int i, int j) const { return data[2 + i*machines()*2 + j*2 + 1]; } protected: /// Find instance by name \a s static const int* find(const char* s) { for (int i=0; ::name[i] != nullptr; i++) if (!strcmp(s,::name[i])) return js[i]; return nullptr; } /// Compute lower bound int clower(void) const { int l = 0; Region r; int* mach = r.alloc(machines()); for (int j=0; j(home).afc(x,i); } /// Action information IntAction iaction; BoolAction baction; /// Action-based cost double action(int i) const { return ((baction[i] + iaction[fst[i]] + iaction[snd[i]]) / (start[fst[i]].size() + start[fst[i]].size())); } /// Trampoline function for Action-based cost static double actionmerit(const Space& home, BoolVar, int i) { return static_cast(home).action(i); } /// CHB information IntCHB ichb; BoolCHB bchb; /// CHB-based cost double chb(int i) const { return ((bchb[i] + ichb[fst[i]] + ichb[snd[i]]) / (start[fst[i]].size() + start[fst[i]].size())); } /// Trampoline function for CHB-based cost static double chbmerit(const Space& home, BoolVar, int i) { return static_cast(home).chb(i); } /// Random number generator for probing and relaxation Rnd rnd; public: /// Branching to use enum { BRANCH_AFC, ///< Branch using AFC BRANCH_ACTION, ///< Branch using action BRANCH_CHB ///< Branch using CHB }; /// Propagation to use enum { PROP_ORDER, ///< Only propagate order constraints PROP_UNARY ///< Also post unary constraints }; /// Actual model JobShopSolve(const JobShopOptions& o) : JobShopBase(o), sorder(*this, spec.machines()*spec.jobs()*(spec.jobs()-1)/2, 0, 1), rnd(o.seed()) { if (opt.propagation() == PROP_UNARY) nooverload(); // Number of jobs and machines/steps int n = spec.jobs(), m = spec.machines(); fst.init(m*n*(n-1)/2); snd.init(m*n*(n-1)/2); IntArgs jobs(m*n), dur(m*n); for (int i=0; i dur[j*n+i2]) { order(*this, start[jobs[j*n+i1]], dur[j*n+i1], start[jobs[j*n+i2]], dur[j*n+i2], sorder[l]); fst[l] = j*n+i1; snd[l] = j*n+i2; } else { order(*this, start[jobs[j*n+i2]], dur[j*n+i2], start[jobs[j*n+i1]], dur[j*n+i1], sorder[l]); fst[l] = j*n+i2; snd[l] = j*n+i1; } l++; } assert(l == (j+1)*n*(n-1)/2); } double tbf = opt.tbf(); switch (opt.branching()) { case BRANCH_AFC: iafc.init(*this,start,opt.decay()); if (tbf > 0.0) { auto tbl = [tbf] (const Space&, double w, double b) { assert(b >= w); return b - (b - w) * tbf; }; branch(*this, sorder, tiebreak(BOOL_VAR_MERIT_MAX(&afcmerit,tbl), BOOL_VAR_RND(rnd)), BOOL_VAL_MIN()); } else { branch(*this, sorder, BOOL_VAR_MERIT_MAX(&afcmerit), BOOL_VAL_MIN()); } break; case BRANCH_ACTION: iaction.init(*this,start,opt.decay()); baction.init(*this,sorder,opt.decay()); if (tbf > 0.0) { auto tbl = [tbf] (const Space&, double w, double b) { assert(b >= w); return b - (b - w) * tbf; }; branch(*this, sorder, tiebreak(BOOL_VAR_MERIT_MAX(&actionmerit,tbl), BOOL_VAR_RND(rnd)), BOOL_VAL_MIN()); } else { branch(*this, sorder, BOOL_VAR_MERIT_MAX(&actionmerit), BOOL_VAL_MIN()); } break; case BRANCH_CHB: ichb.init(*this,start); bchb.init(*this,sorder); if (tbf > 0.0) { auto tbl = [tbf] (const Space&, double w, double b) { assert(b >= w); return b - (b - w) * tbf; }; branch(*this, sorder, tiebreak(BOOL_VAR_MERIT_MAX(&chbmerit,tbl), BOOL_VAR_RND(rnd)), BOOL_VAL_MIN()); } else { branch(*this, sorder, BOOL_VAR_MERIT_MAX(&chbmerit), BOOL_VAL_MIN()); } break; } assign(*this, start, INT_VAR_MIN_MIN(), INT_ASSIGN_MIN()); assign(*this, makespan, INT_ASSIGN_MIN()); } /// Constructor for cloning \a s JobShopSolve(JobShopSolve& s) : JobShopBase(s), sorder(s.sorder), fst(s.fst), snd(s.snd), iafc(s.iafc), iaction(s.iaction), baction(s.baction), ichb(s.ichb), bchb(s.bchb), rnd(s.rnd) {} /// Copy during cloning virtual Space* copy(void) { return new JobShopSolve(*this); } }; /// Stop object combining time and failuresa class FailTimeStop : public Search::Stop { protected: Search::FailStop* fs; ///< Used fail stop object Search::TimeStop* ts; ///< Used time stop object public: /// Initialize stop object FailTimeStop(unsigned int fail, unsigned int time) : fs(new Search::FailStop(fail)), ts(new Search::TimeStop(time)) {} /// Test whether search must be stopped virtual bool stop(const Search::Statistics& s, const Search::Options& o) { return fs->stop(s,o) || ts->stop(s,o); } /// Whether the stop was due to failures bool fail(const Search::Statistics& s, const Search::Options& o) const { return fs->stop(s,o); } /// Whether the stop was due to time bool time(const Search::Statistics& s, const Search::Options& o) const { return ts->stop(s,o); } /// Destructor ~FailTimeStop(void) { delete fs; delete ts; } }; /// Print statistics void print(const Search::Statistics& stat, bool restart) { using namespace std; cout << "\t\t\tnodes: " << stat.node << endl << "\t\t\tfailures: " << stat.fail << endl; if (restart) cout << "\t\t\trestarts: " << stat.restart << endl << "\t\t\tno-goods: " << stat.nogood << endl; cout << "\t\t\tpeak depth: " << stat.depth << endl; } /// Solver void solve(const JobShopOptions& opt) { Rnd rnd(opt.seed()); /* * Invariant: * - There is a solution with makespan u, * - There is no solution with makespan l */ int l, u; { Support::Timer t; t.start(); Search::Statistics stat; JobShopProbe* master = new JobShopProbe(opt); if (master->status() != SS_SOLVED) { delete master; std::cerr << "Error: has no solution..." << std::endl; return; } l = master->cost().min(); u = master->cost().max(); FailTimeStop fts(opt.fail_probe(),opt.time_probe()); CommonOptions so(opt); so.stop = &fts; bool stopped = false; std::cout << "\tProbing..." << std::endl; std::cout << "\t\tBounds: [" << l << "," << u << "]" << std::endl; for (unsigned int p=0; p(master->clone()); jsp->branch(p,rnd); DFS dfs(jsp,so); JobShopProbe* s = dfs.next(); Search::Statistics statj = dfs.statistics(); if (s != nullptr) { if (u > s->cost().val()) { u = s->cost().val(); s->print(std::cout); } delete s; } else if (fts.time(statj,so)) { stopped = true; break; } stat += statj; } delete master; print(stat,false); std::cout << "\t\t\truntime: "; Driver::stop(t,std::cout); std::cout << std::endl; if (stopped) { std::cout << "\t\t\t\tstopped due to time-out..." << std::endl; } } std::cout << std::endl << "\tAdjusting..." << std::endl; // Dichotomic search { JobShopSolve* master = new JobShopSolve(opt); if (master->status() == SS_FAILED) { delete master; std::cerr << "Error: has no solution..." << std::endl; return; } { Support::Timer t; t.start(); Search::Statistics stat; CommonOptions so(opt); if (opt.time_adjust() > 0U) so.stop = Search::Stop::time(opt.time_adjust()); bool stopped = false; while (l < u) { std::cout << "\t\tBounds: [" << l << "," << u << "]" << std::endl; JobShopSolve* jss = static_cast(master->clone()); int m = (l + u) / 2; rel(*jss, jss->cost() >= l); rel(*jss, jss->cost() <= m); so.cutoff = Search::Cutoff::geometric(JobShopConfig::restart_scale, JobShopConfig::restart_base); RBS rbs(jss,so); JobShopSolve* s = rbs.next(); stat += rbs.statistics(); if (s != nullptr) { s->print(std::cout); u = s->cost().val(); delete s; } else if (rbs.stopped()) { stopped = true; break; } else { l = m+1; } } print(stat,true); std::cout << "\t\t\truntime: "; Driver::stop(t,std::cout); std::cout << std::endl; if (stopped) { std::cout << "\t\t\t\tstopped due to time-out..." << std::endl; } } if (l == u) { delete master; std::cout << std::endl << "\tFound best solution and proved optimality." << std::endl; return; } { Support::Timer t; t.start(); std::cout << std::endl << "\tSolving..." << std::endl; rel(*master, master->cost() >= l); rel(*master, master->cost() < u); CommonOptions so(opt); if (opt.time_solve() > 0U) so.stop = Search::Stop::time(opt.time_solve()); so.cutoff = Search::Cutoff::geometric(JobShopConfig::restart_scale, JobShopConfig::restart_base); RBS rbs(master,so); while (JobShopSolve* s = rbs.next()) { s->print(std::cout); u = s->cost().val(); delete s; } print(rbs.statistics(),true); std::cout << "\t\t\truntime: "; Driver::stop(t,std::cout); std::cout << std::endl; if (rbs.stopped()) { std::cout << "\t\t\t\tstopped due to time-out..." << std::endl; std::cout << std::endl << "\tSolution at most "; double a = (static_cast(u-l+1) / u) * 100.0; std::cout << std::setprecision(2) << a << "% away from optimum." << std::endl; } else { std::cout << std::endl << "\tFound best solution and proved optimality." << std::endl; } } } } /** \brief Main-function * \relates JobShop */ int main(int argc, char* argv[]) { JobShopOptions opt("JobShop"); opt.branching(JobShopSolve::BRANCH_AFC); opt.branching(JobShopSolve::BRANCH_AFC, "afc"); opt.branching(JobShopSolve::BRANCH_ACTION, "action"); opt.branching(JobShopSolve::BRANCH_CHB, "chb"); opt.propagation(JobShopSolve::PROP_UNARY); opt.propagation(JobShopSolve::PROP_ORDER,"order"); opt.propagation(JobShopSolve::PROP_UNARY,"unary"); opt.instance("ft06"); opt.restart_base(JobShopConfig::restart_base); opt.restart_scale(JobShopConfig::restart_scale); opt.nogoods(true); opt.parse(argc,argv); if (!Spec(opt.instance()).valid()) { std::cerr << "Error: unkown instance" << std::endl; return 1; } solve(opt); return 0; } #include "examples/job-shop-instances.hpp" // STATISTICS: example-any