RcsPySim
A robot control and simulation library
ActionModelIK.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 "ActionModelIK.h"
32 
33 #include <Rcs_typedef.h>
34 #include <Rcs_macros.h>
35 #include <Rcs_kinematics.h>
36 #include <Rcs_basicMath.h>
37 #include <IkSolverConstraintRMR.h>
38 
39 
40 /**
41  Value freeRatio is the ratio of the joint's half range in which the joint limit gradient kicks in. For instance if
42  freeRatio is 1, the whole joint range is without gradient. If freeRatio is 0, the gradient follows a quadratic cost
43  over the whole range.
44 
45  Value freeRange is the range starting from q0, where the gradient is 0. The value penaltyRange is the range where
46  the gradient is not 0. Here is an illustration:
47 
48  q_lowerLimit q_lowerRange q_0 q_upperRange q_upperLimit
49  | | | | |
50  | | | | |
51  \__________________/\__________/\____________/\_______________/
52  a b c d
53 
54  freeRatio: b/(a+b) c/(c+d)
55  freeRange: b c
56  penaltyRange: a d
57 
58  cost =
59  q in a: 0.5 * wJL * ((q-q_lowerRange)/a)^2 => c(limit) = 0.5*wJL
60  q in b: 0
61  q in c: 0
62  q in d: 0.5 * wJL * ((q-q_upperRange)/d)^2 => c(limit) = 0.5*wJL
63 
64  gradient =
65  q in a: (q-q_lowerRange)*wJL/a^2 => grad(limit) = 0.5*wJL/a
66  q in b: 0
67  q in c: 0
68  q in d: (q-q_upperRange)*wJL/a^2 => grad(limit) = 0.5*wJL/d
69 
70  For the default case wJL=1 and freeRange=0, we get: dH_i = 0.5/halfRange
71  and for symmetric joint centers, we get: dH_i = 1.0/range
72  If we interpret it as a velocity, it's the inverse of the joint range.
73  */
74 /*
75 static void RcsGraph_jointLimitBorderGradient3(const RcsGraph* self, MatNd* dH, double range, double maxSpeedScaling)
76 {
77  RCHECK(range > 0.0);
78  RCHECK(maxSpeedScaling > 0.0);
79 
80  unsigned int dimension = dH->m*dH->n;
81 
82  RcsStateType type;
83 
84  if (dimension == self->dof)
85  {
86  type = RcsStateFull;
87  }
88  else if (dimension == self->nJ)
89  {
90  type = RcsStateIK;
91  }
92  else
93  {
94  RFATAL("Wrong gradient size: dof=%d nJ=%d m=%d n=%d", self->dof, self->nJ, dH->m, dH->n);
95  }
96 
97  MatNd_reshapeAndSetZero(dH, 1, dimension);
98 
99  RCSGRAPH_TRAVERSE_JOINTS(self)
100  {
101  if (JNT->constrained && (type == RcsStateIK))
102  {
103  continue;
104  }
105 
106  // Coupled joints don't contribute. Limit avoidance is the master joint's job
107  if (JNT->coupledTo != nullptr)
108  {
109  continue;
110  }
111 
112  int index = (type == RcsStateIK) ? JNT->jacobiIndex : JNT->jointIndex;
113  double q = Math_clip(self->q->ele[JNT->jointIndex], JNT->q0 - range, JNT->q0 + range);
114  dH->ele[index] = maxSpeedScaling*JNT->speedLimit*JNT->weightJL*(q - JNT->q0)/range;
115 
116  } // RCSGRAPH_TRAVERSE_JOINTS
117 
118 }
119 */
120 namespace Rcs
121 {
122 ActionModelIK::ActionModelIK(RcsGraph* graph) : ActionModel(graph), alpha(1e-4), lambda(1e-6)
123 {
124  // Create controller using a separate graph. That graph will later hold the desired state.
125  desiredGraph = RcsGraph_clone(graph);
126  controller = new ControllerBase(desiredGraph);
127 
128  // Initialize temp storage where possible
129  dq_ref = MatNd_create(graph->dof, 1);
130  dq_ref = MatNd_create(graph->dof, 1);
131  dH = MatNd_create(1, graph->nJ);
132 
133  // Since the subclass must initialize the dynamicalSystems first, we defer creation of these to the first reset call.
134  solver = nullptr;
135  dx_des = nullptr;
136 
137  // Must be set manually
138  collisionMdl = nullptr;
139 
140  dimFixedTasks = 0;
141  fixedTasksValues = nullptr;
142 }
143 
144 ActionModelIK::ActionModelIK(RcsGraph* graph, std::vector<Task*> tasks) : ActionModelIK(graph)
145 {
146  // Populate tasks
147  for (auto task : tasks) {
148  addTask(task);
149  }
150  // Create solver eagerly
151  reset();
152 }
153 
155 {
156  MatNd_destroy(dH);
157  MatNd_destroy(dx_des);
158  MatNd_destroy(dq_ref);
159  RcsCollisionModel_destroy(collisionMdl);
160  delete solver;
161  delete controller;
162  // desiredGraph was destroyed by controller
163 }
164 
165 void ActionModelIK::addTask(Task* task)
166 {
167  RCHECK_MSG(!solver, "Cannot add a Task to the action model after the first reset() call.");
168  // It's pretty easy to create a task using the wrong graph, which leads to all kinds of untraceable errors.
169  // So, we use clone to make sure the graph and bodies are the correct instances. We don't need it in all cases,
170  // but it also doesn't hurt. Since we officially take ownership of task, we just delete it right after.
171  controller->add(task->clone(desiredGraph));
172  delete task;
173 }
174 
176 {
177  if (solver == nullptr) {
178  // Init solver now that the dynamicalSystems are created
179  solver = new IkSolverRMR(controller);
180  // Init temp storage
181  dx_des = MatNd_create(controller->getTaskDim(), 1);
182  }
183 
184  // Updated the controller's graph with the potentially randomized shapes
185  RcsGraph_copyResizeableShapes(desiredGraph, graph, 1);
186 
187  // Copy init state to desired state graph
188  RcsGraph_setState(desiredGraph, graph->q, graph->q_dot);
189 }
190 
191 void ActionModelIK::computeIK(MatNd* q_des, MatNd* q_dot_des, MatNd* T_des, const MatNd* x_des, double dt)
192 {
193  // Compute dx from x_des and x_curr (NOTE: dx is the error, not the time derivative)
194  controller->computeDX(dx_des, x_des);
195 
196  // Compute IK from dx_des
197  ikFromDX(q_des, q_dot_des, dt);
198 }
199 
200 void ActionModelIK::computeIKVel(MatNd* q_des, MatNd* q_dot_des, MatNd* T_des, const MatNd* x_dot_des, double dt)
201 {
202  // Compute dx from x_dot_des. dx is the distance we want to move during this step
203  MatNd_constMul(dx_des, x_dot_des, dt);
204 
205  // Compute IK from dx_des
206  ikFromDX(q_des, q_dot_des, dt);
207 }
208 
209 
210 void ActionModelIK::ikFromDX(MatNd* q_des, MatNd* q_dot_des, double dt) const
211 {
212  // Print desired task space delta if debug level is exceeded
213  REXEC(6) {
214  MatNd_printComment("dx_des (from controller->computeDX)", dx_des);
215  }
216 
217  // Compute nullptr space gradients
218  RcsGraph_jointLimitGradient(desiredGraph, dH, RcsStateIK); // more robust choice
219 // RcsGraph_jointLimitBorderGradient3(desiredGraph, dH, RCS_DEG2RAD(90.0), 1.0); // sensitive to range value
220 
221  // Add collision cost if available
222  if (collisionMdl != nullptr) {
223  MatNd* dH_ca = MatNd_create(1, controller->getGraph()->nJ);
224  // Compute the collision gradient
225  RcsCollisionModel_compute(collisionMdl);
226  RcsCollisionMdl_gradient(collisionMdl, dH_ca);
227  // Add it to nullptrspace
228  MatNd_addSelf(dH, dH_ca);
229  MatNd_destroy(dH_ca);
230  }
231 
232  // Compute joint space position error with IK
233  MatNd_constMulSelf(dH, alpha);
234  solver->solveRightInverse(dq_ref, dx_des, dH, nullptr, lambda); // tries to solve everything exactly
235 
236 // MatNd* dq_ref_ts = MatNd_create(graph->dof, 1);
237 // MatNd* dq_ref_ns = MatNd_create(graph->dof, 1);
238 // MatNd* a_temp = MatNd_create(controller->getNumberOfTasks(), 1);
239 // solver->solveLeftInverse(dq_ref_ts, dq_ref_ns, dx_des, dH, a_temp, lambda); // does not try to solve everything exactly
240 // MatNd_add(dq_ref, dq_ref_ts, dq_ref_ns);
241 // MatNd_destroy(dq_ref_ts);
242 // MatNd_destroy(dq_ref_ns);
243 // MatNd_destroy(a_temp);
244 
245  // Check for speed limit violations
246  RcsGraph_limitJointSpeeds(desiredGraph, dq_ref, dt, RcsStateFull);
247 
248  // Integrate desired joint positions
249  MatNd_add(q_des, desiredGraph->q, dq_ref);
250 
251  // Compute velocity from error
252  MatNd_constMul(q_dot_des, dq_ref, 1/dt);
253 
254  // Update desired state graph for next step
255  RcsGraph_setState(desiredGraph, q_des, q_dot_des);
256 
257  // Also compute gravity compensation force (Bullet might need it, Vortex ignores it)
258  // RcsGraph_computeGravityTorque(desiredGraph, T_des);
259  // MatNd_constMulSelf(T_des, -1.0);
260 }
261 
263 {
264  return desiredGraph;
265 }
266 
267 void ActionModelIK::setDesiredGraph(RcsGraph* newGraph)
268 {
269  desiredGraph = newGraph;
270 }
271 
272 void ActionModelIK::setupCollisionModel(const RcsCollisionMdl* modelToCopy)
273 {
274  // Copy collision model for desired graph
275  if (modelToCopy != nullptr) {
276  collisionMdl = RcsCollisionModel_clone(modelToCopy, desiredGraph);
277  }
278  else {
279  collisionMdl = nullptr;
280  }
281 }
282 
283 void ActionModelIK::addFixedTask(Task* task, MatNd* value)
284 {
285  if (fixedTasksValues) {
286  // Not the first time, append (copies the value MatNd)
287  MatNd_appendRows(fixedTasksValues, value);
288  }
289  else {
290  // The first time, copy
291  fixedTasksValues = MatNd_clone(value);
292  }
293 
294  dimFixedTasks += task->getDim();
295  this->addTask(task);
296 }
297 
299 {
300  return controller->getNumberOfTasks() - dimFixedTasks;
301 }
302 
303 /*
304  * AMIKGeneric
305  */
306 unsigned int AMIKGeneric::getDim() const
307 {
308  return (unsigned int) controller->getTaskDim() - dimFixedTasks;
309 }
310 
311 void AMIKGeneric::getMinMax(double* min, double* max) const
312 {
313  unsigned int idx = 0;
314  for (unsigned int ti = 0; ti < this->getNumActiveTasks(); ++ti) {
315  Task* task = controller->getTask(ti);
316  for (unsigned int tp = 0; tp < task->getDim(); ++tp) {
317  auto param = task->getParameter(tp);
318  min[idx] = param.minVal;
319  max[idx] = param.maxVal;
320  idx++;
321  }
322  }
323 }
324 
325 std::vector<std::string> AMIKGeneric::getNames() const
326 {
327  std::vector<std::string> result;
328  for (unsigned int ti = 0; ti < this->getNumActiveTasks(); ++ti) {
329  Task* task = controller->getTask(ti);
330  for (unsigned int tp = 0; tp < task->getDim(); ++tp) {
331  auto param = task->getParameter(tp);
332  result.push_back(param.name);
333  }
334  }
335  return result;
336 }
337 
338 void AMIKGeneric::computeCommand(MatNd* q_des, MatNd* q_dot_des, MatNd* T_des, const MatNd* action, double dt)
339 {
340  MatNd* augmentedAction = MatNd_clone(action);
341  if (fixedTasksValues) {
342  // Augment the action with the fixed values
343  MatNd_appendRows(augmentedAction, fixedTasksValues);
344  }
345 
346  // Copy the ExperimentConfig graph which has been updated by the physics simulation into the desired graph
347  RcsGraph_copyRigidBodyDofs(desiredGraph->q, graph, nullptr);
348 
349  computeIK(q_des, q_dot_des, T_des, static_cast<const MatNd*>(augmentedAction), dt);
350 
351  MatNd_destroy(augmentedAction);
352 }
353 
354 void AMIKGeneric::getStableAction(MatNd* action) const
355 {
356  MatNd* augmentedAction = MatNd_clone(action);
357  if (fixedTasksValues) {
358  // Augment the action with the fixed values
359  MatNd_appendRows(augmentedAction, fixedTasksValues);
360  }
361 
362  controller->computeX(augmentedAction);
363 
364  MatNd_destroy(augmentedAction);
365 }
366 
367 ActionModel* AMIKGeneric::clone(RcsGraph* newGraph) const
368 {
369  auto res = new AMIKGeneric(newGraph);
370  for (auto task : controller->getTasks()) {
371  res->addTask(task->clone(newGraph));
372  }
373  res->setupCollisionModel(collisionMdl);
374  return res;
375 }
376 
377 } /* namespace Rcs */
friend class AMIKGeneric
unsigned int dimFixedTasks
Store the cumulative number of task dimensions which are fixed to one target value.
void addFixedTask(Task *task, MatNd *value)
Add Rcs controller tasks that is always fixed to one target value. Do this after adding the regular t...
double alpha
Scaling factor for the nullspace gradient.
RcsGraph * getDesiredGraph() const
The graph holding the desired state of the IK solver.
virtual void computeCommand(MatNd *q_des, MatNd *q_dot_des, MatNd *T_des, const MatNd *action, double dt)
IkSolverRMR * solver
IK solver (owned)
void addTask(Task *task)
virtual void getMinMax(double *min, double *max) const
void setupCollisionModel(const RcsCollisionMdl *modelToCopy)
RcsGraph * desiredGraph
virtual void getStableAction(MatNd *action) const
double lambda
Regularization factor for the task space weighting.
void computeIKVel(MatNd *q_des, MatNd *q_dot_des, MatNd *T_des, const MatNd *x_dot_des, double dt)
ActionModelIK(RcsGraph *graph)
void ikFromDX(MatNd *q_des, MatNd *q_dot_des, double dt) const
ControllerBase * controller
Holder for the IK tasks (owned)
virtual unsigned int getDim() const
void computeIK(MatNd *q_des, MatNd *q_dot_des, MatNd *T_des, const MatNd *x_des, double dt)
RcsCollisionMdl * collisionMdl
virtual std::vector< std::string > getNames() const
unsigned int getNumActiveTasks() const
virtual void reset()
MatNd * fixedTasksValues
Store the target values of all fixed tasks.
ActionModel * clone() const
Definition: ActionModel.h:72
RcsGraph * graph
Definition: ActionModel.h:194
void setDesiredGraph(RcsGraph *newGraph)