|
| 1 | +# optestim_bench.py - benchmarks for optimal/moving horizon estimation |
| 2 | +# RMM, 14 Mar 2023 |
| 3 | +# |
| 4 | +# This benchmark tests the timing for the optimal estimation routines and |
| 5 | +# is intended to be used for helping tune the performance of the functions |
| 6 | +# used for optimization-based estimation. |
| 7 | + |
| 8 | +import numpy as np |
| 9 | +import math |
| 10 | +import control as ct |
| 11 | +import control.optimal as opt |
| 12 | + |
| 13 | +minimizer_table = { |
| 14 | + 'default': (None, {}), |
| 15 | + 'trust': ('trust-constr', {}), |
| 16 | + 'trust_bigstep': ('trust-constr', {'finite_diff_rel_step': 0.01}), |
| 17 | + 'SLSQP': ('SLSQP', {}), |
| 18 | + 'SLSQP_bigstep': ('SLSQP', {'eps': 0.01}), |
| 19 | + 'COBYLA': ('COBYLA', {}), |
| 20 | +} |
| 21 | + |
| 22 | +# Table to turn on and off process disturbances and measurement noise |
| 23 | +noise_table = { |
| 24 | + 'noisy': (1e-1, 1e-3), |
| 25 | + 'nodist': (0, 1e-3), |
| 26 | + 'nomeas': (1e-1, 0), |
| 27 | + 'clean': (0, 0) |
| 28 | +} |
| 29 | + |
| 30 | + |
| 31 | +# Assess performance as a function of optimization and integration methods |
| 32 | +def time_oep_minimizer_methods(minimizer_name, noise_name, initial_guess): |
| 33 | + # Use fixed system to avoid randome errors (was csys = ct.rss(4, 2, 5)) |
| 34 | + csys = ct.ss( |
| 35 | + [[-0.5, 1, 0, 0], [0, -1, 1, 0], [0, 0, -2, 1], [0, 0, 0, -3]], # A |
| 36 | + [[0, 0.1], [0, 0.1], [0, 0.1], [1, 0.1]], # B |
| 37 | + [[1, 0, 0, 0], [0, 0, 1, 0]], # C |
| 38 | + 0, dt=0) |
| 39 | + # dsys = ct.c2d(csys, dt) |
| 40 | + # sys = csys if dt == 0 else dsys |
| 41 | + sys = csys |
| 42 | + |
| 43 | + # Decide on process disturbances and measurement noise |
| 44 | + dist_mag, meas_mag = noise_table[noise_name] |
| 45 | + |
| 46 | + # Create disturbances and noise (fixed, to avoid random errors) |
| 47 | + Rv = 0.1 * np.eye(1) # scalar disturbance |
| 48 | + Rw = 0.01 * np.eye(sys.noutputs) |
| 49 | + timepts = np.arange(0, 10.1, 1) |
| 50 | + V = np.array( |
| 51 | + [0 if t % 2 == 1 else 1 if t % 4 == 0 else -1 for t in timepts] |
| 52 | + ).reshape(1, -1) * dist_mag |
| 53 | + W = np.vstack([np.sin(2*timepts), np.cos(3*timepts)]) * meas_mag |
| 54 | + |
| 55 | + # Generate system data |
| 56 | + U = np.sin(timepts).reshape(1, -1) |
| 57 | + res = ct.input_output_response(sys, timepts, [U, V]) |
| 58 | + Y = res.outputs + W |
| 59 | + |
| 60 | + # Decide on the initial guess to use |
| 61 | + if initial_guess == 'xhat': |
| 62 | + initial_guess = (res.states, V*0) |
| 63 | + elif initial_guess == 'both': |
| 64 | + initial_guess = (res.states, V) |
| 65 | + else: |
| 66 | + initial_guess = None |
| 67 | + |
| 68 | + |
| 69 | + # Set up optimal estimation function using Gaussian likelihoods for cost |
| 70 | + traj_cost = opt.gaussian_likelihood_cost(sys, Rv, Rw) |
| 71 | + init_cost = lambda xhat, x: (xhat - x) @ (xhat - x) |
| 72 | + oep = opt.OptimalEstimationProblem( |
| 73 | + sys, timepts, traj_cost, terminal_cost=init_cost) |
| 74 | + |
| 75 | + # Noise and disturbances (the standard case) |
| 76 | + est = oep.compute_estimate(Y, U, initial_guess=initial_guess) |
| 77 | + assert est.success |
| 78 | + np.testing.assert_allclose( |
| 79 | + est.states[:, -1], res.states[:, -1], atol=1e-1, rtol=1e-2) |
| 80 | + |
| 81 | + |
| 82 | +# Parameterize the test against different choices of integrator and minimizer |
| 83 | +time_oep_minimizer_methods.param_names = ['minimizer', 'noise', 'initial'] |
| 84 | +time_oep_minimizer_methods.params = ( |
| 85 | + ['default', 'trust', 'SLSQP', 'COBYLA'], |
| 86 | + ['noisy', 'nodist', 'nomeas', 'clean'], |
| 87 | + ['none', 'xhat', 'both']) |
0 commit comments