Skip to content

Commit e92abc0

Browse files
committed
realized dt was being used as simulation dt in pi_cruise_control and went on a quest to standardize the simulations a little more. All the others had a simdt, which means you can just downsample, but for cc I like how the function comes out with dt=0.01, so I introduced a way to upsample. There was then a consideration about when to add noise, which now happens *after* slicing for all sims, which has the added benefit of not needing to produce so many noise samples and of being sure the number of outliers is always 1%, if I want those. Prior to this change, outliers might be sampled in at a random 1% of 40000k locations, and then the signal would be downsampled after, which might leave behind more or fewer of them.
1 parent f70ed72 commit e92abc0

2 files changed

Lines changed: 38 additions & 37 deletions

File tree

pynumdiff/tests/test_utils.py

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -96,22 +96,23 @@ def test_simulations(request):
9696
"""Just sprint through running them all to make sure they go. Optionally plot with flag."""
9797
if request.config.getoption("--plot"):
9898
from matplotlib import pyplot
99-
fig, axes = pyplot.subplots(2, 3, figsize=(18,7), constrained_layout=True)
100-
101-
for i,(sim,title) in enumerate(zip([pi_cruise_control, sine, triangle, pop_dyn, linear_autonomous, lorenz_x],
102-
["Cruise Control", "Sum of Sines", "Triangles", "Logistic Growth", "Linear Autonomous", "Lorenz First Dimension"])):
103-
104-
y, x, dxdt = sim(duration=4, dt=0.01, noise_type='normal', noise_parameters=[0,0.1])
105-
assert len(y) == len(x) == len(dxdt)
106-
107-
if request.config.getoption("--plot"):
108-
t = np.arange(len(y))*0.01
109-
ax = axes[i//3, i%3]
110-
ax.plot(t, x, 'k--', linewidth=3, label=r"true $x$")
111-
ax.plot(t, y, '.', color='blue', zorder=-100, markersize=5, label="noisy data")
112-
if i//3 == 0: ax.set_xticklabels([])
113-
ax.set_title(title, fontsize=18)
114-
if i == 5: ax.legend(loc='lower right', fontsize=12)
99+
axes = [pyplot.subplots(2, 3, figsize=(18,7), constrained_layout=True)[1] for i in range(3)]
100+
101+
for j,dt in enumerate([0.005, 0.01, 0.02]):
102+
for i,(sim,title) in enumerate(zip([pi_cruise_control, sine, triangle, pop_dyn, linear_autonomous, lorenz_x],
103+
["Cruise Control", "Sum of Sines", "Triangles", "Logistic Growth", "Linear Autonomous", "Lorenz First Dimension"])):
104+
105+
y, x, dxdt = sim(duration=4, dt=dt, noise_type='normal', noise_parameters=[0,0.1], outliers=True)
106+
assert len(y) == len(x) == len(dxdt) == 4/dt # duration/dt
107+
108+
if request.config.getoption("--plot"):
109+
t = np.arange(len(y))*dt
110+
ax = axes[j][i//3, i%3]
111+
ax.plot(t, x, 'k--', linewidth=3, label=r"true $x$")
112+
ax.plot(t, y, '.', color='blue', zorder=-100, markersize=5, label="noisy data")
113+
if i//3 == 0: ax.set_xticklabels([])
114+
ax.set_title(title, fontsize=18)
115+
if i == 5: ax.legend(loc='lower right', fontsize=12)
115116

116117

117118
def test_robust_rme():

pynumdiff/utils/simulate.py

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,10 @@ def triangle(duration=4, noise_type='normal', noise_parameters=(0, 0.5), outlier
109109

110110
pos = np.interp(t, reversal_ts, reversal_vals)
111111
_, vel = finitediff(pos, dt=simdt)
112-
noisy_pos = _add_noise(pos, random_seed, noise_type, noise_parameters, outliers)
112+
idx = np.arange(0, len(t), int(dt/simdt)) # downsample
113+
noisy_pos = _add_noise(pos[idx], random_seed, noise_type, noise_parameters, outliers)
113114

114-
idx = np.arange(0, len(t), int(dt/simdt))
115-
return noisy_pos[idx], pos[idx], vel[idx]
115+
return noisy_pos, pos[idx], vel[idx]
116116

117117

118118
def pop_dyn(duration=4, noise_type='normal', noise_parameters=(0, 0.5), outliers=False, random_seed=1,
@@ -182,15 +182,15 @@ def linear_autonomous(duration=4, noise_type='normal', noise_parameters=(0, 0.5)
182182
xs = np.vstack(xs).T
183183
pos = xs[0,:]
184184

185-
smooth_pos, vel = finitediff(pos, simdt)
186-
noisy_pos = _add_noise(pos, random_seed, noise_type, noise_parameters, outliers)
187-
185+
pos, vel = finitediff(pos, simdt)
188186
idx = slice(0, len(t), int(dt/simdt)) # downsample so things are dt apart
189-
return noisy_pos[1:][idx], smooth_pos[1:][idx], vel[1:][idx]
187+
noisy_pos = _add_noise(pos[idx], random_seed, noise_type, noise_parameters, outliers)
188+
189+
return noisy_pos, pos[idx], vel[idx]
190190

191191

192192
def pi_cruise_control(duration=4, noise_type='normal', noise_parameters=(0, 0.5), outliers=False,
193-
random_seed=1, dt=0.01):
193+
random_seed=1, dt=0.01, simdt=0.01):
194194
"""Create a toy example of linear proportional integral controller with nonlinear control inputs.
195195
Simulate proportional integral control of a car attempting to maintain constant velocity while going
196196
up and down hills. We assume the car has arbitrary power and can achieve whatever acceleration it wants;
@@ -215,7 +215,7 @@ def pi_cruise_control(duration=4, noise_type='normal', noise_parameters=(0, 0.5)
215215
- **vel** -- a true derivative information of the time series
216216
"""
217217
# disturbance
218-
t = np.arange(0, duration, dt)
218+
t = np.arange(0, duration, simdt)
219219
slope = 0.01*(np.sin(2*np.pi*t) + 0.3*np.sin(4*2*np.pi*t + 0.5) + 1.2*np.sin(1.7*2*np.pi*t + 0.5))
220220

221221
# parameters
@@ -226,33 +226,33 @@ def pi_cruise_control(duration=4, noise_type='normal', noise_parameters=(0, 0.5)
226226
vd = 0.5 # desired velocity
227227

228228
# Here state is [pos, vel, accel, cumulative pos error]
229-
A = np.array([[1, dt, (dt**2)/2, 0], # Taylor expand out to accel
230-
[0, 1, dt, 0],
231-
[0, -fr, 0, ki/(dt**2)], # (pos error) / dt^2 puts it in units of accel
232-
[0, 0, 0, 1]])
229+
A = np.array([[1, simdt, (simdt**2)/2, 0], # Taylor expand out to accel
230+
[0, 1, simdt, 0],
231+
[0, -fr, 0, ki/(simdt**2)], # (pos error) / dt^2 puts it in units of accel
232+
[0, 0, 0, 1]])
233233

234234
# Here inputs are [slope, vel_desired - vel_estimated]
235235
B = np.array([[0, 0],
236236
[0, 0],
237-
[-mg, kp/dt], # (vel error) / dt puts it in units of accel
238-
[0, dt]])
237+
[-mg, kp/simdt], # (vel error) / dt puts it in units of accel
238+
[0, simdt]])
239239

240240
# run simulation
241241
states = [np.array([0, 0, 0, 0])] # x0 is all zeros
242242
controls = []
243-
for i in range(len(slope)):
243+
for i in range(len(t)):
244244
u = np.array([slope[i], vd - states[-1][1]]) # current vel is in 1st position of last state
245-
xnew = A @ states[-1] + B @ u
246-
states.append(xnew)
245+
states.append(A @ states[-1] + B @ u)
247246
controls.append(u)
248247

249248
states = np.vstack(states).T
250249
controls = np.vstack(controls).T
251250

252-
pos = states[0, :]
253-
vel = states[1, :]
251+
idx = slice(1, len(t)+1, int(dt/simdt)) if dt >= simdt else np.arange(1, len(t)+1, dt/simdt).astype(int) # upsampling allowed here
252+
pos = states[0, idx]
253+
vel = states[1, idx]
254254
noisy_pos = _add_noise(pos, random_seed, noise_type, noise_parameters, outliers)
255-
255+
256256
return noisy_pos, pos, vel
257257

258258

0 commit comments

Comments
 (0)