add locomotion template models (ongoing, S. Xin)

This commit is contained in:
Brian Delhaisse
2019-04-13 14:04:07 +02:00
parent e9ce7d1df7
commit acb0b66fb7
5 changed files with 849 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
## Template (simplified) models
This folder contains template models such as the Linear Inverted Pendulum (LIP), Spring Loaded Inverted Pendulum (SLIP), and other models.
These simplified models are often used in legged locomotion to understand the fundamental principles and predict the underlying locomotive behavior for a certain time horizon. Specifically, instead of using directly the robot model which often has a high number of degrees of freedom, these template models allows for a more tractable approach.
*Notes*: the code presented here still need to be cleaned and tested.
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env python
"""Provide the Soft-Actor Critic algorithm.
Define the SAC reinforcement learning algorithm. This is a model-free, off-policy, actor-critic method.
"""
import numpy as np
__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 LIPM:
"""Linear Inverted Pendulum (model)."""
def __init__(self, X0, z, g=9.81):
"""
Initialize the LIP model.
Args:
X0 (np.float[2]): LIP state (position and velocity).
z (float): height of the inverted pendulum.
g (float): gravity in the z direction.
"""
self.z, self.g = z, g
self.Tc = np.sqrt(self.z / self.g)
self.x0, self.xd0 = X0
def __call__(self, t):
t = np.asarray(t)
x_t = self.x0 * np.cosh(t / self.Tc) + self.Tc * self.xd0 * np.sinh(t / self.Tc)
xd_t = self.x0 / self.Tc * np.sinh(t / self.Tc) + self.xd0 * np.cosh(t / self.Tc)
X_t = [x_t, xd_t]
return np.asarray(X_t)
+491
View File
@@ -0,0 +1,491 @@
#!/usr/bin/env python
"""Provide the 2D 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 SLIP2D(object):
r"""2D Spring Loaded Inverted Pendulum (SLIP) model
The axes are :math:`x` and :math:`y`, where the former points to the right and the latter 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 given by the position and velocity of the
mass :math:`[x, y, \dot{x}, \dot{y}]`, while the angle of the spring :math:`\theta` is the control parameter.
"""
def __init__(self, mass, length, stiffness, position=(0., 1.), velocity=(0., 0.), angle=0.,
angle_limits=(0, 2*np.pi), gravity=9.81, dt=1e-3):
"""
Initialize the 2D SLIP model.
Args:
mass (float): mass.
length (float): the rest length of the spring.
stiffness (float): spring stiffness.
position (np.float[2]): initial position.
velocity (np.float[2]): initial velocity.
angle (float): initial angle
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.theta = angle
##############
# 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))
###########
# 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 max_velocity(self, Emax):
"""
Compute the max velocity in the x-direction when in the flight phase, given the total energy of the system
and assuming that this energy is conserved.
Args:
Emax (float): maximum total energy of the system
Returns:
float: velocity in the x direction
"""
P_min = self.m * self.g * self.l0
K_max = Emax - P_min # conservation of energy
v_max = np.sqrt(2 * K_max / self.m)
return v_max
def apex_energy(self, y, vx):
"""
Compute the energy at the apex (which is the maximum height and is the point where the velocity in the
y-direction is equal to 0).
Args:
y (float): height
vx (float): velocity in the x-direction
Returns:
float: total energy
"""
P = self.m * self.g * y
K = 0.5 * self.m * (vx ** 2)
E = K + P
return E
def apex_height_to_vel(self, E, y):
"""
Return the velocity in the x-direction at the apex given the energy.
Args:
E (float): total energy of the system
y (float): apex height of the pendulum
Returns:
float: velocity in x
"""
P = self.m * self.g * y
K = E - P
v = np.sqrt(2 / self.m * K)
return v
def apex_vel_to_height(self, E, vx):
"""
Return the apex height given the velocity in the x-direction and the energy of the system.
Args:
E (float): total energy of the system
vx (float): velocity in the x-direction at the apex point
Returns:
float: apex height of the system
"""
K = 0.5 * self.m * vx**2
P = E - K
y = P / (self.m * self.g)
return y
def apex_height_to_state(self, E, y, x=0):
"""
Return the state at the given apex height and energy level.
Args:
E (float): total energy of the pendulum
y (float): height of the apex point
Returns:
np.array: state of the inverted pendulum
"""
vx = self.apex_height_to_vel(E, y)
X = np.array([x, y, vx, 0])
return X
def apex_vel_to_state(self, E, vx, x=0):
"""
Return the state at the given apex velocity in the x-direction and energy level.
Args:
E (float): total energy of the system
vx (float): velocity in the x-direction at the apex point
Returns:
np.array: state of the system
"""
y = self.apex_vel_to_height(E, vx)
X = np.array([x, y, vx, 0])
return X
def in_stance_phase(self, pos, foot_pos):
"""
Return True if in stance phase.
Args:
pos (np.array): position of the inverted pendulum
foot_pos (np.array): foot position
Returns:
bool: True if in stance phase
"""
return np.linalg.norm(pos - foot_pos) <= self.l0 + 1e-9
def in_flight_phase(self, pos, foot_pos):
"""
Return True if in flight phase.
Args:
pos (np.array): position of the inverted pendulum
foot_pos (np.array): foot position
Returns:
bool: True if in flight phase
"""
return not self.in_stance_phase(pos, foot_pos)
# Dynamics
def flight_dynamic(self, X=None):
r"""
Compute the dynamics of the system during the flight phase; the system is not in contact with the ground.
In this phase, the mass follows a ballistic projectile trajectory formulated by:
.. math::
\ddot{x} &= 0 \\
\ddot{y} &= -\frac{g}{m}
Args:
X (np.array, None): state of the inverted pendulum
Returns:
np.array: derivative of the state
"""
if X is None:
X = self.state
x, y, dx, dy = X
ddx, ddy = 0.0, - self.g / self.m
self.acc = np.array([ddx, ddy])
dX = np.array([dx, dy, ddx, ddy])
return dX
def stance_dynamic(self, X=None, foot_pos=(0.,0.)):
r"""
Compute the dynamics of the system during the stance phase; the system is in contact with the ground.
.. math::
\ddot{x} &= k (x - x_f) (l0 - l) / (m l) \\
\ddot{y} &= k (y - y_f) (l0 - l) / (m l)
Args:
X (np.array, None): state of the inverted pendulum
foot_pos (np.array): foot position
Returns:
"""
if X is None:
X = self.state
x, y, dx, dy = X
xf, yf = foot_pos
l = np.sqrt((x - xf) ** 2 + (y - yf) ** 2)
ddx = self.k * (x - xf) * (self.l0 - l) / (self.m * l)
ddy = self.k * (y - yf) * (self.l0 - l) / (self.m * l) - self.g
self.acc = np.array([ddx, ddy])
dX = np.array([dx, dy, ddx, ddy])
return dX
def step(self):
"""
Perform one step.
Returns:
float [4]: next state
"""
pass
# # check which phase we are in
# if self.in_stance_phase(X[:2], ):
# pass
# else: # flight phase
# pass
def rollout(self, E, vx, theta0=None, plot=False):
"""
Perform a complete rollout which has three phases (flight, stance, and flight) separated by a touchdown (TD)
and takeoff (TO) events. It starts from the initial apex state and finish when reaching the other apex
state.
Args:
E (float): total energy of the system
vx (float): initial velocity in the x direction
theta0 (float, None): initial angle
plot (bool): if True, plot.
Returns:
float[4]: initial apex state
float[4]: touchdown state
float[4]: takeoff state
float[4]: final apex state
float[3*T]: time trajectory
float[4,3*T]: state trajectory
"""
if theta0 is None:
theta0 = self.theta
# compute initial apex state
X = self.apex_vel_to_state(E, vx)
X_init, t = np.copy(X), 0.
# free fall and compute touchdown position
T_fall, X_TD, t_fall, X_fall = self.free_fall(X, theta0)
t += T_fall
foot_pos_TD = np.array([X_TD[0] + self.l0 * np.sin(theta0), 0])
# stance phase and compute takeoff position
X = np.copy(X_TD)
X_stance, t_stance = [], []
xf_TD, yf_TD = foot_pos_TD
while np.sqrt((X[0] - xf_TD) ** 2 + (X[1] - yf_TD) ** 2) <= self.l0 + 1e-9: #self.in_stance_phase(X_TD[:2], foot_pos_TD):
X += self.dt * self.stance_dynamic(X, foot_pos=foot_pos_TD)
t += self.dt
X_stance.append(X)
t_stance.append(t)
X_TO = np.copy(X)
X_stance, t_stance = np.array(X_stance).T, np.array(t_stance).T
# flight phase and compute final apex position
T_rise, X_apex, t_rise, X_rise = self.free_rise(X)
t_rise += t
t += T_rise
X_final = X_apex
# combine free-fall, stance, and flight phases
ts = np.concatenate((t_fall, t_stance, t_rise))
Xs = np.hstack((X_fall, X_stance, X_rise))
if plot is True:
if X_TO[3] < 0: # touchdown vx < 0
plt.plot(Xs[0], Xs[1], 'k--')
else:
plt.plot(Xs[0], Xs[1])
plt.axis('equal')
plt.pause(0.001)
return X_init, X_TD, X_TO, X_final, ts, Xs
def free_fall(self, X_apex, theta, dt=1e-3):
"""
Free fall phase.
Args:
X_apex (np.array): apex state
Returns:
float: rising time
float[4]: touchdown state (pos and vel)
float[T]: time trajectory
float[4,T]: state trajectory
"""
x, y, dx, dy = X_apex
# get total distance and time to fall
y_fall = y - self.l0 * np.cos(theta)
T_fall = np.sqrt(2 * y_fall / self.g)
# compute touchdown state
x_TD, y_TD = x + dx * T_fall, y - y_fall
dx_TD, dy_TD = dx, dy - self.g * T_fall
X_TD = np.array([x_TD, y_TD, dx_TD, dy_TD])
# generate the whole trajectory
T = np.arange(start=0, stop=T_fall, step=dt)
X, Y = x + dx * T, y + dy * T - 0.5 * self.g * T ** 2
dX, dY = dx * np.ones(T.shape), dy - self.g * T
X_traj = np.vstack((X, Y, dX, dY))
return T_fall, X_TD, T, X_traj
def free_rise(self, X_TO, dt=1e-3):
"""
Free rise flight phase.
Args:
X_TO (np.array): take off state
Returns:
float: rising time
float[4]: apex state (pos and vel)
float[T]: time trajectory
float[4,T]: state trajectory
"""
x, y, dx, dy = X_TO
# get total time and distance to rise
T_rise = dy / self.g
y_rise = 0.5 * self.g * T_rise ** 2
# compute apex state
x_apex, y_apex = x + dx * T_rise, y + y_rise
dx_apex, dy_apex = dx, 0.0
X_apex = np.array([x_apex, y_apex, dx_apex, dy_apex])
# generate the whole trajectory
T = np.arange(start=0, stop=T_rise, step=dt)
X, Y = x + dx * T, y + dy * T - 0.5 * self.g * T**2
dX, dY = dx * np.ones(T.shape), dy - self.g * T
X_traj = np.vstack((X, Y, dX, dY))
return T_rise, X_apex, T, X_traj
def stance_GRF(self, X=None, foot_pos=(0,0)):
"""
Compute the ground reaction forces during the stance phase.
Args:
X (np.array, None): state of the inverted pendulum.
foot_pos (np.array): foot position
Returns:
np.array: ground reaction forces
"""
if X is None:
X = self.state
x, y, dx, dy = X
xf, yf = foot_pos
l = np.sqrt((x - xf) ** 2 + (y - yf) ** 2)
leg_force = self.k * (self.l0 - l)
leg_angle = np.arctan2(y - yf, x - xf)
# print("leg_angle: ", np.rad2deg(leg_angle))
GRF_x = leg_force * np.cos(leg_angle)
GRF_y = leg_force * np.sin(leg_angle)
return np.array([GRF_x, GRF_y])
def plot_trajectory(self, X, title='', block=True):
"""
Plot state trajectory.
"""
fig = plt.figure()
plt.plot(X[0], X[1])
plt.plot([X[0, 0]], [X[1, 0]], marker='o', markersize=3, color='green')
plt.plot([X[0, -1]], [X[1, -1]], marker='o', markersize=3, color='red')
plt.xlim(np.min(X[0]) - 0.1, np.max(X[0]) + 0.2)
plt.ylim(0, np.max(X[1]) + 0.2)
plt.xlabel('x')
plt.ylabel('y')
plt.title(title)
plt.show(block=block)
# Tests
if __name__ == '__main__':
# define some variables
mass = 85.0
# create 2D slip model
theta_min, theta_max = 0., np.deg2rad(15)
theta = np.random.uniform(theta_min, theta_max)
model = SLIP2D(mass=mass, length=0.8, stiffness=mass*500, gravity=9.81, dt=1.e-3, angle=theta)
# plot trajectory
E = 750
vx = np.random.uniform(0, model.max_velocity(E))
X = model.rollout(E=E, vx=vx)[-1]
model.plot_trajectory(X, title='E: {} - theta: {:.2} - vx: {:.2}'.format(str(E), str(np.rad2deg(theta)), str(vx)))
+310
View File
@@ -0,0 +1,310 @@
#!/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