1
0
This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.

832 lines
23 KiB
C++
Executable File

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
* Main authors:
* Christian Schulte <schulte@gecode.org>
*
* 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 <gecode/driver.hh>
#include <gecode/int.hh>
#include <gecode/minimodel.hh>
#include <algorithm>
#include <iostream>
#include <iomanip>
#include <fstream>
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<int>(machines());
for (int j=0; j<machines(); j++)
mach[j]=0;
for (int i=0; i<jobs(); i++) {
int job = 0;
for (int j=0; j<machines(); j++) {
mach[machine(i,j)] += duration(i,j);
job += duration(i,j);
}
l = std::max(l,job);
}
for (int j=0; j<machines(); j++)
l = std::max(l,mach[j]);
return l;
}
/// Compute naive upper bound
int cupper(void) const {
int u = 0;
for (int i=0; i<jobs(); i++)
for (int j=0; j<machines(); j++)
u += duration(i,j);
return u;
}
public:
/// Initialize
Spec(const char* s) : data(find(s)), l(0), u(0), n(s) {
if (valid()) {
l = clower(); u = cupper();
}
}
/// Return lower bound
int lower(void) const {
return l;
}
/// Return upper bound
int upper(void) const {
return u;
}
/// Return name
const char* name(void) const {
return n;
}
};
}
/**
* \brief %Options for %JobShop problems
*
*/
class JobShopOptions : public InstanceOptions {
private:
/// Whether to print schedule
Driver::BoolOption _verbose;
/// How many probes
Driver::UnsignedIntOption _probes;
/// How many failures per probe
Driver::UnsignedIntOption _fail_probe;
/// Time for probing
Driver::UnsignedIntOption _time_probe;
/// Time for adjusting
Driver::UnsignedIntOption _time_adjust;
/// Time for solving
Driver::UnsignedIntOption _time_solve;
/// Tie-breaking factor
Driver::DoubleOption _tbf;
public:
/// Initialize options for example with name \a s
JobShopOptions(const char* s)
: InstanceOptions(s),
_verbose("verbose","whether to print schedule",
JobShopConfig::verbose),
_probes("probes","how many probes to perform",
JobShopConfig::probes),
_fail_probe("fail-probe","failure limit per probe",
JobShopConfig::fail_probe),
_time_probe("time-probe","time-out for probing (in milliseconds)",
JobShopConfig::time_probe),
_time_adjust("time-adjust","time-out for adjusting (in milliseconds)",
JobShopConfig::time_adjust),
_time_solve("time-solve","time-out for solving (in milliseconds)",
JobShopConfig::time_solve),
_tbf("tbf", "tie-breaking factor", 0.0) {
add(_verbose);
add(_probes);
add(_fail_probe);
add(_time_probe);
add(_time_adjust);
add(_time_solve);
add(_tbf);
}
/// Return whether to print schedule
bool verbose(void) const {
return _verbose.value();
}
/// Return number of probes
unsigned int probes(void) const {
return _probes.value();
}
/// Return number of failures per probe
unsigned int fail_probe(void) const {
return _fail_probe.value();
}
/// Return time-out for probe
unsigned int time_probe(void) const {
return _time_probe.value();
}
/// Return time-out for adjust
unsigned int time_adjust(void) const {
return _time_adjust.value();
}
/// Return time-out for solve
unsigned int time_solve(void) const {
return _time_solve.value();
}
/// Return tie-breaking factor
double tbf(void) const {
return _tbf.value();
}
/// Print help text for list of instances
virtual void help(void) {
InstanceOptions::help();
std::cerr << "\tAvailable instances:" << std::endl << "\t\t";
for (int i=1; ::name[i] != nullptr; i++) {
std::cerr << ::name[i] << ", ";
if (i % 4 == 0)
std::cerr << std::endl << "\t\t";
}
std::cerr << std::endl;
}
};
/**
* \brief %Example: Job Shop Scheduling
*
* \ingroup Example
*
*/
class JobShopBase : public IntMinimizeScript {
protected:
/// Options
const JobShopOptions& opt;
/// Specification
const Spec spec;
/// Start times for each step in a job
IntVarArray start;
/// Makespan
IntVar makespan;
public:
/// Actual model
JobShopBase(const JobShopOptions& o)
: IntMinimizeScript(o), opt(o), spec(opt.instance()),
start(*this, spec.machines() * spec.jobs(), 0, spec.upper()),
makespan(*this, spec.lower(), spec.upper()) {
// Number of jobs and machines/steps
int n = spec.jobs(), m = spec.machines();
// Endtimes for each job
IntVarArgs end(*this, n, 0, spec.upper());
// Precedence constraints and makespan
for (int i=0; i<n; i++) {
for (int j=1; j<m; j++)
rel(*this, start[i*m+j-1] + spec.duration(i,j) <= start[i*m+j]);
rel(*this, start[i*m+m-1] + spec.duration(i,m-1) <= makespan);
}
}
/// Do not overload machines
void nooverload(void) {
// Number of jobs and machines/steps
int n = spec.jobs(), m = spec.machines();
IntVarArgs jobs(m*n);
IntArgs dur(m*n);
for (int i=0; i<n; i++)
for (int j=0; j<m; j++) {
jobs[spec.machine(i,j)*n+i] = start[i*m+j];
dur[spec.machine(i,j)*n+i] = spec.duration(i,j);
}
for (int j=0; j<m; j++) {
IntVarArgs jpm(n);
IntArgs dpm(n);
for (int i=0; i<n; i++) {
jpm[i] = jobs[j*n+i]; dpm[i] = dur[j*n+i];
}
unary(*this, jpm, dpm);
}
}
/// Return cost
virtual IntVar cost(void) const {
return makespan;
}
/// Constructor for cloning \a s
JobShopBase(JobShopBase& s)
: IntMinimizeScript(s), opt(s.opt), spec(s.spec) {
start.update(*this, s.start);
makespan.update(*this, s.makespan);
}
/// Print solution
virtual void
print(std::ostream& os) const {
os << "\t\t" << spec.name()
<< " [makespan: " << makespan << "]" << std::endl;
if (opt.verbose()) {
// Number of jobs
int n = spec.jobs();
// Number of machines/steps
int m = spec.machines();
for (int i=0; i<n; i++) {
os << "\t\t\t[" << i << "]: ";
for (int j=0; j<m; j++)
os << start[i*m+j] << " ";
os << std::endl;
}
}
}
};
/// Common command line options
class CommonOptions : public Search::Options {
public:
/// Initialize
CommonOptions(const JobShopOptions& opt) {
clone = false;
threads = opt.threads();
c_d = opt.c_d();
a_d = opt.a_d();
nogoods_limit = opt.nogoods() ? opt.nogoods_limit() : 0U;
}
};
/// Model for probing
class JobShopProbe : public JobShopBase {
public:
/// Actual model
JobShopProbe(const JobShopOptions& o)
: JobShopBase(o) {
nooverload();
}
void branch(unsigned int p, Rnd r) {
switch (p) {
case 0U:
Gecode::branch(*this, start, INT_VAR_MIN_MIN(), INT_VAL_MIN());
break;
case 1U:
Gecode::branch(*this, start, INT_VAR_MAX_MIN(), INT_VAL_MIN());
break;
case 2U:
Gecode::branch(*this, start, INT_VAR_SIZE_MIN(), INT_VAL_MIN());
break;
case 3U:
Gecode::branch(*this, start, tiebreak(INT_VAR_MIN_MIN(),
INT_VAR_RND(r)), INT_VAL_MIN());
break;
case 4U:
Gecode::branch(*this, start, tiebreak(INT_VAR_MAX_MIN(),
INT_VAR_RND(r)), INT_VAL_MIN());
break;
default:
if (p & 1U)
Gecode::branch(*this, start, INT_VAR_RND(r), INT_VAL_MIN());
else
Gecode::branch(*this, start, INT_VAR_RND(r), INT_VAL_SPLIT_MIN());
break;
}
assign(*this, makespan, INT_ASSIGN_MIN());
}
/// Constructor for cloning \a s
JobShopProbe(JobShopProbe& s)
: JobShopBase(s) {}
/// Copy during cloning
virtual Space*
copy(void) {
return new JobShopProbe(*this);
}
};
/// Model for solving
class JobShopSolve : public JobShopBase {
protected:
/// Step order variables
BoolVarArray sorder;
/// Record which step is first in order
IntSharedArray fst;
/// Record which step is second in order
IntSharedArray snd;
/// AFC information
IntAFC iafc;
/// AFC-based cost
double afc(BoolVar x, int i) const {
return ((x.afc() + start[fst[i]].afc() + start[snd[i]].afc()) /
(start[fst[i]].size() + start[fst[i]].size()));
}
/// Trampoline function for AFC-based cost
static double afcmerit(const Space& home, BoolVar x, int i) {
return static_cast<const JobShopSolve&>(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<const JobShopSolve&>(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<const JobShopSolve&>(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<n; i++)
for (int j=0; j<m; j++) {
jobs[spec.machine(i,j)*n+i] = i*m+j;
dur[spec.machine(i,j)*n+i] = spec.duration(i,j);
}
int l=0;
for (int j=0; j<m; j++) {
for (int i1=0; i1<n; i1++)
for (int i2=i1+1; i2<n; i2++) {
if (dur[j*n+i1] > 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<opt.probes(); p++) {
JobShopProbe* jsp = static_cast<JobShopProbe*>(master->clone());
jsp->branch(p,rnd);
DFS<JobShopProbe> 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<JobShopSolve*>(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<JobShopSolve,DFS> 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<JobShopSolve,BAB> 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<double>(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