@@ -323,7 +323,7 @@ def test_correlation():
323323 tau , Rtau = ct .correlation (T , V )
324324
325325@pytest .mark .slow
326- @pytest .mark .parametrize ('dt' , [0 , 1 ])
326+ @pytest .mark .parametrize ('dt' , [0 , 0.2 ])
327327def test_oep (dt ):
328328 # Define the system to test, with additional input
329329 # Use fixed system to avoid random errors (was csys = ct.rss(4, 2, 5))
@@ -336,127 +336,60 @@ def test_oep(dt):
336336 sys = csys if dt == 0 else dsys
337337
338338 # Create disturbances and noise (fixed, to avoid random errors)
339- Rv = 0.1 * np .eye (1 ) # scalar disturbance
340- Rw = 0.01 * np .eye (sys .noutputs )
341- timepts = np .arange (0 , 10.1 , 1 )
339+ dist_mag = 1e-1 # disturbance magnitude
340+ meas_mag = 1e-3 # measurement noise magnitude
341+ Rv = dist_mag ** 2 * np .eye (1 ) # scalar disturbance
342+ Rw = meas_mag ** 2 * np .eye (sys .noutputs )
343+ timepts = np .arange (0 , 1 , 0.2 )
342344 V = np .array (
343- [0 if t % 2 == 1 else 1 if t % 4 == 0 else - 1 for t in timepts ]
344- ).reshape (1 , - 1 ) * 0.1
345- W = np .vstack ([np .sin (2 * timepts ), np .cos (3 * timepts )]) * 1e-3
345+ [0 if i % 2 == 1 else 1 if i % 4 == 0 else - 1
346+ for i in range (timepts .size )]
347+ ).reshape (1 , - 1 ) * dist_mag / 10
348+ W = np .vstack ([
349+ np .sin (10 * timepts / timepts [- 1 ]), np .cos (15 * timepts )/ timepts [- 1 ]
350+ ]) * meas_mag / 10
346351
347352 # Generate system data
348353 U = np .sin (timepts ).reshape (1 , - 1 )
349354
350- # No disturbances
351- res0 = ct .input_output_response (sys , timepts , [U , V * 0 ])
352- Y0 = res0 .outputs
353-
354355 # With disturbances and noise
355- res1 = ct .input_output_response (sys , timepts , [U , V ])
356- Y1 = res1 .outputs + W
356+ res = ct .input_output_response (sys , timepts , [U , V ])
357+ Y = res .outputs + W
357358
358359 # Set up optimal estimation function using Gaussian likelihoods for cost
359360 traj_cost = opt .gaussian_likelihood_cost (sys , Rv , Rw )
360361 init_cost = lambda xhat , x : (xhat - x ) @ (xhat - x )
361- oep = opt .OptimalEstimationProblem (
362+ oep1 = opt .OptimalEstimationProblem (
362363 sys , timepts , traj_cost , terminal_cost = init_cost )
363364
364- #
365- # Internal testing to make sure all our functions are OK (developers)
366- #
367- if False :
368- # _cost_function
369- oep .compute_estimate (Y0 , U , X0 = 0 )
370- assert oep ._cost_function (np .hstack (
371- [res0 .states .reshape (- 1 ), V .reshape (- 1 ) * 0 ])) == 0
372- assert oep ._cost_function (np .hstack (
373- [res0 .states .reshape (- 1 ), V .reshape (- 1 )])) != 0
374- if sys .isdtime ():
375- # Collocation contstraint should be satisified for discrete time
376- np .testing .assert_allclose (oep ._collocation_constraint (
377- np .hstack ([res0 .states .reshape (- 1 ), V .reshape (- 1 ) * 0 ])), 0 )
378-
379- # _compute_states_inputs: states and inputs with no noise
380- # oep.compute_estimate(Y0, U)
381- xhat , u , v , w = oep ._compute_states_inputs (
382- np .hstack ([res0 .states .reshape (- 1 ), V .reshape (- 1 ) * 0 ]))
383- np .testing .assert_allclose (xhat , res0 .states )
384- np .testing .assert_allclose (u , U .reshape (1 , - 1 ))
385- np .testing .assert_allclose (v , 0 )
386- np .testing .assert_allclose (w , 0 )
387-
388- # _compute_states_inputs: states and inputs with no noise
389- oep .compute_estimate (Y1 , U )
390- xhat , u , v , w = oep ._compute_states_inputs (
391- np .hstack ([res1 .states .reshape (- 1 ), V .reshape (- 1 )]))
392- np .testing .assert_allclose (xhat , res1 .states )
393- np .testing .assert_allclose (u , U .reshape (1 , - 1 ))
394- np .testing .assert_allclose (v , V )
395- np .testing .assert_allclose (w , W )
396-
397- #
398- # oep.compute_estimate testing
399- #
400-
401- # Noise free and disturbance free
402- nonoise_cost = opt .gaussian_likelihood_cost (sys , Rv , Rw / 1e6 )
403- oep0 = opt .OptimalEstimationProblem (
404- sys , timepts , nonoise_cost , terminal_cost = init_cost )
405- est0 = oep0 .compute_estimate (Y0 , U )
406- if sys .isdtime ():
407- # Full state estimate should be near perfect
408- np .testing .assert_allclose (
409- est0 .states [:, - 1 ], res0 .states [:, - 1 ], atol = 1e-3 , rtol = 1e-3 )
410- np .testing .assert_allclose (est0 .inputs , 0 , atol = 1e-2 , rtol = 1e-3 )
411- np .testing .assert_allclose (est0 .outputs , 0 , atol = 1e-2 , rtol = 1e-3 )
412- else :
413- # Estimate at end of trajectory should be very close
414- assert est0 .success
415- np .testing .assert_allclose (
416- est0 .states [:, - 1 ], res0 .states [:, - 1 ], atol = 1e-2 , rtol = 1e-2 )
417-
418- # Noise free, but with disturbances and good initial guess
419- if False :
420- oep1 = opt .OptimalEstimationProblem (
421- sys , timepts , nonoise_cost , terminal_cost = init_cost )
422- est1 = oep1 .compute_estimate (
423- res1 .outputs , U , initial_guess = (res1 .states , V ), X0 = 0 )
424- np .testing .assert_allclose (
425- est1 .states [:, - 1 ], res1 .states [:, - 1 ], atol = 1e-2 , rtol = 1e-2 )
426- if sys .isdtime ():
427- # For discrete time, estimated disturbance and noise should be close
428- np .testing .assert_allclose (
429- est1 .inputs [:- 1 ], V [:- 1 ], atol = 1e-2 , rtol = 1e-2 )
430- np .testing .assert_allclose (est1 .outputs , 0 , atol = 1e-2 , rtol = 1e-2 )
431-
432- # Noise and disturbances (the standard case)
433- est2 = oep .compute_estimate (Y1 , U ) # back to original OEP
434- assert est2 .success
365+ # Compute the optimal estimate
366+ est1 = oep1 .compute_estimate (Y , U )
367+ assert est1 .success
435368 np .testing .assert_allclose (
436- est2 .states [:, - 1 ], res1 .states [:, - 1 ], atol = 1e-1 , rtol = 1e-2 )
369+ est1 .states [:, - 1 ], res .states [:, - 1 ], atol = meas_mag , rtol = meas_mag )
370+
371+ # Recompute using initial guess (should be pretty fast)
372+ est2 = oep1 .compute_estimate (
373+ Y , U , initial_guess = (est1 .states , est1 .inputs ))
374+ assert est2 .success
437375
438376 # Change around the inputs and disturbances
439377 sys2 = ct .ss (sys .A , sys .B [:, ::- 1 ], sys .C , sys .D [::- 1 ], sys .dt )
440378 oep2a = opt .OptimalEstimationProblem (
441379 sys2 , timepts , traj_cost , terminal_cost = init_cost ,
442380 control_indices = [1 ])
443381 est2a = oep2a .compute_estimate (
444- Y1 , U , initial_guess = (est2 .states , est2 .inputs ))
445- np .testing .assert_allclose (
446- est2a .states [:, - 1 ], res1 .states [:, - 1 ], atol = 1e-1 , rtol = 1e-2 )
382+ Y , U , initial_guess = (est1 .states , est1 .inputs ))
383+ np .testing .assert_allclose (est2a .states , est2 .states )
447384
448385 oep2b = opt .OptimalEstimationProblem (
449386 sys2 , timepts , traj_cost , terminal_cost = init_cost ,
450387 disturbance_indices = [0 ])
451388 est2b = oep2b .compute_estimate (
452- Y1 , U , initial_guess = (est2 .states , est2 .inputs ))
453- np .testing .assert_allclose (
454- est2b .states [:, - 1 ], res1 .states [:, - 1 ], atol = 1e-1 , rtol = 1e-2 )
455-
456- #
457- # Disturbance constraints
458- #
389+ Y , U , initial_guess = (est1 .states , est1 .inputs ))
390+ np .testing .assert_allclose (est2b .states , est2 .states )
459391
392+ # Add disturbance constraints
460393 V3 = np .clip (V , 0.5 , 1 )
461394 traj_constraint = opt .disturbance_range_constraint (sys , 0.5 , 1 )
462395 oep3 = opt .OptimalEstimationProblem (
@@ -466,17 +399,11 @@ def test_oep(dt):
466399 res3 = ct .input_output_response (sys , timepts , [U , V3 ])
467400 Y3 = res3 .outputs + W
468401
469- # Make sure the constraint screws up the estimation problem
470- with pytest .raises (AssertionError ):
471- est3 = oep .compute_estimate (Y3 , U )
472- np .testing .assert_allclose (
473- est3 .states [:, - 1 ], res3 .states [:, - 1 ], atol = 1e-1 , rtol = 1e-2 )
474-
475402 # Make sure estimation is correct with constraint in place
476403 est3 = oep3 .compute_estimate (Y3 , U )
477404 assert est3 .success
478405 np .testing .assert_allclose (
479- est3 .states [:, - 1 ], res3 .states [:, - 1 ], atol = 1e-1 , rtol = 1e-2 )
406+ est3 .states [:, - 1 ], res3 .states [:, - 1 ], atol = meas_mag , rtol = meas_mag )
480407
481408
482409@pytest .mark .slow
0 commit comments