Skip to content

Commit bfdce28

Browse files
committed
discrete DMPs test successfully
1 parent 88e2280 commit bfdce28

6 files changed

Lines changed: 231 additions & 3 deletions

File tree

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"python.pythonPath": "D:\\ProgramFiles\\Anaconda\\envs\\python36\\python.exe"
3+
}

README.assets/DMP_discrete.png

64.2 KB
Loading

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,23 +34,27 @@ Requirements:
3434

3535
## Discrete DMP test
3636

37-
[img]
37+
The DMP model is used to model and reproduce sine and cosine trajectories with a limited time.
38+
39+
![DMP_discrete](pic/DMP_discrete.png)
40+
41+
The solid curves represent the demonstrated trajectories, the dashed curves represent the reproduced trajectories by DMP models with the same and different initial and goal positions.
3842

3943

4044

4145
---
4246

4347
## Rhythmic DMP test
4448

45-
[img]
49+
![DMP_discrete](README.assets/DMP_rhythmic.png)
4650

4751

4852

4953
---
5054

5155
## Reference:
5256

53-
The reference paper can be found at:
57+
The reference paper can be found at the folder named 'paper', and can also be downloaded at:
5458

5559
- [2002 Stefan Schaal](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.142.3886)
5660

code/DMP/cs.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,11 @@ def reset_state(self): # reset state
5151
def step_discrete(self, tau=1.0):
5252
dx = -self.alpha_x*self.x*self.dt
5353
self.x += tau*dx
54+
return self.x
5455

5556
def step_rhythmic(self, tau=1.0):
5657
self.x += tau*self.dt
58+
return self.x
5759

5860

5961
#%% test code

code/DMP/dmp_discrete.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,222 @@
66

77
#%% import package
88
import numpy as np
9+
from scipy.interpolate import interp1d
10+
import matplotlib.pyplot as plt
11+
12+
from cs import CanonicalSystem
13+
14+
#%% define discrete dmp
15+
class dmp_discrete():
16+
def __init__(self, n_dmps=1, n_bfs=100, dt=0, alpha_y=None, beta_y=None, **kwargs):
17+
self.n_dmps = n_dmps # number of data dimensions, one dmp for one degree
18+
self.n_bfs = n_bfs # number of basis functions
19+
self.dt = dt
20+
21+
self.y0 = np.zeros(n_dmps) # for multiple dimensions
22+
self.goal = np.ones(n_dmps) # for multiple dimensions
23+
24+
self.alpha_y = np.ones(n_dmps) * 25.0 if alpha_y is None else alpha_y
25+
self.beta_y = self.alpha_y / 4.0 if beta_y is None else beta_y
26+
self.tau = 1.0
27+
28+
self.w = np.zeros((n_dmps, n_bfs)) # weights for forcing term
29+
self.psi_centers = np.zeros(self.n_bfs) # centers over canonical system for Gaussian basis functions
30+
self.psi_h = np.zeros(self.n_bfs) # variance over canonical system for Gaussian basis functions
31+
32+
# canonical system
33+
self.cs = CanonicalSystem(dt=self.dt, **kwargs)
34+
self.timesteps = int(self.cs.run_time / self.dt)
35+
36+
# generate centers for Gaussian basis functions
37+
self.generate_centers()
38+
39+
# self.h = np.ones(self.n_bfs) * self.n_bfs / self.psi_centers # original
40+
self.h = np.ones(self.n_bfs) * self.n_bfs**1.5 / self.psi_centers / self.cs.alpha_x # chose from trail and error
41+
42+
# reset state
43+
self.reset_state()
44+
45+
# Reset the system state
46+
def reset_state(self):
47+
self.y = self.y0.copy()
48+
self.dy = np.zeros(self.n_dmps)
49+
self.ddy = np.zeros(self.n_dmps)
50+
self.cs.reset_state()
51+
52+
def generate_centers(self):
53+
t_centers = np.linspace(0, self.cs.run_time, self.n_bfs) # centers over time
54+
55+
cs = self.cs
56+
x_track = cs.run() # get all x over run time
57+
t_track = np.linspace(0, cs.run_time, cs.timesteps) # get all time ticks over run time
58+
59+
for n in range(len(t_centers)):
60+
for i, t in enumerate(t_track):
61+
if abs(t_centers[n] - t) <= cs.dt: # find the x center corresponding to the time center
62+
self.psi_centers[n] = x_track[i]
63+
64+
return self.psi_centers
65+
66+
def generate_psi(self, x):
67+
if isinstance(x, np.ndarray):
68+
x = x[:, None]
69+
70+
self.psi = np.exp(-self.h * (x - self.psi_centers)**2)
71+
72+
return self.psi
73+
74+
def generate_weights(self, f_target):
75+
x_track = self.cs.run()
76+
psi_track = self.generate_psi(x_track)
77+
78+
for d in range(self.n_dmps):
79+
delta = self.goal[d] - self.y0[d]
80+
for b in range(self.n_bfs):
81+
# as both number and denom has x(g-y_0) term, thus we can simplify the calculation process
82+
numer = np.sum(x_track * psi_track[:,b] * f_target[:,d])
83+
denom = np.sum(x_track**2 * psi_track[:,b])
84+
# numer = np.sum(psi_track[:,b] * f_target[:,d]) # the simpler calculation
85+
# denom = np.sum(x_track * psi_track[:,b])
86+
self.w[d, b] = numer / (denom*delta)
87+
88+
return self.w
89+
90+
def learning(self, y_demo, plot=False):
91+
if y_demo.ndim == 1: # data is with only one dimension
92+
y_demo = y_demo.reshape(1, len(y_demo))
93+
94+
self.y0 = y_demo[:,0].copy()
95+
self.goal = y_demo[:,-1].copy()
96+
self.y_demo = y_demo.copy()
97+
98+
# interpolate the demonstrated trajectory to be the same length with timesteps
99+
x = np.linspace(0, self.cs.run_time, y_demo.shape[1])
100+
y = np.zeros((self.n_dmps, self.timesteps))
101+
for d in range(self.n_dmps):
102+
y_tmp = interp1d(x, y_demo[d])
103+
for t in range(self.timesteps):
104+
y[d, t] = y_tmp(t*self.dt)
105+
106+
# calculate velocity and acceleration of y_demo
107+
108+
# method 1: using gradient
109+
dy_demo = np.gradient(y, axis=1) / self.dt
110+
ddy_demo = np.gradient(dy_demo, axis=1) / self.dt
111+
112+
# method 2: using diff
113+
# dy_demo = np.diff(y) / self.dt
114+
# # let the first gradient same as the second gradient
115+
# dy_demo = np.hstack((np.zeros((self.n_dmps, 1)), dy_demo)) # Not sure if is it a bug?
116+
# # dy_demo = np.hstack((dy_demo[:,0].reshape(self.n_dmps, 1), dy_demo))
117+
118+
# ddy_demo = np.diff(dy_demo) / self.dt
119+
# # let the first gradient same as the second gradient
120+
# ddy_demo = np.hstack((np.zeros((self.n_dmps, 1)), ddy_demo))
121+
# # ddy_demo = np.hstack((ddy_demo[:,0].reshape(self.n_dmps, 1), ddy_demo))
122+
123+
f_target = np.zeros((y_demo.shape[1], self.n_dmps))
124+
for d in range(self.n_dmps):
125+
f_target[:,d] = ddy_demo[d] - (self.alpha_y[d]*(self.beta_y[d]*(self.goal[d] - y_demo[d]) - dy_demo[d]))
126+
127+
self.generate_weights(f_target)
128+
129+
if plot is True:
130+
# plot the basis function activations
131+
plt.figure()
132+
plt.subplot(211)
133+
psi_track = self.generate_psi(self.cs.run())
134+
plt.plot(psi_track)
135+
plt.title('basis functions')
136+
137+
# plot the desired forcing function vs approx
138+
plt.subplot(212)
139+
plt.plot(f_target[:,0])
140+
plt.plot(np.sum(psi_track * self.w[0], axis=1) * self.dt)
141+
plt.legend(['f_target', 'w*psi'])
142+
plt.title('DMP forcing function')
143+
plt.tight_layout()
144+
plt.show()
145+
146+
# reset state
147+
self.reset_state()
148+
149+
150+
def reproduce(self, tau=None, initial=None, goal=None):
151+
# set initial state
152+
if initial != None:
153+
self.y0 = initial
154+
155+
# set goal state
156+
if goal != None:
157+
self.goal = goal
158+
159+
# set temporal scaling
160+
if tau != None:
161+
self.tau = tau
162+
self.timesteps = int(self.timesteps/tau)
163+
164+
# reset state
165+
self.reset_state()
166+
167+
168+
y_reproduce = np.zeros((self.timesteps, self.n_dmps))
169+
dy_reproduce = np.zeros((self.timesteps, self.n_dmps))
170+
ddy_reproduce = np.zeros((self.timesteps, self.n_dmps))
171+
172+
for t in range(self.timesteps):
173+
y_reproduce[t], dy_reproduce[t], ddy_reproduce[t] = self.step(tau=tau)
174+
175+
return y_reproduce, dy_reproduce, ddy_reproduce
176+
177+
def step(self, tau=None):
178+
# run canonical system
179+
if tau == None:
180+
tau = self.tau
181+
x = self.cs.step_discrete(tau)
182+
183+
# generate basis function activation
184+
psi = self.generate_psi(x)
185+
186+
for d in range(self.n_dmps):
187+
# generate forcing term
188+
f = np.dot(psi, self.w[d])*x*(self.goal[d] - self.y0[d]) / np.sum(psi)
189+
190+
# generate reproduced trajectory
191+
self.ddy[d] = (tau**2)*(self.alpha_y[d]*(self.beta_y[d]*(self.goal[d] - self.y[d]) - self.dy[d]/tau) + f)
192+
self.dy[d] += tau*self.ddy[d]*self.dt
193+
self.y[d] += self.dy[d]*self.dt
194+
195+
return self.y, self.dy, self.ddy
196+
197+
198+
#%% test code
199+
if __name__ == "__main__":
200+
data_len = 100
201+
y_demo = np.zeros((2, data_len))
202+
t = np.linspace(0, 1.5*np.pi, data_len)
203+
y_demo[0,:] = np.sin(t)
204+
y_demo[1,:] = np.cos(t)
205+
206+
# DMP learning
207+
dmp = dmp_discrete(n_dmps=2, n_bfs=100, dt=0.01)
208+
dmp.learning(y_demo, plot=False)
209+
210+
# reproduce learned trajectory
211+
y_reproduce, dy_reproduce, ddy_reproduce = dmp.reproduce()
212+
213+
# set new initial and goal poisitions
214+
y_reproduce_2, dy_reproduce_2, ddy_reproduce_2 = dmp.reproduce(tau=0.5, initial=[0.2, 0.8], goal=[-0.6, 0.2])
215+
216+
plt.figure()
217+
plt.plot(y_demo[0,:], 'g', label='demo sine')
218+
plt.plot(y_demo[1,:], 'b', label='demo cosine')
219+
plt.plot(y_reproduce[:,0], 'r--', label='reproduce sine')
220+
plt.plot(y_reproduce[:,1], 'm--', label='reproduce cosine')
221+
plt.plot(y_reproduce_2[:,0], 'r-.', label='reproduce 2 sine')
222+
plt.plot(y_reproduce_2[:,1], 'm-.', label='reproduce 2 cosine')
223+
plt.legend()
224+
plt.grid()
225+
plt.show()
226+
227+
# %%

pic/DMP_discrete.png

64.2 KB
Loading

0 commit comments

Comments
 (0)