@@ -211,9 +211,13 @@ def linear_autonomous(duration=4, noise_type='normal', noise_parameters=(0, 0.5)
211211 return np .ravel (noisy_x )[1 :][idx ], smooth_x [1 :][idx ], dxdt [1 :][idx ], None
212212
213213
214- def pi_control (duration = 4 , noise_type = 'normal' , noise_parameters = (0 , 0.5 ),
214+ def pi_cruise_control (duration = 4 , noise_type = 'normal' , noise_parameters = (0 , 0.5 ),
215215 random_seed = 1 , dt = 0.01 ):
216- """Create a toy example of linear proportional integral controller with nonlinear control inputs
216+ """Create a toy example of linear proportional integral controller with nonlinear control inputs.
217+ Simulate proportional integral control of a car attempting to maintain constant velocity while going
218+ up and down hills. We assume the car has arbitrary power and can achieve whatever acceleration it wants;
219+ its mass only factors in via -mg pulling it downhill. This is a linear interpretation of something
220+ similar to what is described in Astrom and Murray 2008 Chapter 3.
217221
218222 :param float duration: governs the length of the series, duration/dt
219223 :param str noise_type: type of noise, compatible with :code:`np.random` functions
@@ -232,83 +236,54 @@ def pi_control(duration=4, noise_type='normal', noise_parameters=(0, 0.5),
232236 - **vel** -- a true derivative information of the time series;
233237 - **[measurements, controls]** -- a list of extra measurements and controls
234238 """
235- t = np .arange (0 , duration , dt )
236-
237- actual_vals , extra_measurements , controls = _pi_cruise_control (duration , dt )
238- x = np .ravel (actual_vals [0 , :])
239- dxdt = np .ravel (actual_vals [1 , :])
240-
241- noisy_x = _add_noise (x , random_seed , noise_type , noise_parameters )
242-
243- actual_vals = np .array (np .vstack ((x , dxdt )))
244- noisy_measurements = np .array (noisy_x )
245-
246- noisy_pos = np .ravel (noisy_measurements )
247- pos = np .ravel (actual_vals [0 , :])
248- vel = np .ravel (actual_vals [1 , :])
249-
250- return noisy_pos , pos , vel , \
251- [np .array (extra_measurements ), np .array (controls )]
252-
253-
254- def _pi_cruise_control (duration = 4 , dt = 0.01 ):
255- """Simulate proportional integral control of a car attempting to maintain constant velocity while going
256- up and down hills. This function is used for testing differentiation methods. This is a linear
257- interpretation of something similar to what is described in Astrom and Murray 2008 Chapter 3.
258-
259- :param float duration: number of seconds to simulate
260- :param dt: timestep in seconds
261-
262- :return: tuple[np.array, np.array, np.array] of arrays of shape (N, M), where M is the
263- number of time steps\n
264- - **state_vals** -- state of the car, i.e. position and velocity as a function of time
265- - **disturbances** -- disturbances from hills that the car is subjected to
266- - **controls** -- control inputs applied by the car
267- """
268- _np = np # for compatibility with original code
269- t = _np .arange (0 , duration + dt , dt )
270-
271239 # disturbance
272- hills = _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 )
273- hills = 0.01 * hills
240+ t = np . arange ( 0 , duration , dt )
241+ 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 ))
274242
275243 # parameters
276244 mg = 10000 # mass*gravity
277245 fr = 0.9 # friction
278- ki = 5 / 0.01 * dt # integral control
279- kp = 25 / 0.01 * dt # proportional control
246+ ki = 0.05 # integral control
247+ kp = 0.25 # proportional control
280248 vd = 0.5 # desired velocity
281249
282- A = _np .array ([[1 , dt , 0 , 0 , 0 ],
283- [0 , 1 , dt , 0 , 0 ],
284- [0 , - fr , 0 , - mg , ki ],
285- [0 , 0 , 0 , 0 , 0 ],
286- [0 , 0 , 0 , 0 , 1 ]])
287-
288- B = _np .array ([[0 , 0 ],
289- [0 , 0 ],
290- [0 , kp ],
291- [1 , 0 ],
292- [0 , 1 ]])
250+ # Here state is [pos, vel, accel, cumulative pos error]
251+ A = np .array ([[1 , dt , (dt ** 2 )/ 2 , 0 ], # Taylor expand out to accel
252+ [0 , 1 , dt , 0 ],
253+ [0 , - fr , 0 , ki / (dt ** 2 )], # (pos error) / dt^2 puts it in units of accel
254+ [0 , 0 , 0 , 1 ]])
293255
294- x0 = _np .array ([0 , 0 , 0 , hills [0 ], 0 ]).reshape (A .shape [0 ], 1 )
256+ # Here inputs are [slope, vel_desired - vel_estimated]
257+ B = np .array ([[0 , 0 ],
258+ [0 , 0 ],
259+ [- mg , kp / dt ], # (vel error) / dt puts it in units of accel
260+ [0 , dt ]])
295261
296262 # run simulation
297- xs = [x0 ]
298- us = [_np .array ([0 , 0 ]).reshape ([2 ,1 ])]
299- for i in range (1 , len (hills )- 1 ):
300- u = _np .array ([hills [i ], vd - xs [- 1 ][1 ,0 ]]).reshape ([2 ,1 ])
301- xnew = A @xs [- 1 ] + B @u
302- xs .append (xnew )
303- us .append (u )
263+ states = [np .array ([0 , 0 , 0 , 0 ])] # x0 is all zeros
264+ controls = []
265+ for i in range (len (slope )):
266+ u = np .array ([slope [i ], vd - states [- 1 ][1 ]]) # current vel is in 1st position of last state
267+ xnew = A @ states [- 1 ] + B @ u
268+ states .append (xnew )
269+ controls .append (u )
270+
271+ states = np .vstack (states ).T
272+ controls = np .vstack (controls ).T
273+
274+ x = np .ravel (states [0 , :])
275+ dxdt = np .ravel (states [1 , :])
276+
277+ noisy_x = _add_noise (x , random_seed , noise_type , noise_parameters )
304278
305- xs = _np . hstack ( xs )
306- us = _np . hstack ( us )
279+ states = np . array ( np . vstack (( x , dxdt )) )
280+ noisy_measurements = np . array ( noisy_x )
307281
308- if len (hills .shape ) == 1 :
309- hills = _np .reshape (hills , [1 , len (hills )])
282+ noisy_pos = np .ravel (noisy_measurements )
283+ pos = np .ravel (states [0 , :])
284+ vel = np .ravel (states [1 , :])
310285
311- return xs , hills , us
286+ return noisy_pos , pos , vel , [ slope , controls ]
312287
313288
314289def lorenz_x (duration = 4 , noise_type = 'normal' , noise_parameters = (0 , 0.5 ),
0 commit comments