RcsPySim
A robot control and simulation library
RcsSimEnv.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 "RcsSimEnv.h"
32 #include "action/ActionModel.h"
33 #include "action/ActionModelIK.h"
36 #include "physics/ForceDisturber.h"
37 
38 #include <Rcs_typedef.h>
39 #include <Rcs_macros.h>
40 #include <Rcs_VecNd.h>
41 #include <Rcs_basicMath.h>
42 
43 #ifdef GRAPHICS_AVAILABLE
44 
45 #include <RcsViewer.h>
46 #include <HUD.h>
47 #include <GraphNode.h>
48 #include <PhysicsNode.h>
49 #include <KeyCatcher.h>
50 
51 #endif
52 
53 #include <stdexcept>
54 #include <sstream>
55 
56 
58 {
59  // Set random seed
60  Math_srand48(0);
61 
62  // Load experiment config
63  config = ExperimentConfig::create(propertySource);
64 
65  // Check if all joints are position controlled for skipping a later inverse dynamics control (compliance)
66  unsigned int numPosCtrlJoints = 0;
67  RCSGRAPH_TRAVERSE_JOINTS(config->graph) {
68  if (JNT->jacobiIndex != -1) {
69  if (JNT->ctrlType == RCSJOINT_CTRL_TYPE::RCSJOINT_CTRL_POSITION) {
70  numPosCtrlJoints++;
71  }
72  }
73  }
74  allJointsPosCtrl = config->graph->nJ == numPosCtrlJoints;
75 
76  // Load physics parameter manager from config. Creates a pointer to config->graph
78  // Physics simulator instance is nullptr at the start. It will only be created once reset is called.
79  physicsSim = nullptr;
80 
81  // Create force disturber
83 
84  // Load init state setter
86 
87  // Initialize temporary matrices
88  q_ctrl = MatNd_clone(config->graph->q);
89  qd_ctrl = MatNd_clone(config->graph->q_dot);
90  T_ctrl = MatNd_create(config->graph->dof, 1);
91 
92  // Other state-related stuff
93  viewer = nullptr;
94  usePhysicsNode = propertySource->getPropertyBool("usePhysicsNode", false);
95  hud = nullptr;
98 // adWidgetHandle = -1;
99 
100  currentStep = 0;
101  currentTime = 0;
102 
103  // Initialize transition noise (unused if not explicitly flagged)
104  transitionNoiseBuffer = nullptr;
105  tnbIndex = 0;
106  transitionNoiseIncludeVelocity = propertySource->getPropertyBool("transitionNoiseIncludeVelocity");
107 }
108 
110 {
111  // Close rendering, will free associated resources
112  render("", true);
113 
114  MatNd_destroy(q_ctrl);
115  MatNd_destroy(qd_ctrl);
116  MatNd_destroy(T_ctrl);
117 
118  MatNd_destroy(currentAction);
119  MatNd_destroy(currentObservation);
120 
121  delete initStateSetter;
122  delete physicsSim;
123  delete physicsManager;
124  delete disturber;
125  delete config;
126 }
127 
128 MatNd* Rcs::RcsSimEnv::reset(PropertySource* domainParam, const MatNd* initState)
129 {
130  // Set random seed
131  Math_srand48(0);
132 
133  // Lock the graph
134  std::unique_lock<std::mutex> lock(graphLock);
135 
136  // Set graph to initial state
137  RcsGraph_setDefaultState(config->graph);
138  RCSGRAPH_TRAVERSE_SENSORS(config->graph) {
139  MatNd_setZero(SENSOR->rawData);
140  }
141 
142  // Apply the initial state
143  if (initState && initStateSetter != nullptr) {
144  // TODO find a more flexible solution to set arbitrary init states when needed
145  /*
146  std::string errorMsg;
147  if (!initStateSetter->getSpace()->contains(initState, &errorMsg)) {
148  throw std::invalid_argument("init state space does not contain the init state: " + errorMsg);
149  }
150  */
152  }
153 
154 #ifdef GRAPHICS_AVAILABLE
155  // If we are resetting the simulation the viewer already exists
156  if (viewer && usePhysicsNode) {
157  viewer->removeInternal("PhysicsNode");
158  }
159 #endif
160 
161  // Rebuild physics sim with new parameters
162  // We set it to nullptr before creating the new one since there might be exceptions during parameter loading
163  delete physicsSim;
164  physicsSim = nullptr;
165  physicsSim = physicsManager->createSimulator(domainParam);
166 
167 #ifdef GRAPHICS_AVAILABLE
168  // If we are resetting the simulation the viewer already exists
169  if (viewer) {
170  if (usePhysicsNode) {
171  Rcs::PhysicsNode* pNode = new Rcs::PhysicsNode(physicsSim, true);
172  viewer->add(pNode);
173  pNode->physicsNd->togglePhysicsModel(); // switch off
174  pNode->physicsNd->toggleGraphicsModel(); // switch on
175  }
176  }
177 #endif
178 
179  // Reset control command vectors
180  MatNd_copy(q_ctrl, config->graph->q);
181  MatNd_setZero(qd_ctrl);
182  MatNd_setZero(T_ctrl);
183 
184  // Reset stateful models
187  // Reset the physics simulator
188 // physicsSim->reset(); // this causes a GUI error
189  physicsSim->resetTime();
190 
191  // Unlock the graph
192  lock.unlock();
193 
194  // Reset local counters
195  currentStep = 0;
196  currentTime = 0;
197 
198  // Return initial observation
199  return config->observationModel->computeObservation(nullptr, config->dt);
200 }
201 
202 MatNd* Rcs::RcsSimEnv::step(const MatNd* action, const MatNd* disturbance)
203 {
204  REXEC(6) {
205  std::cout << "--------------------------------------------------" << std::endl;
206  std::cout << "Simulation Step " << currentStep << std::endl;
207  std::cout << "--------------------------------------------------" << std::endl;
208  }
209 
210  // Validate actions
211  std::string iaMsg;
212  if (!actionSpace()->checkDimension(action, &iaMsg)) {
213  throw std::invalid_argument("Invalid action - " + iaMsg);
214  }
215 
216  // Store the action for the HUD
217  MatNd_copy(currentAction, action);
218 
219  // Lock the graph
220  std::unique_lock<std::mutex> lock(graphLock);
221 
222  /*-------------------------------------------------------------------------*
223  * Obtain simulator inputs from action model
224  *-------------------------------------------------------------------------*/
226 
227  if (config->checkJointLimits) {
228  std::ostringstream jvMsg;
229  unsigned int numJV = 0;
230  RCSGRAPH_TRAVERSE_JOINTS(config->graph) {
231  // Skip the non-IK joints
232  if (JNT->constrained) {
233  continue;
234  }
235  // Check the remaining joints' limits
236  double qi = MatNd_get2(q_ctrl, JNT->jointIndex, 0);
237  if ((qi < JNT->q_min) || (qi > JNT->q_max)) {
238  numJV++;
239  jvMsg << " " << JNT->name << ": " << qi << " [" << JNT->q_min << ", " << JNT->q_max << "["
240  << std::endl;
241  }
242  }
243  if (numJV > 0) {
244  std::ostringstream os;
245  os << "Detected " << numJV << " joint limit violations: " << std::endl << jvMsg.str();
246  throw JointLimitException(os.str());
247  }
248  }
249 
250  if (disturbance != nullptr && disturber != nullptr) {
251  // Check disturbance shape
252  if (disturbance->m != 3 || disturbance->n != 1) {
253  throw std::invalid_argument("Invalid disturbance");
254  }
255  disturber->apply(physicsSim, disturbance->ele);
256  }
257 
258  /*-------------------------------------------------------------------------*
259  * Inverse dynamics in joint space (compliance control)
260  *-------------------------------------------------------------------------*/
261  if (!allJointsPosCtrl) {
262  Rcs::ControllerBase::computeInvDynJointSpace(T_ctrl, config->graph, q_ctrl, 1000.);
263  }
264 
265  /*-------------------------------------------------------------------------*
266  * Call the physicssimulation and get the new current state
267  *-------------------------------------------------------------------------*/
268  physicsSim->setControlInput(q_ctrl, qd_ctrl, T_ctrl);
269 
270  physicsSim->simulate(config->dt, config->graph);
271 
272  /*-------------------------------------------------------------------------*
273  * Apply transition noise if desired
274  *-------------------------------------------------------------------------*/
275  if (transitionNoiseBuffer != nullptr) {
276  // Get noise set to use for this timestamp
277  MatNd* noise;
278  MatNd_fromStack(noise, getInternalStateDim(), 1);
279  MatNd_getColumn(noise, tnbIndex, transitionNoiseBuffer);
280 
282  // Apply first part to state and second part to velocity
283  VecNd_addSelf(config->graph->q->ele, noise->ele, config->graph->dof);
284  VecNd_addSelf(config->graph->q_dot->ele, &noise->ele[config->graph->dof], config->graph->dof);
285  }
286  else {
287  // Apply to state values only
288  MatNd_addSelf(config->graph->q, noise);
289  }
290 
291  // Increment buffer index
292  tnbIndex++;
293  if (tnbIndex >= transitionNoiseBuffer->n) {
294  tnbIndex = 0;
295  }
296 
297  // Cleanup
298  MatNd_destroy(noise);
299  }
300 
301  /*-------------------------------------------------------------------------*
302  * Store new physics simulation state in the ExperimentConfig's graph
303  *-------------------------------------------------------------------------*/
304  // Apply forward kinematics
305 // RcsGraph_setState(config->graph, nullptr, config->graph->q_dot); // nullptr leads to using the q vector from the graph
306  RcsGraph_setState(config->graph, config->graph->q, config->graph->q_dot);
307 
308  // Unlock the graph
309  lock.unlock();
310 
311  /*-------------------------------------------------------------------------*
312  * For transition noise, we need to update the state of the physics
313  * simulator to match the state of the graph after applying the noise.
314  *-------------------------------------------------------------------------*/
315  if (transitionNoiseBuffer != nullptr) {
316  RCSGRAPH_TRAVERSE_BODIES(config->graph) {
317  physicsSim->applyTransform(BODY, BODY->A_BI);
318  physicsSim->applyLinearVelocity(BODY, BODY->x_dot);
319  physicsSim->applyAngularVelocity(BODY, BODY->omega);
320  }
321  }
322 
323  // Update step counters
324  currentStep++;
325  currentTime += config->dt;
326 
327  /*-------------------------------------------------------------------------*
328  * Assemble results
329  *-------------------------------------------------------------------------*/
330  // Compute observation
331  MatNd* observation = config->observationModel->computeObservation(action, config->dt);
332  MatNd_copy(currentObservation, observation);
333 
334  /*
335  // compute state for goal monitor
336  MatNd* state;
337  if (config->stateModel == config->observationModel) {
338  // use observation if it is the same
339  state = observation;
340  } else {
341  // get explicitly if not
342  state = config->stateModel->computeObservation(config->dt);
343  }
344  */
345 
346  return observation;
347 }
348 
349 void Rcs::RcsSimEnv::render(std::string mode, bool close)
350 {
351 #ifdef GRAPHICS_AVAILABLE
352  if (close) {
353  // Close the viewer
354  if (viewer) {
355  hud = nullptr;
356  delete viewer;
357  viewer = nullptr;
358  }
359  }
360  else if (mode == "human") {
361  if (!viewer) {
362  // Start viewer without shadows, since the shadow renderer has some issues when multiple simulations
363  // are opened in the same process. You can still enable shadows via the key binding.
364  viewer = new Rcs::Viewer(true, false);
365  auto kc = new Rcs::KeyCatcher();
366 
367  // Make nodes resizable so it can update on physics param changes setting that for relevant nodes only
368  // would be better, but it's not possible right now.
369  auto amIK = config->actionModel->unwrap<ActionModelIK>();
370  if (amIK != nullptr) {
371  // Visualize the IK controller graph (a.k.a. desired graph)
372  Rcs::GraphNode* gDesNode = new Rcs::GraphNode(amIK->getController()->getGraph(), true, false);
373  gDesNode->setGhostMode(true);
374  viewer->add(gDesNode);
375  }
376  if (usePhysicsNode) {
377  Rcs::PhysicsNode* pNode = new Rcs::PhysicsNode(physicsSim, true);
378  viewer->add(pNode);
379  pNode->physicsNd->togglePhysicsModel(); // switch off
380  pNode->physicsNd->toggleGraphicsModel(); // switch on
381  }
382  else {
383  Rcs::GraphNode* gNode = new Rcs::GraphNode(config->graph, true, false);
384  gNode->toggleReferenceFrames();
385  viewer->add(gNode);
386  }
387 
388  hud = new Rcs::HUD();
389  // HUD color from config
390  std::string hudColor;
391  if (config->properties->getProperty(hudColor, "hudColor") && !hudColor.empty()) {
392  hud->setColor(hudColor.c_str());
393  }
394  hud->resize(1024, 100);
395 
396  viewer->setWindowSize(0, 0, 1024, 768);
397  viewer->setUpdateFrequency(50.0);
398  viewer->add(hud);
399  viewer->add(kc);
400  viewer->setBackgroundColor("PEWTER");
401 
402  // Experiment specific settings
404 
405  viewer->runInThread(graphLock.native_handle());
406  // viewer->toggleVideoRecording(); // start recording right from the start of the sim
407  }
408  // Update HUD
409  auto hudText = config->getHUDText(
411  );
412  hud->setText(hudText);
413  }
414 #else
415  // Warn about headless mode
416  static bool warned = false;
417  if (!warned)
418  {
419  warned = true;
420  RMSGS("RcsPySim compiled in headless mode, therefore rendering is not available!");
421  }
422 #endif
423 }
424 
426 {
427 #ifdef GRAPHICS_AVAILABLE
428  if (viewer == nullptr) {
429  // The viewer has not been opened yet, so do that
430  render("human", false);
431  // If we start the recording right away, the recording target frame will be off
432  RPAUSE_MSG("Please move the graphics window so the window position is initialized properly."
433  "Press ENTER to continue.");
434  }
435  viewer->toggleVideoRecording();
436 #else
437  // Warn about headless mode
438  static bool warned = false;
439  if(!warned) {
440  warned = true;
441  RMSGS("RcsPySim compiled in headless mode, therefore rendering not available!");
442  }
443 #endif
444 }
445 
447 {
448  return config->observationModel->getSpace();
449 }
450 
452 {
453  return config->actionModel->getSpace();
454 }
455 
457 {
458  if (initStateSetter == nullptr) {
459  return nullptr;
460  }
461  return initStateSetter->getSpace();
462 }
463 
464 //const pybind11::dict Rcs::RcsSimEnv::domainParams()
465 //{
466 // pybind11::dict domainParams = pybind11::dict("test"=1);
467 // return domainParams;
468 //}
469 
471 {
472  if (tnb == nullptr) {
473  // Remove it
474  MatNd_destroy(transitionNoiseBuffer);
475  transitionNoiseBuffer = nullptr;
476  }
477  else if (tnb->m != getInternalStateDim()) {
478  throw std::invalid_argument("Transition noise dimension must match internal state dimension");
479  }
480  else {
481  // Store copy
482  MatNd_resizeCopy(&transitionNoiseBuffer, tnb);
483  tnbIndex = 0;
484  }
485 }
486 
488 {
490  return config->graph->dof*2;
491  }
492  return config->graph->dof;
493 }
virtual bool getProperty(std::string &out, const char *property)=0
void setTransitionNoiseBuffer(const MatNd *tnb)
Definition: RcsSimEnv.cpp:470
virtual bool getPropertyBool(const char *property, bool def=false)=0
MatNd * createValueMatrix() const
Definition: BoxSpace.cpp:197
unsigned int getInternalStateDim()
Definition: RcsSimEnv.cpp:487
virtual void render(std::string mode="human", bool close=false)
Definition: RcsSimEnv.cpp:349
virtual MatNd * reset(PropertySource *domainParam=PropertySource::empty(), const MatNd *initState=NULL)
Definition: RcsSimEnv.cpp:128
const BoxSpace * actionSpace()
Definition: RcsSimEnv.cpp:451
PhysicsBase * createSimulator(PropertySource *values)
virtual ~RcsSimEnv()
Definition: RcsSimEnv.cpp:109
virtual void initViewer(Rcs::Viewer *viewer)
bool usePhysicsNode
Definition: RcsSimEnv.h:205
const BoxSpace * observationSpace()
Definition: RcsSimEnv.cpp:446
unsigned int currentStep
Counters.
Definition: RcsSimEnv.h:191
Viewer * viewer
Visualization.
Definition: RcsSimEnv.h:204
const BoxSpace * getSpace() const
virtual ForceDisturber * createForceDisturber()
bool checkJointLimits
Flag to enable joint limit checking.
double dt
The time step [s].
bool allJointsPosCtrl
Definition: RcsSimEnv.h:173
MatNd * currentObservation
Observation and action at last step.
Definition: RcsSimEnv.h:200
RcsSimEnv(PropertySource *propertySource)
Definition: RcsSimEnv.cpp:57
virtual void computeCommand(MatNd *q_des, MatNd *q_dot_des, MatNd *T_des, const MatNd *action, double dt)=0
void toggleVideoRecording()
Definition: RcsSimEnv.cpp:425
MatNd * T_ctrl
Definition: RcsSimEnv.h:188
MatNd * computeObservation(const MatNd *currentAction, double dt) const
virtual void applyInitialState(const MatNd *initialState)=0
PhysicsParameterManager * physicsManager
Physics simulator factory.
Definition: RcsSimEnv.h:176
MatNd * qd_ctrl
Definition: RcsSimEnv.h:187
MatNd * currentAction
Definition: RcsSimEnv.h:201
virtual InitStateSetter * createInitStateSetter()
unsigned int tnbIndex
Definition: RcsSimEnv.h:196
ForceDisturber * disturber
Disturbance force simulator.
Definition: RcsSimEnv.h:180
PhysicsBase * physicsSim
Physics simulator.
Definition: RcsSimEnv.h:178
MatNd * q_ctrl
Temporary matrices.
Definition: RcsSimEnv.h:186
static ExperimentConfig * create(PropertySource *properties)
double currentTime
Definition: RcsSimEnv.h:192
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)
MatNd * transitionNoiseBuffer
Transition noise values (every column is one set of noise values for every state variable) ...
Definition: RcsSimEnv.h:195
const AM * unwrap() const
Definition: ActionModel.h:142
virtual void reset()
Definition: ActionModel.cpp:49
bool transitionNoiseIncludeVelocity
Definition: RcsSimEnv.h:197
InitStateSetter * initStateSetter
Initial state setter.
Definition: RcsSimEnv.h:183
ExperimentConfig * config
Experiment configuration.
Definition: RcsSimEnv.h:172
void apply(Rcs::PhysicsBase *sim, double force[3])
ActionModel * actionModel
Action model (pluggable)
virtual MatNd * step(const MatNd *action, const MatNd *disturbance=NULL)
Definition: RcsSimEnv.cpp:202
std::mutex graphLock
Guards for parallel access to graph (can happen from gui)
Definition: RcsSimEnv.h:169
RcsGraph * graph
Graph description.
const BoxSpace * initStateSpace()
Definition: RcsSimEnv.cpp:456
PhysicsParameterManager * createPhysicsParameterManager()