mirror of
https://github.com/wassname/pyrobolearn.git
synced 2026-09-20 13:11:01 +08:00
310 lines
10 KiB
Python
310 lines
10 KiB
Python
#!/usr/bin/env python
|
|
"""Provide the 3D Spring Loaded Inverted Pendulum (SLIP) model.
|
|
"""
|
|
|
|
import numpy as np
|
|
import matplotlib.pyplot as plt
|
|
|
|
|
|
__author__ = ["Songyan Xin", "Brian Delhaisse"]
|
|
__copyright__ = "Copyright 2018, PyRoboLearn"
|
|
__credits__ = ["Songyan Xin"]
|
|
__license__ = "MIT"
|
|
__version__ = "1.0.0"
|
|
__maintainer__ = "Brian Delhaisse"
|
|
__email__ = "briandelhaisse@gmail.com"
|
|
__status__ = "Development"
|
|
|
|
|
|
class SLIP3D(object):
|
|
r"""3D Spring Loaded Inverted Pendulum (SLIP) model
|
|
|
|
The axes are :math:`(x,y,z)`, where :math:`x` points in front, :math:`y` to the left, and :math:`z` is up.
|
|
The SLIP model consists of a point mass :math:`m`, a massless spring with stiffness :math:`k`, and a rest length
|
|
:math:`l_0`. Three phases (flight, stance, and flight) are involved in one rollout of the running motion separated
|
|
by a touchdown (TD) and takeoff (TO) events. The state of the system is the position and velocity of the mass
|
|
:math:`[x, y, z, \dot{x}, \dot{y}, \dot{z}]`, while the 2 angles :math:`[\theta, \phi]` are the control
|
|
parameters.
|
|
|
|
References:
|
|
[1] "A Dual-SLIP Model For Dynamic Walking In A Humanoid Over Uneven Terrain" (Diss), Yiping, 2015.
|
|
"""
|
|
|
|
def __init__(self, mass, length, stiffness, position=(0., 0., 1.), velocity=(0., 0., 0.), angles=(0., 0.),
|
|
angle_limits=(0, 2*np.pi), gravity=9.81, dt=1e-3):
|
|
"""
|
|
Initialize the 3D SLIP model.
|
|
|
|
Args:
|
|
mass (float): mass.
|
|
length (float): the rest length of the spring.
|
|
stiffness (float): spring stiffness.
|
|
position (np.float[3]): initial position.
|
|
velocity (np.float[3]): initial velocity.
|
|
angle (np.float[2]): initial angles (pitch, roll).
|
|
angle_limits (tuple of 2 floats): angle limits (lower bound, upper bound).
|
|
gravity (float): gravity in the z direction.
|
|
dt (float): integration time step.
|
|
"""
|
|
# properties
|
|
self.m = mass
|
|
self.l0 = length
|
|
self.k = stiffness
|
|
self.g = gravity
|
|
self.dt = dt
|
|
|
|
# state
|
|
self.pos = np.array(position)
|
|
self.vel = np.array(velocity)
|
|
self.acc = np.zeros(self.pos.shape)
|
|
|
|
# control
|
|
self.angles = np.array(angles)
|
|
|
|
##############
|
|
# Properties #
|
|
##############
|
|
|
|
@property
|
|
def state(self):
|
|
"""
|
|
Return the state of the system, i.e. the position and velocity vectors.
|
|
"""
|
|
return np.concatenate((self.pos, self.vel))
|
|
|
|
@property
|
|
def control(self):
|
|
"""
|
|
Return the control input of the system, i.e. the two angles.
|
|
"""
|
|
return self.angles
|
|
|
|
###########
|
|
# Methods #
|
|
###########
|
|
|
|
def kinetic_energy(self, vel=None):
|
|
"""
|
|
Return the kinetic energy of the inverted pendulum.
|
|
|
|
Args:
|
|
vel (np.ndarray, None): velocity of the inverted pendulum
|
|
|
|
Returns:
|
|
float: kinetic energy
|
|
"""
|
|
if vel is None:
|
|
vel = self.vel
|
|
return 0.5 * self.m * vel.dot(vel)
|
|
|
|
def potential_energy(self, pos=None):
|
|
"""
|
|
Return the potential energy due to gravity.
|
|
|
|
Args:
|
|
pos (np.ndarray, None): position of the inverted pendulum
|
|
|
|
Returns:
|
|
float: potential energy
|
|
"""
|
|
if pos is None:
|
|
pos = self.pos
|
|
return self.m * self.g * pos[-1]
|
|
|
|
def energy(self, X=None):
|
|
"""
|
|
Compute the total energy :math:`E=K+P` where :math:`K` is the kinetic energy of the system, and :math:`P`
|
|
is the potential energy.
|
|
|
|
Args:
|
|
X (np.array, None): state [pos, vel] of the inverted pendulum
|
|
|
|
Returns:
|
|
float: total energy
|
|
"""
|
|
if X is None:
|
|
X = self.state
|
|
P = self.potential_energy(X[:len(X)/2])
|
|
K = self.kinetic_energy(X[len(X)/2:])
|
|
E = K + P
|
|
return E
|
|
|
|
def check_energy(self, key_state):
|
|
X0, X_touchdown, X_takeoff, X_apex = key_state
|
|
for X in key_state:
|
|
print("X: ", X)
|
|
print("E: ", self.energy(X))
|
|
|
|
def foot_position(self, X=None, U=None):
|
|
"""
|
|
Compute the foot position given the state and control law.
|
|
|
|
Args:
|
|
X (np.array): state of the SLIP model
|
|
U (np.array): control law
|
|
|
|
Returns:
|
|
np.array: foot position
|
|
"""
|
|
if X is None: X = self.state
|
|
if U is None: U = self.control
|
|
x, y, z, dx, dy, dz = X
|
|
theta, phi = U
|
|
return np.array([x, y, z]) + self.l0 * np.array([np.sin(theta) * np.cos(phi),
|
|
np.sin(theta) * np.sin(phi),
|
|
-np.cos(theta)])
|
|
|
|
# Dynamics
|
|
def flight_dynamics(self, X=None):
|
|
"""
|
|
Compute the flight dynamics of the SLIP model given the state.
|
|
|
|
Args:
|
|
X (np.array, None): state of the system
|
|
|
|
Returns:
|
|
np.array: derivative of the state
|
|
"""
|
|
if X is None: X = self.state
|
|
x, y, z, dx, dy, dz = X
|
|
return np.array([dx, dy, dz, 0, 0, self.g])
|
|
|
|
def stance_dynamics(self, X, foot_pos):
|
|
"""
|
|
Compute the stance dynamics of the SLIP model given the state and foot position.
|
|
|
|
Args:
|
|
X (np.array): state of the system
|
|
foot_pos (np.array): foot position
|
|
|
|
Returns:
|
|
np.array: derivative of the state
|
|
"""
|
|
x, y, z, dx, dy, dz = X
|
|
l = np.array([x, y, z]) - foot_pos
|
|
l_unit = l / np.linalg.norm(l)
|
|
(ddx, ddy, ddz) = self.k / self.m * (self.l0 - np.linalg.norm(l)) * l_unit + np.array([0, 0, self.g])
|
|
return np.array([dx, dy, dz, ddx, ddy, ddz])
|
|
|
|
# Phases
|
|
def flight_fall_phase(self, X, U):
|
|
x, y, z, dx, dy, dz = X
|
|
theta, phi = U
|
|
cur_foot_pos = self.foot_position(X, U)
|
|
# print("cur foot pos: ", cur_foot_pos)
|
|
if cur_foot_pos[2] <= 0.0:
|
|
print("foot penetrate into the ground!")
|
|
else:
|
|
fall_duration = np.sqrt(2 * cur_foot_pos[2] / np.abs(g))
|
|
# tt = np.arange(0, fall_duration, dt)
|
|
tt = np.linspace(0, fall_duration, num=fall_duration / self.dt)
|
|
xx = x + dx * tt
|
|
yy = y + dy * tt
|
|
zz = z + dz * tt + 0.5 * self.g * tt ** 2
|
|
dxx = dx * np.ones(tt.shape)
|
|
dyy = dy * np.ones(tt.shape)
|
|
dzz = dz + self.g * tt
|
|
fall_traj = np.vstack((xx, yy, zz, dxx, dyy, dzz)).T
|
|
X_touchdown = (xx[-1], yy[-1], zz[-1], dxx[-1], dyy[-1], dzz[-1])
|
|
touchdown_foot_pos = self.foot_position(X_touchdown, U)
|
|
return fall_traj, X_touchdown, touchdown_foot_pos
|
|
|
|
def stance_phase(self, X, foot_pos):
|
|
XX = []
|
|
while np.linalg.norm(X[:3] - foot_pos) <= self.l0 + 1e-9:
|
|
X = X + self.dt * self.stance_dynamics(X, foot_pos)
|
|
XX.append(X)
|
|
X_takeoff = X
|
|
return np.asarray(XX), X_takeoff, foot_pos
|
|
|
|
def flight_rise_phase(self, X):
|
|
x, y, z, dx, dy, dz = X
|
|
|
|
if zd <= 0.0:
|
|
print("Take off speed nagetive!")
|
|
return None, None
|
|
else:
|
|
rise_duration = dz / np.abs(g)
|
|
# tt = np.arange(0, rise_duration, dt)
|
|
tt = np.linspace(0, rise_duration, num=rise_duration / self.dt)
|
|
xx = x + dx * tt
|
|
yy = y + dy * tt
|
|
zz = z + dz * tt + 0.5 * self.g * tt ** 2
|
|
dxx = dx * np.ones(tt.shape)
|
|
dyy = dy * np.ones(tt.shape)
|
|
dzz = dz + self.g * tt
|
|
rise_traj = np.vstack((xx, yy, zz, dxx, dyy, dzz)).T
|
|
X_apex = (xx[-1], yy[-1], zz[-1], dxx[-1], dyy[-1], dzz[-1])
|
|
return rise_traj, X_apex
|
|
|
|
def step(self):
|
|
pass
|
|
|
|
def rollout(self):
|
|
X0, U0 = self.X0, self.U0
|
|
traj_freefall, X_touchdown, pos_touchdown = self.flight_fall_phase(X0, U0)
|
|
traj_stance, X_takeoff, pos_takeoff = self.stance_phase(X_touchdown, pos_touchdown)
|
|
traj_freerise, X_apex = self.flight_rise_phase(X_takeoff)
|
|
return (traj_freefall, traj_stance, traj_freerise), (X0, X_touchdown, X_takeoff, X_apex), pos_touchdown
|
|
|
|
# alias
|
|
cycle = rollout
|
|
|
|
def plot_cycle(self, traj, key_state, pos_touchdown, ax):
|
|
(traj_freefall, traj_stance, traj_freerise) = traj
|
|
(X0, X_touchdown, X_takeoff, X_apex) = key_state
|
|
|
|
# fig = plt.figure(111)
|
|
# ax = fig.add_subplot(111, projection='3d')
|
|
worldFrame = SimpleFrame(ax, pos=[0, 0, 0], quat=trans.quaternion_from_euler(0, 0, 0, axes='sxyz'))
|
|
|
|
ax.plot(traj_freefall[:, 0], traj_freefall[:, 1], traj_freefall[:, 2], 'r', label='free fall trajec')
|
|
ax.plot(traj_stance[:, 0], traj_stance[:, 1], traj_stance[:, 2], 'g', label='stance trajec')
|
|
ax.plot(traj_freerise[:, 0], traj_freerise[:, 1], traj_freerise[:, 2], 'b', label='free rise trajec')
|
|
ax.scatter(pos_touchdown[0], pos_touchdown[1], pos_touchdown[2], c="g", s=30)
|
|
|
|
# plot leg
|
|
|
|
ax.plot([traj_stance[0, 0], pos_touchdown[0]],
|
|
[traj_stance[0, 1], pos_touchdown[1]],
|
|
[traj_stance[0, 2], pos_touchdown[2]],
|
|
color='g', linestyle=':')
|
|
|
|
ax.plot([traj_stance[-1, 0], pos_touchdown[0]],
|
|
[traj_stance[-1, 1], pos_touchdown[1]],
|
|
[traj_stance[-1, 2], pos_touchdown[2]],
|
|
color='g', linestyle=':')
|
|
|
|
# ax.legend()
|
|
# plt.show()
|
|
plt.draw()
|
|
plt.pause(0.01)
|
|
|
|
def plot(self, x, y, z, c, xlabel, ylabel, zlabel, title=None):
|
|
# plot 3D surface with color
|
|
|
|
# set up a figure twice as wide as it is tall
|
|
fig = plt.figure(figsize=plt.figaspect(0.5))
|
|
if title is not None:
|
|
plt.title(title)
|
|
|
|
ax = fig.add_subplot(1, 2, 1, projection='3d')
|
|
scatter = ax.scatter(x, y, z, c=c[:, 0], cmap='coolwarm') # plt.hot())
|
|
ax.set_xlabel(xlabel)
|
|
ax.set_ylabel(ylabel)
|
|
ax.set_zlabel(zlabel)
|
|
bar_ax = fig.colorbar(scatter, shrink=0.5, aspect=5)
|
|
bar_ax.set_label('theta')
|
|
|
|
ax = fig.add_subplot(1, 2, 2, projection='3d')
|
|
scatter = ax.scatter(x, y, z, c=c[:, 1], cmap='coolwarm') # plt.hot())
|
|
ax.set_xlabel(xlabel)
|
|
ax.set_ylabel(ylabel)
|
|
ax.set_zlabel(zlabel)
|
|
bar_ax = fig.colorbar(scatter, shrink=0.5, aspect=5)
|
|
bar_ax.set_label('phi')
|
|
|
|
|
|
# Tests
|
|
if __name__ == '__main__':
|
|
pass |