~spidernet/PermutationFlowShopScheduling

b9fd010323e5ba48b9a4775163867e704488f530 — Gabriele Venturato 1 year, 9 months ago f78dba8
[LocalSearch] Little bug fix in output state in file. Changed parameters of SwapJobs move. New move: MoveJob.
M LocalSearch/include/FP_Basics.hh => LocalSearch/include/FP_Basics.hh +12 -1
@@ 16,7 16,6 @@ class FP_State {
  /*void reset();*/
  void ComputeTimes(size_t job_index = 0);
  size_t getEndTime(size_t j, size_t m) const { return end_times[j][m]; }
  size_t getSchedule(size_t j) const {return schedule[j]; }
  size_t getScheduleIndex(size_t job) const;

 protected:


@@ 35,6 34,18 @@ class SwapJobs {

 public:
  SwapJobs(size_t i = 0, size_t j = 0);
  size_t j1, j2;
};

class MoveJob {
  friend bool operator==(const MoveJob& m1, const MoveJob& m2);
  friend bool operator!=(const MoveJob& m1, const MoveJob& m2);
  friend bool operator<(const MoveJob& m1, const MoveJob& m2);
  friend ostream& operator<<(ostream& os, const MoveJob& c);
  friend istream& operator>>(istream& is, MoveJob& c);

public:
  MoveJob(size_t i = 0, size_t j = 0);
  size_t p1, p2;
};
#endif

M LocalSearch/include/FP_Helpers.hh => LocalSearch/include/FP_Helpers.hh +28 -1
@@ 63,8 63,35 @@ class SwapJobsNeighborhoodExplorer : public NeighborhoodExplorer<FP_Input, FP_St
  void MakeMove(FP_State&, const SwapJobs&) const;
  void FirstMove(const FP_State&, SwapJobs&) const;
  bool NextMove(const FP_State&, SwapJobs&) const;
};

/***************************************************************************
 * MoveJob Neighborhood Explorer
 ***************************************************************************/

class MoveJobDeltaMakespan : public DeltaCostComponent<FP_Input, FP_State, MoveJob> {
public:
  MoveJobDeltaMakespan(const FP_Input& in, Makespan& cc)
      : DeltaCostComponent<FP_Input, FP_State, MoveJob>(in, cc, "MoveJobDeltaMakespan") {}
  int ComputeDeltaCost(const FP_State& st, const MoveJob& mv) const;
};

class MoveJobDeltaTardiness : public DeltaCostComponent<FP_Input, FP_State, MoveJob> {
public:
  MoveJobDeltaTardiness(const FP_Input& in, Tardiness& cc)
      : DeltaCostComponent<FP_Input, FP_State, MoveJob>(in, cc, "MoveJobDeltaTardiness") {}
  int ComputeDeltaCost(const FP_State& st, const MoveJob& mv) const;
};

 protected:
class MoveJobNeighborhoodExplorer : public NeighborhoodExplorer<FP_Input, FP_State, MoveJob> {
public:
  MoveJobNeighborhoodExplorer(const FP_Input& pin, StateManager<FP_Input, FP_State>& psm)
      : NeighborhoodExplorer<FP_Input, FP_State, MoveJob>(pin, psm, "MoveJobNeighborhoodExplorer") {}
  void RandomMove(const FP_State&, MoveJob&) const;
  bool FeasibleMove(const FP_State&, const MoveJob&) const;
  void MakeMove(FP_State&, const MoveJob&) const;
  void FirstMove(const FP_State&, MoveJob&) const;
  bool NextMove(const FP_State&, MoveJob&) const;
};

/***************************************************************************

M LocalSearch/src/FP_Basics.cc => LocalSearch/src/FP_Basics.cc +41 -6
@@ 59,21 59,56 @@ size_t FP_State::getScheduleIndex(size_t job) const {
}

SwapJobs::SwapJobs(size_t i, size_t j) {
  j1 = i;
  j2 = j;
}

bool operator==(const SwapJobs& mv1, const SwapJobs& mv2) {
  if (mv1.j1 != mv2.j1 || mv1.j2 != mv2.j2) return false;
  return true;
}

bool operator!=(const SwapJobs& mv1, const SwapJobs& mv2) {
  if (mv1.j1 == mv1.j2 && mv2.j1 == mv2.j2) return false;
  return true;
}

bool operator<(const SwapJobs& mv1, const SwapJobs& mv2) {
  if (mv1.j1 < mv2.j1)
    return true;
  else {
    if (mv1.j1 == mv2.j1 && mv1.j2 < mv2.j2) return true;
  }
  return false;
}

istream& operator>>(istream& is, SwapJobs& mv) {
  char ch;
  is >> ch >> mv.j1 >> ch >> mv.j2 >> ch;
  return is;
}

ostream& operator<<(ostream& os, const SwapJobs& mv) {
  os << "(" << mv.j1 << "," << mv.j2 << ")";
  return os;
}

MoveJob::MoveJob(size_t i, size_t j) {
  p1 = i;
  p2 = j;
}

bool operator==(const SwapJobs& mv1, const SwapJobs& mv2) {
bool operator==(const MoveJob& mv1, const MoveJob& mv2) {
  if (mv1.p1 != mv2.p1 || mv1.p2 != mv2.p2) return false;
  return true;
}

bool operator!=(const SwapJobs& mv1, const SwapJobs& mv2) {
bool operator!=(const MoveJob& mv1, const MoveJob& mv2) {
  if (mv1.p1 == mv1.p2 && mv2.p1 == mv2.p2) return false;
  return true;
}

bool operator<(const SwapJobs& mv1, const SwapJobs& mv2) {
bool operator<(const MoveJob& mv1, const MoveJob& mv2) {
  if (mv1.p1 < mv2.p1)
    return true;
  else {


@@ 82,13 117,13 @@ bool operator<(const SwapJobs& mv1, const SwapJobs& mv2) {
  return false;
}

istream& operator>>(istream& is, SwapJobs& mv) {
istream& operator>>(istream& is, MoveJob& mv) {
  char ch;
  is >> ch >> mv.p1 >> ch >> mv.p2 >> ch;
  return is;
}

ostream& operator<<(ostream& os, const SwapJobs& mv) {
ostream& operator<<(ostream& os, const MoveJob& mv) {
  os << "(" << mv.p1 << "," << mv.p2 << ")";
  return os;
}
}
\ No newline at end of file

M LocalSearch/src/FP_Data.cc => LocalSearch/src/FP_Data.cc +1 -1
@@ 122,7 122,7 @@ FP_Output& FP_Output::operator=(const FP_Output& out) {
}

ostream& operator<<(ostream& os, const FP_Output& out) {
  os << "Schedule: [";
  os << "[";
  for (size_t i = 0; i < out.in.getJobs(); ++i) {
    os << out.schedule[i];
    if (i < out.in.getJobs() - 1) {

M LocalSearch/src/FP_Helpers.cc => LocalSearch/src/FP_Helpers.cc +216 -37
@@ 39,7 39,7 @@ void FP_OutputManager::OutputState(const FP_State& st, FP_Output& out) const {
 ***************************************************************************/

int Makespan::ComputeCost(const FP_State& st) const {
  return static_cast<int>(st.getEndTime(st.getSchedule( in.getJobs()-1 ), in.getMachines()-1));
  return static_cast<int>(st.getEndTime(st[in.getJobs()-1], in.getMachines()-1));
}

void Makespan::PrintViolations(const FP_State& st, ostream& os) const {


@@ 50,7 50,7 @@ int Tardiness::ComputeCost(const FP_State& st) const {
  unsigned cost = 0;
  size_t j;
  for (unsigned i = 0; i < in.getJobs(); ++i) {
    j = st.getSchedule(i);
    j = st[i];
    if (in.getDueDates(j) != -1) {
      if (static_cast<size_t>(in.getDueDates(j)) < st.getEndTime(j, in.getMachines()-1)) {
        cost += (st.getEndTime(j, in.getMachines()-1) - in.getDueDates(j)) * in.getWeight(j);


@@ 63,7 63,7 @@ int Tardiness::ComputeCost(const FP_State& st) const {
void Tardiness::PrintViolations(const FP_State& st, ostream& os) const {
  size_t j;
  for (unsigned i = 0; i < in.getJobs(); ++i) {
    j = st.getSchedule(i);
    j = st[i];
    if (in.getDueDates(j) != -1) {
      if (static_cast<size_t>(in.getDueDates(j)) < st.getEndTime(j, in.getMachines()-1)) {
        os << "Tardiness of job " << j << " is " << (st.getEndTime(j, in.getMachines()-1) - in.getDueDates(j)) *


@@ 79,39 79,39 @@ void Tardiness::PrintViolations(const FP_State& st, ostream& os) const {

// initial move builder
void SwapJobsNeighborhoodExplorer::RandomMove(const FP_State& st, SwapJobs& mv) const {
  mv.p1 = Random::Uniform<size_t>(0, in.getJobs() - 1);
  mv.p2 = Random::Uniform<size_t>(0, in.getJobs() - 2);
  if (mv.p2 >= mv.p1)
    mv.p2++;
  if (mv.p1 > mv.p2) // swap p1 and p2 so that p1 < p2
    swap(mv.p1, mv.p2);
  mv.j1 = Random::Uniform<size_t>(0, in.getJobs() - 1);
  mv.j2 = Random::Uniform<size_t>(0, in.getJobs() - 2);
  if (mv.j2 >= mv.j1)
    mv.j2++;
  if (mv.j1 > mv.j2) // swap j1 and j2 so that j1 < j2
    swap(mv.j1, mv.j2);
}

// check move feasibility
bool SwapJobsNeighborhoodExplorer::FeasibleMove(const FP_State& st, const SwapJobs& mv) const {
  return mv.p1 < mv.p2;
  return mv.j1 < mv.j2;
}

// update the state according to the move
void SwapJobsNeighborhoodExplorer::MakeMove(FP_State& st, const SwapJobs& mv) const {
  size_t idp1 = st.getScheduleIndex(mv.p1);
  size_t idp2 = st.getScheduleIndex(mv.p2);
  swap(st[idp1], st[idp2]);
  st.ComputeTimes( min<size_t>(idp1, idp2) );
  size_t idj1 = st.getScheduleIndex(mv.j1);
  size_t idj2 = st.getScheduleIndex(mv.j2);
  swap(st[idj1], st[idj2]);
  st.ComputeTimes( min<size_t>(idj1, idj2) );
}

void SwapJobsNeighborhoodExplorer::FirstMove(const FP_State& st, SwapJobs& mv) const {
  mv.p1 = 0;
  mv.p2 = 1;
  mv.j1 = 0;
  mv.j2 = 1;
}

bool SwapJobsNeighborhoodExplorer::NextMove(const FP_State& st, SwapJobs& mv) const {
  if (mv.p2 < in.getJobs() - 1) {
    mv.p2++;
  if (mv.j2 < in.getJobs() - 1) {
    mv.j2++;
    return true;
  } else if (mv.p1 < in.getJobs() - 2) {
    mv.p1++;
    mv.p2 = mv.p1 + 1;
  } else if (mv.j1 < in.getJobs() - 2) {
    mv.j1++;
    mv.j2 = mv.j1 + 1;
    return true;
  } else {
    return false;


@@ 120,15 120,15 @@ bool SwapJobsNeighborhoodExplorer::NextMove(const FP_State& st, SwapJobs& mv) co

int SwapJobsDeltaMakespan::ComputeDeltaCost(const FP_State& st, const SwapJobs& mv) const {
  int future_makespan = 0;
  int current_makespan = static_cast<int>(st.getEndTime(st.getSchedule( in.getJobs()-1 ), in.getMachines()-1));
  size_t idp1 = st.getScheduleIndex(mv.p1);
  size_t idp2 = st.getScheduleIndex(mv.p2);
  size_t sidx = min<size_t>(idp1,idp2);
  int current_makespan = static_cast<int>(st.getEndTime(st[in.getJobs()-1], in.getMachines()-1));
  size_t idj1 = st.getScheduleIndex(mv.j1);
  size_t idj2 = st.getScheduleIndex(mv.j2);
  size_t sidx = min<size_t>(idj1,idj2);
  vector<size_t> new_schedule;
  for (size_t j = 0; j < in.getJobs(); ++j) {
    new_schedule.push_back(st.getSchedule(j));
    new_schedule.push_back(st[j]);
  }
  swap(new_schedule[idp1],new_schedule[idp2]);
  swap(new_schedule[idj1],new_schedule[idj2]);

  // retrieve starting time from current state
  size_t start_time;


@@ 136,7 136,7 @@ int SwapJobsDeltaMakespan::ComputeDeltaCost(const FP_State& st, const SwapJobs& 
    // if swapping job 0, start time is 0 (take care of release date later)
    start_time = 0;
  } else {
    start_time = st.getEndTime(st.getSchedule(sidx-1), 0);
    start_time = st.getEndTime(st[sidx-1], 0);
  }

  vector<vector<size_t>> s_times, e_times;


@@ 145,7 145,7 @@ int SwapJobsDeltaMakespan::ComputeDeltaCost(const FP_State& st, const SwapJobs& 

  if (sidx) {
    for (size_t m = 0; m < in.getMachines(); ++m) {
      e_times[st.getSchedule(sidx-1)][m] = st.getEndTime(st.getSchedule(sidx-1),m);
      e_times[st[sidx-1]][m] = st.getEndTime(st[sidx-1],m);
    }
  }



@@ 173,18 173,18 @@ int SwapJobsDeltaMakespan::ComputeDeltaCost(const FP_State& st, const SwapJobs& 

int SwapJobsDeltaTardiness::ComputeDeltaCost(const FP_State& st, const SwapJobs& mv) const {
  int partial_future_tardiness = 0, partial_current_tardiness = 0;
  size_t idp1 = st.getScheduleIndex(mv.p1);
  size_t idp2 = st.getScheduleIndex(mv.p2);
  size_t sidx = min<size_t>(idp1,idp2);
  size_t idj1 = st.getScheduleIndex(mv.j1);
  size_t idj2 = st.getScheduleIndex(mv.j2);
  size_t sidx = min<size_t>(idj1,idj2);
  vector<size_t> new_schedule;
  for (size_t j = 0; j < in.getJobs(); ++j) {
    new_schedule.push_back(st.getSchedule(j));
    new_schedule.push_back(st[j]);
  }
  swap(new_schedule[idp1],new_schedule[idp2]);
  swap(new_schedule[idj1],new_schedule[idj2]);

  size_t j;
  for (size_t i = sidx; i < in.getJobs(); ++i) {
    j = st.getSchedule(i);
    j = st[i];
    if (in.getDueDates(j) != -1) {
      if (static_cast<size_t>(in.getDueDates(j)) < st.getEndTime(j, in.getMachines()-1)) {
        partial_current_tardiness += (st.getEndTime(j, in.getMachines()-1) - in.getDueDates(j)) * in.getWeight(j);


@@ 198,7 198,7 @@ int SwapJobsDeltaTardiness::ComputeDeltaCost(const FP_State& st, const SwapJobs&
    // if swapping job 0, start time is 0 (take care of release date later)
    start_time = 0;
  } else {
    start_time = st.getEndTime(st.getSchedule(sidx-1), 0);
    start_time = st.getEndTime(st[sidx-1], 0);
  }

  vector<vector<size_t>> s_times, e_times;


@@ 207,7 207,7 @@ int SwapJobsDeltaTardiness::ComputeDeltaCost(const FP_State& st, const SwapJobs&

  if (sidx) {
    for (size_t m = 0; m < in.getMachines(); ++m) {
      e_times[st.getSchedule(sidx-1)][m] = st.getEndTime(st.getSchedule(sidx-1),m);
      e_times[ st[sidx-1] ][m] = st.getEndTime(st[sidx-1],m);
    }
  }



@@ 238,3 238,182 @@ int SwapJobsDeltaTardiness::ComputeDeltaCost(const FP_State& st, const SwapJobs&

  return partial_future_tardiness - partial_current_tardiness;
}

/*****************************************************************************
 * MoveJob Neighborhood Explorer
 *****************************************************************************/

// initial move builder
void MoveJobNeighborhoodExplorer::RandomMove(const FP_State& st, MoveJob& mv) const {
  mv.p1 = Random::Uniform<size_t>(0, in.getJobs() - 1);
  mv.p2 = Random::Uniform<size_t>(0, in.getJobs() - 2);
  if (mv.p2 >= mv.p1)
    mv.p2++;
  if (mv.p1 > mv.p2) // swap p1 and p2 so that p1 < p2
    swap(mv.p1, mv.p2);
}

// check move feasibility
bool MoveJobNeighborhoodExplorer::FeasibleMove(const FP_State& st, const MoveJob& mv) const {
  return mv.p1 < mv.p2;
}

// update the state according to the move
void MoveJobNeighborhoodExplorer::MakeMove(FP_State& st, const MoveJob& mv) const {
  size_t moving_job = st[mv.p1];
  for (size_t j = mv.p1; j < mv.p2; ++j) {
    st[j] = st[j+1];
  }
  st[mv.p2] = moving_job;
  st.ComputeTimes( mv.p1 );
}

void MoveJobNeighborhoodExplorer::FirstMove(const FP_State& st, MoveJob& mv) const {
  mv.p1 = 0;
  mv.p2 = 1;
}

bool MoveJobNeighborhoodExplorer::NextMove(const FP_State& st, MoveJob& mv) const {
  if (mv.p2 < in.getJobs() - 1) {
    mv.p2++;
    return true;
  } else if (mv.p1 < in.getJobs() - 2) {
    mv.p1++;
    mv.p2 = mv.p1 + 1;
    return true;
  } else {
    return false;
  }
}

int MoveJobDeltaMakespan::ComputeDeltaCost(const FP_State& st, const MoveJob& mv) const {
  int future_makespan = 0;
  int current_makespan = static_cast<int>(st.getEndTime(st[in.getJobs()-1], in.getMachines()-1));
  vector<size_t> new_schedule;
  size_t moving_job;
  vector<vector<size_t>> s_times, e_times;

  for (size_t j = 0; j < in.getJobs(); ++j) {
    new_schedule.push_back(st[j]);
  }

  // compute new schedule
  moving_job = new_schedule[mv.p1];
  for (size_t j = mv.p1; j < mv.p2; ++j) {
    new_schedule[j] = new_schedule[j+1];
  }
  new_schedule[mv.p2] = moving_job;

  // retrieve starting time from current state
  size_t start_time;
  if (mv.p1 == 0) {
    // if swapping job 0, start time is 0 (take care of release date later)
    start_time = 0;
  } else {
    start_time = st.getEndTime(st[mv.p1-1], 0);
  }

  s_times.resize(in.getJobs(), vector<size_t>(in.getMachines()));
  e_times.resize(in.getJobs(), vector<size_t>(in.getMachines()));

  if (mv.p1) {
    for (size_t m = 0; m < in.getMachines(); ++m) {
      e_times[ st[mv.p1-1] ][m] = st.getEndTime(st[mv.p1-1], m);
    }
  }

  size_t j;
  for (size_t m = 0; m < in.getMachines(); ++m) {
    for (size_t i = mv.p1; i < in.getJobs(); ++i) {
      j = new_schedule[i];
      if (m == 0 && i == 0) {
        s_times[j][m] = max<size_t>(start_time, in.getReleaseDate(new_schedule[0]));
      } else if (m == 0) {
        s_times[j][m] = max<size_t>(e_times[new_schedule[i-1]][m], in.getReleaseDate(j));
      } else if (i == 0) {
        s_times[j][m] = e_times[j][m-1];
      } else {
        s_times[j][m] = max<size_t>(e_times[new_schedule[i-1]][m], e_times[j][m-1]);
      }
      e_times[j][m] = s_times[j][m] + in.getDuration(j,m);
    }
  }

  future_makespan = static_cast<int>(e_times[new_schedule[in.getJobs()-1]][in.getMachines()-1]);

  return future_makespan - current_makespan;
}

int MoveJobDeltaTardiness::ComputeDeltaCost(const FP_State& st, const MoveJob& mv) const {
  int partial_future_tardiness = 0, partial_current_tardiness = 0;
  vector<size_t> new_schedule;
  size_t moving_job, j;
  vector<vector<size_t>> s_times, e_times;

  for (size_t j = 0; j < in.getJobs(); ++j) {
    new_schedule.push_back(st[j]);
  }

  // compute new schedule
  moving_job = new_schedule[mv.p1];
  for (size_t j = mv.p1; j < mv.p2; ++j) {
    new_schedule[j] = new_schedule[j+1];
  }
  new_schedule[mv.p2] = moving_job;

  // compute current tardiness
  for (size_t i = mv.p1; i < in.getJobs(); ++i) {
    j = st[i];
    if (in.getDueDates(j) != -1) {
      if (static_cast<size_t>(in.getDueDates(j)) < st.getEndTime(j, in.getMachines()-1)) {
        partial_current_tardiness += (st.getEndTime(j, in.getMachines()-1) - in.getDueDates(j)) * in.getWeight(j);
      }
    }
  }

  // retrieve starting time from current state
  size_t start_time;
  if (mv.p1 == 0) {
    // if swapping job 0, start time is 0 (take care of release date later)
    start_time = 0;
  } else {
    start_time = st.getEndTime(st[mv.p1-1], 0);
  }

  s_times.resize(in.getJobs(), vector<size_t>(in.getMachines()));
  e_times.resize(in.getJobs(), vector<size_t>(in.getMachines()));

  if (mv.p1) {
    for (size_t m = 0; m < in.getMachines(); ++m) {
      e_times[ st[mv.p1-1] ][m] = st.getEndTime(st[mv.p1-1], m);
    }
  }

  for (size_t m = 0; m < in.getMachines(); ++m) {
    for (size_t i = mv.p1; i < in.getJobs(); ++i) {
      j = new_schedule[i];
      if (m == 0 && i == 0) {
        s_times[j][m] = max<size_t>(start_time, in.getReleaseDate(new_schedule[0]));
      } else if (m == 0) {
        s_times[j][m] = max<size_t>(e_times[new_schedule[i-1]][m], in.getReleaseDate(j));
      } else if (i == 0) {
        s_times[j][m] = e_times[j][m-1];
      } else {
        s_times[j][m] = max<size_t>(e_times[new_schedule[i-1]][m], e_times[j][m-1]);
      }
      e_times[j][m] = s_times[j][m] + in.getDuration(j,m);
    }
  }

  // compute new tardiness
  for (size_t i = mv.p1; i < in.getJobs(); ++i) {
    j = new_schedule[i];
    if (in.getDueDates(j) != -1) {
      if (static_cast<size_t>(in.getDueDates(j)) < e_times[j][in.getMachines()-1]) {
        partial_future_tardiness += (e_times[j][in.getMachines()-1] - in.getDueDates(j)) * in.getWeight(j);
      }
    }
  }

  return partial_future_tardiness - partial_current_tardiness;
}
\ No newline at end of file

M LocalSearch/src/FP_Main.cc => LocalSearch/src/FP_Main.cc +21 -2
@@ 32,10 32,15 @@ int main(int argc, const char* argv[]) {
  SwapJobsDeltaMakespan dcc1(in, cc1);
  SwapJobsDeltaTardiness dcc2(in, cc2);

  MoveJobDeltaMakespan mj_dcc1(in, cc1);
  MoveJobDeltaTardiness mj_dcc2(in, cc2);

  // helpers
  FP_StateManager FP_sm(in);
  SwapJobsNeighborhoodExplorer FP_nhe(in, FP_sm);

  MoveJobNeighborhoodExplorer FP_mj_nhe(in, FP_sm);

  FP_OutputManager FP_om(in);

  // All cost components must be added to the state manager


@@ 46,17 51,25 @@ int main(int argc, const char* argv[]) {
  FP_nhe.AddDeltaCostComponent(dcc1);
  FP_nhe.AddDeltaCostComponent(dcc2);

  FP_mj_nhe.AddDeltaCostComponent(mj_dcc1);
  FP_mj_nhe.AddDeltaCostComponent(mj_dcc2);

  // runners
  HillClimbing<FP_Input, FP_State, SwapJobs> FP_hc(in, FP_sm, FP_nhe, "SwapJobsHillClimbing");
  SteepestDescent<FP_Input, FP_State, SwapJobs> FP_sd(in, FP_sm, FP_nhe, "SwapJobsSteepestDescent");
  SimulatedAnnealing<FP_Input, FP_State, SwapJobs> FP_sa(in, FP_sm, FP_nhe, "SwapJobsSimulatedAnnealing");
  TabuSearch<FP_Input, FP_State, SwapJobs> FP_ts(in, FP_sm, FP_nhe, "SwapJobsTabuSearch",
                                                   [](const SwapJobs& m1, const SwapJobs& m2)->bool
                                                   { return m1.p1 == m2.p1 && m1.p2 == m2.p2; });
                                                   { return m1.j1 == m2.j1 && m1.j2 == m2.j2; });

  HillClimbing<FP_Input, FP_State, MoveJob> FP_mj_hc(in, FP_sm, FP_mj_nhe, "MoveJobHillClimbing");
  SteepestDescent<FP_Input, FP_State, MoveJob> FP_mj_sd(in, FP_sm, FP_mj_nhe, "MoveJobSteepestDescent");
  SimulatedAnnealing<FP_Input, FP_State, MoveJob> FP_mj_sa(in, FP_sm, FP_mj_nhe, "MoveJobSimulatedAnnealing");

  // tester
  Tester<FP_Input, FP_Output, FP_State> tester(in, FP_sm, FP_om);
  MoveTester<FP_Input, FP_Output, FP_State, SwapJobs> swap_move_test(in, FP_sm, FP_om, FP_nhe, "SwapJobs move", tester);
  MoveTester<FP_Input, FP_Output, FP_State, MoveJob> job_move_test(in, FP_sm, FP_om, FP_mj_nhe, "MoveJob move", tester);

  SimpleLocalSearch<FP_Input, FP_Output, FP_State> FP_solver(in, FP_sm, FP_om, "FP solver");
  if (!CommandLineParameters::Parse(argc, argv, true, false)) return 1;


@@ 73,8 86,14 @@ int main(int argc, const char* argv[]) {
      FP_solver.SetRunner(FP_hc);
    } else if (method == string("SwapJobsSteepestDescent")) {
      FP_solver.SetRunner(FP_sd);
    } else {
    } else if (method == string("SwapJobsTabuSearch")) {
      FP_solver.SetRunner(FP_ts);
    } else  if (method == string("MoveJobSimulatedAnnealing")) {
      FP_solver.SetRunner(FP_mj_sa);
    } else if (method == string("MoveJobHillClimbing")) {
      FP_solver.SetRunner(FP_mj_hc);
    } else {
      FP_solver.SetRunner(FP_mj_sd);
    }
    auto result = FP_solver.Solve();
    // result is a tuple: 0: solution, 1: number of violations, 2: total cost, 3: computing time