solver  1.0
HeuristicAlgorithms.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 <chrono>
19 #include "Shared/Algorithms.h"
21 #ifndef NDEBUG
23 #endif
24 
25 using namespace std;
26 using namespace std::chrono;
27 
28 static random_device rd;
29 
37  public:
43  UnimodalFunctionHeuristic(Movement *mv1, Movement *mv2, double duration) : mMv1(mv1), mMv2(mv2), mTotalDuration(duration) {
44  if (mv1 == nullptr || mv2 == nullptr || duration < 0.0)
45  throw InvalidArgument(caller(), "Attempt to pass the malicious constructor parameters!");
46  else if (mv1->minDuration()+mv2->minDuration() >= duration+2*TIME_ERR || mv1->maxDuration()+mv2->maxDuration()+2*TIME_ERR <= duration)
47  throw InvalidArgument(caller(), "Invalid constructor parameters, the solution is infeasible!");
48 
49  mFromX = max(duration-mv2->maxDuration(), mv1->minDuration());
50  mToX = min(duration-mv2->minDuration(), mv1->maxDuration());
51  mDirection = mToX-mFromX;
52  assert(mDirection > -TIME_ERR && "A bug in the UnimodalFunctionHeuristic class, it should be positive!");
53  }
54 
60  double tolerance() const {
61  if (mDirection >= TIME_TOL)
62  return TIME_TOL/mDirection;
63  else
64  return 1.0; // solution is fixed, nothing to be optimized
65  }
66 
71  pair<double, double> durations(const double& alpha) const {
72  if (alpha < 0.0 || alpha > 1.0)
73  throw InvalidArgument(caller(), "Invalid function parameter, it must be in a range from 0.0 to 1.0!");
74 
75  double d1 = mFromX+alpha*mDirection, d2 = mTotalDuration-d1;
76  assert(abs(d1+d2-mTotalDuration) < 5*TIME_ERR && "Invalid golden search of the method, solution infeasible!");
77  assert(mMv1->minDuration() < d1+5*TIME_ERR && d1 < mMv1->maxDuration()+5*TIME_ERR && "Duration of movement 1 is out of bound!");
78  assert(mMv2->minDuration() < d2+5*TIME_ERR && d2 < mMv2->maxDuration()+5*TIME_ERR && "Duration of movement 2 is out of bound!");
79 
80  return { d1, d2 };
81  }
82 
87  double functionValue(const double& alpha) const {
88  const auto& durPair = durations(alpha);
89  return mMv1->energyConsumption(durPair.first)+mMv2->energyConsumption(durPair.second);
90  }
91 
92  private:
94  double mDirection;
96  double mFromX;
98  double mToX;
105 };
106 
108  const vector<Robot*>& robots = mLine.robots();
109  uint32_t numberOfRobots = robots.size();
110  if (ps.locs.size() != numberOfRobots)
111  throw InvalidArgument(caller(), "Invalid initial partial solution!");
112 
113  ps.mvs.clear();
114  bool writePwrms = ps.pwrms.empty();
115  vector<vector<Location*>> fixed(numberOfRobots);
116  for (uint32_t r = 0; r < numberOfRobots; ++r) {
117  vector<Movement*> mvs;
118  const vector<Location*>& locs = ps.locs[r];
119  assert(locs.size() > 1 && "Circuits in the partial solution should be filled in!");
120  for (uint32_t l = 0; l+1 < locs.size(); ++l) {
121  Location *from = locs[l], *to = locs[l+1];
122  Movement *mv = getValue(mMapping.locationsToMovement, {from, to}, caller());
123  assert(mv != nullptr && "Unexpected nullptr! Should be checked by setValue previously!");
124  mvs.push_back(mv);
125  // Check whether the location must be fixed...
126  if (mMapping.sptComp.count(from->parent()) > 0)
127  fixed[r].push_back(from);
128  }
129 
130  ps.mvs.push_back(move(mvs));
131  if (writePwrms == true)
132  ps.pwrms.emplace_back(locs.size(), robots[r]->fastestPowerSavingMode());
133  }
134 
135  return fixed;
136 }
137 
139  Solution s;
140  OptimalTiming ot;
141  bool collisionsFeasible = false;
142  high_resolution_clock::time_point startInit = high_resolution_clock::now();
143  RoboticLineSolverLP solver(mLine, ps, mMapping);
144  double extraTime = duration_cast<duration<double>>(high_resolution_clock::now()-startInit).count();
145  double minCriterion = F64_MAX, maxCriterion = F64_MIN;
146 
147  mKB.recordPartialProblemSolveCall();
148 
149  while (!collisionsFeasible) {
150  high_resolution_clock::time_point startLP = high_resolution_clock::now();
151  s = solver.solve();
152  mKB.recordLPCall(duration_cast<duration<double>>(high_resolution_clock::now()-startLP).count()+extraTime);
153  if (s.status == OPTIMAL) {
154  ot = convert(ps, s);
155  minCriterion = min(minCriterion, ot.totalEnergy);
156  maxCriterion = max(maxCriterion, ot.totalEnergy);
157  const CollisionResolution& r = resolveTheWorstCollision(ps, ot, s);
158  if (r.a1 != nullptr && r.a2 != nullptr)
159  solver.addCollisionResolution(r.a1, r.a2, r.multiplier);
160  else
161  collisionsFeasible = true;
162 
163  extraTime = 0.0;
164  } else {
165  switch (algo) {
166  case LP_INITIAL_PROBLEM:
167  mKB.reportInfDueToLP();
168  break;
169  case POWER_MODE_HEURISTIC:
170  mKB.reportInfDueToPwrmHeur();
171  break;
172  case LOCATION_CHANGE_HEURISTIC:
173  mKB.reportInfDueToLocHeur();
174  break;
175  case PATH_CHANGE:
176  mKB.reportInfDueToPathChange();
177  break;
178  }
179 
180  mKB.recordInfeasibleLP();
181 
182  throw NoFeasibleSolutionExists(caller(), "Cannot solve the subproblem to optimality!");
183  }
184  }
185 
186  #ifndef NDEBUG
187  SolutionChecker checker(mLine, mMapping, s);
188  if (!checker.checkAll())
189  throw SolverException(caller(), "A bug in solving the partial LP problem, the solution is infeasible!");
190  #endif
191 
192  mKB.candidate(s, t);
193  if (minCriterion > 0.0)
194  mKB.recordLPFixDeterioration(maxCriterion/minCriterion-1.0);
195 
196  return ot;
197 }
198 
200  OptimalTiming timing;
201  timing.totalEnergy = s.totalEnergy;
202  vector<double> startTimes, durations;
203  uint32_t numberOfRobots = mLine.robots().size();
204  for (uint32_t r = 0; r < numberOfRobots; ++r) {
205  const vector<Location*>& locs = ps.locs[r];
206  for (Location *loc : locs) {
207  assert(loc != nullptr && loc->parent() != nullptr && "Unexpected null pointer!");
208 
209  pair<double, double> val;
210  const auto& sit = s.startTimeAndDuration.find(loc->parent()->aid());
211  if (sit != s.startTimeAndDuration.cend())
212  val = sit->second;
213  else
214  throw SolverException(caller(), "Given the incomplete solution!");
215 
216  startTimes.push_back(val.first);
217  durations.push_back(val.second);
218  timing.actToStartAndDur[loc->parent()] = val;
219  timing.modeToStartAndDur[loc] = val;
220  }
221 
222  timing.startLocs.push_back(move(startTimes));
223  timing.durLocs.push_back(move(durations));
224  startTimes.clear(); durations.clear();
225 
226  const vector<Movement*>& mvs = ps.mvs[r];
227  for (Movement *mv : mvs) {
228  assert(mv != nullptr && mv->parent() != nullptr && "Unexpected null pointer!");
229 
230  pair<double, double> val;
231  const auto& sit = s.startTimeAndDuration.find(mv->parent()->aid());
232  if (sit != s.startTimeAndDuration.cend())
233  val = sit->second;
234  else
235  throw SolverException(caller(), "Given the incomplete solution!");
236 
237  startTimes.push_back(val.first);
238  durations.push_back(val.second);
239  timing.actToStartAndDur[mv->parent()] = val;
240  timing.modeToStartAndDur[mv] = val;
241  }
242 
243  timing.startMvs.push_back(move(startTimes));
244  timing.durMvs.push_back(move(durations));
245  startTimes.clear(); durations.clear();
246  }
247 
248  return timing;
249 }
250 
251 CollisionResolution HeuristicAlgorithms::resolveCollision(ActivityMode* m1, double s1, double d1, ActivityMode* m2, double s2, double d2) const {
253  double cycleTime = mLine.productionCycleTime();
254  double s1m = fmod(s1, cycleTime), s2m = fmod(s2, cycleTime);
255  if (s1m > s2m) {
256  swap(m1, m2); swap(s1, s2); swap(d1, d2); swap(s1m, s2m);
257  }
258 
259  double intersection1 = min(max(s1m+d1-s2m, 0.0), d2);
260  double intersection2 = min(max(s2m+d2-(s1m+cycleTime), 0.0), d1);
261  cl.intersection = intersection1 + intersection2;
262 
263  // Remark: Since d1 + d2 <= CT, not both intersections are possible at once.
264  if (cl.intersection > TIME_TOL || !(s2m+d2-s1m <= cycleTime+TIME_TOL && s1m+d1 <= s2m+TIME_TOL)) {
265  if (intersection2 > TIME_TOL)
266  s1m += cycleTime;
267 
268  if (s1m+0.5*d1 <= s2m+0.5*d2) {
269  cl.a1 = m1->baseParent();
270  cl.a2 = m2->baseParent();
271  cl.multiplier = (int32_t) round(((s2-s2m)/cycleTime)-((s1-s1m)/cycleTime));
272  } else {
273  cl.a1 = m2->baseParent();
274  cl.a2 = m1->baseParent();
275  cl.multiplier = (int32_t) round(((s1-s1m)/cycleTime)-((s2-s2m)/cycleTime));
276  }
277  }
278 
279  return cl;
280 }
281 
284  for (const auto& mit1 : ot.modeToStartAndDur) {
285  ActivityMode *m1 = mit1.first;
286  double s1 = mit1.second.first, d1 = mit1.second.second;
287  const auto& mit2 = mMapping.collisionSearch.find(m1);
288  if (mit2 != mMapping.collisionSearch.cend()) {
289  for (ActivityMode *m2 : mit2->second) {
290  const auto& sit = ot.modeToStartAndDur.find(m2);
291  if (sit != ot.modeToStartAndDur.cend()) {
292  double s2 = sit->second.first, d2 = sit->second.second;
293  // It is necessary to check the possible collision.
294  CollisionResolution cl = resolveCollision(m1, s1, d1, m2, s2, d2);
295  if (ret.intersection < cl.intersection)
296  ret = cl;
297  }
298  }
299  }
300  }
301 
302  return ret;
303 }
304 
305 double HeuristicAlgorithms::heuristicLocationChanges(OptimalTiming& ot, PartialSolution& ps, const vector<vector<Location*>>& fixed, bool& solutionChanged) const {
306 
307  high_resolution_clock::time_point startLocHeur = high_resolution_clock::now();
308 
309  solutionChanged = false;
310  double energyImprovement = 0.0;
311  default_random_engine threadGenerator(rd());
312  double cycleTime = mLine.productionCycleTime();
313  uint32_t numberOfRobots = mLine.robots().size();
314  double averageInputPower = ot.totalEnergy/(numberOfRobots*cycleTime);
315  for (uint32_t r = 0; r < numberOfRobots; ++r) {
316  uint32_t numberOfLocations = ps.locs[r].size();
317  uniform_int_distribution<uint32_t> randomOffset(0u, 1u);
318  for (uint32_t i = randomOffset(threadGenerator); i+1u < numberOfLocations; i += 2u) {
319  Location *bestLoc = ps.locs[r][i], *selLoc = bestLoc;
320  assert(bestLoc != nullptr && bestLoc->parent() != nullptr && "Invalid initialization of the robotic cell!");
321  if (count(fixed[r].cbegin(), fixed[r].cend(), bestLoc) == 0) {
322  // It is possible to change location without spatial compatibility breaking.
323  int64_t prevIdxMv = (i > 0 ? i-1 : ps.mvs[r].size()-1);
324  int64_t prevIdxLoc = (i > 0 ? i-1 : numberOfLocations-2);
325  assert(prevIdxMv >= 0 && prevIdxLoc >= 0 && "Invalid index calculation, were the vectors initialized?!");
326  Movement *bestEntering = ps.mvs[r][prevIdxMv], *bestLeaving = ps.mvs[r][i];
327  RobotPowerMode *selectedPowerSavingMode = ps.pwrms[r][i];
328  Location *prevLoc = ps.locs[r][prevIdxLoc], *nextLoc = ps.locs[r][i+1];
329  // Get the current timing...
330  double bestStart1 = ot.startMvs[r][prevIdxMv], bestDur1 = ot.durMvs[r][prevIdxMv]; // entering
331  double bestStart2 = ot.startLocs[r][i], bestDur2 = ot.durLocs[r][i]; // inner location
332  double bestStart3 = ot.startMvs[r][i], bestDur3 = ot.durMvs[r][i]; // leaving
333  double stationaryDuration = bestDur2, dynamicDuration = bestDur1+bestDur3;
334  // Remove a part from a robot gantt to update it later.
335  ot.modeToStartAndDur.erase(bestEntering); ot.actToStartAndDur.erase(bestEntering->parent());
336  ot.modeToStartAndDur.erase(bestLoc); ot.actToStartAndDur.erase(bestLoc->parent());
337  ot.modeToStartAndDur.erase(bestLeaving); ot.actToStartAndDur.erase(bestLeaving->parent());
338  // Calculate the current energy consumption.
339  double initialEnergyConsumption = bestEntering->energyConsumption(bestDur1), bestEnergyConsumption = F64_INF;
340  initialEnergyConsumption += bestLoc->energyConsumption(stationaryDuration, selectedPowerSavingMode);
341  initialEnergyConsumption += bestLeaving->energyConsumption(bestDur3);
342  // Try to select another location if it is possible and more energy efficient.
343  for (Location *loc : selLoc->parent()->locations()) {
344  const auto& sit1 = mMapping.locationsToMovement.find({prevLoc, loc});
345  const auto& sit2 = mMapping.locationsToMovement.find({loc, nextLoc});
346  if (sit1 != mMapping.locationsToMovement.cend() && sit2 != mMapping.locationsToMovement.cend()) {
347  Movement *entering = sit1->second, *leaving = sit2->second;
348  bool callGoldenSearch = entering->minDuration()+leaving->minDuration() < dynamicDuration+TIME_ERR;
349  callGoldenSearch = callGoldenSearch && entering->maxDuration()+leaving->maxDuration()+TIME_ERR > dynamicDuration;
350  if (callGoldenSearch == true) {
351  // Calculate total energy required for the pair of movements and location.
352  UnimodalFunctionHeuristic unifFce(entering, leaving, dynamicDuration);
353  const pair<double, double>& res = goldenSearch(unifFce);
354  double totalEnergy = res.second;
355  totalEnergy += loc->energyConsumption(stationaryDuration, selectedPowerSavingMode);
356  // Get new timing for 'mv1 -> loc2 -> mv3'.
357  const pair<double, double>& mvsDurs = unifFce.durations(res.first);
358  double s1 = ot.startMvs[r][prevIdxMv], d1 = mvsDurs.first;
359  double s2 = s1+d1, d2 = stationaryDuration;
360  double s3 = s2+d2, d3 = mvsDurs.second;
361  if (loc->parent()->lastInCycle())
362  s3 -= cycleTime;
363  // Estimate time violation after switching the location and penalize it in terms of energy.
364  vector<ActivityModeInfo> partOfGantt = { { entering, s1, d1 }, { loc, s2, d2 }, { leaving, s3, d3 } };
365  double timePenalty = calculateBreakagePenalty(partOfGantt, ot);
366  totalEnergy += mPenaltyMultiplier*averageInputPower*timePenalty;
367  // Decide whether it is an improvement or not...
368  if (totalEnergy < bestEnergyConsumption) {
369  bestEnergyConsumption = totalEnergy;
370  bestEntering = entering; bestLoc = loc; bestLeaving = leaving;
371  bestStart1 = s1; bestDur1 = d1; bestStart2 = s2; bestDur2 = d2; bestStart3 = s3; bestDur3 = d3;
372  }
373  }
374  }
375  }
376 
377  if (bestEnergyConsumption < F64_INF) {
378  // Add an energy improvement after switching a location.
379  energyImprovement += initialEnergyConsumption-bestEnergyConsumption;
380  // Update the part of the Gantt according the the best selected location.
381  ps.mvs[r][prevIdxMv] = bestEntering;
382  ot.durMvs[r][prevIdxMv] = bestDur1;
383  setValue(ot.modeToStartAndDur, bestEntering, {bestStart1, bestDur1}, caller());
384  setValue(ot.actToStartAndDur, bestEntering->parent(), {bestStart1, bestDur1}, caller());
385  ps.locs[r][i] = bestLoc;
386  if (i == 0)
387  ps.locs[r].back() = ps.locs[r].front();
388  ot.startLocs[r][i] = bestStart2;
389  setValue(ot.modeToStartAndDur, bestLoc, {bestStart2, bestDur2}, caller());
390  setValue(ot.actToStartAndDur, bestLoc->parent(), {bestStart2, bestDur2}, caller());
391  ps.mvs[r][i] = bestLeaving;
392  ot.startMvs[r][i] = bestStart3;
393  ot.durMvs[r][i] = bestDur3;
394  setValue(ot.modeToStartAndDur, bestLeaving, {bestStart3, bestDur3}, caller());
395  setValue(ot.actToStartAndDur, bestLeaving->parent(), {bestStart3, bestDur3}, caller());
396  if (bestLoc != selLoc)
397  solutionChanged = true;
398  } else {
399  throw SolverException(caller(), "Cannot select a suitable location, either the heuristic bug or invalid partial solution!");
400  }
401  }
402  }
403  }
404 
405  mKB.recordLocHeurCall(duration_cast<duration<double>>(high_resolution_clock::now()-startLocHeur).count());
406 
407  return energyImprovement;
408 }
409 
410 double HeuristicAlgorithms::heuristicPowerModeSelection(const OptimalTiming& ot, PartialSolution& ps, TabuList& tabuList, bool& solutionChanged) const {
411 
412  high_resolution_clock::time_point startPwrmHeur = high_resolution_clock::now();
413 
414  solutionChanged = false;
415  const vector<Robot*>& robots = mLine.robots();
416  double cycleTime = mLine.productionCycleTime();
417  uint32_t numberOfRobots = robots.size();
418  vector<uint32_t> numberOfLocations(numberOfRobots);
419  vector<vector<ActivityModeInfo>> info(numberOfRobots);
420  vector<double> consumedEnergyPerRobot(numberOfRobots, 0.0);
421  double averageInputPower = ot.totalEnergy/(numberOfRobots*cycleTime);
422 
423  for (uint32_t r = 0; r < numberOfRobots; ++r) {
424  numberOfLocations[r] = ps.locs[r].size();
425  for (uint32_t l = 0; l+1 < numberOfLocations[r]; ++l) {
426  Location *loc = ps.locs[r][l];
427  RobotPowerMode *pwrm = ps.pwrms[r][l];
428  double startLoc = ot.startLocs[r][l], durLoc = ot.durLocs[r][l], energyLoc = loc->energyConsumption(durLoc, pwrm);
429  info[r].push_back({loc, pwrm, energyLoc, startLoc, durLoc,
430  max(pwrm->minimalDelay(), loc->parent()->minAbsDuration()), loc->parent()->maxAbsDuration()});
431  consumedEnergyPerRobot[r] += energyLoc;
432  }
433 
434  for (uint32_t l = 0; l+1 < numberOfLocations[r]; ++l) {
435  Movement *mv = ps.mvs[r][l];
436  double startMv = ot.startMvs[r][l], durMv = ot.durMvs[r][l], energyMv = mv->energyConsumption(durMv);
437  info[r].push_back({mv, nullptr, energyMv, startMv, durMv, mv->minDuration(), mv->maxDuration()});
438  consumedEnergyPerRobot[r] += energyMv;
439  }
440  }
441 
442  double originalEnergyConsumption = accumulate(consumedEnergyPerRobot.cbegin(), consumedEnergyPerRobot.cend(), 0.0);
443 
444  vector<ModeSwitchInfo> candidates;
445  for (uint32_t r = 0; r < numberOfRobots; ++r) {
446  for (uint32_t l = 0; l+1 < numberOfLocations[r]; ++l) {
447  Location *selLoc = ps.locs[r][l];
448  RobotPowerMode *selMode = ps.pwrms[r][l];
449  StaticActivity *selAct = selLoc->parent();
450  for (RobotPowerMode *pwrm : robots[r]->powerModes()) {
451  if (pwrm != selMode && pwrm->minimalDelay() <= selAct->maxAbsDuration()) {
452  vector<ActivityModeInfo> robotTiming = info[r];
453  double minReqDur = max(selAct->minAbsDuration(), pwrm->minimalDelay());
454 
455  robotTiming[l].pwrm = pwrm;
456  robotTiming[l].energy = selLoc->energyConsumption(minReqDur, pwrm);
457  robotTiming[l].minDuration = robotTiming[l].curDuration = minReqDur;
458  robotTiming[l].maxDuration = selAct->maxAbsDuration();
459 
460  double energyScaledRobot = scaleGanttToCycleTime(robotTiming);
461  if (energyScaledRobot < F64_INF) {
462  double timePenalty = calculateBreakagePenalty(robotTiming, ot);
463  double estimatedEnergy = originalEnergyConsumption-consumedEnergyPerRobot[r];
464  estimatedEnergy += energyScaledRobot+mPenaltyMultiplier*averageInputPower*timePenalty;
465  candidates.emplace_back(r, l, selLoc, selMode, pwrm, estimatedEnergy);
466  }
467  }
468  }
469  }
470  }
471 
472  double energyImprovement = 0.0;
473  if (!candidates.empty()) {
474  ModeSwitchInfo selected;
475  // Sort candidates according their energy estimation.
476  sort(candidates.begin(), candidates.end());
477  // Select the best candidate not in tabu list.
478  const auto& sit = find_if(candidates.cbegin(), candidates.cend(), [&](const ModeSwitchInfo& i) { return !tabuList.isTabu(i.loc, i.from, i.to); });
479  if (sit != candidates.cend()) {
480  selected = *sit;
481  // The selected mode switch is being added to the tabu list.
482  tabuList.addTabu(selected.loc, selected.from, selected.to);
483  } else {
484  // All candidates are tabu, random selection and tabu list diversification.
485  default_random_engine threadGenerator(rd());
486  uniform_int_distribution<uint32_t> randSel(0, candidates.size()-1);
487  uint32_t selIdx = randSel(threadGenerator);
488  selected = candidates[selIdx];
489  tabuList.diversify();
490  }
491 
492  ps.pwrms[selected.robotIdx][selected.locationIdx] = selected.to;
493  if (selected.locationIdx == 0)
494  ps.pwrms[selected.robotIdx].back() = ps.pwrms[selected.robotIdx].front();
495 
496  energyImprovement = originalEnergyConsumption-selected.estimatedEnergy;
497  solutionChanged = true;
498  }
499 
500  mKB.recordPwrmHeurCall(duration_cast<duration<double>>(high_resolution_clock::now()-startPwrmHeur).count());
501 
502  return energyImprovement;
503 }
504 
505 double HeuristicAlgorithms::scaleGanttToCycleTime(vector<ActivityModeInfo>& robotGantt) const {
506  double sumOfDur = 0.0;
507  double robotEnergy = 0.0;
508  double cycleTime = mLine.productionCycleTime();
509  for (const ActivityModeInfo& ami : robotGantt) {
510  sumOfDur += ami.curDuration;
511  robotEnergy += ami.energy;
512  }
513 
514  double durDiff = sumOfDur-cycleTime;
515  vector<bool> changeAllowed(robotGantt.size(), false);
516  bool shorten = durDiff > 0.0, prolong = durDiff < 0.0;
517 
518  while (abs(durDiff) > TIME_TOL) {
519  double maxFeasChange = F64_INF;
520  uint32_t numberOfModifiable = 0u;
521  for (uint32_t i = 0; i < robotGantt.size(); ++i) {
522  if (shorten && robotGantt[i].curDuration > robotGantt[i].minDuration) {
523  maxFeasChange = min(maxFeasChange, robotGantt[i].curDuration-robotGantt[i].minDuration);
524  changeAllowed[i] = true;
525  ++numberOfModifiable;
526  } else if (prolong && robotGantt[i].curDuration < robotGantt[i].maxDuration) {
527  maxFeasChange = min(maxFeasChange, robotGantt[i].maxDuration-robotGantt[i].curDuration);
528  changeAllowed[i] = true;
529  ++numberOfModifiable;
530  } else {
531  changeAllowed[i] = false;
532  }
533  }
534 
535  if (numberOfModifiable > 0 && maxFeasChange < F64_INF) {
536  maxFeasChange = min(abs(durDiff)/numberOfModifiable, maxFeasChange);
537  if (shorten)
538  maxFeasChange = -maxFeasChange;
539 
540  for (uint32_t i = 0; i < robotGantt.size(); ++i) {
541  if (changeAllowed[i]) {
542  double energyUpdated = F64_INF;
543  ActivityMode* mode = robotGantt[i].mode;
544  double durationUpdated = robotGantt[i].curDuration + maxFeasChange;
545  assert(robotGantt[i].minDuration <= durationUpdated+TIME_ERR
546  && durationUpdated <= robotGantt[i].maxDuration+TIME_ERR && "Invalid scaling of the robot graph!");
547 
548  Location *loc = dynamic_cast<Location*>(mode);
549  if (loc != nullptr)
550  energyUpdated = loc->energyConsumption(durationUpdated, robotGantt[i].pwrm);
551 
552  Movement *mv = dynamic_cast<Movement*>(mode);
553  if (mv != nullptr)
554  energyUpdated = mv->energyConsumption(durationUpdated);
555 
556  robotEnergy += energyUpdated-robotGantt[i].energy;
557 
558  robotGantt[i].energy = energyUpdated;
559  robotGantt[i].curDuration = durationUpdated;
560  }
561  }
562  } else {
563  #ifndef NDEBUG
564  double minCycleTime = 0.0;
565  for (const ActivityModeInfo& ami : robotGantt)
566  minCycleTime += ami.minDuration;
567 
568  if (minCycleTime+TIME_TOL < cycleTime)
569  throw SolverException(caller(), "A bug in the method, the gantt should be scalable!");
570  #endif
571 
572  // Cannot be scaled -> it will lead to infeasible solution.
573  return F64_INF;
574  }
575 
576  durDiff += numberOfModifiable*maxFeasChange;
577  }
578 
579  // Update start times.
580  sort(robotGantt.begin(), robotGantt.end());
581  for (uint32_t i = 0; i+1 < robotGantt.size(); ++i)
582  robotGantt[i+1].curStartTime = robotGantt[i].curStartTime+robotGantt[i].curDuration;
583 
584  #ifndef NDEBUG
585  double checkCycleTime = 0.0;
586  for (const ActivityModeInfo& ami : robotGantt) {
587  checkCycleTime += ami.curDuration;
588  if (ami.minDuration > ami.curDuration+TIME_TOL || ami.maxDuration+TIME_TOL < ami.curDuration)
589  throw SolverException(caller(), "Invalid update step of the algorithm.");
590  }
591 
592  if (abs(checkCycleTime-cycleTime) > TIME_TOL)
593  throw SolverException(caller(), "The robot gantt was not scaled to the desired cycle time, a bug in the algorithm!");
594  #endif
595 
596  return robotEnergy;
597 }
598 
599 double HeuristicAlgorithms::calculateBreakagePenalty(const std::vector<ActivityModeInfo>& robotGantt, const OptimalTiming& ot) const {
600 
601  double timePenalty = 0.0;
602  double cycleTime = mLine.productionCycleTime();
603 
604  for (uint32_t i = 0; i < robotGantt.size(); ++i) {
605  ActivityMode *m1 = robotGantt[i].mode;
606  double s1 = robotGantt[i].curStartTime, d1 = robotGantt[i].curDuration;
607 
608  // check collisions
609  const auto& sit = mMapping.collisionSearch.find(m1);
610  if (sit != mMapping.collisionSearch.cend()) {
611  for (ActivityMode *m2 : sit->second) {
612  const auto& mit = ot.modeToStartAndDur.find(m2);
613  if (mit != ot.modeToStartAndDur.cend()) {
614  double s2 = mit->second.first, d2 = mit->second.second;
615  CollisionResolution cl = resolveCollision(m1, s1, d1, m2, s2, d2);
616  timePenalty += cl.intersection;
617  }
618  }
619  }
620 
621  // check time lags
622  uint32_t idx = 0;
623  for (const map<Activity*, vector<TimeLag>>* mapPtr : { &mMapping.timeLagsFromAct, &mMapping.timeLagsToAct }) {
624  const auto& sit1 = mapPtr->find(m1->baseParent());
625  if (sit1 != mapPtr->cend()) {
626  for (const TimeLag& tl : sit1->second) {
627  Activity *relAct = (idx == 0 ? tl.to() : tl.from());
628  const auto& mit = ot.actToStartAndDur.find(relAct);
629  if (mit != ot.actToStartAndDur.cend()) {
630  double s2 = mit->second.first;
631  if (idx == 0)
632  timePenalty += max(s1-s2+tl.length()-tl.height()*cycleTime, 0.0);
633  else
634  timePenalty += max(s2-s1+tl.length()-tl.height()*cycleTime, 0.0);
635  }
636  }
637  }
638 
639  ++idx;
640  }
641  }
642 
643  assert(timePenalty >= 0.0 && "Time penalty must be positive, a bug in the algorithm!");
644 
645  return timePenalty;
646 }
647 
The instance of the class corresponds to a robot movement between two coordinates.
Definition: RoboticLine.h:133
double mFromX
The minimal duration of the first movement.
bool isTabu(Location *l, RobotPowerMode *from, RobotPowerMode *to) const
It checks whether the modification of a partial solution is allowed, i.e. the move/modification is no...
std::vector< std::vector< double > > durLocs
Durations assigned to static activities' locations.
void addTabu(Location *l, RobotPowerMode *from, RobotPowerMode *to)
It adds a performed modification into the tabu list.
double totalEnergy
The amount of energy required per robot cycle.
Definition: Solution.h:57
void addCollisionResolution(Activity *i, Activity *j, const int32_t &multipleOfCycleTime)
It adds a constraint to resolve a collision where and are start time and duration of the activity ...
void diversify()
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
double scaleGanttToCycleTime(std::vector< ActivityModeInfo > &robotGantt) const
It scales durations of activities in such a way that the robot cycle time is met. Updated start times...
CollisionResolution resolveTheWorstCollision(const PartialSolution &ps, const OptimalTiming &ot, const Solution &s) const
It finds the worst collision, i.e. with the longest time of active collision, and returns how to reso...
std::vector< std::vector< double > > startMvs
Start times assigned to dynamic activities' movements.
std::vector< std::vector< Location * > > locs
Selected locations for each robot, time ordered.
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< RobotPowerMode * > > pwrms
Selected power saving modes of related locations.
RobotPowerMode * to
The candidate power saving mode to switch to.
Movement * mMv2
The second movement to be considered.
An unimodal function for two movements and their fixed total time.
State convert(const Status &ilpStatus)
Conversion between the ILP solver status and the solution state.
Definition: Solution.cpp:29
STL namespace.
Activity * a2
Second activity involved in the collision.
An instance of this class is devoted to the solution checking.
double estimatedEnergy
Estimated energy consumption after switching to the candidate power saving mode.
A general exception of the program.
Definition: Exceptions.h:58
The structure stores how to resolve one collision between robots.
double mTotalDuration
The time needed for both the movements is equal to this fixed value.
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...
double functionValue(const double &alpha) const
Returns a function value, i.e. total energy consumption of both movements.
CollisionResolution resolveCollision(ActivityMode *m1, double s1, double d1, ActivityMode *m2, double s2, double d2) const
It checks whether there is a collision between two specified activities, and eventually returns how t...
double mDirection
A maximal possible difference in duration for both the movements.
The optimization sub-heuristics and Linear Programming solver for partially fixed problems...
std::vector< std::vector< double > > startLocs
Start times assigned to static activities' locations.
int32_t multiplier
Multiplier addressing the time shift in the number of cycles.
bool checkAll()
It calls the private member methods to verify the solution.
constexpr double TIME_ERR
Minimal recognizable difference in the time.
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.
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
Collection of locations in which a robot operation (or waiting) can be performed. ...
Definition: RoboticLine.h:304
State status
Enum specifying whether the solution is optimal, feasible, or infeasible...
Definition: Solution.h:55
Structure encapsulates the time and energy properties of an activity with its selected mode (a moveme...
Fixed locations, power saving modes, and movements.
Movement * mMv1
The first movement to be considered.
It represents the power saving mode of the robot.
Definition: RoboticLine.h:359
Determines an optimal timing of a partially fixed problem.
pair< double, double > durations(const double &alpha) const
Calculates durations of the movements based on the relative step.
std::pair< double, double > goldenSearch(const T &unimodalFce)
It finds the minimal function value of a unimodal convex function.
Definition: Algorithms.h:72
Instance of TimeLag class defines a time relation between two different robots.
Definition: RoboticLine.h:481
Universal algorithms like Floyd-Warshall, Golden Search, etc.
UnimodalFunctionHeuristic(Movement *mv1, Movement *mv2, double duration)
Constructor initializes the member variables.
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...
Algo
Defining constants for different states of the heuristic.
Solution solve() const
The energy optimal timing is determined by solving Linear Programming problem and the solution is ret...
double energyConsumption(const double &duration) const
Definition: RoboticLine.cpp:31
double mToX
The maximal duration of the first movement.
double intersection
Duration of the collision between activities a1 and a2.
uint32_t locationIdx
Index of the related location of the robot.
OptimalTiming convert(const PartialSolution &ps, const Solution &s) const
Auxiliary method that converts a general form of the solution to the timing of the robotic cell...
std::vector< std::vector< Movement * > > mvs
Selected movements, implied from the selected locations and their order.
RobotPowerMode * from
The original power saving mode.
Either a movement or location.
Definition: RoboticLine.h:54
The file declares a class responsible for checking of solutions.
Records a potential energy impact if a power saving mode of a robot is switched to another one...
Location of the robot used either during work (welding) or waiting.
Definition: RoboticLine.h:192
std::map< Activity *, std::pair< double, double > > actToStartAndDur
Activity to the assigned start time and duration.
Location * loc
A pointer to the considered location.
double energyConsumption(const double &duration, RobotPowerMode *m) const
Definition: RoboticLine.cpp:56
std::map< ActivityMode *, std::pair< double, double > > modeToStartAndDur
Selected location/movement to the assigned start time and duration.
std::vector< std::vector< double > > durMvs
Durations assigned to dynamic activities' movements.
OptimalTiming solvePartialProblem(const PartialSolution &ps, const CircuitTuple &t, Algo algo)
Employs Linear Programming to obtain energy-efficient timing for a given tuple.
Obtained timing for a partial problem.
uint32_t robotIdx
Index of the robot.
double calculateBreakagePenalty(const std::vector< ActivityModeInfo > &robotGantt, const OptimalTiming &ot) const
It calculates the penalty for the violation of time lags and collisions.
std::map< uint32_t, std::pair< double, double > > startTimeAndDuration
Mapping of the activity identification to its start time and duration.
Definition: Solution.h:65
Activity * a1
First activity involved in the collision.