Skip to content

Commit a6ff0dc

Browse files
committedOct 1, 2021
v0.0.1 Initial package structure
1 parent aa4ce35 commit a6ff0dc

9 files changed

+306
-106
lines changed
 

‎README.md

+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Spring-loaded Inverted Pendulum (SLIP) Control
2+
3+
This repository intends to provide a base for the implementation, visualization, and *open-access* sharing of
4+
SLIP control for research and education. The SLIP model is a bio-inspired low-order template model for dynamics of
5+
(high-speed) legged locomotion. <cite>[[1]][1]</cite> It has been proven valuable in modeling the sagittal dynamics of the Center of Mass (CoM)
6+
of animals at high-speed locomotion, and extensions of this model were to model 3D bipedal walking and running.
7+
8+
![slip_repo_into](https://user-images.githubusercontent.com/8356912/135597417-ea86cddf-d9bd-4dc6-8f34-6a32abb94521.png)
9+
10+
https://user-images.githubusercontent.com/8356912/135605031-1d53dafc-bf50-4a66-b331-90fb9c892caf.mp4
11+
12+
*Motivation: There is a lot of research and educational content based on the SLIP model but no shared code or initiative
13+
that allows playing with the model control without implementing everything from scratch. The ideal scenario is for this repo to become a mechanism for people to contribute different approaches to SLIP control, visualization, debugging,
14+
and educational tools.*
15+
16+
### Main Features
17+
The repository offers python class objects with several utility functions to create SLIP models and plan passive or controlled trajectories, plus visualization tools for these trajectories. The main classes are:
18+
19+
- `SlipModel`: a python class representation of a SLIP model. The class enables the generation of passive
20+
trajectories by integration of the hybrid-dynamics differential equations governing SLIP, and some additional utility
21+
functions for visualization and state handling.
22+
23+
- `SlipGaitCycle` and `SlipGaitCycleCtrl`: python classes representing a full gait cycle (flight and stance phases) of
24+
a SLIP model. The class provides an intuitive interface to storing the cartesian and polar trajectories of the SLIP
25+
gait cycle model, along with accessors to full-cycle cartesian trajectories, foot contact positions, touch-down/take-off leg angles, and more. The controlled version is intended to store both the passive and controlled trajectories of the model.
26+
- `SlipTrajectory`: a class containing a sequence of consecutive `SlipGaitCycle`s, ensuring continuity in the trajectory. This class helps in saving and restoring a trajectory, plotting, and more.
27+
28+
- `SlipTrajectoryTree`: a tree data structure intended to be used to plan multi-cycle trajectories.
29+
It offers the capability to store multiple possible trajectories emerging from different future touch down angles,
30+
selection of the most optimal and some plotting utilities.
31+
32+
- Plotting: both for control development, debugging and especially for education, visual aid of the model dynamics and response. In utils, you will find plotting and (soon) animating utility functions to generate rich visualizations of
33+
`SlipTrajectory`s and `SlipTrajectoryTree`s.
34+
35+
The structure and functions of the classes are based on what I found useful when developing a multi-cycle
36+
trajectory planner for SLIP. Any modifications and enhancements are more than welcome. Submit a PR! :).
37+
38+
### Controllers
39+
40+
In the `slip_control/controllers` you will find single-cycle and multi-cycle trajectory control controllers. There
41+
is only one controller so far, but I encourage you to contribute many:
42+
43+
- [x] Differentially Flat optimal control of SLIP: A controller of an extended version of the SLIP model using hip torque and leg force actuation. Details in [[3]] (Chapter 4.1). This controller is an adapted version of [[2]].
44+
45+
### Instalation
46+
47+
This repo is still in early development therefore, there is no release, but installation is easy through pip via:
48+
49+
```buildoutcfg
50+
# Clone repo
51+
git clone https://github.com/Danfoa/slip_control.git
52+
# Move to repo folder
53+
cd slip_control
54+
# Use pip to install repo and dependencies
55+
pip install -e .
56+
```
57+
58+
#### Examples
59+
60+
A preliminary example of the visualization tools and the Differentially Flat Controller can be run by
61+
62+
```buildoutcfg
63+
cd <repo>
64+
# Run example
65+
python examples/diff_flat_control_and_visualization.py
66+
```
67+
68+
69+
### References and recommended literature
70+
71+
By far, the best summary of the SLIP model and its control is [[1]]. For the model, biomechanical foundations [[4]] offers a
72+
a great introduction to the value of SLIP as a generic locomotion model.
73+
74+
- [[1]] Geyer, H., et al. "Gait based on the spring-loaded inverted pendulum." Humanoid Robotics: A Reference. Springer Netherlands, 2018. 1-25.
75+
- [[2]] Chen, Hua, Patrick M. Wensing, and Wei Zhang. "Optimal control of a differentially flat two-dimensional spring-loaded inverted pendulum model." IEEE Robotics and Automation Letters 5.2 (2019): 307-314.
76+
- [[3]] Ordoñez Apraez, Daniel Felipe. Learning to run naturally: guiding policies with the Spring-Loaded Inverted Pendulum. MS thesis. Universitat Politècnica de Catalunya, 2021.
77+
- [[4]] Full, Robert J., Claire T. Farley, and Jack M. Winters. "Musculoskeletal dynamics in rhythmic systems: a comparative approach to legged locomotion." Biomechanics and neural control of posture and movement. Springer, New York, NY, 2000. 192-205.
78+
79+
80+
[1]: https://link.springer.com/referenceworkentry/10.1007%2F978-94-007-7194-9_43-1#:~:text=The%20spring%2Dloaded%20inverted%20pendulum%20(SLIP)%20describes%20gait%20with,control%20of%20compliant%20legged%20locomotion. "Hello"
81+
[2]: https://ieeexplore.ieee.org/abstract/document/8917684?casa_token=u34S9aFjkVAAAAAA:JqTD1t2tg4w_Xojfzc18fPggjZGO3mztxcMUzrY-pzuCeqwhvxjL5Dz7lHbFgkgOwUVzu6YZsEE
82+
[3]: https://upcommons.upc.edu/handle/2117/348196
83+
[4]: https://link.springer.com/chapter/10.1007/978-1-4612-2104-3_13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time : 1/10/21
4+
# @Author : Daniel Ordonez
5+
# @email : daniels.ordonez@gmail.com
6+
from math import pi as PI
7+
import numpy as np
8+
import matplotlib.pyplot as plt
9+
from argparse import ArgumentParser
10+
11+
from slip_control.slip.slip_trajectory import SlipTrajectory
12+
from slip_control.slip.slip_model import SlipModel, X, X_DOT, X_DDOT, Z, Z_DOT, Z_DDOT
13+
from slip_control.controllers.target_to_states_generator import CycleStateGenerator, ForwardSpeedStateGenerator
14+
from slip_control.controllers.diff_flat_slip_controller import SlipDiffFlatController
15+
from slip_control.utils import plot_utils
16+
17+
cmap = plt.cm.get_cmap('gist_heat')
18+
19+
if __name__ == "__main__":
20+
21+
# Instantiate SLIP model
22+
m = 80 # [kg]
23+
r0 = 1.0 # [m]
24+
n_legs = 1
25+
k_rel = 10.7
26+
slip = SlipModel(mass=m, leg_length=r0, k_rel=k_rel * n_legs)
27+
28+
g = SlipModel.g
29+
30+
# Error deviation weights during the stance trajectory
31+
traj_weights = np.array([1., 1., 1., 1., 1., 1.])
32+
traj_weights /= np.linalg.norm(traj_weights)
33+
# Error deviation weights of target take-off states
34+
take_off_state_error_weights = np.array([0.0, 1.0, 0., 1.0, 1.0, 0.])
35+
take_off_state_error_weights /= np.linalg.norm(take_off_state_error_weights)
36+
37+
38+
n_cycles = 5 # Generate a trajectory of 5 cycles
39+
max_theta_dot = 4*PI # [rad/s] max angular leg velocity during flight
40+
# Define a forward velocity
41+
forward_speed = 4 * slip.r0 # [m/s]
42+
# Define a desired gait duty cycle (time of stance / time of cycle) in [0.2, 1.0]
43+
duty_cycle = 0.8
44+
45+
z_init = slip.r0
46+
# Set an initial state (assumed to be a flight phase state) [x, x', x'', z, z', z'']
47+
init_to_state = np.array([0.0, forward_speed, 0.0, z_init, 0.0, -g])
48+
# Set a desired take off state defining the forward and vertical velocity desired
49+
to_des_state = init_to_state
50+
51+
# Configure Differentially flat controller
52+
slip_controller = SlipDiffFlatController(slip_model=slip,
53+
traj_weights=traj_weights,
54+
max_flight_theta_dot=max_theta_dot,
55+
debug=False)
56+
to_state_generator = ForwardSpeedStateGenerator(slip_model=slip, target_state_weights=take_off_state_error_weights,
57+
desired_forward_speed=forward_speed,
58+
desired_duty_cycle=duty_cycle)
59+
slip_controller.target_to_state_generator = to_state_generator
60+
61+
# Generate SLIP trajectory tree without future cycle planning
62+
tree = slip_controller.generate_slip_trajectory_tree(desired_gait_cycles=n_cycles,
63+
initial_state=init_to_state,
64+
max_samples_per_cycle=30,
65+
angle_epsilon=np.deg2rad(.02),
66+
look_ahead_cycles=0)
67+
slip_traj_no_future = tree.get_optimal_trajectory()
68+
plot_utils.plot_slip_trajectory(slip_traj_no_future, plot_passive=True, plot_td_angles=True,
69+
title="Without future cycle planning",
70+
color=(23/255., 0/255., 194/255.))
71+
plt.show()
72+
73+
# Generate SLIP trajectory tree with future cycle planning
74+
tree = slip_controller.generate_slip_trajectory_tree(desired_gait_cycles=n_cycles,
75+
initial_state=init_to_state,
76+
max_samples_per_cycle=30,
77+
angle_epsilon=np.deg2rad(.02),
78+
look_ahead_cycles=1)
79+
slip_traj = tree.get_optimal_trajectory()
80+
plot_utils.plot_slip_trajectory(slip_traj, plot_passive=True, plot_td_angles=True,
81+
title="With future cycle planing",
82+
color=(23 / 255., 154 / 255., 194 / 255.))
83+
plt.show()
84+
85+
# Plot controlled trajectory tree
86+
print("This takes a while... should optimize soon")
87+
tree.plot()
88+
plt.show()
89+
90+
# Compare first two cycles.
91+
short_traj = SlipTrajectory(slip, slip_gait_cycles=slip_traj.gait_cycles[:2])
92+
short_traj_no_future = SlipTrajectory(slip, slip_gait_cycles=slip_traj_no_future.gait_cycles[:2])
93+
axs = plot_utils.plot_slip_trajectory(short_traj, plot_passive=True, plot_td_angles=True,
94+
color=(23 / 255., 154 / 255., 194 / 255.))
95+
axs = plot_utils.plot_slip_trajectory(short_traj_no_future, plot_passive=True, plot_td_angles=True, plt_axs=axs,
96+
color=(23/255., 0/255., 194/255.))
97+
plt.show()
98+
99+
# Plot limit cycles of controlled trajectory
100+
phase_axs = plot_utils.plot_limit_cycles(slip_traj)
101+
plt.show()

‎setup.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# @Time : 1/10/21
4+
# @Author : Daniel Ordoñez
5+
# @email : daniels.ordonez@gmail.com
6+
7+
from distutils.core import setup
8+
9+
with open("README.md", "r", encoding="utf-8") as fh:
10+
long_description = fh.read()
11+
12+
setup(name='slip_control',
13+
version='0.0.1',
14+
description='Spring Loaded Inverted Pendulum control and visualization python tools',
15+
long_description=long_description,
16+
long_description_content_type="text/markdown",
17+
author='Daniel Ordonez',
18+
author_email='daniel.ordonez@gmail.com',
19+
url='https://github.com/Danfoa/slip_control',
20+
packages=['slip', 'controllers', 'utils'],
21+
package_dir={'': "slip_control"},
22+
classifiers=[
23+
"Programming Language :: Python :: 3",
24+
"License :: OSI Approved :: MIT License",
25+
"Operating System :: OS Independent",
26+
],
27+
requires=['numpy', 'scipy', 'matplotlib', 'tqdm', 'cvxpy'],
28+
python_requires=">=3.6",
29+
)

‎slip_control/controllers/diff_flat_slip_controller.py

+11
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,17 @@ def __init__(self, slip_model: SlipModel, traj_weights,
5757
def generate_slip_trajectory_tree(self, initial_state, desired_gait_cycles=np.inf, desired_duration=np.inf,
5858
look_ahead_cycles=0, max_samples_per_cycle=20, angle_epsilon=np.deg2rad(1),
5959
verbose=True) -> TrajectoryTreeNode:
60+
"""
61+
Generate a trajectory tree by testing and controlling multiple touch down angles per gait cycle
62+
:param initial_state: Initial cartesian state assumed to be part of a flight phase
63+
:param desired_gait_cycles: Number of gait cycles to plan for
64+
:param desired_duration: Maximum time of a trajectory to be returned
65+
:param look_ahead_cycles: Number of gait cycles in the future to look for when planning.
66+
:param max_samples_per_cycle: Maximum number of touch down angles evaluated per gait cycle
67+
:param angle_epsilon: Minimum difference in touch down angle to consider optimal
68+
:param verbose:
69+
:return: A trajectory tree with all the evaluated trajectories.
70+
"""
6071
assert xor(np.isinf(desired_gait_cycles), np.isinf(
6172
desired_duration)), 'Provide ONE stopping criteria: `desired_gait_cycles` or `desired_duration`'
6273
opt_start = time.time()

‎slip_control/slip/slip_gait_cycle.py

+9-9
Original file line numberDiff line numberDiff line change
@@ -160,25 +160,25 @@ def __init__(self, slip_model, t_flight, flight_cartesian_traj, t_stance, stance
160160
@property
161161
def stance_cartesian_traj(self) -> ndarray:
162162
if self._stance_cartesian_traj is None:
163-
self._stance_cartesian_traj = self.slip_model.polar_to_cartesian(trajectory=(self.stance_polar_traj),
164-
control_signal=(self.control_signal),
165-
foot_contact_pos=(self.foot_contact_pos))
163+
self._stance_cartesian_traj = self.slip_model.polar_to_cartesian(trajectory=self.stance_polar_traj,
164+
control_signal=self.control_signal,
165+
foot_contact_pos=self.foot_contact_pos)
166166
return self._stance_cartesian_traj
167167

168168
@property
169169
def take_off_state(self) -> ndarray:
170170
if self._stance_cartesian_traj is None:
171-
return self.slip_model.polar_to_cartesian((self.stance_polar_traj[:, -1]),
172-
control_signal=(self.control_signal[:, -1]),
173-
foot_contact_pos=(self.foot_contact_pos))
171+
return self.slip_model.polar_to_cartesian(self.stance_polar_traj[:, -1],
172+
control_signal=self.control_signal[:, -1],
173+
foot_contact_pos=self.foot_contact_pos)
174174
else:
175175
return np.array(self.stance_cartesian_traj[:, -1])
176176

177177
@property
178178
def touch_down_state(self):
179179
if self._stance_cartesian_traj is None:
180-
return self.slip_model.polar_to_cartesian((self.stance_polar_traj[:, 0]),
181-
control_signal=(self.control_signal[:, 0]),
182-
foot_contact_pos=(self.foot_contact_pos))
180+
return self.slip_model.polar_to_cartesian(self.stance_polar_traj[:, 0],
181+
control_signal=self.control_signal[:, 0],
182+
foot_contact_pos=self.foot_contact_pos)
183183
else:
184184
return np.array(self.stance_cartesian_traj[:, 0])

‎slip_control/slip/slip_model.py

+8-10
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,15 @@ def polar_to_cartesian(self, trajectory, control_signal=None, foot_contact_pos=0
156156
assert polar_traj.shape[0] == 4, 'Provide a valid (4, k) polar trajectory: %s' % polar_traj.shape
157157
if polar_traj.ndim == 1:
158158
polar_traj = np.expand_dims(polar_traj, axis=1)
159-
u_ctrl = control_signal
159+
160+
if control_signal is None:
161+
u_ctrl = np.zeros((2, polar_traj.shape[-1]))
160162
else:
161-
if control_signal is None:
162-
u_ctrl = np.zeros((2, polar_traj.shape[(-1)]))
163-
else:
164-
u_ctrl = np.array(control_signal)
165-
if u_ctrl.ndim == 1:
166-
u_ctrl = np.expand_dims(u_ctrl, axis=1)
167-
assert u_ctrl.shape[0] == 2, 'Provide a valid (2, k) control input vector'
168-
assert polar_traj.shape[1] == u_ctrl.shape[1], 'Len of trajectory: polar = %d | control_input = %d' % (
169-
polar_traj.shape[1], u_ctrl.shape[1])
163+
u_ctrl = np.expand_dims(control_signal, axis=1) if control_signal.ndim == 1 else np.array(control_signal)
164+
165+
assert u_ctrl.shape[0] == 2, 'Provide a valid (2, k) control input vector'
166+
assert polar_traj.shape[1] == u_ctrl.shape[1], 'Len of trajectory: polar = %d | control_input = %d' % \
167+
(polar_traj.shape[1], u_ctrl.shape[1])
170168
theta = polar_traj[0, :]
171169
theta_dot = polar_traj[1, :]
172170
r = polar_traj[2, :]

‎slip_control/slip/slip_trajectory.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ def gen_continuous_gait_phase_signal(self) -> interp1d:
138138
touch_down_time = cycle.t_flight[(-1)]
139139
t.extend([cycle.start_time, touch_down_time, cycle.end_time - dt])
140140

141-
t.append(self.end_time)
141+
# t.append(self.end_time)
142142
gait_cycle_phase = np.linspace(0, 2 * np.pi, 3)
143-
period_cycle_phase = np.concatenate([gait_cycle_phase] * len(self) + gait_cycle_phase[(-1)])
143+
period_cycle_phase = np.concatenate([gait_cycle_phase] * len(self))
144144
continuous_gait_phase_signal = interp1d(x=t, y=period_cycle_phase, kind='linear', fill_value='extrapolate',
145145
assume_sorted=True)
146146
return continuous_gait_phase_signal

‎slip_control/slip/slip_trajectory_tree.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def optimal_td_angle(self) -> Optional[float]:
7171
td_angles = list(self.child_nodes.keys())
7272
costs = [node.compute_opt_cost() for node in self.child_nodes.values()]
7373
if np.all(np.isnan(costs)):
74-
self._optimal_td_angle = td_angles[(-1)]
74+
best_branch_idx = 0
7575
else:
7676
best_branch_idx = np.nanargmin(costs)
7777
self._optimal_td_angle = td_angles[best_branch_idx]
@@ -148,4 +148,3 @@ def plot(self, optimal_color=(0, 0, 0, 1), sub_optimal_color=(1, 0, 0, 0.15), un
148148
outer_pbar.update()
149149

150150
outer_pbar.close()
151-
return plt_axs.get_figure()

‎slip_control/utils/plot_utils.py

+62-83
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def plot_cartesian_trajectory(traj, t, target_state=None, foot_pos=None, touch_d
5757

5858
if touch_down_angle is not None:
5959
xz_ax.text(foot_pos + 0.01, 0.05, '%.1f°' % touch_down_angle)
60-
xz_ax.grid('on', alpha=0.3)
60+
xz_ax.grid('on', alpha=0.15)
6161
xz_ax.set_ylim(bottom=0.0, auto=True)
6262
xz_ax.set_xlabel('$\\mathbf{y[m]}$', fontweight='bold', fontsize=LABEL_FONT_SIZE)
6363
xz_ax.set_ylabel('$\\mathbf{z[m]}$', fontweight='bold', fontsize=LABEL_FONT_SIZE)
@@ -71,7 +71,7 @@ def plot_cartesian_trajectory(traj, t, target_state=None, foot_pos=None, touch_d
7171
ax.set_ylabel((x_titles[i]), fontweight='bold', fontsize=LABEL_FONT_SIZE)
7272
if i == len(x_axs) - 1:
7373
ax.set_xlabel('t[s]', fontweight='bold', fontsize=LABEL_FONT_SIZE)
74-
ax.grid('on', alpha=0.3)
74+
ax.grid('on', alpha=0.15)
7575
ax.set_xlim(xmin=0.0, auto=True)
7676

7777
z_titles = ['$\\mathbf{z [m]}$', '$\\mathbf{\\dot{z} [m/s]}$', '$\\mathbf{\\ddot{z} [m/s^2]}$']
@@ -82,7 +82,7 @@ def plot_cartesian_trajectory(traj, t, target_state=None, foot_pos=None, touch_d
8282
ax.set_ylabel((z_titles[i]), fontweight='bold', fontsize=LABEL_FONT_SIZE)
8383
if i == len(z_axs) - 1:
8484
ax.set_xlabel('t[s]', fontweight='bold', fontsize=LABEL_FONT_SIZE)
85-
ax.grid('on', alpha=0.3)
85+
ax.grid('on', alpha=0.15)
8686
ax.set_xlim(xmin=0.0, auto=True)
8787

8888
return (fig, plt_axs)
@@ -109,15 +109,15 @@ def plot_polar_trajectory(polar_traj, t, target_state=None, axs=None, color='k',
109109
ax.plot((t[0]), (polar_traj[(i + 2, 0)]), color=color, marker='o', markersize='6', markeredgecolor='k')
110110
ax.plot((t[(-1)]), (target_state[(i + 2)]), 'D', color=target_color, markersize='10')
111111
ax.set_ylabel((y_titles[i]), rotation=0)
112-
ax.grid('on', alpha=0.3)
112+
ax.grid('on', alpha=0.15)
113113

114114
z_titles = ['$\\theta$', '$\\dot{\\theta}$', '$\\ddot{\\theta}$']
115115
for i, ax in enumerate(theta_axs.flatten()):
116116
ax.plot(t, (np.rad2deg(polar_traj[i, :])), 'k-', markersize='2', label=label, color=color)
117117
ax.plot((t[0]), (np.rad2deg(polar_traj[(i, 0)])), color=color, marker='o', markersize='6', markeredgecolor='k')
118118
ax.plot((t[(-1)]), (np.rad2deg(target_state[i])), 'D', color=target_color, markersize='10')
119119
ax.set_ylabel((z_titles[i]), rotation=0)
120-
ax.grid('on', alpha=0.3)
120+
ax.grid('on', alpha=0.15)
121121

122122
return (
123123
fig, axs)
@@ -141,7 +141,7 @@ def plot_control_trajectory(u_traj, t, theta, r, r0, k, axs=None, color='k', lab
141141
ax.plot(t, displacement, 'k-o', label=label, color=color, markersize=marker_size, linestyle=linestyle,
142142
marker=marker)
143143
ax.set_ylabel('Displacement [m]')
144-
ax.grid('on', alpha=0.3)
144+
ax.grid('on', alpha=0.15)
145145
ax = extension_ax[1]
146146
ax.set_title('Leg length displacement $u_{1}$')
147147
resultant_force = k * (r0 - r + u_traj[0, :])
@@ -155,13 +155,13 @@ def plot_control_trajectory(u_traj, t, theta, r, r0, k, axs=None, color='k', lab
155155
ax.fill_between((theta * 180 / np.pi), resultant_force, passive_force, alpha=0.1, color=color)
156156
ax.set_ylabel('Radial Force [m]')
157157
ax.set_xlabel('Hip angle $\\theta$ [Deg]')
158-
ax.grid('on', alpha=0.3)
158+
ax.grid('on', alpha=0.15)
159159
ax = torque_ax[0]
160160
ax.set_title('Hip torque $u_{2}$')
161161
ax.plot(t, hip_torque, 'k-o', label=label, color=color, markersize=marker_size, linestyle=linestyle, marker=marker)
162162
ax.set_ylabel('Torque [Nm]')
163163
ax.set_xlabel('Time [s]')
164-
ax.grid('on', alpha=0.3)
164+
ax.grid('on', alpha=0.15)
165165
ax = torque_ax[1]
166166
ax.set_title('Hip torque $u_{2}$ vs $\\theta$')
167167
ax.plot((theta * 180 / np.pi), hip_torque, 'k-o', label=label, color=color, markersize=marker_size,
@@ -170,7 +170,7 @@ def plot_control_trajectory(u_traj, t, theta, r, r0, k, axs=None, color='k', lab
170170
ax.fill_between((theta * 180 / np.pi), hip_torque, alpha=0.1, color=color)
171171
ax.set_ylabel('Torque [Nm]')
172172
ax.set_xlabel('Hip angle $\\theta$ [Deg]')
173-
ax.grid('on', alpha=0.3)
173+
ax.grid('on', alpha=0.15)
174174
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
175175
return (
176176
fig, axs)
@@ -229,96 +229,75 @@ def plot_slip_trajectory(slip_traj: Union[(SlipTrajectory, SlipGaitCycleCtrl)],
229229
return plt_axs
230230

231231

232-
def plot_limit_cycles(slip_traj: SlipTrajectory, start_cycle=0, axs=None, cmap_name='copper'):
233-
print(('Plotting slip_traj limit gait_cycle for %d cycles...' % slip_traj.num_cycles), end='')
234-
fig_size = (15, 10)
232+
def plot_limit_cycles(slip_traj: SlipTrajectory, axs=None, cmap_name='copper', fig_size=(15, 10)):
235233
LABEL_FONT_SIZE = 12
236234
cmap = plt.cm.get_cmap(cmap_name)
237-
if axs is None:
238-
fig, axs = plt.subplots(4, 3, figsize=fig_size)
239235

240236
def plot_limit_cycle_metric(x_flight, y_flight, x_stance, y_stance, x_name, y_name, flight_phase, stance_phase, ax,
241237
ax2, ax3, color):
242238
if x_flight is not None:
243239
ax.plot(x_flight, y_flight, label=label, color=color, markersize=marker_size, linestyle='dotted')
244-
else:
245-
ax.plot(x_stance, y_stance, label=label, color=color, markersize=marker_size, linestyle='-')
246-
ax.plot((x_stance[0]), (y_stance[0]), 'D', color=color, markersize='6', markeredgecolor='k')
247-
ax.plot((x_stance[(-1)]), (y_stance[(-1)]), 'X', color=color, markersize='6', markeredgecolor='k')
248-
ax.set_xlabel(x_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
249-
ax.set_ylabel(y_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
250-
ax.grid('on', alpha=0.3)
251-
if x_flight is not None:
252-
ax2.plot(flight_phase, y_flight, label=label, color=color, markersize=marker_size, linestyle='dotted')
253-
ax2.plot((stance_phase[0]), (y_stance[0]), 'D', color=color, markersize='6', markeredgecolor='k')
254-
ax2.plot((stance_phase[(-1)]), (y_stance[(-1)]), 'X', color=color, markersize='6', markeredgecolor='k')
255-
ax2.plot(stance_phase, y_stance, label=label, color=color, markersize=marker_size, linestyle='-')
256-
ax2.set_ylabel(y_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
257-
ax2.set_xlabel('$\\phi$', fontweight='bold', fontsize=LABEL_FONT_SIZE)
258-
ax2.grid('on', alpha=0.8)
259-
if x_flight is not None:
260-
ax3.plot(flight_phase, x_flight, label=label, color=color, markersize=marker_size, linestyle='dotted')
261-
ax3.plot((stance_phase[0]), (x_stance[0]), 'D', color=color, markersize='6', markeredgecolor='k')
262-
ax3.plot((stance_phase[(-1)]), (x_stance[(-1)]), 'X', color=color, markersize='6', markeredgecolor='k')
240+
ax.plot(x_stance, y_stance, label=label, color=color, markersize=marker_size, linestyle='-')
241+
ax.plot((x_stance[0]), (y_stance[0]), 'D', color=color, markersize='4', markeredgecolor='k')
242+
ax.plot((x_stance[(-1)]), (y_stance[(-1)]), 'X', color=color, markersize='4', markeredgecolor='k')
243+
ax.set_xlabel(x_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
244+
ax.set_ylabel(y_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
245+
ax.grid('on', alpha=0.15)
246+
if x_flight is not None:
247+
ax2.plot(flight_phase, y_flight, label=label, color=color, markersize=marker_size, linestyle='dotted')
248+
ax2.plot(stance_phase, y_stance, label=label, color=color, markersize=marker_size, linestyle='-')
249+
ax2.plot((stance_phase[0]), (y_stance[0]), 'D', color=color, markersize='4', markeredgecolor='k')
250+
ax2.plot((stance_phase[(-1)]), (y_stance[(-1)]), 'X', color=color, markersize='4', markeredgecolor='k')
251+
ax2.set_ylabel(y_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
252+
ax2.set_xlabel('$\\phi$', fontweight='bold', fontsize=LABEL_FONT_SIZE)
253+
ax2.grid('on', alpha=0.15)
254+
if x_flight is not None:
255+
ax3.plot(flight_phase, x_flight, label=label, color=color, markersize=marker_size, linestyle='dotted')
263256
ax3.plot(stance_phase, x_stance, label=label, color=color, markersize=marker_size, linestyle='-')
257+
ax3.plot((stance_phase[0]), (x_stance[0]), 'D', color=color, markersize='4', markeredgecolor='k')
258+
ax3.plot((stance_phase[(-1)]), (x_stance[(-1)]), 'X', color=color, markersize='4', markeredgecolor='k')
264259
ax3.set_ylabel(x_name, fontweight='bold', fontsize=LABEL_FONT_SIZE)
265260
ax3.set_xlabel('$\\phi$', fontweight='bold', fontsize=LABEL_FONT_SIZE)
266-
ax3.grid('on', alpha=0.8)
261+
ax3.grid('on', alpha=0.15)
262+
263+
if axs is None:
264+
fig, axs = plt.subplots(4, 3, figsize=fig_size)
267265

268-
for cycle in range(start_cycle, slip_traj.num_cycles):
269-
color = cmap(cycle / slip_traj.num_cycles)
266+
for i, cycle in enumerate(slip_traj.gait_cycles):
267+
color = cmap(i / len(slip_traj))
270268
marker_size = 1
271269
label = None
272-
flight_traj = slip_traj.flight_traj[cycle]
273-
stance_traj = slip_traj.stance_traj[cycle]
274-
stance_traj_polar = slip_traj.stance_polar_traj[cycle]
275-
flight_phase = slip_traj.get_gait_phase(
276-
np.linspace(start=(slip_traj.flight_times[cycle][0]), stop=(slip_traj.flight_times[cycle][(-1)]),
277-
num=(len(flight_traj[Z_DOT]))))
278-
stance_phase = slip_traj.get_gait_phase(
279-
np.linspace(start=(slip_traj.stance_times[cycle][0]), stop=(slip_traj.stance_times[cycle][(-2)]),
280-
num=(len(stance_traj[Z_DOT]))))
270+
flight_traj = cycle.flight_cartesian_traj
271+
stance_traj = cycle.stance_cartesian_traj
272+
stance_traj_polar = cycle.stance_polar_traj
273+
274+
flight_phase = slip_traj.get_gait_phase(np.linspace(start=cycle.t_flight[0], stop=cycle.t_flight[-1],
275+
num=len(cycle.t_flight)))
276+
stance_phase = slip_traj.get_gait_phase(np.linspace(start=cycle.t_stance[0], stop=cycle.t_stance[-2],
277+
num=len(cycle.t_stance)))
278+
281279
plot_limit_cycle_metric(x_flight=(flight_traj[X_DOT]), y_flight=(flight_traj[Z_DOT]),
282-
x_stance=(stance_traj[X_DOT]),
283-
y_stance=(stance_traj[Z_DOT]),
284-
x_name='$\\dot{y}[m/s]}$',
285-
y_name='$\\dot{z}[m/s]}$',
286-
flight_phase=flight_phase,
287-
stance_phase=stance_phase,
288-
ax=(axs[(0, 0)]),
289-
ax2=(axs[(0, 1)]),
290-
ax3=(axs[(0, 2)]),
291-
color=color)
280+
x_stance=(stance_traj[X_DOT]), y_stance=(stance_traj[Z_DOT]),
281+
x_name='$\\dot{x}[m/s]}$', y_name='$\\dot{z}[m/s]}$',
282+
flight_phase=flight_phase, stance_phase=stance_phase,
283+
ax=(axs[(0, 0)]), ax2=(axs[(0, 1)]), ax3=(axs[(0, 2)]), color=color)
292284
plot_limit_cycle_metric(x_flight=(flight_traj[X_DDOT]), y_flight=(flight_traj[Z_DDOT]),
293-
x_stance=(stance_traj[X_DDOT]),
294-
y_stance=(stance_traj[Z_DDOT]),
295-
x_name='$\\ddot{y}[m/s]}$',
296-
y_name='$\\ddot{z}[m/s]}$',
297-
flight_phase=flight_phase,
298-
stance_phase=stance_phase,
299-
ax=(axs[(1, 0)]),
300-
ax2=(axs[(1, 1)]),
301-
ax3=(axs[(1, 2)]),
285+
x_stance=(stance_traj[X_DDOT]), y_stance=(stance_traj[Z_DDOT]),
286+
x_name='$\\ddot{x}[m/s]}$', y_name='$\\ddot{z}[m/s]}$',
287+
flight_phase=flight_phase, stance_phase=stance_phase,
288+
ax=(axs[(1, 0)]), ax2=(axs[(1, 1)]), ax3=(axs[(1, 2)]),
302289
color=color)
303-
plot_limit_cycle_metric(x_flight=None, y_flight=None, x_stance=(np.rad2deg(stance_traj_polar[THETA])),
304-
y_stance=(stance_traj_polar[R]),
305-
x_name='$\\theta[deg]$',
306-
y_name='$r[m]$',
307-
flight_phase=flight_phase,
308-
stance_phase=stance_phase,
309-
ax=(axs[(2, 0)]),
310-
ax2=(axs[(2, 1)]),
311-
ax3=(axs[(2, 2)]),
290+
plot_limit_cycle_metric(x_flight=None, y_flight=None,
291+
x_stance=(np.rad2deg(stance_traj_polar[THETA])), y_stance=(stance_traj_polar[R]),
292+
x_name='$\\theta[deg]$', y_name='$r[m]$',
293+
flight_phase=flight_phase, stance_phase=stance_phase,
294+
ax=(axs[(2, 0)]), ax2=(axs[(2, 1)]), ax3=(axs[(2, 2)]),
312295
color=color)
313-
plot_limit_cycle_metric(x_flight=None, y_flight=None, x_stance=(np.rad2deg(stance_traj_polar[THETA_DOT])),
314-
y_stance=(stance_traj_polar[R_DOT]),
315-
x_name='$\\dot{\\theta}[deg/s]$',
316-
y_name='$\\dot{r}[m/s]$',
317-
flight_phase=flight_phase,
318-
stance_phase=stance_phase,
319-
ax=(axs[(3, 0)]),
320-
ax2=(axs[(3, 1)]),
321-
ax3=(axs[(3, 2)]),
296+
plot_limit_cycle_metric(x_flight=None, y_flight=None,
297+
x_stance=(np.rad2deg(stance_traj_polar[THETA_DOT])), y_stance=(stance_traj_polar[R_DOT]),
298+
x_name='$\\dot{\\theta}[deg/s]$', y_name='$\\dot{r}[m/s]$',
299+
flight_phase=flight_phase, stance_phase=stance_phase,
300+
ax=(axs[(3, 0)]), ax2=(axs[(3, 1)]), ax3=(axs[(3, 2)]),
322301
color=color)
323-
302+
plt.tight_layout()
324303
return axs

0 commit comments

Comments
 (0)
Please sign in to comment.