RcsPySim
A robot control and simulation library
ExperimentConfig.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  Copyright (c) 2020, Fabio Muratore, Honda Research Institute Europe GmbH, and
3  Technical University of Darmstadt.
4  All rights reserved.
5 
6  Redistribution and use in source and binary forms, with or without
7  modification, are permitted provided that the following conditions are met:
8  1. Redistributions of source code must retain the above copyright
9  notice, this list of conditions and the following disclaimer.
10  2. Redistributions in binary form must reproduce the above copyright
11  notice, this list of conditions and the following disclaimer in the
12  documentation and/or other materials provided with the distribution.
13  3. Neither the name of Fabio Muratore, Honda Research Institute Europe GmbH,
14  or Technical University of Darmstadt, nor the names of its contributors may
15  be used to endorse or promote products derived from this software without
16  specific prior written permission.
17 
18  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  DISCLAIMED. IN NO EVENT SHALL FABIO MURATORE, HONDA RESEARCH INSTITUTE EUROPE GMBH,
22  OR TECHNICAL UNIVERSITY OF DARMSTADT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26  IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  POSSIBILITY OF SUCH DAMAGE.
29 *******************************************************************************/
30 
31 #include "ExperimentConfig.h"
32 #include "action/AMNormalized.h"
35 #include "observation/OMPartial.h"
37 
38 #include <Rcs_resourcePath.h>
39 #include <Rcs_macros.h>
40 #include <Rcs_basicMath.h>
41 
42 #include <map>
43 #include <string>
44 #include <stdexcept>
45 #include <sstream>
46 #include <Rcs_parser.h>
47 #include <Rcs_collisionModel.h>
48 
49 namespace Rcs
50 {
51 
52 // Utilities for collision model
53 static inline void
54 copy_prop_to_xml_attr(xmlNodePtr node, PropertySource* source, const char* name, const char* xmlName = nullptr)
55 {
56  if (xmlName == nullptr) {
57  xmlName = name;
58  }
59  // Get string value from source. Falls back to xmlName to allow direct copies. Some of the names are just atrocious.
60  std::string value;
61  if (source->getProperty(value, name) || source->getProperty(value, xmlName)) {
62  // Set to node
63  xmlSetProp(node, BAD_CAST xmlName, BAD_CAST value.c_str());
64  }
65 }
66 
67 RcsCollisionMdl* RcsCollisionModel_createFromConfig(RcsGraph* graph, PropertySource* config)
68 {
69  // Check for filename
70  std::string file;
71  if (config->getProperty(file, "file")) {
72 
73  char txt[256];
74  bool fileExists = Rcs_getAbsoluteFileName(file.c_str(), txt);
75 
76  if (!fileExists) {
77  RMSG("Resource path is:");
78  Rcs_printResourcePath();
79 
80  std::ostringstream os;
81  os << "Could not find collision model file: " << file;
82  throw std::invalid_argument(os.str());
83  }
84 
85  // Load from xml file
86  xmlDocPtr doc = nullptr;
87  xmlNodePtr node = parseXMLFile(txt, "CollisionModel", &doc);
88  if (!node) {
89  throw std::invalid_argument("Error parsing collision model XML.");
90  }
91 
92  // Create the object
93  auto collisionMdl = RcsCollisionModel_createFromXML(graph, node);
94  // free xml
95  xmlFreeDoc(doc);
96 
97  if (!collisionMdl) {
98  throw std::invalid_argument("Error in collision model configuration! (see stderr)");
99  }
100  return collisionMdl;
101  }
102 
103  // Convert collision config to XML.
104  // While this is kind of unnecessary if config is backed by XML, we cannot do this generically for python.
105  xmlDocPtr doc = xmlNewDoc(nullptr);
106  xmlNodePtr cmNode = xmlNewDocNode(doc, nullptr, BAD_CAST "CollisionModel", nullptr);
107  xmlDocSetRootElement(doc, cmNode);
108 
109  copy_prop_to_xml_attr(cmNode, config, "threshold");
110  copy_prop_to_xml_attr(cmNode, config, "mixtureCost", "MixtureCost");
111  copy_prop_to_xml_attr(cmNode, config, "penetrationSlope", "PenetrationSlope");
112 
113  // Convert reference to pointer to allow both 'pairs' and 'CollisionPair' child list.
114  // The latter is for xml compatibility.
115  auto pairs = &config->getChildList("pairs");
116  if (pairs->empty()) {
117  pairs = &config->getChildList("CollisionPair");
118  }
119  for (auto child : *pairs) {
120  xmlNodePtr pairNode = xmlNewDocNode(doc, nullptr, BAD_CAST "CollisionPair", nullptr);
121  xmlAddChild(cmNode, pairNode);
122 
123  copy_prop_to_xml_attr(pairNode, child, "body1");
124  copy_prop_to_xml_attr(pairNode, child, "body2");
125 
126  copy_prop_to_xml_attr(pairNode, child, "threshold");
127  copy_prop_to_xml_attr(pairNode, child, "weight");
128  }
129 
130  // Create the object
131  auto collisionMdl = RcsCollisionModel_createFromXML(graph, cmNode);
132 
133  // Free temporary xml
134  xmlFreeDoc(doc);
135 
136  // Throw exception if collision model failed to create
137  if (!collisionMdl) {
138  throw std::invalid_argument("Error in collision model configuration! (see stderr)");
139  }
140  return collisionMdl;
141 }
142 
143 // Experiment type registry
144 static std::map<std::string, ExperimentConfig::ExperimentConfigCreateFunction> registry;
145 
147 {
148  // Store in registry
149  registry[name] = creator;
150 }
151 
153 {
154  ExperimentConfig* result;
155  try {
156  // Get experiment name
157  std::string key;
158  if (!properties->getProperty(key, "envType")) {
159  throw std::invalid_argument("Property source is missing 'envType' entry.");
160  }
161 
162  // Look up factory
163  auto iter = registry.find(key);
164  if (iter == registry.end()) {
165  std::ostringstream os;
166  os << "Unknown experiment type '" << key << "'.";
167  throw std::invalid_argument(os.str());
168  }
169 
170  // Create instance
171  result = iter->second();
172 
173  } catch (...) {
174  // Up to here, this method owns properties, so we must delete them on error
175  delete properties;
176  // The last part that could throw is the creation of result, so we don't need to delete it
177  throw;
178  }
179 
180  // Load transfers ownership of properties to result
181  try {
182  result->load(properties);
183  return result;
184  } catch (...) {
185  // Error, make sure to delete result (will also delete properties)
186  delete result;
187  throw;
188  }
189 }
190 
192 {
193  properties = nullptr;
194  actionModel = nullptr;
195  observationModel = nullptr;
196  dt = 0.01;
197 }
198 
200 {
201  // Delete stored models
202  delete observationModel;
203  delete actionModel;
204 
205  RcsCollisionModel_destroy(collisionMdl);
206  // Destroy graph
207  RcsGraph_destroy(graph);
208 
209  // Delete property source
210  delete properties;
211 }
212 
214 {
215  this->properties = properties;
216 
217  // Init seed
218  int seed;
219  if (properties->getProperty(seed, "seed")) {
220  Math_srand48(seed);
221  }
222 
223  // Init Rcs config dir
224  Rcs_addResourcePath(RCS_CONFIG_DIR);
225  // Read config dir from properties
226  std::string extraConfigDir;
227  if (properties->getProperty(extraConfigDir, "extraConfigDir")) {
228  Rcs_addResourcePath(extraConfigDir.c_str());
229  }
230 
231  // Init graph (this is NOT the graph form the physics simulation)
232  std::string graphFileName = "gScenario.xml";
233  properties->getProperty(graphFileName, "graphFileName");
234  graph = RcsGraph_create(graphFileName.c_str());
235  if (!graph) {
236  throw std::invalid_argument("Graph not found: " + graphFileName);
237  }
238 
239  // Load collision model - action and observation models might use it
240  auto collCfg = properties->getChild("collisionConfig");
241  if (collCfg->exists()) {
243  }
244  else {
245  collisionMdl = nullptr;
246  }
247 
248  // Create action model (the graph is specified in the associated experiment config file)
250  RCHECK(actionModel);
251 
252  // Add normalized action model if desired
253  if (properties->getPropertyBool("normalizeActions", false)) {
254  // The temp var suppresses a UNINIT.HEAP.MUST from klocwork
255  auto inner = actionModel;
256  actionModel = new AMNormalized(inner);
257  }
258 
260  RCHECK(observationModel);
261  // Add normalized observation model if desired
262  if (properties->getPropertyBool("normalizeObservations")) {
263  // Load bound overrides
264  PropertySource* minOverride = properties->getChild("obsNormOverrideLower");
265  PropertySource* maxOverride = properties->getChild("obsNormOverrideUpper");
266 
267  // The temp var suppresses a wrong UNINIT.HEAP.MUST from klocwork
268  auto inner = observationModel;
269  observationModel = new OMNormalized(inner, minOverride, maxOverride);
270  }
271 
272  // Make observation partial if desired
273  auto partialObs = properties->getChild("partialObservation");
274  if (partialObs) {
275  bool exclude = partialObs->getPropertyBool("exclude");
276  std::vector<std::string> names;
277  if (partialObs->getProperty(names, "names")) {
278  auto inner = observationModel;
279  bool autoSelectVelocity = partialObs->getPropertyBool("autoSelectVelocity");
280  observationModel = OMPartial::fromNames(inner, names, exclude, autoSelectVelocity);
281  }
282  }
283 
284  // Load additional common properties
285  properties->getProperty(dt, "dt");
286  checkJointLimits = properties->getPropertyBool("checkJointLimits", true);
287 }
288 
290 {
291  // Return nullptr by default to use state model
292  return nullptr;
293 }
294 
296 {
297  // Return nullptr by default to use graph state from xml
298  return nullptr;
299 }
300 
302 {
303  // Return nullptr by default to skip
304  return nullptr;
305 }
306 
308 {
309  // read general physics properties
310  std::string physicsEngine = "Bullet";
311  std::string physicsConfigFile = "physics/physics.xml";
312 
313  properties->getProperty(physicsEngine, "physicsEngine");
314  properties->getProperty(physicsConfigFile, "physicsConfigFile");
315 
316  // Create manager and populate it
317  PhysicsParameterManager* manager = new PhysicsParameterManager(graph, physicsEngine, physicsConfigFile);
318  populatePhysicsParameters(manager);
319 
320  // Try to create simulator to catch config errors eagerly
321  PhysicsBase* testSim = manager->createSimulator(PropertySource::empty());
322  if (testSim) {
323  // Works, delete it again
324  delete testSim;
325  }
326  else {
327  // Does not work, abort here and report error
328  delete manager;
329  std::ostringstream os;
330  os << "Unable to create physics simulator using " << physicsEngine << " engine.";
331  throw std::invalid_argument(os.str());
332  }
333 
334  return manager;
335 }
336 
337 void ExperimentConfig::initViewer(Rcs::Viewer* viewer)
338 {
339  // Do nothing by default
340 }
341 
343 {
344  // Do nothing by default
345 }
346 
348  std::vector<std::string>& linesOut,
349  double currentTime,
350  const MatNd* currentObservation,
351  const MatNd* currentAction,
352  PhysicsBase* simulator,
353  PhysicsParameterManager* physicsManager,
354  ForceDisturber* forceDisturber)
355 {
356  // Obtain simulator name
357  const char* simName;
358  if (simulator != nullptr) {
359  simName = simulator->getClassName();
360  }
361  else{
362  simName = "Robot";
363  }
364 
365  char hudText[2048];
366  sprintf(hudText, "physics engine: %s simulation time: %2.3f s", simName, currentTime);
367  linesOut.emplace_back(hudText);
368 }
369 
371  double currentTime,
372  const MatNd* currentObservation,
373  const MatNd* currentAction,
374  PhysicsBase* simulator,
375  PhysicsParameterManager* physicsManager,
376  ForceDisturber* forceDisturber)
377 {
378  // Get lines using getHUDText function for the specific ExperimentConfig
379  std::vector<std::string> lines;
380  getHUDText(lines, currentTime, currentObservation, currentAction, simulator, physicsManager, forceDisturber);
381 
382  // Concat the lines
383  std::ostringstream os;
384  for (size_t i = 0; i < lines.size(); ++i) {
385  if (i > 0) { os << '\n'; }
386  os << lines[i];
387  }
388 
389  return os.str();
390 }
391 
392 } /* namespace Rcs */
virtual bool getProperty(std::string &out, const char *property)=0
virtual bool getPropertyBool(const char *property, bool def=false)=0
PhysicsBase * createSimulator(PropertySource *values)
virtual void initViewer(Rcs::Viewer *viewer)
virtual ActionModel * createActionModel()=0
virtual PropertySource * getChild(const char *prefix)=0
virtual ForceDisturber * createForceDisturber()
bool checkJointLimits
Flag to enable joint limit checking.
double dt
The time step [s].
virtual const std::vector< PropertySource * > & getChildList(const char *prefix)=0
virtual ObservationModel * createObservationModel()=0
static std::map< std::string, ControlPolicy::ControlPolicyCreateFunction > registry
static void copy_prop_to_xml_attr(xmlNodePtr node, PropertySource *source, const char *name, const char *xmlName=nullptr)
static PropertySource * empty()
virtual void load(PropertySource *properties)
virtual InitStateSetter * createInitStateSetter()
static ExperimentConfig * create(PropertySource *properties)
ObservationModel * observationModel
Observation model (pluggable) used to create the observation which will be returned from step() ...
PropertySource * properties
Property source (owned)
virtual void getHUDText(std::vector< std::string > &linesOut, double currentTime, const MatNd *currentObservation, const MatNd *currentAction, PhysicsBase *simulator, PhysicsParameterManager *physicsManager, ForceDisturber *forceDisturber)
static OMPartial * fromNames(ObservationModel *wrapped, const std::vector< std::string > &names, bool exclude=false, bool autoSelectVelocity=false)
Definition: OMPartial.cpp:133
RcsCollisionMdl * RcsCollisionModel_createFromConfig(RcsGraph *graph, PropertySource *config)
ExperimentConfig *(* ExperimentConfigCreateFunction)()
ActionModel * actionModel
Action model (pluggable)
RcsCollisionMdl * collisionMdl
Collision model to use. Is based on the graph, so take care when using with IK etc.
virtual void populatePhysicsParameters(PhysicsParameterManager *manager)
static void registerType(const char *name, ExperimentConfigCreateFunction creator)
RcsGraph * graph
Graph description.
PhysicsParameterManager * createPhysicsParameterManager()