227 lines
8.7 KiB
Python
227 lines
8.7 KiB
Python
#!/usr/bin/env python
|
|
#
|
|
# config_test.py - test config module
|
|
# RMM, 25 may 2019
|
|
#
|
|
# This test suite checks the functionality of the config module
|
|
|
|
import unittest
|
|
import numpy as np
|
|
import control as ct
|
|
import matplotlib.pyplot as plt
|
|
from math import pi, log10
|
|
|
|
|
|
class TestConfig(unittest.TestCase):
|
|
def setUp(self):
|
|
# Create a simple second order system to use for testing
|
|
self.sys = ct.tf([10], [1, 2, 1])
|
|
|
|
def test_set_defaults(self):
|
|
ct.config.set_defaults('config', test1=1, test2=2, test3=None)
|
|
self.assertEqual(ct.config.defaults['config.test1'], 1)
|
|
self.assertEqual(ct.config.defaults['config.test2'], 2)
|
|
self.assertEqual(ct.config.defaults['config.test3'], None)
|
|
|
|
def test_get_param(self):
|
|
self.assertEqual(
|
|
ct.config._get_param('bode', 'dB'),
|
|
ct.config.defaults['bode.dB'])
|
|
self.assertEqual(ct.config._get_param('bode', 'dB', 1), 1)
|
|
ct.config.defaults['config.test1'] = 1
|
|
self.assertEqual(ct.config._get_param('config', 'test1', None), 1)
|
|
self.assertEqual(ct.config._get_param('config', 'test1', None, 1), 1)
|
|
|
|
ct.config.defaults['config.test3'] = None
|
|
self.assertEqual(ct.config._get_param('config', 'test3'), None)
|
|
self.assertEqual(ct.config._get_param('config', 'test3', 1), 1)
|
|
self.assertEqual(
|
|
ct.config._get_param('config', 'test3', None, 1), None)
|
|
|
|
self.assertEqual(ct.config._get_param('config', 'test4'), None)
|
|
self.assertEqual(ct.config._get_param('config', 'test4', 1), 1)
|
|
self.assertEqual(ct.config._get_param('config', 'test4', 2, 1), 2)
|
|
self.assertEqual(ct.config._get_param('config', 'test4', None, 3), 3)
|
|
|
|
self.assertEqual(
|
|
ct.config._get_param('config', 'test4', {'test4':1}, None), 1)
|
|
|
|
|
|
def test_fbs_bode(self):
|
|
ct.use_fbs_defaults();
|
|
|
|
# Generate a Bode plot
|
|
plt.figure()
|
|
omega = np.logspace(-3, 3, 100)
|
|
ct.bode_plot(self.sys, omega)
|
|
|
|
# Get the magnitude line
|
|
mag_axis = plt.gcf().axes[0]
|
|
mag_line = mag_axis.get_lines()
|
|
mag_data = mag_line[0].get_data()
|
|
mag_x, mag_y = mag_data
|
|
|
|
# Make sure the x-axis is in rad/sec and y-axis is in natural units
|
|
np.testing.assert_almost_equal(mag_x[0], 0.001, decimal=6)
|
|
np.testing.assert_almost_equal(mag_y[0], 10, decimal=3)
|
|
|
|
# Get the phase line
|
|
phase_axis = plt.gcf().axes[1]
|
|
phase_line = phase_axis.get_lines()
|
|
phase_data = phase_line[0].get_data()
|
|
phase_x, phase_y = phase_data
|
|
|
|
# Make sure the x-axis is in rad/sec and y-axis is in degrees
|
|
np.testing.assert_almost_equal(phase_x[-1], 1000, decimal=0)
|
|
np.testing.assert_almost_equal(phase_y[-1], -180, decimal=0)
|
|
|
|
# Override the defaults and make sure that works as well
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, dB=True)
|
|
mag_x, mag_y = (((plt.gcf().axes[0]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(mag_y[0], 20*log10(10), decimal=3)
|
|
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, Hz=True)
|
|
mag_x, mag_y = (((plt.gcf().axes[0]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(mag_x[0], 0.001 / (2*pi), decimal=6)
|
|
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, deg=False)
|
|
phase_x, phase_y = (((plt.gcf().axes[1]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(phase_y[-1], -pi, decimal=2)
|
|
|
|
ct.reset_defaults()
|
|
|
|
def test_matlab_bode(self):
|
|
ct.use_matlab_defaults();
|
|
|
|
# Generate a Bode plot
|
|
plt.figure()
|
|
omega = np.logspace(-3, 3, 100)
|
|
ct.bode_plot(self.sys, omega)
|
|
|
|
# Get the magnitude line
|
|
mag_axis = plt.gcf().axes[0]
|
|
mag_line = mag_axis.get_lines()
|
|
mag_data = mag_line[0].get_data()
|
|
mag_x, mag_y = mag_data
|
|
|
|
# Make sure the x-axis is in Hertz and y-axis is in dB
|
|
np.testing.assert_almost_equal(mag_x[0], 0.001 / (2*pi), decimal=6)
|
|
np.testing.assert_almost_equal(mag_y[0], 20*log10(10), decimal=3)
|
|
|
|
# Get the phase line
|
|
phase_axis = plt.gcf().axes[1]
|
|
phase_line = phase_axis.get_lines()
|
|
phase_data = phase_line[0].get_data()
|
|
phase_x, phase_y = phase_data
|
|
|
|
# Make sure the x-axis is in Hertz and y-axis is in degrees
|
|
np.testing.assert_almost_equal(phase_x[-1], 1000 / (2*pi), decimal=1)
|
|
np.testing.assert_almost_equal(phase_y[-1], -180, decimal=0)
|
|
|
|
# Override the defaults and make sure that works as well
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, dB=True)
|
|
mag_x, mag_y = (((plt.gcf().axes[0]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(mag_y[0], 20*log10(10), decimal=3)
|
|
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, Hz=True)
|
|
mag_x, mag_y = (((plt.gcf().axes[0]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(mag_x[0], 0.001 / (2*pi), decimal=6)
|
|
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, deg=False)
|
|
phase_x, phase_y = (((plt.gcf().axes[1]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(phase_y[-1], -pi, decimal=2)
|
|
|
|
ct.reset_defaults()
|
|
|
|
def test_custom_bode_default(self):
|
|
ct.config.defaults['bode.dB'] = True
|
|
ct.config.defaults['bode.deg'] = True
|
|
ct.config.defaults['bode.Hz'] = True
|
|
|
|
# Generate a Bode plot
|
|
plt.figure()
|
|
omega = np.logspace(-3, 3, 100)
|
|
ct.bode_plot(self.sys, omega, dB=True)
|
|
mag_x, mag_y = (((plt.gcf().axes[0]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(mag_y[0], 20*log10(10), decimal=3)
|
|
|
|
# Override defaults
|
|
plt.figure()
|
|
ct.bode_plot(self.sys, omega, Hz=True, deg=False, dB=True)
|
|
mag_x, mag_y = (((plt.gcf().axes[0]).get_lines())[0]).get_data()
|
|
phase_x, phase_y = (((plt.gcf().axes[1]).get_lines())[0]).get_data()
|
|
np.testing.assert_almost_equal(mag_x[0], 0.001 / (2*pi), decimal=6)
|
|
np.testing.assert_almost_equal(mag_y[0], 20*log10(10), decimal=3)
|
|
np.testing.assert_almost_equal(phase_y[-1], -pi, decimal=2)
|
|
|
|
ct.reset_defaults()
|
|
|
|
def test_bode_number_of_samples(self):
|
|
# Set the number of samples (default is 50, from np.logspace)
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys, omega_num=87)
|
|
self.assertEqual(len(mag_ret), 87)
|
|
|
|
# Change the default number of samples
|
|
ct.config.defaults['freqplot.number_of_samples'] = 76
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys)
|
|
self.assertEqual(len(mag_ret), 76)
|
|
|
|
# Override the default number of samples
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys, omega_num=87)
|
|
self.assertEqual(len(mag_ret), 87)
|
|
|
|
ct.reset_defaults()
|
|
|
|
def test_bode_feature_periphery_decade(self):
|
|
# Generate a sample Bode plot to figure out the range it uses
|
|
ct.reset_defaults() # Make sure starting state is correct
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys, Hz=False)
|
|
omega_min, omega_max = omega_ret[[0, -1]]
|
|
|
|
# Reset the periphery decade value (should add one decade on each end)
|
|
ct.config.defaults['freqplot.feature_periphery_decades'] = 2
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys, Hz=False)
|
|
np.testing.assert_almost_equal(omega_ret[0], omega_min/10)
|
|
np.testing.assert_almost_equal(omega_ret[-1], omega_max * 10)
|
|
|
|
# Make sure it also works in rad/sec, in opposite direction
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys, Hz=True)
|
|
omega_min, omega_max = omega_ret[[0, -1]]
|
|
ct.config.defaults['freqplot.feature_periphery_decades'] = 1
|
|
mag_ret, phase_ret, omega_ret = ct.bode_plot(self.sys, Hz=True)
|
|
np.testing.assert_almost_equal(omega_ret[0], omega_min*10)
|
|
np.testing.assert_almost_equal(omega_ret[-1], omega_max/10)
|
|
|
|
ct.reset_defaults()
|
|
|
|
def test_reset_defaults(self):
|
|
ct.use_matlab_defaults()
|
|
ct.reset_defaults()
|
|
self.assertEqual(ct.config.defaults['bode.dB'], False)
|
|
self.assertEqual(ct.config.defaults['bode.deg'], True)
|
|
self.assertEqual(ct.config.defaults['bode.Hz'], False)
|
|
self.assertEqual(
|
|
ct.config.defaults['freqplot.number_of_samples'], None)
|
|
self.assertEqual(
|
|
ct.config.defaults['freqplot.feature_periphery_decades'], 1.0)
|
|
|
|
def tearDown(self):
|
|
# Get rid of any figures that we created
|
|
plt.close('all')
|
|
|
|
# Reset the configuration defaults
|
|
ct.config.reset_defaults()
|
|
|
|
def suite():
|
|
return unittest.TestLoader().loadTestsFromTestCase(TestTimeresp)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|