3

I want to ask a question related to a workflow like this:
1.obtain manipulator equation of multibody plant, 2.re-write it to a form of qdd = f(q,u), 3.calculate next state(x_next), 4.calculate the gradient of x_next w.r.t x and u.

I find the most difficult step is to get the inverse of Inertial matrix which support the gradient calculation in next following step. Drake provide many good tools support double, AutoDiffXd, Symbolic, could we use them to get thing done? Thanks!

Here is is a simple example of double pendulum.( I expect the work flow will be applied to more complicate system.)

def discrete_dynamics(x, u):
    
    dt = 0.05
    q = x[0:2]
    qd = x[2:4] 

    plant = MultibodyPlant(time_step=dt)
    parser = Parser(plant)
    parser.AddModelFromFile("double_pendulum.sdf")
    plant.Finalize()
    (M, Cv, tauG, B, tauExt) = ManipulatorDynamics(plant.ToAutoDiffXd(), q, qd)
  
    M_inv = inv(M) <------ # ????, how to do it 
 
    qdd = np.dot(M_inv,(tauG + np.dot(B ,u) - np.dot(Cv,qd)))   
    
    x_d_list = qd.tolist()
    qdd_list = qdd.tolist()
    x_d_list.extend(qdd_list) 
    x_d = np.array(x_d_list)

    x_next = x+x_d*dt

    return x_next

f = discrete_dynamics(x, u)
f_x = ...  #calculate gradient of f w.r.t x 
f_u = ...  #calculate gradient of f w.r.t u
Wc Chang
  • 97
  • 6

1 Answers1

2

In order to get the gradient of x_next w.r.t x and u, you will need to use automatic differentiation. You could refer to pydrake.autodiffutils.initializeAutoDiff and pydrake.autodiffutils.autoDiffToGradientMatrix to extract the gradient. Here is an example

from pydrake.autodiffutils import initializeAutoDiff, autoDiffToGradientMatrix, AutoDiffXd
from pydrake.systems.framework import BasicVector_


def get_dynamics_gradient(plant_ad, context_ad, input_port, x_val, u_val):
    xu_val = np.hstack((x_val, u_val))
    nx = context_ad.num_continuous_states()
    # Set the gradient (default to identity), namely the gradient field of
    # xu_ad records the gradient w.r.t x and u. All of the  autodiff
    # scalars computed using xu_ad now carry the gradient w.r.t x and u.
    xu_ad = initializeAutoDiff(xu_val)
    x_ad = xu_ad[:nx]
    u_ad = xu_ad[nx:]
    context_ad.SetContinuousState(x_ad)
    plant_ad.get_input_port(input_port).FixValue(context_ad, BasicVector_[AutoDiffXd](u_ad))
    derivatives = plant_ad.AllocateTimeDerivatives()
    plant_ad.CalcTimeDerivatives(context_ad, derivatives)
    xdot_ad = derivatives.get_vector().CopToVector()    
    x_next_ad = xdot_ad * dt + x_ad
    AB = autoDiffToGradientMatrix(x_next_ad)
    A = AB[:, :nx]
    B = AB[:, nx:]
    return A, B

plant = MultibodyPlant(time_step=0.)
parser = Parser(plant)
parser.AddModelFromFile("double_pendulum.sdf")
plant.Finalize()
plant_autodiff = plant.ToAutoDiffXd()
context_ad = plant_ad.CreateDefaultContext()
x = np.array([1., 2., 3., 4.])
u = np.array([5., 6.])
# I don't know the name of the input port, please adjust this name
# based on your parsed plant.
input_port = 1
print(f"input port {input_port} name is {plant_autodiff.get_input_port(input_port).get_name()}")
f_x, f_u = get_dynamics_gradient(plant_autodiff, context_ad, input_port x, u)

BTW, I don't see ManipulatorDynamics function in drake. I assume you implemented this function by yourself. Typically to compute the dynamics, I use CalcTimeDerivatives for continuous time system, and CalcDiscreteVariableUpdates for discrete time system.You could refer to our tutorial for more details on setting Context for calling these two functions.

Hongkai Dai
  • 2,546
  • 11
  • 12
  • ManipulatorDynamic is in text book supplier downloaded from https://github.com/RussTedrake/underactuated.git. The example is in the example/double_pendulum/dynamics.ipynb. Thank you so much for your reply again. I will study your reply. – Wc Chang Oct 28 '20 at 04:27
  • Sure, the typical process to compute the gradient of a function `f(x)` w.r.t `x` is to first create `x_ad` using `initializeAutoDiff`, and then compute `y_ad = f(x_ad)`. If you want to store the gradient in a matrix, then you call `autoDiffToGradientMatrix(y_ad)` to extract the gradient matrix. – Hongkai Dai Oct 28 '20 at 05:36
  • I rewrote this function to use `CalcTimeDerivatives` to compute the xdot. Notice that the plant is created as a continuous system (time_step=0). On the other hand, you could create the system as a discrete time system (time_step set to a positive value), and then call `CalcDiscreateVariableUpdates()` to compute `x_next` directly. – Hongkai Dai Oct 29 '20 at 16:22
  • one minor issue:AttributeError: 'MultibodyPlant_[AutoDiffXd]' object has no attribute 'num_continuous_states' --> I use num_positions() + num_velocities() – Wc Chang Oct 30 '20 at 10:16
  • x = np.array([1,2,3,4]). --> 4 states – Wc Chang Oct 30 '20 at 10:18
  • one major issues, I don't know how to solve. plant_ad.get_input_port(0).FixValue(context_ad, u_ad) TypeError: set_value(): incompatible function arguments. The following argument types are supported: 1. (self: pydrake.common.value.Value[QueryObject_[AutoDiffXd]], arg0: pydrake.geometry.QueryObject_[AutoDiffXd]) -> None Invoked with: , array([[]], dtype=object) – Wc Chang Oct 30 '20 at 10:19
  • However, I like this code very much! I will try to solve it. Thank you so much! – Wc Chang Oct 30 '20 at 10:21
  • Sorry for the belated reply, I had problem running pydrake in the last few days. I updated the code to fix the problem in `FixValue`. I think you need to set the `input_port_name` correctly. The error with `QueryObject` means it uses the wrong input port. One way to get the right input port name is to call `plant.get_input_port(0).get_name()` to print out the name of port 0. Likewise you can print out the name of other input ports until you find the right one. – Hongkai Dai Nov 02 '20 at 22:08