solver  1.0
SolutionChecker.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 <array>
20 #include <cmath>
21 #include <iterator>
22 #include <typeinfo>
23 #include <string>
24 #include <utility>
25 #include "SolverConfig.h"
28 
29 using namespace std;
30 
32  if (mSolution.status == OPTIMAL || mSolution.status == FEASIBLE) {
33  // Feasible solution.
34  void (SolutionChecker::* fce[])() = {
38  };
39 
40  // Call all checks.
41  for (auto f : fce) {
42  try {
43  (this->*f)();
44  } catch (...) {
45  throw_with_nested(SolverException(caller(), "Mapping or data structures are flawed!"));
46  }
47  }
48  }
49 
50  return mErrorMsg.empty();
51 }
52 
54  double consumedEnergy = 0.0;
55  for (const auto& p : mSolution.startTimeAndDuration) {
56  Activity* a = getValue(mMapping.aidToActivity, p.first, caller());
57  double duration = p.second.second;
58 
59  StaticActivity* sa = dynamic_cast<StaticActivity*>(a);
60  if (sa != nullptr) {
61  const auto j = getValue(mSolution.pointAndPowerMode, sa->aid(), caller());
62  uint32_t lid = getValue(mMapping.pointToLocation, j.first, caller())->lid();
63  consumedEnergy += sa->energyConsumption(duration, lid, j.second);
64  }
65 
66  DynamicActivity* da = dynamic_cast<DynamicActivity*>(a);
67  if (da != nullptr) {
68  Movement *mv = getSelectedMovement(mSolution, mMapping, da);
69  consumedEnergy += mv->energyConsumption(duration);
70  }
71  }
72 
73  if (consumedEnergy < 0.0 || abs(consumedEnergy-mSolution.totalEnergy)/(consumedEnergy+F64_EPS) > CRITERION_RTOL) {
74  mErrorMsg.emplace_back("Invalid calculation of the criterion!");
75  mErrorMsg.emplace_back("Calculated - "+to_string(consumedEnergy)+" J; given - "+to_string(mSolution.totalEnergy)+" J.");
76  mErrorMsg.emplace_back("Discretization of energy functions could be imprecise (more than "+to_string(CRITERION_RTOL*100.0)+" % error)!");
77  mErrorMsg.emplace_back("You can try to increase the number of segments for each energy function.");
78  }
79 }
80 
81 
83  for (Robot* r : mLine.robots()) {
84  double cycleTime = 0.0;
85  for (Activity *a : r->activities()) {
86  const auto& sit = mSolution.startTimeAndDuration.find(a->aid());
87  if (sit != mSolution.startTimeAndDuration.cend())
88  cycleTime += sit->second.second;
89  }
90 
91  if (abs(cycleTime-mLine.productionCycleTime()) > TIME_TOL) {
92  mErrorMsg.emplace_back("Cycle time for robot '"+r->name()+"' does not correspond to the production cycle time!");
93  mErrorMsg.emplace_back("(robot cycle time "+to_string(cycleTime)+")/(production cycle time "+to_string(mLine.productionCycleTime())+")");
94  }
95  }
96 }
97 
99  for (const auto& p : mSolution.pointAndPowerMode) {
100  uint32_t aid = p.first, pwrm = p.second.second;
101  double duration = getValue(mSolution.startTimeAndDuration, aid, caller()).second;
102 
103  Activity *a = getValue(mMapping.aidToActivity, aid, caller());
104  assert(a->parent() != nullptr && "Each activity should belong to some robot!");
105 
106  double minDuration = a->minAbsDuration();
107  vector<RobotPowerMode*> m = a->parent()->powerModes();
108  auto sit = find_if(m.cbegin(), m.cend(), [&pwrm](RobotPowerMode* k) { return k->pid() == pwrm; });
109  if (sit != m.cend()) {
110  minDuration = max(minDuration, (*sit)->minimalDelay());
111  if (duration-minDuration < -TIME_TOL || duration-a->maxAbsDuration() > TIME_TOL)
112  mErrorMsg.emplace_back("Static activity "+to_string(aid)+" (power saving mode "+to_string(pwrm)+") has invalid duration!");
113  } else {
114  mErrorMsg.emplace_back("Cannot find the robot power mode "+to_string(pwrm)+" for static activity "+to_string(aid));
115  }
116  }
117 }
118 
120  for (const auto& p : mSolution.startTimeAndDuration) {
121  DynamicActivity *da = dynamic_cast<DynamicActivity*>(getValue(mMapping.aidToActivity, p.first, caller()));
122  if (da != nullptr) {
123  double duration = p.second.second;
124  Movement *mv = getSelectedMovement(mSolution, mMapping, da);
125  if (duration-mv->minDuration() < -TIME_TOL || duration-mv->maxDuration() > TIME_TOL)
126  mErrorMsg.emplace_back("Activity "+to_string(da->aid())+"'s movement "+to_string(mv->mid())+" has set invalid duration!");
127  }
128  }
129 }
130 
132  for (Robot* r : mLine.robots()) {
133  set<uint32_t> activityId;
134  for (const auto& p : mSolution.startTimeAndDuration)
135  activityId.insert(p.first);
136 
137  vector<Activity*> filteredRobotActivities, robotActivities = r->activities();
138  copy_if(robotActivities.cbegin(), robotActivities.cend(), back_inserter(filteredRobotActivities),
139  [&activityId](Activity* a) { return activityId.count(a->aid()) > 0 ? true : false; });
140 
141  uint32_t endId = 0;
142  map<uint32_t, Activity*> successors;
143  try {
144  for (Activity *a : filteredRobotActivities) {
145  DynamicActivity* da = dynamic_cast<DynamicActivity*>(a);
146  if (da != nullptr) {
147  Activity *suc = da->successor(), *pred = da->predecessor();
148  setValue(successors, pred->aid(), a, caller());
149  setValue(successors, a->aid(), suc, caller());
150  }
151 
152  if (a->lastInCycle())
153  endId = a->aid();
154  }
155 
156  if (successors.size() != filteredRobotActivities.size() || filteredRobotActivities.empty()) {
157  mErrorMsg.emplace_back("Uncomplete graph of the solution!");
158  return;
159  }
160  } catch (...) {
161  // Method 'setValue' indirectly checks whether each activity has exactly one successor in the solution graph.
162  mErrorMsg.emplace_back("The process flow of the robot '"+r->name()+"' is broken!");
163  mErrorMsg.emplace_back("The graph of the solution is either acyclic or disconnected.");
164  return;
165  }
166 
167  bool cycleFinished = false;
168  Activity *curAct = getValue(successors, endId, caller());
169  while (!cycleFinished) {
170  const auto p = getValue(mSolution.startTimeAndDuration, curAct->aid(), caller());
171  double startCur = p.first, duration = p.second;
172  Activity *next = getValue(successors, curAct->aid(), caller());
173  double startNext = getValue(mSolution.startTimeAndDuration, next->aid(), caller()).first;
174 
175  if (startNext-startCur < -TIME_TOL || abs(startNext-startCur-duration) > TIME_TOL) {
176  mErrorMsg.emplace_back("The process flow of the robot '"+r->name()+"' is broken!");
177  mErrorMsg.emplace_back("Check start times and activity durations!");
178  return;
179  }
180 
181  if (next->aid() == endId)
182  cycleFinished = true;
183  else
184  curAct = next;
185  }
186  }
187 }
188 
190  double productionCycleTime = mLine.productionCycleTime();
191  for (InterRobotOperation* o : mLine.interRobotOperations()) {
192  for (const TimeLag& tl : o->timeLags()) {
193  Activity *from = tl.from(), *to = tl.to();
194  const auto& sit1 = mSolution.startTimeAndDuration.find(from->aid());
195  const auto& sit2 = mSolution.startTimeAndDuration.find(to->aid());
196  if (sit1 != mSolution.startTimeAndDuration.cend() && sit2 != mSolution.startTimeAndDuration.cend()) {
197  double s1 = sit1->second.first, s2 = sit2->second.first;
198  if (s2-s1+TIME_TOL < tl.length()-productionCycleTime*tl.height()) {
199  mErrorMsg.emplace_back("Time lag '"+to_string(from->aid())+" -> "+to_string(to->aid())+"' is broken!");
200  mErrorMsg.emplace_back("Check the formulation of the problem.");
201  }
202  }
203  }
204  }
205 
206  for (const auto& t : mMapping.sptCompVec) {
207  Activity *a1 = get<2>(t), *a2 = get<3>(t);
208  try {
209  const array<uint32_t, 2>& m = extractModes(a1, a2);
210  const vector<pair<Location*, Location*>>& modes = get<4>(t);
211  const auto sit = find_if(modes.cbegin(), modes.cend(), [=](pair<Location*, Location*> p) { return m[0] == p.first->lid() && m[1] == p.second->lid(); });
212  if (sit == modes.cend()) {
213  mErrorMsg.emplace_back("Incompatible modes for activities "+to_string(a1->aid())+" and "+to_string(a2->aid())+"!");
214  mErrorMsg.emplace_back("Check the used algorithm!");
215  }
216  } catch (...) {
217  string msg = "Cannot find the modes for static activities "+to_string(a1->aid())+" and "+to_string(a2->aid())+"!";
218  throw_with_nested(SolverException(caller(), msg+"\nCorrupted data structures!"));
219  }
220  }
221 }
222 
224  for (const pair<ActivityMode*, ActivityMode*>& col : mLine.collisions()) {
225  try {
226  ActivityMode *am1 = col.first, *am2 = col.second;
227  uint32_t m1 = am1->id(), m2 = am2->id();
228  Activity *a1 = am1->baseParent(), *a2 = am2->baseParent();
229 
230  array<uint32_t, 2> selectedModes = extractModes(a1, a2);
231  if (selectedModes[0] == m1 && selectedModes[1] == m2) {
232  double cycleTime = mLine.productionCycleTime();
233  const auto& p1 = getValue(mSolution.startTimeAndDuration, a1->aid(), caller());
234  const auto& p2 = getValue(mSolution.startTimeAndDuration, a2->aid(), caller());
235 
236  double d1 = p1.second, d2 = p2.second;
237  double s1 = fmod(p1.first, cycleTime), s2 = fmod(p2.first, cycleTime);
238  if (s1 > s2) {
239  swap(s1, s2);
240  swap(d1, d2);
241  }
242 
243  if (!(s2+d2-s1 <= cycleTime+TIME_TOL && s1+d1 <= s2+TIME_TOL)) {
244  mErrorMsg.emplace_back("Collision for activity "+to_string(a1->aid())+" (mode "+to_string(m1)
245  +") and "+to_string(a2->aid())+" (mode "+to_string(m2)+") not resolved!");
246  mErrorMsg.emplace_back("Please check the collision resolution in the selected algorithm.");
247  }
248  }
249  } catch (...) {
250  // Collision not applicable...
251  }
252  }
253 }
254 
255 array<uint32_t, 2> SolutionChecker::extractModes(Activity* a1, Activity* a2) const {
256 
257  uint32_t i = 0;
258  array<uint32_t, 2> mode = {{ 0xffffffff, 0xffffffff }};
259  for (Activity* a : {a1, a2}) {
260  StaticActivity *sa = dynamic_cast<StaticActivity*>(a);
261  if (sa != nullptr) {
262  uint32_t point = getValue(mSolution.pointAndPowerMode, sa->aid(), caller()).first;
263  mode[i] = getValue(mMapping.pointToLocation, point, caller())->lid();
264  }
265 
266  DynamicActivity *da = dynamic_cast<DynamicActivity*>(a);
267  if (da != nullptr)
268  mode[i] = getSelectedMovement(mSolution, mMapping, da)->mid();
269 
270  ++i;
271  }
272 
273  return mode;
274 }
275 
The instance of the class corresponds to a robot movement between two coordinates.
Definition: RoboticLine.h:133
The base class incorporating common properties of robot operations and movements. ...
Definition: RoboticLine.h:68
Instance of this class includes all the data structures and methods related to a robot.
Definition: RoboticLine.h:432
void checkDynamicActivities()
Checks durations of dynamic activities.
void checkCriterion()
Checks the calculation of the criterion.
Collection of movements between two static activities.
Definition: RoboticLine.h:261
void checkStaticActivities()
Checks durations of static activities.
STL namespace.
An instance of this class is devoted to the solution checking.
A general exception of the program.
Definition: Exceptions.h:58
constexpr double CRITERION_RTOL
A maximal relative tolerance of the criterion error imposed by the piece-wise linearization of energy...
bool checkAll()
It calls the private member methods to verify the solution.
constexpr double TIME_TOL
A minimal time difference that is considered significant for a solution.
void checkGlobalConstraints()
Verifies that time lags and spatial compatibility are not violated.
Collection of locations in which a robot operation (or waiting) can be performed. ...
Definition: RoboticLine.h:304
std::array< uint32_t, 2 > extractModes(Activity *a1, Activity *a2) const
Auxiliary method that extracts selected modes (location/movement identifications) for activities a1 a...
void checkCollisionZones()
Verifies that a collision does not occur in the solution.
double energyConsumption(double duration, const uint32_t &lid, const uint32_t &pid) const
It represents the power saving mode of the robot.
Definition: RoboticLine.h:359
void checkScheduleContinuity()
Checks whether each robot path is closed and hamiltonian (each static activity is visited just once)...
Instance of TimeLag class defines a time relation between two different robots.
Definition: RoboticLine.h:481
Movement * getSelectedMovement(const Solution &s, const PrecalculatedMapping &m, DynamicActivity *da)
It extracts the selected movement of the given dynamic activity from the solution.
double energyConsumption(const double &duration) const
Definition: RoboticLine.cpp:31
void checkProductionCycleTime()
Checks whether the robot, i.e. production, cycle time is met.
The inter-robot operation corresponding to the workpiece/weldment handling.
Definition: RoboticLine.h:518
Either a movement or location.
Definition: RoboticLine.h:54
The file declares a class responsible for checking of solutions.
The file defines allowed inaccuracies in a solution and constants for floats.