solver  1.0
ParallelHeuristicSolver.cpp
1 /*
2  This file is part of the EnergyOptimizatorOfRoboticCells program.
3 
4  EnergyOptimizatorOfRoboticCells is free software: you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation, either version 3 of the License, or
7  (at your option) any later version.
8 
9  EnergyOptimizatorOfRoboticCells is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with EnergyOptimizatorOfRoboticCells. If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #include <algorithm>
19 #include <chrono>
20 #include <cmath>
21 #include <fstream>
22 #include <numeric>
23 #include <set>
24 #include <random>
25 #include <thread>
26 #include <vector>
27 #include <unordered_map>
28 #include "SolverConfig.h"
30 #include "Shared/Utils.h"
32 
33 using namespace std;
34 using namespace std::chrono;
35 
36 static random_device rd;
37 
38 ParallelHeuristicSolver::ParallelHeuristicSolver(const RoboticLine& l, const PrecalculatedMapping& m) : mLine(l), mMapping(m), algo(mKB, mLine, mMapping) {
39  mTabuListSize = 0u;
40  uint32_t maxStaticActivities = 0u, maxLocations = 0u;
41  for (Robot* r : l.robots()) {
42  uint32_t numberOfStaticActivities = 0u, numberOfLocations = 0u, numberOfPowerModes = r->powerModes().size();
43  for (Activity *a : r->activities()) {
44  StaticActivity *sa = dynamic_cast<StaticActivity*>(a);
45  if (sa != nullptr) {
46  uint32_t numberOfActivityLocations = sa->locations().size();
47  maxLocations = max(maxLocations, numberOfActivityLocations);
48  numberOfLocations += numberOfActivityLocations;
49  ++numberOfStaticActivities;
50  }
51  }
52 
53  maxStaticActivities = max(maxStaticActivities, numberOfStaticActivities);
54  mTabuListSize += numberOfLocations*numberOfPowerModes;
55  }
56 
57  mDist.resize(Settings::NUMBER_OF_THREADS+1, vector<vector<double>>(maxStaticActivities+1, vector<double>(maxLocations, F64_INF)));
58  mPreds.resize(Settings::NUMBER_OF_THREADS+1, vector<vector<int64_t>>(maxStaticActivities+1, vector<int64_t>(maxLocations, -1)));
59  mLocs.resize(Settings::NUMBER_OF_THREADS+1, vector<vector<Location*>>(maxStaticActivities+1, vector<Location*>(maxLocations, nullptr)));
60 
61  mTabuListSize = (uint32_t) ceil(0.1*mTabuListSize);
62 }
63 
65  Solution s;
66  try {
67  controlThread();
68  s = mKB.bestSolution();
69  s.status = FEASIBLE;
70  } catch (NoFeasibleSolutionExists& e) {
71  s.status = INFEASIBLE;
72  } catch (EmptySolutionPool& e) {
73  s.status = UNKNOWN;
74  } catch (...) {
75  throw_with_nested(SolverException(caller(), "Cannot get the solution, an exception occurred!"));
76  }
77 
78  s.runTime = mRuntime;
79 
80  return s;
81 }
82 
84  // Set constants and the feasible flag.
85  mRuntime = 0.0;
86  mFeasible = true;
87  mStopCondition = false;
88  // Start timing...
89  time_point<system_clock, duration<double>> startTime = high_resolution_clock::now();
90  time_point<system_clock, duration<double>> expectedFinishTime = startTime+duration<double>(Settings::MAX_RUNTIME);
91  // Initial phase of the heuristic.
92  PrecalculatedCircuits precalculatedCircuits(1024);
93  vector<vector<CircuitRecord>> initialCircuits = generateShortestCircuits(precalculatedCircuits);
94  if (mFeasible == false)
95  throw NoFeasibleSolutionExists(caller(), "The problem was proved to be infeasible!");
96 
97  double initializationTime = duration_cast<duration<double>>(high_resolution_clock::now()-startTime).count();
99  clog<<endl<<"Initial phase: "<<initializationTime<<" s"<<endl;
100 
101  #ifdef GUROBI_SOLVER
103  #endif
104 
105  vector<thread> threads;
106  for (uint32_t t = 0; t < Settings::NUMBER_OF_THREADS; ++t)
107  threads.emplace_back(&ParallelHeuristicSolver::workerThread, this, t+1, initialCircuits, precalculatedCircuits);
108 
109  duration<double> timeToNextWakeUp = duration<double>(max(Settings::MAX_RUNTIME/10.0, 5.0));
110  while (mStopCondition == false) {
111  duration<double> remainingTimeToWait = expectedFinishTime-high_resolution_clock::now();
112  if (remainingTimeToWait > timeToNextWakeUp)
113  remainingTimeToWait = timeToNextWakeUp;
114 
115  this_thread::sleep_for(remainingTimeToWait);
116 
117  mRuntime = duration_cast<duration<double>>(high_resolution_clock::now()-startTime).count();
119  mStopCondition = true;
120  } else {
121  try {
122  if (Settings::VERBOSE)
124 
125  generatePromisingTuples(0u); // threadId of the control thread is 0
126  } catch (exception& e) {
127  mKB.addErrorMessage(e.what());
128  mStopCondition = true;
129  }
130  }
131  }
132 
133  // do stuff - util wait condition is satisfied...
134  for (thread& th : threads)
135  th.join();
136 
137  // Get the final heuristic runtime.
138  mRuntime = duration_cast<duration<double>>(high_resolution_clock::now()-startTime).count();
139  writePerformanceRecordToLogFile(initializationTime, mRuntime);
140  if (Settings::VERBOSE)
141  cout<<"Total time: "<<mRuntime<<" s"<<endl;
142 
143  const vector<string>& msgs = mKB.errorMessages();
144  if (!msgs.empty()) {
145  string bigMessage = "\n";
146  for (uint32_t m = 0; m < msgs.size(); ++m) {
147  bigMessage += msgs[m];
148  if (m+1 < msgs.size())
149  bigMessage += "\n\n";
150  }
151 
152  throw SolverException(caller(), bigMessage);
153  }
154 }
155 
156 void ParallelHeuristicSolver::workerThread(uint32_t threadId, vector<vector<CircuitRecord>> initialCircuits, PrecalculatedCircuits precalculatedCircuits) {
157  try {
158  TabuList tabuList(mTabuListSize);
159  const uint32_t numOfTuplesBatch = 50u;
160 
161  while (!mStopCondition) {
162  /* INITIAL PHASE - FEASIBILITY PROBLEM */
163  CircuitTuple t;
164  PartialSolution ps;
165  OptimalTiming timing;
166  vector<vector<Location*>> fixed;
167  Algo selAlgo = LP_INITIAL_PROBLEM;
168  try {
169  // A fresh tuple to solve.
170  t = mKB.getTuple();
171  // Initialize the partial solution suitable for the LP solver.
172  for (const CircuitRecord& cr : t.tuple)
173  ps.locs.push_back(cr.hc.circuit);
174  fixed = algo.initializePartialSolution(ps);
175  // Solve the LP problem to optimality with the collision resolution.
176  timing = algo.solvePartialProblem(ps, t, selAlgo);
177  } catch (EmptySolutionPool& e) {
178  addRandomTuplesToKB(threadId, numOfTuplesBatch, initialCircuits, precalculatedCircuits);
179  continue;
180  } catch (NoFeasibleSolutionExists& e) {
181  // Try another tuple. This tuple is infeasible probably...
182  continue;
183  }
184 
185  /* FINAL PHASE - LOCAL OPTIMIZATION OF THE PROBLEM */
186  selAlgo = POWER_MODE_HEURISTIC;
187  PartialSolution prevPartSol = ps;
188  OptimalTiming prevOptTiming = timing;
189  bool readNextTuple = false, solutionChanged = false;
190  const uint32_t lpLength = Settings::MIN_ITERS_PER_TUPLE;
191  constexpr uint32_t locHeurLength = 3, pwrmHeurLength = 5;
192  uint32_t numberOfIter = 0, infeasibilityConsecutiveCounter = 0;
193  double energyDiffEstimation = 0.0, bestCriterion = timing.totalEnergy;
194  MovingAverage<double> imprLocChg(locHeurLength), imprPwrmChg(pwrmHeurLength), imprLP(lpLength);
195  do {
196  switch (selAlgo) {
197  case POWER_MODE_HEURISTIC:
198  energyDiffEstimation = algo.heuristicPowerModeSelection(timing, ps, tabuList, solutionChanged);
199  break;
200  case LOCATION_CHANGE_HEURISTIC:
201  energyDiffEstimation = algo.heuristicLocationChanges(timing, ps, fixed, solutionChanged);
202  break;
203  case PATH_CHANGE:
204  changeRobotPaths(threadId, t, ps, fixed);
205  fixed = algo.initializePartialSolution(ps);
206  imprLocChg.clear(); imprPwrmChg.clear();
207  default:
208  break;
209  }
210 
211  bool solutionInfeasible = false;
212  double relativeImprovement = 0.0, heurRelEstError = 0.0;
213  try {
214  double previousCriterion = timing.totalEnergy;
215  timing = algo.solvePartialProblem(ps, t, selAlgo);
216  bestCriterion = min(bestCriterion, timing.totalEnergy);
217  double energyDiffExact = previousCriterion-timing.totalEnergy;
218  relativeImprovement = max(bestCriterion-timing.totalEnergy, 0.0)/(timing.totalEnergy+F64_EPS);
219  prevPartSol = ps; prevOptTiming = timing;
220  imprLP.addValue(relativeImprovement);
221 
222  if (abs(energyDiffExact) > F64_EPS)
223  heurRelEstError = abs(energyDiffEstimation-energyDiffExact)/abs(energyDiffExact);
224 
225  infeasibilityConsecutiveCounter = 0u;
226  readNextTuple = (imprLP.filled() && imprLP.average() < CRITERION_RTOL);
227  } catch (NoFeasibleSolutionExists& e) {
228  // The solution was made infeasible by heuristic changes, restore the previous solution and switch to the next heuristic.
229  solutionInfeasible = true;
230  ps = prevPartSol; timing = prevOptTiming;
231  if (infeasibilityConsecutiveCounter >= 4)
232  readNextTuple = true;
233 
234  ++infeasibilityConsecutiveCounter;
235  }
236 
237  switch (selAlgo) {
238  case POWER_MODE_HEURISTIC:
239  mKB.recordPwrmHeurRelErr(heurRelEstError);
240  imprPwrmChg.addValue(relativeImprovement);
241  if ((imprPwrmChg.filled() && imprPwrmChg.average() < CRITERION_RTOL) || !solutionChanged || solutionInfeasible)
242  selAlgo = LOCATION_CHANGE_HEURISTIC;
243  break;
244  case LOCATION_CHANGE_HEURISTIC:
245  mKB.recordLocHeurRelErr(heurRelEstError);
246  imprLocChg.addValue(relativeImprovement);
247  if ((imprLocChg.filled() && imprLocChg.average() < CRITERION_RTOL) || !solutionChanged || solutionInfeasible)
248  selAlgo = PATH_CHANGE;
249  break;
250  case PATH_CHANGE:
251  selAlgo = POWER_MODE_HEURISTIC;
252  default:
253  break;
254  }
255 
256  ++numberOfIter;
257 
258  } while (!readNextTuple && !mStopCondition);
259 
260  mKB.recordNumberOfItersPerTuple(numberOfIter);
261  }
262  } catch (exception& e) {
264  }
265 }
266 
268  uint32_t id = 0;
269  vector<StaticActivity*> idToActivity;
270  map<StaticActivity*, uint32_t> ident;
271  StaticActivity *home = nullptr;
272 
273  for (Activity* a : r->activities()) {
274  StaticActivity *sa = dynamic_cast<StaticActivity*>(a);
275  if (sa != nullptr) {
276  setValue(ident, sa, id++, caller());
277  idToActivity.push_back(sa);
278 
279  if (sa->lastInCycle())
280  home = sa;
281  }
282  }
283 
284  assert(home != nullptr && "Invalid checking of the correctness of instances!");
285 
286  uint32_t startId = id++, endId = getValue(ident, home, caller());
287  idToActivity.push_back(home);
288 
289  vector<Edge> graphEdges;
290  for (Activity *s : home->successors()) {
291  DynamicActivity *da = dynamic_cast<DynamicActivity*>(s);
292  assert(da != nullptr && "Logic error, the successor of the static activity must be a dynamic activity!");
293  if (da != nullptr) {
294  StaticActivity *to = da->successor();
295  graphEdges.push_back({home, to, startId, getValue(ident, to, caller()), home->minAbsDuration()+da->minAbsDuration()});
296  }
297  }
298 
299  for (Activity* a : r->activities()) {
300  DynamicActivity *da = dynamic_cast<DynamicActivity*>(a);
301  if (da != nullptr && da->predecessor() != home) {
302  StaticActivity *from = da->predecessor(), *to = da->successor();
303  graphEdges.push_back({from, to, getValue(ident, from, caller()), getValue(ident, to, caller()), from->minAbsDuration()+da->minAbsDuration()});
304  }
305  }
306 
307  vector<vector<Edge>> idToSucs(idToActivity.size());
308  for (const Edge& e : graphEdges) {
309  assert(e.i < idToActivity.size() && "Invalid generation of identifications!");
310  idToSucs[e.i].push_back(e);
311  }
312 
313  return {graphEdges, startId, endId, idToActivity, idToSucs};
314 }
315 
317  size_t matrixSize = g.idToActivity.size();
318  DistanceMatrix<double> mat(matrixSize, vector<double>(matrixSize, F64_INF));
319  for (const Edge& e : g.edges) {
320  assert(e.i < matrixSize && e.j < matrixSize && "Invalid creation of the graph!");
321  mat[e.i][e.j] = e.length;
322  }
323 
324  for (uint32_t i = 0; i < matrixSize; ++i)
325  mat[i][i] = 0.0;
326 
327  return mat;
328 }
329 
330 vector<CircuitRecord> ParallelHeuristicSolver::generateRandomAlternatives(const uint32_t& threadId, const Graph& g, const DistanceMatrix<double>& m) {
331 
332  bool allFound = false;
333  set<vector<uint32_t>> forbiddenPaths;
334  default_random_engine threadGenerator(rd());
335  vector<CircuitRecord> shortestAlternatives;
336  double cycleTime = mLine.productionCycleTime();
338  const uint32_t maxCircuits = Settings::MAX_ALTERNATIVES;
339  uint64_t numOfNodes = g.idToSuccessors.size(), numOfSol = 0;
340 
341  while (allFound != true && numOfSol < maxCircuits && mFeasible) {
342 
343  set<uint32_t> visited;
344  vector<double> edgeDistances;
345  uint32_t currentNode = g.startIdent;
346  vector<StaticActivity*> path = { home };
347  vector<uint32_t> nodePath = { currentNode };
348 
349  while ((currentNode != g.endIdent || nodePath.size() != numOfNodes) && allFound == false) {
350 
351  bool backtrack = false;
352 
353  // Calculate the current length of the partial path.
354  double pathLength = accumulate(edgeDistances.cbegin(), edgeDistances.cend(), 0.0);
355 
356  // Check the reachability from currentNode to unvisited nodes.
357  for (uint32_t n = 0; n < numOfNodes && !backtrack; ++n) {
358  if (visited.count(n) == 0 && pathLength+m[currentNode][n]+m[n][g.endIdent] > cycleTime)
359  backtrack = true;
360  }
361 
362  if (backtrack == false) {
363  // Find eligible edges from the current node to successor nodes.
364  vector<Edge> feasEdges;
365  for (const Edge& e : g.idToSuccessors[currentNode]) {
366  nodePath.push_back(e.j);
367 
368  if (visited.count(e.j) == 0 && forbiddenPaths.count(nodePath) == 0)
369  feasEdges.push_back(e);
370 
371  nodePath.pop_back();
372  }
373 
374  if (!feasEdges.empty()) {
375  // It selects a random edge to move.
376  uniform_int_distribution<uint32_t> edgeSelection(0, feasEdges.size()-1);
377  const Edge& edge = feasEdges[edgeSelection(threadGenerator)];
378  path.push_back(edge.t); nodePath.push_back(edge.j);
379  edgeDistances.push_back(edge.length);
380  visited.insert(edge.i); currentNode = edge.j;
381  } else {
382  // Cannot expand the node -> backtrack.
383  backtrack = true;
384  }
385  }
386 
387  // Backtracking - add a new forbidden path and remove the last node.
388  if (backtrack == true) {
389  assert(path.size() == nodePath.size() && "Expected the same size! A bug in the algorithm.");
390  if (nodePath.size() >= 2) {
391  const vector<uint32_t>& prefixPath = nodePath;
392  auto p = forbiddenPaths.emplace(nodePath.cbegin(), nodePath.cend());
393  set<vector<uint32_t>>::iterator it = p.first, eraseStart = ++it, eraseStop = eraseStart;
394  while (it != forbiddenPaths.end()) {
395  const vector<uint32_t>& nextPath = *it;
396  if (prefixPath.size() < nextPath.size()) {
397  const auto& mitp = mismatch(prefixPath.cbegin(), prefixPath.cend(), nextPath.cbegin());
398  if (mitp.first == prefixPath.end())
399  eraseStop = ++it;
400  else
401  break;
402  } else {
403  break;
404  }
405  }
406  forbiddenPaths.erase(eraseStart, eraseStop);
407  visited.erase(nodePath.back());
408  path.pop_back(); nodePath.pop_back(); edgeDistances.pop_back();
409  currentNode = nodePath.back();
410  } else {
411  // All hamiltonian paths found...
412  allFound = true;
413  }
414  }
415  }
416 
417  // Save the found hamiltonian path...
418  if (currentNode == g.endIdent) {
419  HamiltonianCircuit<Location> cl = getShortestCircuit(threadId, path, {}); // closed path - penalty free
420  if (cl.minLength >= 0.0 && cl.minLength <= cycleTime) {
421  ShortestCircuit sc;
422  sc.circuit = move(path);
423  shortestAlternatives.emplace_back(move(sc), move(cl));
424  ++numOfSol;
425  }
426 
427  forbiddenPaths.insert(nodePath);
428  }
429  }
430 
431  return shortestAlternatives;
432 }
433 
434 HamiltonianCircuit<Location> ParallelHeuristicSolver::getShortestCircuit(const uint32_t& threadId, const vector<StaticActivity*>& circuit, const vector<Location*>& fixed, bool writeCircuit) {
435 
436  double bestDist = F64_INF;
437  vector<vector<int64_t>> bestPreds;
438  vector<vector<Location*>> bestLocs;
439  uint32_t closedPathSize = circuit.size();
440  double cycleTime = mLine.productionCycleTime();
441  const double penaltyForViolation = cycleTime+1.0;
442 
443  assert(circuit.size() >= 2 && "Unexpected empty path! Invalid algorithm!");
444 
445  vector<StaticActivity*>::const_iterator bestStartIter = min_element(circuit.cbegin(), circuit.cend(),
446  [](StaticActivity* sa1, StaticActivity* sa2) {
447  return sa1->locations().size() < sa2->locations().size();
448  }
449  );
450 
451  vector<StaticActivity*> closedPath;
452  closedPath.reserve(closedPathSize);
453  StaticActivity *startAndEndAct = *bestStartIter;
454  if (startAndEndAct != circuit.front()) {
455  closedPath.insert(closedPath.end(), bestStartIter, circuit.cend());
456  closedPath.insert(closedPath.end(), circuit.cbegin()+1, bestStartIter+1);
457  } else {
458  closedPath.insert(closedPath.end(), circuit.cbegin(), circuit.cend());
459  }
460 
461  assert(closedPath.size() == circuit.size() && "Invalid rotation of the circuit, a bug in the algorithm!");
462 
463  for (Location* startAndEnd : startAndEndAct->locations()) {
464 
465  mDist[threadId][0][0] = 0.0;
466  mPreds[threadId][0][0] = -1;
467  mLocs[threadId][0][0] = startAndEnd;
468 
469  for (uint32_t i = 0; i+1 < closedPathSize; ++i) {
470 
471  uint32_t fromSize = 1u, toSize = 1u;
472  Location **from = &startAndEnd, **to = &startAndEnd;
473  double staticActDur = closedPath[i]->minAbsDuration();
474  if (i > 0) {
475  fromSize = closedPath[i]->mLocations.size();
476  from = closedPath[i]->mLocations.data();
477  }
478 
479  if (i+2 < closedPathSize) {
480  toSize = closedPath[i+1]->mLocations.size();
481  to = closedPath[i+1]->mLocations.data();
482  }
483 
484  pair<Location*, Location*> edge;
485  fill(mDist[threadId][i+1].begin(), mDist[threadId][i+1].begin()+toSize, F64_INF);
486 
487  for (uint32_t f = 0; f < fromSize; ++f) {
488  edge.first = from[f];
489  double distance = mDist[threadId][i][f]+staticActDur;
490  const auto& sit = find_if(fixed.cbegin(), fixed.cend(), [&](Location *l) { return l->parent() == edge.first->parent(); });
491  if (sit != fixed.cend() && *sit != edge.first)
492  distance += penaltyForViolation;
493 
494  for (uint32_t t = 0; t < toSize; ++t) {
495  edge.second = to[t];
496  const auto& mit = mMapping.locationsToMovement.find(edge);
497  if (mit != mMapping.locationsToMovement.cend()) {
498  Movement *mv = mit->second;
499  double newDist = distance+mv->minDuration();
500  if (newDist < mDist[threadId][i+1][t]) {
501  mDist[threadId][i+1][t] = newDist;
502  mLocs[threadId][i+1][t] = to[t];
503  if (writeCircuit == true)
504  mPreds[threadId][i+1][t] = f;
505  }
506  }
507  }
508  }
509  }
510 
511  double circuitLength = mDist[threadId][closedPathSize-1][0];
512  if (circuitLength < bestDist) {
513  bestDist = circuitLength;
514  if (writeCircuit == true) {
515  bestPreds = mPreds[threadId];
516  bestLocs = mLocs[threadId];
517  }
518  }
519  }
520 
521  HamiltonianCircuit<Location> shortestCircuit;
522  shortestCircuit.minLength = bestDist;
523 
524  if (writeCircuit == true && bestDist < F64_INF) {
525  // The shortest circuit found...
526  vector<Location*> locs;
527  int64_t currentPred = 0;
528  locs.reserve(closedPathSize);
529  for (int64_t i = closedPathSize-1; i >= 0; --i) {
530  locs.push_back(bestLocs[i][currentPred]);
531  currentPred = bestPreds[i][currentPred];
532  assert((currentPred != -1 || i == 0) && "Cannot reconstruct the path! A bug in the algorithm!");
533  }
534 
535  assert(locs.size() == closedPathSize && "Invalid reconstruction of the path! Please report a bug.");
536 
537  reverse(locs.begin(), locs.end());
538  shortestCircuit.circuit = move(locs);
539  }
540 
541  // It can return a potentially infeasible solution, i.e. a solution with the cycle time greater than the production cycle time.
542  return shortestCircuit;
543 }
544 
545 vector<vector<CircuitRecord>> ParallelHeuristicSolver::generateShortestCircuits(PrecalculatedCircuits& precalculatedCircuits) {
546  const vector<Robot*>& robots = mLine.robots();
547  uint32_t numberOfRobots = robots.size();
548  double cycleTime = mLine.productionCycleTime();
549  constexpr double tuplesPer1000s = 1000000.0; // 1 ms per tuple processing
550  const uint32_t maxAlternativesPerRobot = 4*ceil(pow(tuplesPer1000s, 1.0/numberOfRobots));
551  vector<vector<CircuitRecord>> shortestCircuits(numberOfRobots);
552 
553  uint32_t id = 1u;
554  #pragma omp parallel num_threads(Settings::NUMBER_OF_THREADS)
555  {
556  uint32_t threadId;
557  #pragma omp critical
558  threadId = id++;
559 
560  #pragma omp for schedule(dynamic)
561  for (uint32_t r = 0; r < numberOfRobots; ++r) {
562  Graph robotGraph = constructMinimalDurationGraph(robots[r]);
563  DistanceMatrix<double> initialMatrix = createInitialDistanceMatrix(robotGraph);
564  DistanceMatrix<double> minDistMatrix = floyd(initialMatrix);
565  vector<CircuitRecord> allCircuits = generateRandomAlternatives(threadId, robotGraph, minDistMatrix);
566  // From the potentially fastest robot cycles to the slowest ones.
567  sort(allCircuits.begin(), allCircuits.end());
568  // Select the suitable subset of alternatives.
569  if (!allCircuits.empty()) {
570  shortestCircuits[r].reserve(maxAlternativesPerRobot+2);
571  if (allCircuits.size() > maxAlternativesPerRobot+2) {
572  // select shortest alternatives
573  double minCycleTime = allCircuits.front().hc.minLength;
574  double shortestAmount = 0.2+0.6*(penaltyFunction(minCycleTime, cycleTime)/10.0);
575  uint32_t numOfShortest = ceil(maxAlternativesPerRobot*shortestAmount);
576  shortestCircuits[r].insert(shortestCircuits[r].end(), allCircuits.cbegin(), allCircuits.cbegin()+numOfShortest);
577 
578  // select random alternatives
579  default_random_engine threadGenerator(rd());
580  uint32_t numOfRandom = ceil((1.0-shortestAmount)*maxAlternativesPerRobot);
581  shuffle(allCircuits.begin()+numOfShortest, allCircuits.end(), threadGenerator);
582  shortestCircuits[r].insert(shortestCircuits[r].end(), allCircuits.begin()+numOfShortest, allCircuits.begin()+numOfShortest+numOfRandom);
583 
584  // keep it sorted
585  sort(shortestCircuits[r].begin(), shortestCircuits[r].end());
586  } else {
587  // all alternatives are added
588  shortestCircuits[r] = allCircuits;
589  }
590  } else {
591  mFeasible = false;
592  }
593 
594  for (CircuitRecord& cr : shortestCircuits[r]) {
595  cr.hc = getShortestCircuit(threadId, cr.sc.circuit, {});
596  vector<Location*> fixed;
597  for (uint32_t l = 0; l+1 < cr.hc.circuit.size(); ++l) {
598  Location *loc = cr.hc.circuit[l];
599  if (mMapping.sptComp.count(loc->parent()) > 0)
600  fixed.push_back(loc);
601  }
602 
603  cr.sc.fixedLocations = move(fixed);
604 
605  #pragma omp critical
606  precalculatedCircuits.emplace(cr.sc, cr.hc);
607  }
608  }
609  }
610 
611  if (mFeasible) {
613  sort(mSortedSptCmp.begin(), mSortedSptCmp.end(),
614  [&](const SpatialCmpTuple& t1, const SpatialCmpTuple& t2) {
615  uint32_t r11 = get<0>(t1), r12 = get<1>(t1);
616  uint32_t r21 = get<0>(t2), r22 = get<1>(t2);
617  double ct11 = shortestCircuits[r11].front().hc.minLength, ct12 = shortestCircuits[r12].front().hc.minLength;
618  double ct21 = shortestCircuits[r21].front().hc.minLength, ct22 = shortestCircuits[r22].front().hc.minLength;
619  double penalty1 = penaltyFunction(ct11, cycleTime)+penaltyFunction(ct12, cycleTime);
620  double penalty2 = penaltyFunction(ct21, cycleTime)+penaltyFunction(ct22, cycleTime);
621  return penalty1 > penalty2; // First the tuples with the highest penalty.
622  }
623  );
624  }
625 
626  return shortestCircuits;
627 }
628 
629 double ParallelHeuristicSolver::penaltyFunction(const double& minCycle, const double& demandedCycleTime) {
630  double penalty;
631  // Penalization function: f(x) = k1/(k2-x) if 0 <= x <= 1 where 'x' is the normalized cycle time
632  constexpr double k1 = 10.0/99.0, k2 = 100.0/99.0; // f(0) = 0.1, f(1) = 10.0, 0.1 <= f(x) <= 10.0
633  if (minCycle <= demandedCycleTime)
634  penalty = k1/(k2-(minCycle/demandedCycleTime));
635  else
636  penalty = 30.0; // Extra penalty for guaranteed infeasibility, indication value.
637 
638  return penalty;
639 }
640 
641 uint32_t ParallelHeuristicSolver::selectAlternative(const vector<CircuitRecord>& alternatives) const {
642 
643  if (alternatives.empty())
644  throw InvalidArgument(caller(), "No alternatives to select!");
645 
646  default_random_engine threadGenerator(rd());
647  double cycleTime = mLine.productionCycleTime();
648  uint32_t selIdx = 0, numberOfAlternatives = alternatives.size();
649  const HamiltonianCircuit<Location>& ha = alternatives.front().hc, & hb = alternatives.back().hc;
650  const double a = ha.minLength/cycleTime, b = hb.minLength/cycleTime, minDiff = TIME_TOL/cycleTime;
651 
652  if (b-a >= minDiff) {
653  // scaled distribution function (t in <0,1>): y(t) = n*(2-k*e^{l*t})
654  constexpr double l = 2.0;
655  const double k = 2.0/exp(l);
656  const double sa = 2*a-(k/l)*exp(l*a);
657  const double sb = 2*b-(k/l)*exp(l*b);
658  const double n = 1/(sb-sa); // integrate_{a}^{b} y(t) dt = 1
659 
660  // ,,Wheel of fortune" - select the area 's', i.e. 's' = integrate_{a}^{x} y(t) dt.
661  uniform_real_distribution<double> area(0.0, 1.0);
662  const double s = area(threadGenerator);
663 
664  /*
665  * Solve previously mentioned 'x' by the Newton method.
666  * Equation to solve: c = 2*x-(k/l)*e^{l*x}
667  * Update formula: x_{i+1} = x_i - ((k/l)*e^{l*x}-2*x+c)/(k*e^{l*x}-2)
668  * Initial estimate: x_0 = a+s*(b-a)
669  */
670  uint32_t iter = 0, maxIter = 10;
671  double c = s/n+sa, x0 = a+s*(b-a), x = x0;
672 
673  do {
674  double diff = k*exp(l*x)-2.0;
675  if (abs(diff) > 10.0*F64_EPS)
676  x = x-((k/l)*exp(l*x)-2*x+c)/diff;
677  else
678  break;
679  } while (x >= a && x <= b && ++iter < maxIter);
680 
681  if (x < a || x > b) {
682  // Newton method diverged, select initial estimate.
683  x = x0;
684  }
685 
686  // With respect to 'x' value calculate ,,float index'' of the circuit to select.
687  const double selIdxFloat = (x-a)/(b-a);
688  // "float index" -> "integer index"
689  selIdx = static_cast<uint32_t>(selIdxFloat*numberOfAlternatives);
690  selIdx = (selIdx < numberOfAlternatives) ? selIdx : numberOfAlternatives-1;
691  } else {
692  // Too small cycle time differences or only one alternative.
693  uniform_int_distribution<uint32_t> selAlt(0, numberOfAlternatives-1);
694  selIdx = selAlt(threadGenerator);
695  }
696 
697  return selIdx;
698 }
699 
700 double ParallelHeuristicSolver::calculateNewCycleTime(const uint32_t& threadId, const double& currentCycleTime, vector<StaticActivity*>& alternative,
701  vector<Location*>& fixed, Location *toFix, PrecalculatedCircuits& precalculatedCircuits) {
702 
703  const uint64_t& idx = find_if(fixed.cbegin(), fixed.cend(), [&toFix](Location* l) { return toFix->parent() == l->parent(); })-fixed.cbegin();
704  if (idx < fixed.size()) {
705  Location *previous = fixed[idx];
706  // Check whether the location to be fixed is not already fixed.
707  if (previous != toFix) {
708  // Create the set of newly fixed locations.
709  ShortestCircuit sc;
710  fixed[idx] = toFix;
711  sc.circuit = move(alternative);
712  sc.fixedLocations = move(fixed);
713  // Check whether the cycle time was already calculated for the given alternative and fixed locations.
714  const auto& sit = precalculatedCircuits.find(sc);
715  if (sit != precalculatedCircuits.cend()) {
716  // Yes, it was already calculated, get it from the hash map...
717  fixed = move(sc.fixedLocations); fixed[idx] = previous;
718  alternative = move(sc.circuit);
719  return (sit->second).minLength;
720  } else {
721  // Copy values back as they need to be preserved.
722  fixed = sc.fixedLocations; fixed[idx] = previous;
723  alternative = sc.circuit;
724  // Unfortunately, it is not ,,cached''. Insert a new record.
725  const auto& p = precalculatedCircuits.emplace(move(sc), getShortestCircuit(threadId, sc.circuit, sc.fixedLocations, false));
726  // Return the shortest possible circuit length.
727  return (*p.first).second.minLength;
728  }
729  } else {
730  // Since the location to be fixed has already been fixed, the cycle time will not change.
731  return currentCycleTime;
732  }
733  } else {
734  throw InvalidArgument(caller(), "Invalid toFix argument, it should belong to the related robot!");
735  }
736 }
737 
738 void ParallelHeuristicSolver::addRandomTuplesToKB(const uint32_t& threadId, const uint32_t& numOfTuples,
739  vector<vector<CircuitRecord>>& initialCircuits, PrecalculatedCircuits& precalculatedCircuits) {
740 
741  high_resolution_clock::time_point start = high_resolution_clock::now();
742 
743  default_random_engine threadGenerator(rd());
744  uint32_t numberOfAddedTuples = 0, iter = 0;
745  double cycleTime = mLine.productionCycleTime();
746  uint32_t numberOfRobots = mLine.robots().size();
747  const uint32_t maxTuples = numOfTuples, maxIter = 10*maxTuples;
748 
749  while (numberOfAddedTuples < maxTuples && iter < maxIter) {
750 
751  // For each robot just one vector item.
752  vector<double> currentLengths(numberOfRobots);
753  vector<CircuitRecord*> selectedCircuits(numberOfRobots);
754  vector<vector<Location*>> currentFixedLocations(numberOfRobots);
755 
756  // Select robot circuits
757  for (uint32_t r = 0; r < numberOfRobots; ++r) {
758  uint32_t selIdx = selectAlternative(initialCircuits[r]);
759  CircuitRecord *cr = &initialCircuits[r][selIdx];
760  currentLengths[r] = cr->hc.minLength;
761  selectedCircuits[r] = cr;
762  currentFixedLocations[r] = cr->sc.fixedLocations;
763  }
764 
765  bool feasibleTuple = true;
766  // Try to resolve spatial compatibility for the selected circuits.
767  for (auto it = mSortedSptCmp.cbegin(); it != mSortedSptCmp.cend() && feasibleTuple; ++it) {
768 
769  // Extract information about the spatial compatibility pair.
770  uint32_t r1 = get<0>(*it), r2 = get<1>(*it); // related robots
771  const vector<pair<Location*, Location*>>& spatialCompatibility = get<4>(*it); // compatible location pairs.
772  assert(!spatialCompatibility.empty() && "Invalid initialization of spatialCompatibility!");
773 
774  // Evaluate suitability of each spatial compatibility pair in terms of penalty value.
775  double maxPenalty = 0.0;
776  vector<double> preferences;
777  vector<pair<double, double>> lengths;
778  lengths.reserve(spatialCompatibility.size());
779  preferences.reserve(spatialCompatibility.size());
780 
781  for (const pair<Location*, Location*>& p : spatialCompatibility) {
782  uint32_t i = 0;
783  double penalty = 0.0;
784  array<double, 2> newLengths;
785  vector<pair<uint32_t, Location*>> vec = { {r1, p.first}, {r2, p.second}};
786  for (const pair<uint32_t, Location*>& e : vec) {
787  uint32_t robotId = e.first;
788  Location *toFix = e.second;
789  double curLength = currentLengths[robotId];
790  // It calculates the cycle time for a newly fixed location 'toFix'.
791  double newMinCycleTime = calculateNewCycleTime(threadId, curLength, selectedCircuits[robotId]->sc.circuit,
792  currentFixedLocations[robotId], toFix, precalculatedCircuits);
793  penalty += penaltyFunction(newMinCycleTime, cycleTime);
794  newLengths[i++] = newMinCycleTime;
795  }
796 
797  maxPenalty = max(maxPenalty, penalty);
798  preferences.push_back(penalty); // penalty will be transformed to the preference later
799  lengths.emplace_back(newLengths[0], newLengths[1]);
800  }
801 
802  // Transform penalties to selection preferences.
803  double sumOfPreferences = 0.0;
804  vector<double>::iterator prefArray = preferences.begin();
805  for (uint32_t p = 0; p < preferences.size(); ++p) {
806  prefArray[p] = maxPenalty-prefArray[p];
807  sumOfPreferences += prefArray[p];
808  }
809 
810  // Wheel of fortune - wheelValue decides which pair will be used to resolve compatibility.
811  uniform_real_distribution<double> wheel(0.0, sumOfPreferences);
812  double wheelValue = wheel(threadGenerator), accumulatedValue = 0.0;
813 
814  // Find the index to the ,,randomly" selected pair.
815  uint32_t selIdx = 0;
816  for (uint32_t p = 0; p < preferences.size(); ++p) {
817  double currentPref = prefArray[p];
818  if (accumulatedValue <= wheelValue && wheelValue <= accumulatedValue+currentPref) {
819  selIdx = p;
820  break;
821  }
822 
823  accumulatedValue += currentPref;
824  }
825 
826  if (maxPenalty-prefArray[selIdx] <= 20.0) {
827  // The tuple is still feasible, i.e. only feasible circuits were selected as a spatial compatibility resolution.
828  // Update the minimal lengths of the circuits going through the newly fixed locations.
829  currentLengths[r1] = lengths[selIdx].first;
830  currentLengths[r2] = lengths[selIdx].second;
831  // Update the currently fixed locations with respect to the selected pair.
832  const pair<Location*, Location*>& cmpPair = spatialCompatibility[selIdx];
833  Location *newlyFixed1 = cmpPair.first, *newlyFixed2 = cmpPair.second;
834  vector<Location*> *fl1 = &currentFixedLocations[r1], *fl2 = &currentFixedLocations[r2];
835  auto pred = [&](Location* l) {
836  return l->parent() == newlyFixed1->parent() || l->parent() == newlyFixed2->parent();
837  };
838 
839  replace_if(fl1->begin(), fl1->end(), pred, newlyFixed1);
840  replace_if(fl2->begin(), fl2->end(), pred, newlyFixed2);
841  } else {
842  // Penalty higher than 20.0 indicates that the selected pair is resulting in an infeasible solution.
843  feasibleTuple = false;
844  }
845  }
846 
847  if (feasibleTuple == true) {
848  CircuitTuple candidate;
849  for (uint32_t r = 0; r < numberOfRobots; ++r) {
850  const vector<StaticActivity*> alternative = selectedCircuits[r]->sc.circuit;
851  const vector<Location*>& fixed = currentFixedLocations[r];
852  auto sit = precalculatedCircuits.find(ShortestCircuit(alternative, fixed));
853  if (sit != precalculatedCircuits.cend()) {
854  if (sit->second.circuit.empty())
855  sit->second = getShortestCircuit(threadId, alternative, fixed);
856 
857  candidate.tuple.emplace_back(sit->first, sit->second);
858  } else {
859  throw SolverException(caller(), "A bug in the generation of tuples, it should be stored in the map!");
860  }
861  }
862 
863  mKB.addTuple(move(candidate));
864  ++numberOfAddedTuples;
865  }
866 
867  ++iter;
868  }
869 
870  mKB.recordAddTuplesCall(duration_cast<duration<double>>(high_resolution_clock::now()-start).count());
871 }
872 
873 void ParallelHeuristicSolver::generatePromisingTuples(const uint32_t& threadId) {
874  PrecalculatedCircuits precalculated;
875  const uint32_t numOfPromisingTuples = 10u;
876  uint32_t numberOfRobots = mLine.robots().size();
877  // vector<set<CircuitRecord>> should be used instead...
878  vector<vector<CircuitRecord>> initialCircuits(numberOfRobots);
879  list<pair<Solution, CircuitTuple>> elites = mKB.eliteSolutions();
880  for (const pair<Solution, CircuitTuple>& p : elites) {
881  const vector<CircuitRecord>& circuits = p.second.tuple;
882  assert(circuits.size() == numberOfRobots && "Invalid tuple stored in the knowledge base!");
883  for (uint32_t r = 0; r < circuits.size(); ++r) {
884  initialCircuits[r].push_back(circuits[r]);
885  precalculated.emplace(circuits[r].sc, circuits[r].hc);
886  }
887  }
888 
889  for (uint32_t r = 0; r < numberOfRobots; ++r)
890  sort(initialCircuits[r].begin(), initialCircuits[r].end());
891 
892  if (elites.size() > 1u) {
893  // Remark: Control thread id is zero.
894  addRandomTuplesToKB(threadId, numOfPromisingTuples, initialCircuits, precalculated);
895  }
896 }
897 
898 double ParallelHeuristicSolver::changeRobotPaths(uint32_t threadId, const CircuitTuple& t, PartialSolution& ps, const vector<vector<Location*>>& fixed) {
899  double minCycleTime = 0.0;
900  const vector<Robot*>& robots = mLine.robots();
901  uint32_t numberOfRobots = robots.size();
902  double cycleTime = mLine.productionCycleTime();
903  for (uint32_t r = 0; r < numberOfRobots; ++r) {
904  vector<Location*> candidatesToFix;
905  for (Activity *a : robots[r]->activities()) {
906  StaticActivity *sa = dynamic_cast<StaticActivity*>(a);
907  if (sa != nullptr && mMapping.sptComp.count(sa) == 0) {
908  for (Location* l : sa->locations())
909  candidatesToFix.push_back(l);
910  }
911  }
912 
913  shuffle(candidatesToFix.begin(), candidatesToFix.end(), default_random_engine(rd()));
914 
915  assert(r < fixed.size() && "Invalid dimensions of the input argument!");
916 
917  uint32_t idx = 0;
918  bool pathChanged = false;
919  vector<Location*> toFix = fixed[r], path = ps.locs[r];
920  const vector<StaticActivity*>& alternative = t.tuple[r].sc.circuit;
921  while (!pathChanged && idx < candidatesToFix.size()) {
922  toFix.push_back(candidatesToFix[idx]);
923  HamiltonianCircuit<Location> hc = getShortestCircuit(threadId, alternative, toFix, true);
924  if (hc.minLength <= cycleTime) {
925  path = hc.circuit;
926  minCycleTime = max(minCycleTime, hc.minLength);
927  pathChanged = true;
928  }
929  toFix.pop_back();
930  ++idx;
931  }
932 
933  ps.locs[r] = path;
934  }
935 
937 
938  return minCycleTime;
939 }
940 
941 void ParallelHeuristicSolver::printProgressInfo(const double& currentRuntime) {
942  const auto& tupleGen = mKB.infoTuplesGeneration(), &itersPerTuple = mKB.infoItersPerTuple();
943  const auto& LPInfo = mKB.infoLP(), &infoLocHeur = mKB.infoLocHeur(), &infoPwrmHeur = mKB.infoPwrmHeur();
944  clog<<string(35, '=')<<" STAT INFO "<<string(35, '=')<<endl;
945  clog<<"current runtime: "<<currentRuntime<<endl;
946  clog<<string(81, '-')<<endl;
947  clog<<"generated tuples: "<<tupleGen.first<<" ("<<tupleGen.second*1000.0<<" ms per tuple)"<<endl;
948  clog<<"percentage of processed: "<<mKB.percentageOfProcessed()<<" %"<<endl;
949  clog<<"average number of iters per tuple: "<<itersPerTuple.second<<endl;
950  clog<<string(81, '-')<<endl;
951  clog<<"Linear Programming solver: "<<LPInfo.first<<" calls ("<<LPInfo.second*1000.0<<" ms per call)"<<endl;
952  clog<<"Infeasibility rate: "<<mKB.infeasibilityRate()*100.0<<" %"<<endl;
953  clog<<"Average number of LP calls for collisions resolution: "<<mKB.averageNumberOfLPCallsForLPFix()<<endl;
954  clog<<"Average deterioration in quality after collisions resolution: "<<mKB.averageLPFixDeterioration()*100.0<<" %"<<endl;
955  clog<<"Number of feasibility breaks: "<<mKB.numberOfLPBreaks()<<endl;
956  clog<<string(81, '-')<<endl;
957  clog<<"Change location heuristic: "<<infoLocHeur.first<<" calls ("<<infoLocHeur.second*1000.0<<" ms per call)"<<endl;
958  clog<<"Average error of energy estimation: "<<mKB.averageErrOfLocHeur()*100.0<<" %"<<endl;
959  clog<<"Number of feasibility breaks: "<<mKB.numberOfLocHeurBreaks()<<endl;
960  clog<<string(81, '-')<<endl;
961  clog<<"Change power mode heuristic: "<<infoPwrmHeur.first<<" calls ("<<infoPwrmHeur.second*1000.0<<" ms per call)"<<endl;
962  clog<<"Average error of energy estimation: "<<mKB.averageErrOfPwrmHeur()*100.0<<" %"<<endl;
963  clog<<"Number of feasibility breaks: "<<mKB.numberOfPwrmHeurBreaks()<<endl;
964  clog<<string(81, '-')<<endl;
965  clog<<"Change path algorithm: "<<mKB.pathChangeCalls()<<" calls"<<endl;
966  clog<<"Number of feasibility breaks: "<<mKB.numberOfChangePathBreaks()<<endl;
967  clog<<string(34, '=')<<" END OF INFO "<<string(34, '=')<<endl;
968 }
969 
970 void ParallelHeuristicSolver::writePerformanceRecordToLogFile(const double& initializationTime, const double& finalRuntime) {
971  if (!Settings::RESULTS_DIRECTORY.empty()) {
972  string pathToPerformaceLog = Settings::RESULTS_DIRECTORY+"performance_log.csv";
973  bool exists = fileExists(pathToPerformaceLog);
974  ofstream perfLog(pathToPerformaceLog.c_str(), ofstream::app);
975  if (perfLog.good()) {
976  if (!exists) {
977  perfLog<<"initialization time [s], generated tuples, time per tuple [ms], processed tuples [%], average number of iters per tuple, ";
978  perfLog<<"number of calls of Linear Programming, time per call [ms], infeasibility rate [%], LP calls to collisions resolution, ";
979  perfLog<<"average deterioration after collisions resolution [%], feasibility breaks, number of calls of change location heuristic, ";
980  perfLog<<"time per call [ms], estimation error [%], feasibility breaks, number of calls of power mode heuristic, time per call [ms], ";
981  perfLog<<"estimation error [%], feasibility breaks, number of calls of change path, feasibility breaks, runtime [s]"<<endl;
982  }
983 
984  const auto& tupleGen = mKB.infoTuplesGeneration(), &itersPerTuple = mKB.infoItersPerTuple();
985  const auto& LPInfo = mKB.infoLP(), &infoLocHeur = mKB.infoLocHeur(), &infoPwrmHeur = mKB.infoPwrmHeur();
986  perfLog<<initializationTime<<", "<<tupleGen.first<<", "<<1000.0*tupleGen.second<<", "<<mKB.percentageOfProcessed()<<", "<<itersPerTuple.second<<", ";
987  perfLog<<LPInfo.first<<", "<<1000.0*LPInfo.second<<", "<<100.0*mKB.infeasibilityRate()<<", "<<mKB.averageNumberOfLPCallsForLPFix()<<", ";
988  perfLog<<100.0*mKB.averageLPFixDeterioration()<<", "<<mKB.numberOfLPBreaks()<<", "<<infoLocHeur.first<<", "<<1000.0*infoLocHeur.second<<", ";
989  perfLog<<100.0*mKB.averageErrOfLocHeur()<<", "<<mKB.numberOfLocHeurBreaks()<<", "<<infoPwrmHeur.first<<", "<<1000.0*infoPwrmHeur.second<<", ";
990  perfLog<<100.0*mKB.averageErrOfPwrmHeur()<<", "<<mKB.numberOfPwrmHeurBreaks()<<", "<<mKB.pathChangeCalls()<<", ";
991  perfLog<<mKB.numberOfChangePathBreaks()<<", "<<finalRuntime<<endl;
992  perfLog.close();
993  } else {
994  cerr<<"Warning: Cannot open '"<<pathToPerformaceLog<<"' file for writing!"<<endl;
995  }
996  }
997 }
998 
void initializeLocalEnvironments(int numberOfThreads)
It enables the solver to initialize all the data structures (e.g. expensive to construct) required fo...
The instance of the class corresponds to a robot movement between two coordinates.
Definition: RoboticLine.h:133
std::vector< T * > circuit
Closed path through locations or static activities, each static activity is visited just once...
uint64_t numberOfChangePathBreaks() const
Returns how many times the change of paths resulted in infeasibility.
Definition: KnowledgeBase.h:85
std::vector< SpatialCmpTuple > mSortedSptCmp
The vector of spatial compatibility pairs heuristically sorted in decreasing order of difficulty...
void addTuple(CircuitTuple &&t)
Add a tuple to the mutex protected queue of tuples.
RoboticLine mLine
Robotic cell to be optimized.
The base class incorporating common properties of robot operations and movements. ...
Definition: RoboticLine.h:68
The structure representing a solution found by an algorithm.
Definition: Solution.h:50
uint64_t numberOfLocHeurBreaks() const
Returns how many times the change locations sub-heuristic caused the infeasibility of the solution...
Definition: KnowledgeBase.h:77
void addRandomTuplesToKB(const uint32_t &threadId, const uint32_t &numOfTuples, std::vector< std::vector< CircuitRecord >> &initialCircuits, PrecalculatedCircuits &precalculatedCircuits)
It generates and adds a requested number of tuples to the KnowledgeBase.
ParallelHeuristicSolver(const RoboticLine &l, const PrecalculatedMapping &m)
It initializes the heuristic, i.e. preallocates some arrays, calculates the length of the tabu list...
uint32_t MAX_ALTERNATIVES
The maximal number of alternative orders generated for each robot.
Definition: Settings.cpp:43
Instance of this class includes all the data structures and methods related to a robot.
Definition: RoboticLine.h:432
std::vector< StaticActivity * > idToActivity
Node index to static activity, note that dynamic activities are already incorporated in the lengths o...
std::vector< std::vector< Location * > > locs
Selected locations for each robot, time ordered.
std::string exceptionToString(const std::exception &e, uint32_t level=0)
The recursive method creates the formatted error message for the given exception and their nested sub...
Definition: Exceptions.cpp:49
std::unordered_map< ShortestCircuit, HamiltonianCircuit< Location >> PrecalculatedCircuits
Mapping of ShortestCircuit to the related shortest closed path through locations. ...
std::vector< std::vector< Location * > > initializePartialSolution(PartialSolution &ps) const
It appends the fixed movements (implied from fixed locations), and optionally fastest power saving mo...
std::vector< std::vector< CircuitRecord > > generateShortestCircuits(PrecalculatedCircuits &precalculatedCircuits)
It finds random alternatives for each robot and calculates their shortest closed paths through locati...
void workerThread(uint32_t threadId, std::vector< std::vector< CircuitRecord >> initialCircuits, PrecalculatedCircuits precalculatedCircuits)
The worker thread generates initial tuples, and optimizes them by local modifications proposed by sub...
double changeRobotPaths(uint32_t threadId, const CircuitTuple &t, PartialSolution &ps, const std::vector< std::vector< Location * >> &fixed)
It randomly modifies the robot paths to enable the heuristic to search otherwise unreachable parts of...
Collection of movements between two static activities.
Definition: RoboticLine.h:261
double runTime
Run time (seconds) of the optimization algorithm required for obtaining this solution.
Definition: Solution.h:61
std::atomic< bool > mFeasible
Used to stop other OpenMP threads if the instance infeasibility is detected by a thread.
CircuitTuple getTuple()
Returns a partially fixed solution called tuple.
PrecalculatedMapping mMapping
Fast searching in the data structure of the robotic cell.
Shortest closed path through locations.
static double penaltyFunction(const double &minCycle, const double &demandedCycleTime)
The function expressing the risk of exceeding the robot cycle time depending on its lower estimation...
Hamiltonian circuit through static activities and the fixed locations.
std::list< std::pair< Solution, CircuitTuple > > eliteSolutions()
Returns a list of elite solutions and their tuples from which they have been calculated.
STL namespace.
Solution solve()
It optimizes the robotic cell and returns the best found solution.
std::vector< CircuitRecord > generateRandomAlternatives(const uint32_t &threadId, const Graph &g, const DistanceMatrix< double > &m)
Generates some random alternatives, i.e. candidates for a feasible order, from a distance graph and d...
uint64_t numberOfLPBreaks() const
Returns how many times it was not possible to obtain an initial solution, i.e. timing.
Definition: KnowledgeBase.h:73
std::pair< uint64_t, double > infoLocHeur()
Returns the total number of HeuristicAlgorithms::heuristicLocationChanges calls and average runtime f...
A general exception of the program.
Definition: Exceptions.h:58
std::vector< CircuitRecord > tuple
Tuple, i.e. a partially fixed problem.
double percentageOfProcessed()
Percentage of the processed tuples.
uint32_t selectAlternative(const std::vector< CircuitRecord > &alternatives) const
It randomly selects a robot alternative according to a distribution function that slightly prefers th...
bool filled() const
Returns whether the average is calculated from the full length.
ShortestCircuit sc
Selected alternative and fixed locations.
std::vector< SpatialCmpTuple > sptCompVec
Enables to fast iterate through spatial compatibility elements.
void controlThread()
Generates various alternatives, launches worker threads, prints progress info, joins the workers...
A graph edge in the distance graph.
std::vector< Edge > edges
Graph edges.
std::map< StaticActivity *, std::map< StaticActivity *, std::vector< std::pair< Location *, Location * > > > > sptComp
Two-step mapping taking two static activities and returning a list of compatible pairs of locations...
constexpr double CRITERION_RTOL
A maximal relative tolerance of the criterion error imposed by the piece-wise linearization of energy...
std::atomic< bool > mStopCondition
The control thread sets this flag to true to stop all the worker threads.
double heuristicPowerModeSelection(const OptimalTiming &ot, PartialSolution &ps, TabuList &tabuList, bool &solutionChanged) const
The sub-heuristic tries to switch from one power saving mode to another one for a location in order t...
A short-term memory, containing a list of forbidden moves, that mitigates the risk of cycling...
bool fileExists(const std::string &pathToFile)
It checks the existence of the file.
Definition: Utils.cpp:85
Various auxiliary functions used across the program.
bool VERBOSE
Boolean flag determining verbosity of the program.
Definition: Settings.cpp:28
std::pair< uint64_t, double > infoLP()
Returns the total number of Linear Programming calls and its average runtime for one worker thread...
Solution bestSolution()
Returns the best found solution, throws an exception if not available.
void recordChangePathCall()
Reports that robot paths were diversified, i.e. ParallelHeuristicSolver::changeRobotPaths method was ...
uint32_t mTabuListSize
The number of tabu list elements calculated in the constructor.
uint32_t NUMBER_OF_THREADS
Maximal number of threads to be used.
Definition: Settings.cpp:30
std::vector< std::vector< std::vector< Location * > > > mLocs
Preallocated 2-D array of locations for getShortestCircuit method, indexed by thread.
double mRuntime
The runtime of this heuristic, updated from time to time.
void addErrorMessage(const std::string &msg)
Add an error message to the mutex protected vector of messages.
constexpr double TIME_TOL
A minimal time difference that is considered significant for a solution.
double totalEnergy
Energy consumption of a robotic cell for this timing.
uint64_t pathChangeCalls() const
The total number of ParallelHeuristicSolver::changeRobotPaths calls.
A partially fixed problem, i.e. tuple.
Exception is thrown if a method is given invalid parameters or a user provides invalid program argume...
Definition: Exceptions.h:120
Thrown if no feasible solution is found by the heuristic.
Definition: Exceptions.h:134
double calculateNewCycleTime(const uint32_t &threadId, const double &currentCycleTime, std::vector< StaticActivity * > &alternative, std::vector< Location * > &fixed, Location *toFix, PrecalculatedCircuits &precalculatedCircuits)
A highly optimized function calculates a lower estimation on the robot cycle time if toFix location i...
State status
Enum specifying whether the solution is optimal, feasible, or infeasible...
Definition: Solution.h:55
Collection of locations in which a robot operation (or waiting) can be performed. ...
Definition: RoboticLine.h:304
std::vector< std::vector< std::vector< double > > > mDist
Preallocated 2-D array of distances for getShortestCircuit method, indexed by thread.
Fixed locations, power saving modes, and movements.
void recordLocHeurRelErr(double relativeEstError)
Reports a relative estimation error of the change locations sub-heuristic for an energy improvement...
void recordNumberOfItersPerTuple(uint64_t iters)
Reports the number of optimization iterations performed for a loaded tuple.
Graph constructMinimalDurationGraph(Robot *r) const
Generates a distance graph for the given robot.
DistanceMatrix< double > createInitialDistanceMatrix(const Graph &g) const
It creates an initial matrix of distances based on edge lengths.
void addValue(const T &val)
Add a new value to the moving average.
std::pair< uint64_t, double > infoItersPerTuple()
Returns the total number of tuples used for optimization and the average number of optimization itera...
void recordPwrmHeurRelErr(double relativeEstError)
Reports a relative estimation error of the (de)select power mode sub-heuristic for an energy improvem...
KnowledgeBase mKB
Various data of the heuristic, e.g. generated tuples, performance statistics, etc.
A parallel hybrid heuristic solving the energy optimization problem of robotic cells.
double averageErrOfLocHeur()
Returns an average estimation error of the change locations sub-heuristic.
void generatePromisingTuples(const uint32_t &threadId)
It generates promising tuples and adds them to the KnowledgeBase.
DistanceMatrix< T > floyd(DistanceMatrix< T > m, const C &cmp=std::greater< T >())
It calculates all-to-all shortest paths and returns the matrix with their lengths.
Definition: Algorithms.h:50
double minLength
The lower bound on the robot cycle time for this circuit (neglecting global constraints).
A graph in which random alternatives will be searched for.
double heuristicLocationChanges(OptimalTiming &ot, PartialSolution &ps, const std::vector< std::vector< Location * >> &fixed, bool &solutionChanged) const
The sub-heuristic locally optimizes the robot paths (change locations) to reduce energy consumption...
double averageNumberOfLPCallsForLPFix()
Returns an average number of added precedences required for collisions avoidance. ...
Algo
Defining constants for different states of the heuristic.
std::vector< std::vector< Edge > > idToSuccessors
Node index to leaving edges that enters to its (node) successors.
string RESULTS_DIRECTORY
If not empty, then the optimization results will be written to this directory.
Definition: Settings.cpp:34
uint32_t MIN_ITERS_PER_TUPLE
The minimal number of optimization iterations per each tuple.
Definition: Settings.cpp:44
std::pair< uint64_t, double > infoPwrmHeur()
Returns the total number of HeuristicAlgorithms::heuristicPowerModeSelection calls and average runtim...
uint32_t endIdent
An index of the end node.
void writePerformanceRecordToLogFile(const double &initializationTime, const double &finalRuntime)
It writes various statistics about the heuristic performance to a CSV file.
double averageErrOfPwrmHeur()
Returns an average estimation error of the (de)select power mode sub-heuristic.
HamiltonianCircuit< Location > hc
Shortest closed path through locations, fixed locations are included.
HamiltonianCircuit< Location > getShortestCircuit(const uint32_t &threadId, const std::vector< StaticActivity * > &circuit, const std::vector< Location * > &fixed, bool writeCircuit=true)
It calculates the shortest circuit through locations that visits each static activity in the same ord...
std::vector< std::vector< std::vector< int64_t > > > mPreds
Preallocated 2-D array of predecessors for getShortestCircuit method, indexed by thread.
void printProgressInfo(const double &currentRuntime)
Prints various information about the performance of the sub-heuristics, generation of tuples...
uint32_t startIdent
An index of the start node.
The file defines allowed inaccuracies in a solution and constants for floats.
std::vector< std::vector< T >> DistanceMatrix
Definition of the matrix data type.
Definition: Algorithms.h:39
uint32_t j
An index assigned to the activity t, used for an access to the distance matrix.
Location of the robot used either during work (welding) or waiting.
Definition: RoboticLine.h:192
The robotic cell corresponds to an instance of this class.
Definition: RoboticLine.h:563
The structure contains the maps for fast searching in the robotic cell.
double averageLPFixDeterioration()
Returns an average quality deterioration caused by the collisions resolution.
double length
std::pair< uint64_t, double > infoTuplesGeneration()
Returns the total number of generated tuples and the average generation time for one worker thread...
uint64_t numberOfPwrmHeurBreaks() const
Returns how many times the (de)select power mode sub-heuristic caused infeasibility.
Definition: KnowledgeBase.h:81
std::vector< StaticActivity * > circuit
Selected alternative, i.e. order of operations.
double MAX_RUNTIME
Maximal run time of the solver.
Definition: Settings.cpp:31
std::unordered_map< std::pair< Location *, Location * >, Movement * > locationsToMovement
It finds the movement between two locations if exists.
OptimalTiming solvePartialProblem(const PartialSolution &ps, const CircuitTuple &t, Algo algo)
Employs Linear Programming to obtain energy-efficient timing for a given tuple.
void recordAddTuplesCall(double runtime)
Reports a real time (measured by a timer) required for ParallelHeuristicSolver::addRandomTuplesToKB c...
std::vector< Location * > fixedLocations
Locations required to be fixed to enforce the spatial compatibility between robots.
The class encapsulating the calculation of the moving average.
Obtained timing for a partial problem.
HeuristicAlgorithms algo
Optimization sub-heuristics and the related Linear Programming solver for partially fixed problems...
uint32_t i
An index assigned to the activity f, used for an access to the distance matrix.
double average() const
Current value of the running average.
double infeasibilityRate()
Infeasibility rate of Linear Programming, i.e. the proportion of infeasible solutions to feasible and...
Thrown if the best solution of the heuristic cannot be returned since the solution pool is empty...
Definition: Exceptions.h:141
std::tuple< uint32_t, uint32_t, StaticActivity *, StaticActivity *, std::vector< std::pair< Location *, Location * >>> SpatialCmpTuple
Defines the data type for an element of the spatial compatibility, i.e. (rob1 idx, rob2 idx, act1, act2, {{loc1, loc2}*}).
std::vector< std::string > errorMessages() const
Returns all the error messages that occurred in the threads of the heuristic.
Definition: KnowledgeBase.h:66