solver  1.0
XmlReader.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 <map>
20 #include <iostream>
21 #include <string>
22 #include <typeinfo>
23 #include <utility>
24 
25 #if VALIDATE_INPUT_FILE_AGAINST_SCHEMA == 1
26 #include <libxml++/validators/schemavalidator.h>
27 #endif
28 
29 #include "SolverConfig.h"
30 #include "RoboticLine.h"
31 #include "Shared/Utils.h"
32 #include "Shared/Exceptions.h"
35 
36 using namespace std;
37 using namespace xmlpp;
38 
40  DomParser parser(Settings::DATASET_FILE);
41  if (parser) {
42  #ifdef VALIDATE_INPUT_FILE_AGAINST_SCHEMA
43  SchemaValidator validator(string(INSTALL_PATH)+"/share/"+string(PROJECT_NAME)+"-"+string(PROGRAM_VERSION)+"/dataset.xsd");
44  validator.validate(parser.get_document());
45  #endif
46  const Node *rootNode = parser.get_document()->get_root_node();
47  processRootNode(rootNode);
48  } else {
49  throw InvalidDatasetFile(caller(), "Cannot parse input xml file!");
50  }
51 }
52 
53 void XmlReader::processRootNode(const Node *rootNode) {
54  const Element *dataset = castToElement(rootNode);
55  mDatasetName = getTextFromElement(dataset, "name", false);
56  mDatasetDescription = getTextFromElement(dataset, "desc", false);
57  mLines.clear();
58 
59  const Node::NodeList instances = dataset->get_children("instance");
60  for (Node *instance : instances)
61  processInstanceNode(instance);
62 }
63 
64 void XmlReader::processInstanceNode(const Node *node) {
65  RoboticLine line;
66  mAidToActivity.clear();
67  mPointToLocation.clear();
68  const Element *instance = castToElement(node);
69  line.name(getTextFromElement(instance, "name", false));
70  line.description(getTextFromElement(instance, "desc", false));
71 
72  const NodeSet robots = instance->find("robots/robot");
73  for (const Node* robotNode : robots)
74  line.addRobot(processRobotNode(robotNode));
75 
76  for (Robot* r : line.robots()) {
77  for (Activity *a : r->activities())
78  setValue(mAidToActivity, a->aid(), a, caller());
79  }
80 
81  const NodeSet interRobotOperations = instance->find("inter-robot-operations/operation");
82  for (const Node* op : interRobotOperations)
83  line.addInterRobotOperation(processInterRobotOperationNode(op));
84 
85  const NodeSet collisionZones = instance->find("collision-zones/collision-pair");
86  for (const Node* cz : collisionZones)
87  line.addCollision(processCollisionPairNode(cz));
88 
89  line.productionCycleTime(stod(getTextFromElement(instance, "production-cycle-time")));
90  line.initialiseDataStructures(mPointToLocation, mMovementToPoints);
91 
92  PrecalculatedMapping mapping;
93  mapping.aidToActivity = move(mAidToActivity);
94  mapping.pointToLocation = move(mPointToLocation);
95 
96  mLines.push_back(line);
97  mMapping.push_back(move(mapping));
98 }
99 
100 Robot* XmlReader::processRobotNode(const Node *robotNode) {
101 
102  Robot* robot = new Robot();
103  const Element *robotElement = castToElement(robotNode);
104 
105  robot->name(getTextFromElement(robotElement, "name", false));
106  robot->description(getTextFromElement(robotElement, "desc", false));
107 
108  // In reality it is vector<Node*> powerModes!
109  const NodeSet powerModes = robotElement->find("power-saving-modes/power-mode");
110  for (const Node* node : powerModes) {
111  const Element *powerMode = castToElement(node);
112  RobotPowerMode *mode = new RobotPowerMode(stoul(getAttribute(powerMode, "pid")));
113  mode->name(getTextFromElement(powerMode, "name", false));
114  mode->description(getTextFromElement(powerMode, "desc", false));
115  mode->minimalDelay(stod(getTextFromElement(powerMode, "minimal-idle-time")));
116  string inputPower = getTextFromElement(powerMode, "expected-input-power", false);
117  if (!inputPower.empty())
118  mode->expectedInputPower(stod(inputPower));
119 
120  robot->addPowerMode(mode);
121  }
122 
123  const NodeSet staticActivities = robotElement->find("activities/static-activity");
124  for (const Node* node : staticActivities)
125  robot->addActivity(processStaticActivityNode(node, robot->powerModes()));
126 
127  const NodeSet dynamicActivities = robotElement->find("activities/dynamic-activity");
128  for (const Node* node : dynamicActivities)
129  robot->addActivity(processDynamicActivityNode(node));
130 
131  return robot;
132 }
133 
134 StaticActivity* XmlReader::processStaticActivityNode(const Node *node, const vector<RobotPowerMode*>& robotModes) {
135 
136  const Element *activityElement = castToElement(node);
137  StaticActivity *staticActivity = new StaticActivity(stoul(getAttribute(activityElement, "aid")));
138 
139  if (getAttribute(activityElement, "last_in_cycle", false) == "true")
140  staticActivity->lastInCycle(true);
141 
142  staticActivity->name(getTextFromElement(activityElement, "name", false));
143  staticActivity->description(getTextFromElement(activityElement, "desc", false));
144 
145  const NodeSet locations = activityElement->find("locations/location");
146  for (const Node *loc : locations) {
147 
148  const Element *locElement = castToElement(loc);
149  Location *location = new Location(stoul(getAttribute(locElement, "lid")));
150  location->point(stoul(getTextFromElement(locElement, "point")));
151 
152  const NodeSet locDepPowerInput = locElement->find("location-dependent-power-consumption/consumption");
153  for (const Node *ldpcNode : locDepPowerInput) {
154  const Element *ldpcElement = castToElement(ldpcNode);
155  uint32_t pid = stoul(getAttribute(ldpcElement, "pid"));
156  auto spmIt = find_if(robotModes.cbegin(), robotModes.cend(), [pid](RobotPowerMode* m) { return pid == m->pid(); });
157  if (spmIt != robotModes.cend()) {
159  ldpc.inputPower(stod(getAttribute(ldpcElement, "input_power")));
160  location->addLocationDependentPowerConsumption(ldpc);
161  } else {
162  throw InvalidDatasetFile(caller(), "Invalid value of 'pid' attribute!", ldpcNode->get_line());
163  }
164  }
165 
166  location->parent(staticActivity);
167  setValue(mPointToLocation, location->point(), location, caller());
168 
169  staticActivity->addLocation(location);
170  }
171 
172  double minimalDelay = F64_MAX;
173  for (RobotPowerMode* pwrm : robotModes)
174  minimalDelay = min(minimalDelay, pwrm->minimalDelay());
175 
176  staticActivity->minAbsDuration(max(minimalDelay, stod(getTextFromElement(activityElement, "min-duration"))));
177  staticActivity->maxAbsDuration(stod(getTextFromElement(activityElement, "max-duration")));
178  // Workaround the incapability of ILP solvers to solve the problem with the fixed variables.
179  if (abs(staticActivity->maxAbsDuration()-staticActivity->minAbsDuration()) < TIME_ERR)
180  staticActivity->maxAbsDuration(staticActivity->minAbsDuration()+TIME_ERR);
181 
182  return staticActivity;
183 }
184 
186 
187  const Element *activityElement = castToElement(node);
188  DynamicActivity *dynamicActivity = new DynamicActivity(stoul(getAttribute(activityElement, "aid")));
189 
190  dynamicActivity->name(getTextFromElement(activityElement, "name", false));
191  dynamicActivity->description(getTextFromElement(activityElement, "desc", false));
192 
193  double minDuration = F64_MAX, maxDuration = F64_MIN;
194  const Element *movements = castToElement(activityElement->get_first_child("movements"));
195  string a1 = getAttribute(movements, "from_aid", false), a2 = getAttribute(movements, "to_aid", false);
196  int64_t fromActivity = (a1.empty() ? -1 : stol(a1)), toActivity = (a2.empty() ? -1 : stol(a2));
197 
198  const NodeSet movementSet = movements->find("movement");
199  for (const Node *movementNode : movementSet) {
200  const Element *movementElement = castToElement(movementNode);
201 
202  Movement *mv = new Movement(stoul(getAttribute(movementElement, "mid")));
203  mv->minDuration(stod(getTextFromElement(movementElement, "min-duration")));
204  mv->maxDuration(stod(getTextFromElement(movementElement, "max-duration")));
205  if (mv->minDuration() < TIME_ERR) {
206  string mid = to_string(mv->mid());
207  delete dynamicActivity; delete mv;
208  throw InvalidDatasetFile(caller(), "Dynamic activity's movement "+mid+" must have positive duration!", movementNode->get_line());
209  }
210 
211  // Workaround the incapability of ILP solvers to solve the problem with the fixed variables.
212  if (abs(mv->maxDuration()-mv->minDuration()) < TIME_ERR)
213  mv->maxDuration(mv->minDuration()+TIME_ERR);
214 
215  const NodeSet monomials = movementElement->find("energy-function/monomial");
216  for (const Node *monomialNode : monomials) {
217  const Element *monomialElement = castToElement(monomialNode);
218  int32_t degree = stoi(getAttribute(monomialElement, "degree"));
219  double coeff = stod(getAttribute(monomialElement, "coeff"));
220  mv->addMonomial({degree, coeff});
221  }
222 
223  uint32_t fromPoint = stoul(getTextFromElement(movementElement, "from-point"));
224  uint32_t toPoint = stoul(getTextFromElement(movementElement, "to-point"));
225 
226  Location *locFrom = nullptr, *locTo = nullptr;
227  StaticActivity *actFrom = nullptr, *actTo = nullptr;
228  auto fSit = mPointToLocation.find(fromPoint), tSit = mPointToLocation.find(toPoint);
229  if (fSit == mPointToLocation.cend() || tSit == mPointToLocation.cend()) {
230  delete dynamicActivity; delete mv;
231  throw InvalidDatasetFile(caller(), "Dynamic activity's point is not linked with any static activity!", movementNode->get_line());
232  } else {
233  locFrom = fSit->second; locTo = tSit->second;
234  assert(locFrom != nullptr && locTo != nullptr && "Unexpected null pointers!");
235  actFrom = locFrom->parent(); actTo = locTo->parent();
236  assert(actFrom != nullptr && actTo != nullptr && "Should not be nullptr as it has been set in processRobotNode method!");
237  }
238 
239  if ((actFrom->aid() != fromActivity && fromActivity != -1) || (actTo->aid() != toActivity && toActivity != -1)) {
240  delete dynamicActivity; delete mv;
241  throw InvalidDatasetFile(caller(), "Optional attributes 'from_aid' and/or 'to_aid' are invalid!", movementNode->get_line());
242  }
243 
244  minDuration = min(minDuration, mv->minDuration());
245  maxDuration = max(maxDuration, mv->maxDuration());
246  setValue(mMovementToPoints, mv, {fromPoint, toPoint}, caller());
247 
248  dynamicActivity->addMovement(mv);
249  }
250 
251  dynamicActivity->minAbsDuration(minDuration);
252  dynamicActivity->maxAbsDuration(maxDuration);
253 
254  return dynamicActivity;
255 }
256 
257 InterRobotOperation* XmlReader::processInterRobotOperationNode(const Node *interRobotOperationNode) const {
258 
259  const Element *operationElement = castToElement(interRobotOperationNode);
260  InterRobotOperation* operation = new InterRobotOperation(stoul(getAttribute(operationElement, "oid")));
261  operation->name(getTextFromElement(operationElement, "name", false));
262  operation->description(getTextFromElement(operationElement, "desc", false));
263 
264  const NodeSet timeLags = operationElement->find("time-compatibility/time-lag");
265  for (const Node *timeLagNode : timeLags) {
266  const Element *timeLagElement = castToElement(timeLagNode);
267 
268  uint32_t fromId = stoul(getTextFromElement(timeLagElement, "from-activity"));
269  uint32_t toId = stoul(getTextFromElement(timeLagElement, "to-activity"));
270  double length = stod(getTextFromElement(timeLagElement, "length"));
271  int32_t height = stoi(getTextFromElement(timeLagElement, "height"));
272 
273  auto sit1 = mAidToActivity.find(fromId), sit2 = mAidToActivity.find(toId);
274  if (sit1 != mAidToActivity.cend() && sit2 != mAidToActivity.cend()) {
275  operation->addTimeLag({sit1->second, sit2->second, length, height});
276  } else {
277  string msg = "Time lag '"+to_string(fromId)+" -> "+to_string(toId)+"' between non-existing activities!\n";
278  throw InvalidDatasetFile(caller(), msg+"Invalid inter-robot time-lag!", timeLagNode->get_line());
279  }
280  }
281 
282  const NodeSet spaceComp = operationElement->find("spatial-compatibility/compatible-pair");
283  for (const Node *cmpPairNode : spaceComp) {
284  const Element *cmpPairElement = castToElement(cmpPairNode);
285  const NodeSet locations = cmpPairElement->find("location");
286 
287  try {
288  vector<Location*> cmpPair;
289  for (uint32_t i = 0; i < min(locations.size(), 2ul); ++i) {
290  const Element *locElement = castToElement(locations[i]);
291  auto sit = mAidToActivity.find(stoul(getAttribute(locElement, "aid")));
292  if (sit != mAidToActivity.cend()) {
293  StaticActivity *sa = dynamic_cast<StaticActivity*>(sit->second);
294  if (sa != nullptr) {
295  Location *l = sa->findLocation(stoul(getAttribute(locElement, "lid")));
296  cmpPair.push_back(l);
297  } else {
298  string msg = "Spatial compatibility can only be resolved between static activities!";
299  throw InvalidDatasetFile(caller(), msg, locElement->get_line());
300  }
301  } else {
302  throw InvalidDatasetFile(caller(), "Invalid 'aid' attribute in '<location>' element!", locElement->get_line());
303  }
304  }
305 
306  if (cmpPair.size() == 2)
307  operation->addSpatialCompatibilityPair(cmpPair.front(), cmpPair.back());
308  else
309  throw InvalidDatasetFile(caller(), "Invalid number of elements in the '<compatible-pair>' element!");
310 
311  } catch (...) {
312  throw_with_nested(InvalidDatasetFile(caller(), "Cannot process the spatial compatibility pair!", cmpPairElement->get_line()));
313  }
314  }
315 
316  return operation;
317 }
318 
319 pair<ActivityMode*, ActivityMode*> XmlReader::processCollisionPairNode(const xmlpp::Node *collisionPairNode) const {
320 
321  const Element *collisionPairElement = castToElement(collisionPairNode);
322 
323  vector<ActivityMode*> collisionPair;
324  NodeSet locations = collisionPairElement->find("location");
325  NodeSet movements = collisionPairElement->find("movement");
326 
327  for (const Node *locNode : locations) {
328  const Element *locElement = castToElement(locNode);
329  auto sit = mAidToActivity.find(stoul(getAttribute(locElement, "aid")));
330  if (sit != mAidToActivity.cend()) {
331  StaticActivity* sa = dynamic_cast<StaticActivity*>(sit->second);
332  if (sa != nullptr) {
333  collisionPair.emplace_back(sa->findLocation(stoul(getAttribute(locElement, "lid"))));
334  } else {
335  string msg = "Referenced 'aid' does not correspond to the static activity for 'location' element!";
336  throw InvalidDatasetFile(caller(), msg, locNode->get_line());
337  }
338  } else {
339  throw InvalidDatasetFile(caller(), "Invalid 'aid' attribute in 'location' element!", locNode->get_line());
340  }
341  }
342 
343  for (const Node *mvNode : movements) {
344  const Element *mvElement = castToElement(mvNode);
345  auto sit = mAidToActivity.find(stoul(getAttribute(mvElement, "aid")));
346  if (sit != mAidToActivity.cend()) {
347  DynamicActivity* da = dynamic_cast<DynamicActivity*>(sit->second);
348  if (da != nullptr) {
349  collisionPair.emplace_back(da->findMovement(stoul(getAttribute(mvElement, "mid"))));
350  } else {
351  string msg = "Referenced 'aid' does not correspond to the dynamic activity for 'movement' element!";
352  throw InvalidDatasetFile(caller(), msg, mvNode->get_line());
353  }
354  } else {
355  throw InvalidDatasetFile(caller(), "Invalid 'aid' attribute in 'movement' element!", mvNode->get_line());
356  }
357  }
358 
359  if (collisionPair.size() != 2)
360  throw InvalidDatasetFile(caller(), "Invalid number of child elements in 'collision-pair' element!");
361 
362  return { collisionPair.front(), collisionPair.back() };
363 }
364 
365 const Element* XmlReader::castToElement(const Node *node) const {
366  const Element *element = dynamic_cast<const Element*>(node);
367  if (element == nullptr)
368  throw InvalidDatasetFile(caller(), "Invalid cast to the 'Element*' type!", (node != nullptr ? node->get_line() : -1));
369  return element;
370 }
371 
372 string XmlReader::getAttribute(const Element* node, const string& attributeName, bool required) const {
373  Attribute *attr = node->get_attribute(attributeName);
374  if (attr == nullptr && required) {
375  string msg = "Cannot get '"+attributeName+"' from the '"+string(node->get_name())+"' element!";
376  throw InvalidDatasetFile(caller(), "Failed to get the attribute from the element!", node->get_line());
377  }
378 
379  if (attr != nullptr)
380  return attr->get_value();
381  else
382  return "";
383 }
384 
385 string XmlReader::getTextFromElement(const Element* elementNode, const string& childName, bool required) const {
386  string text;
387  const Node::NodeList children = elementNode->get_children(childName);
388  if (children.size() == 1) {
389  const Element *elementChild = castToElement(children.front());
390  for (const Node *child : elementChild->get_children()) {
391  const TextNode* textNode = dynamic_cast<const TextNode*>(child);
392  if (textNode != nullptr) {
393  if (!textNode->is_white_space())
394  text += textNode->get_content();
395  } else {
396  throw InvalidDatasetFile(caller(), "Invalid cast to 'TextNode*' type!", (child != nullptr ? child->get_line() : -1));
397  }
398  }
399  } else if (children.size() > 1) {
400  throw InvalidDatasetFile(caller(), "Only from one child the text can be retrieved!", elementNode->get_line());
401  } else if (children.empty() && required) {
402  string msg = "Cannot find the '"+string(childName)+"' element!\n";
403  throw InvalidDatasetFile(caller(), msg+"Invalid input xml file!", elementNode->get_line());
404  }
405 
406  return text;
407 }
408 
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
The instance of this class specifies input power of the robot for a particular robot configuration an...
Definition: RoboticLine.h:403
Instance of this class includes all the data structures and methods related to a robot.
Definition: RoboticLine.h:432
const xmlpp::Element * castToElement(const xmlpp::Node *node) const
The method casts xmlpp::Node to xmlpp::Element, and in case of success the result is returned...
Definition: XmlReader.cpp:365
string DATASET_FILE
Dataset with problems to be solved.
Definition: Settings.cpp:27
Collection of movements between two static activities.
Definition: RoboticLine.h:261
std::map< uint32_t, Location * > pointToLocation
Unique coordinate, i.e. a point, to the related location.
void readDatasetFromXmlFile()
It parses the XML dataset (specified in Settings::DATASET_FILE) and checks it against schema...
Definition: XmlReader.cpp:39
STL namespace.
void processInstanceNode(const xmlpp::Node *node)
It parses the problem instance specified by the node.
Definition: XmlReader.cpp:64
std::pair< ActivityMode *, ActivityMode * > processCollisionPairNode(const xmlpp::Node *collisionPairNode) const
It parses an element containing time disjunctive pair, and returns the pointers to colliding location...
Definition: XmlReader.cpp:319
Various auxiliary functions used across the program.
Movement * findMovement(const uint32_t &mid) const
It finds a movement according to its identification.
Definition: RoboticLine.cpp:67
constexpr double TIME_ERR
Minimal recognizable difference in the time.
Thrown if the dataset file contains ill-specified robotic cells.
Definition: Exceptions.h:99
The file defines extended exceptions for the better error handling in the program.
Collection of locations in which a robot operation (or waiting) can be performed. ...
Definition: RoboticLine.h:304
The file declares XmlReader class which purpose is to parse and check XML datasets.
It represents the power saving mode of the robot.
Definition: RoboticLine.h:359
StaticActivity * processStaticActivityNode(const xmlpp::Node *node, const std::vector< RobotPowerMode * > &robotModes)
An instance of the static activity is created from the parsed data and power saving modes of the robo...
Definition: XmlReader.cpp:134
std::string getTextFromElement(const xmlpp::Element *element, const std::string &childName, bool required=true) const
It tries to extract the text from the child element, e.g. getTextFromElement(instanceElement, "name", false) returns the name of the instance or an empty string if the name is not specified.
Definition: XmlReader.cpp:385
Location * findLocation(const uint32_t &lid) const
It finds a location according to its identification.
The inter-robot operation corresponding to the workpiece/weldment handling.
Definition: RoboticLine.h:518
Robot * processRobotNode(const xmlpp::Node *robotNode)
It parses the data related to the robot, and creates the instance of Robot class. ...
Definition: XmlReader.cpp:100
The file defines allowed inaccuracies in a solution and constants for floats.
std::string getAttribute(const xmlpp::Element *node, const std::string &attributeName, bool required=true) const
It parses the value assigned to the specified attribute and returns it in the form of string...
Definition: XmlReader.cpp:372
void initialiseDataStructures(const std::map< uint32_t, Location * > &pointToLocation, const std::map< Movement *, std::pair< uint32_t, uint32_t >> &movementToPoints)
It recursively sets parents and initializes relations between activities.
DynamicActivity * processDynamicActivityNode(const xmlpp::Node *node)
An instance of the dynamic activity is created from the parsed data.
Definition: XmlReader.cpp:185
Location of the robot used either during work (welding) or waiting.
Definition: RoboticLine.h:192
void processRootNode(const xmlpp::Node *rootNode)
It parses all the dataset and stores the data to member variables.
Definition: XmlReader.cpp:53
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.
The file contains various classes devoted to abstract representation of the robotic cell...
InterRobotOperation * processInterRobotOperationNode(const xmlpp::Node *interRobotOperationNode) const
It creates a new inter-robot operation from parsed data.
Definition: XmlReader.cpp:257
std::map< uint32_t, Activity * > aidToActivity
Activity identification to its pointer.