diff --git a/CITATION.cff b/CITATION.cff index 9d40ccc..38f6d91 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -14,5 +14,5 @@ authors: orcid: "https://orcid.org/0000-0002-1987-9268" affiliation: "McGill University" title: "decargroup/dkpy" -version: v0.1.12 +version: v0.1.9 url: "https://github.com/decargroup/dkpy" diff --git a/README.rst b/README.rst index 677ff7b..40bf832 100644 --- a/README.rst +++ b/README.rst @@ -141,6 +141,6 @@ If you use this software in your research, please cite it as below or see url={https://github.com/decargroup/dkpy}, publisher={Zenodo}, author={Steven Dahdah and Timothy Everett Adams and James Richard Forbes}, - version = {{v0.1.12}}, + version = {{v0.1.9}}, year={2025}, } diff --git a/doc/_static/example_8/8_magnitude_uncertainty_weight.png b/doc/_static/example_8/8_magnitude_uncertainty_weight.png new file mode 100644 index 0000000..e3d1718 Binary files /dev/null and b/doc/_static/example_8/8_magnitude_uncertainty_weight.png differ diff --git a/doc/_static/example_8/8_sval_actuator.png b/doc/_static/example_8/8_sval_actuator.png new file mode 100644 index 0000000..118170b Binary files /dev/null and b/doc/_static/example_8/8_sval_actuator.png differ diff --git a/doc/_static/example_8/8_sval_residual_max.png b/doc/_static/example_8/8_sval_residual_max.png new file mode 100644 index 0000000..a7b44df Binary files /dev/null and b/doc/_static/example_8/8_sval_residual_max.png differ diff --git a/doc/conf.py b/doc/conf.py index df5b6ff..7348137 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,10 +7,10 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information project = "dkpy" -copyright = "2024, Steven Dahdah, Timothy Everett Adams and James Richard Forbes" +copyright = "2025, Steven Dahdah, Timothy Everett Adams and James Richard Forbes" author = "Steven Dahdah, Timothy Everett Adams and James Richard Forbes" -version = "0.1.12" -release = "0.1.12" +version = "0.1.9" +release = "0.1.9" # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/doc/examples.rst b/doc/examples.rst index e3ef9bc..a1326a7 100644 --- a/doc/examples.rst +++ b/doc/examples.rst @@ -178,13 +178,14 @@ particular, the response of the system states and actuator inputs are shown. .. image:: _static/example_6/6_plot_inputs.png -Multi-Model Uncertainty Characterization ----------------------------------------- +Multi-Model Uncertainty Characterization: Academic Model +-------------------------------------------------------- -In this example, an unstructured uncertainty set is characterized from a set -of nominal and off-nominal frequency responses. The off-nominal models are -generated from a nominal model by randomly perturbing the parameters within a -known bound. +In this example, an unstructured uncertainty set is characterized from a set of +nominal and off-nominal frequency responses. This is an academic example as the +uncertain model in this problem is arbitrarily selected for the purpose of +demonstration. The off-nominal models are generated from a nominal model by +randomly perturbing the parameters within a known bound. .. literalinclude:: ../examples/7_uncertainty_characterization.py :language: python @@ -240,7 +241,35 @@ set. The optimal weight frequency responses and fitted weights are shown below. .. image:: _static/example_7/7_uncertainty_weight.png +Multi-Model Uncertainty Characterization: Aircraft Actuator Model +----------------------------------------------------------------- +In this example, an unstructured uncertainty set is characterized from a set of +nominal and off-nominal frequency responses. In this example, the uncertainty +of an aircraft actuator model is characterized using an actuator model from an +example given in Section 14.1 of [M04]_. + +.. literalinclude:: ../examples/8_aircraft_actuator_uncertainty_characterization.py + :language: python + +The off-nominal frequency response data is generated by sampling valid +perturbations from the uncertainty model provided in the problem statement. + +.. image:: _static/example_8/8_sval_actuator.png + +Three different candidate uncertainty models are evaluated: additive +uncertainty, multiplicative input uncertainty, and inverse multiplicative +input uncertainty. The multiplicative input uncertainty is selected as it +yields the smallest residual singular values. + +.. image:: _static/example_8/8_sval_residual_max.png + +The left uncertainty weight is constrained to be scalar whereas the right uncertainty +weight is constrained to the identity matrix. Given that the right uncertainty +weight is the identity matrix, a fit does not need to be performed for this weight +as it will be neglected in the generalized plant. + +.. image:: _static/example_8/8_magnitude_uncertainty_weight.png diff --git a/examples/6_dk_iteration_non_square_perturbation.py b/examples/6_dk_iteration_non_square_perturbation.py index e5877d2..385c152 100644 --- a/examples/6_dk_iteration_non_square_perturbation.py +++ b/examples/6_dk_iteration_non_square_perturbation.py @@ -57,7 +57,7 @@ def example_dk_iter_list_order_aircraft(): # DK-iteration controller synthesis dk_iter = dkpy.DkIterListOrder( controller_synthesis=dkpy.HinfSynLmi( - lmi_strictness=5e-7, # Have to play with tolerances. + lmi_strictness=5e-7, solver_params=dict( solver="MOSEK", eps=5e-8, @@ -74,7 +74,7 @@ def example_dk_iter_list_order_aircraft(): ), ), d_scale_fit=dkpy.DScaleFitSlicot(), - fit_orders=[4, 4], + fit_orders=[4, 4, 4], ) # Alternative MATLAB block structure description # block_structure = np.array([[n_u_delta, n_y_delta], [n_w, n_z]]) diff --git a/examples/7_uncertainty_characterization.py b/examples/7_uncertainty_characterization.py index 0154fa1..eec2817 100644 --- a/examples/7_uncertainty_characterization.py +++ b/examples/7_uncertainty_characterization.py @@ -8,33 +8,12 @@ def example_uncertainty_characterization(): """Multi-model uncertainty characterization from frequency response data.""" - # Generate example data + # Load example data eg = dkpy.example_multimodel_uncertainty() response_nom = eg["complex_response_nominal"] response_offnom_list = eg["complex_response_offnominal_list"] omega = eg["omega"] - # Plot: Magnitude response of nominal and off-nominal systems - fig, _ = dkpy.plot_magnitude_response_uncertain_model_set( - response_nom, - response_offnom_list, - omega, - ) - - # Plot: Phase response of nominal and off-nominal systems - fig, _ = dkpy.plot_phase_response_uncertain_model_set( - response_nom, - response_offnom_list, - omega, - ) - - # Plot: Singular value response of nominal and off-nominal systems - fig, ax = dkpy.plot_singular_value_response_uncertain_model_set( - response_nom, - response_offnom_list, - omega, - ) - # Uncertainty models uncertainty_models = { "additive", @@ -44,41 +23,58 @@ def example_uncertainty_characterization(): "inverse_multiplicative_input", "inverse_multiplicative_output", } + + # Compute uncertainty residual frequency response response_residuals_dict = dkpy.compute_uncertainty_residual_response( response_nom, response_offnom_list, uncertainty_models, ) - # Plot: Singular value response of uncerainty residuals - figure_dict = dkpy.plot_singular_value_response_residual( - response_residuals_dict, omega - ) - - # Plot: Comparison of singular value response of uncerainty residuals for each - # uncertainty model - fig, _ = dkpy.plot_singular_value_response_residual_comparison( - response_residuals_dict, omega - ) - # Compute uncertainty weight frequency response response_weight_left, response_weight_right = ( - dkpy.compute_optimal_uncertainty_weight_response( + dkpy.compute_uncertainty_weight_response( response_residuals_dict["inverse_additive"], "diagonal", "diagonal" ) ) # Fit overbounding stable and minimum-phase uncertainty weight system - weight_left = dkpy.fit_overbounding_uncertainty_weight( - response_weight_left, omega, [4, 5] + weight_left = dkpy.fit_uncertainty_weight(response_weight_left, omega, [4, 5]) + weight_right = dkpy.fit_uncertainty_weight(response_weight_right, omega, [3, 5]) + + # Plot: Magnitude response of nominal and off-nominal systems + dkpy.plot_magnitude_response_uncertain_model_set( + response_nom, + response_offnom_list, + omega, + ) + + # Plot: Phase response of nominal and off-nominal systems + dkpy.plot_phase_response_uncertain_model_set( + response_nom, + response_offnom_list, + omega, ) - weight_right = dkpy.fit_overbounding_uncertainty_weight( - response_weight_right, omega, [3, 5] + + # Plot: Singular value response of nominal and off-nominal systems + dkpy.plot_singular_value_response_uncertain_model_set( + response_nom, + response_offnom_list, + omega, + ) + + # Plot: Singular value response of uncerainty residuals + dkpy.plot_singular_value_response_residual(response_residuals_dict, omega) + + # Plot: Comparison of singular value response of uncerainty residuals for each + # uncertainty model + dkpy.plot_singular_value_response_residual_comparison( + response_residuals_dict, omega ) # Plot: Magnitude response of uncertainty weight frequency response and overbounding # fit - fig, _ = dkpy.plot_magnitude_response_uncertainty_weight( + dkpy.plot_magnitude_response_uncertainty_weight( response_weight_left, response_weight_right, omega, diff --git a/examples/8_aircraft_actuator_uncertainty_characterization.py b/examples/8_aircraft_actuator_uncertainty_characterization.py new file mode 100644 index 0000000..058b226 --- /dev/null +++ b/examples/8_aircraft_actuator_uncertainty_characterization.py @@ -0,0 +1,64 @@ +"""Multi-model aircraft actuator uncertainty characterization from frequency response +data. + +""" + +from matplotlib import pyplot as plt + +import dkpy + + +def example_aircraft_uncertainty_characterization(): + """Multi-model aircraft actuator uncertainty characterization from frequency + response data. + """ + # Load example data + eg = dkpy.example_aircraft_actuator_uncertainty() + response_actuator = eg["response_actuator_nominal"] + response_actuator_offnom_list = eg["response_actuator_offnominal"] + omega = eg["omega"] + + # Compute the residual response for different uncertainty models + response_residual = dkpy.compute_uncertainty_residual_response( + response_actuator, + response_actuator_offnom_list, + {"additive", "multiplicative_input", "inverse_multiplicative_input"}, + ) + + # Compute the optimal uncertainty weights with a given structure + response_weight_left, response_weight_right = ( + dkpy.compute_uncertainty_weight_response( + response_residual["multiplicative_input"], + "scalar", + "identity", + ) + ) + + # Fit an overbounding LTI system to the optimal uncertainty weight response + weight_left = dkpy.fit_uncertainty_weight(response_weight_left, omega, 1) + + # Plot: Singular value response of nominal and off-nominal systems + dkpy.plot_singular_value_response_uncertain_model_set( + response_actuator, response_actuator_offnom_list, omega, hz=True + ) + + # Plot: Comparison of singular value response of uncerainty residuals for each + # uncertainty model + dkpy.plot_singular_value_response_residual_comparison( + response_residual, omega, hz=True + ) + + # Plot: Magnitude response of uncertainty weight frequency response and overbounding + # fit + dkpy.plot_magnitude_response_uncertainty_weight( + response_weight_left, + response_weight_right, + omega, + weight_left=weight_left, + hz=True, + ) + plt.show() + + +if __name__ == "__main__": + example_aircraft_uncertainty_characterization() diff --git a/pyproject.toml b/pyproject.toml index 44b8b2b..bee7ade 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "dkpy" -version = "0.1.12" +version = "0.1.9" dependencies = [ "numpy>=1.21.0", "scipy>=1.7.0", diff --git a/src/dkpy/uncertainty_characterization.py b/src/dkpy/uncertainty_characterization.py index b5b2a9d..f9787cd 100644 --- a/src/dkpy/uncertainty_characterization.py +++ b/src/dkpy/uncertainty_characterization.py @@ -2,8 +2,8 @@ __all__ = [ "compute_uncertainty_residual_response", - "compute_optimal_uncertainty_weight_response", - "fit_overbounding_uncertainty_weight", + "compute_uncertainty_weight_response", + "fit_uncertainty_weight", "plot_magnitude_response_uncertain_model_set", "plot_phase_response_uncertain_model_set", "plot_singular_value_response_uncertain_model_set", @@ -23,6 +23,7 @@ from typing import List, Optional, Union, Tuple, Dict, Callable, Set, Any from matplotlib.figure import Figure from matplotlib.axes import Axes +from matplotlib.legend import Legend from . import utilities @@ -304,13 +305,6 @@ def _compute_uncertainty_residual_multiplicative_input_freq( num_inputs = complex_response_nom_freq.shape[1] num_outputs = complex_response_nom_freq.shape[0] - if num_inputs < num_outputs: - warnings.warn( - "Multipliative input uncertainty models cannot include all possible " - "off-nominal systems when the number of inputs is less than the number of " - "outputs. This may result in an error if an uncertainty residual cannot " - "be found for a given nominal and off-nominal frequency response matrix." - ) A = complex_response_nom_freq B = complex_response_offnom_freq - complex_response_nom_freq @@ -365,13 +359,7 @@ def _compute_uncertainty_residual_multiplicative_output_freq( num_inputs = complex_response_nom_freq.shape[1] num_outputs = complex_response_nom_freq.shape[0] - if num_inputs > num_outputs: - warnings.warn( - "Multipliative output uncertainty models cannot include all possible " - "off-nominal systems when the number of outputs is less than the number of " - "inputs. This may result in an error if an uncertainty residual cannot " - "be found for a given nominal and off-nominal frequency response matrix." - ) + A = complex_response_nom_freq.T B = complex_response_offnom_freq.T - complex_response_nom_freq.T X, residues_lstsq, _, _ = scipy.linalg.lstsq(A, B) @@ -425,13 +413,6 @@ def _compute_uncertainty_residual_inverse_additive_freq( num_inputs = complex_response_nom_freq.shape[1] num_outputs = complex_response_nom_freq.shape[0] - if num_inputs != num_outputs: - warnings.warn( - "Inverse additive uncertainty models cannot include all possible " - "off-nominal systems when the number of inputs is not equal to the number " - "of outputs. This may result in an error if an uncertainty residual cannot " - "be found for a given nominal and off-nominal frequency response matrix." - ) A1 = complex_response_offnom_freq B1 = complex_response_offnom_freq - complex_response_nom_freq @@ -492,14 +473,6 @@ def _compute_uncertainty_residual_inverse_multiplicative_input_freq( num_inputs = complex_response_nom_freq.shape[1] num_outputs = complex_response_nom_freq.shape[0] - if num_inputs < num_outputs: - warnings.warn( - "Inverse multipliative input uncertainty models cannot include all " - "possible off-nominal systems when the number of inputs is less than the " - "number of outputs. This may result in an error if an uncertainty residual " - "cannot be found for a given nominal and off-nominal frequency response " - "matrix." - ) A = complex_response_offnom_freq B = complex_response_offnom_freq - complex_response_nom_freq @@ -554,14 +527,7 @@ def _compute_uncertainty_residual_inverse_multiplicative_output_freq( num_inputs = complex_response_nom_freq.shape[1] num_outputs = complex_response_nom_freq.shape[0] - if num_inputs > num_outputs: - warnings.warn( - "Inverse multipliative output uncertainty models cannot include all " - "possible off-nominal systems when the number of outputs is less than the " - "number of inputs. This may result in an error if an uncertainty residual " - "cannot be found for a given nominal and off-nominal frequency response " - "matrix." - ) + A = complex_response_offnom_freq.T B = complex_response_offnom_freq.T - complex_response_nom_freq.T X, residues_lstsq, _, _ = scipy.linalg.lstsq(A, B) @@ -583,7 +549,7 @@ def _compute_uncertainty_residual_inverse_multiplicative_output_freq( ) -def compute_optimal_uncertainty_weight_response( +def compute_uncertainty_weight_response( complex_response_residual_list: Union[np.ndarray, control.FrequencyResponseList], weight_left_structure: str, weight_right_structure: str, @@ -591,6 +557,9 @@ def compute_optimal_uncertainty_weight_response( ) -> Tuple[np.ndarray, np.ndarray]: """Compute the optimal uncertainty weight frequency response. + The algorithm is based on a modified semidefinite program formulation presented in + [#uncertainty_characterization]_. + Parameters ---------- complex_response_residual_list : Union[np.ndarray, control.FrequencyResponseList] @@ -635,12 +604,19 @@ def compute_optimal_uncertainty_weight_response( ... uncertainty_models, ... ) >>> complex_response_weight_left, complex_response_weight_right = ( - ... dkpy.compute_optimal_uncertainty_weight_response( + ... dkpy.compute_uncertainty_weight_response( ... complex_response_residual_dict["multiplicative_input"], ... "diagonal", ... "diagonal", ... ) ... ) + + References + ---------- + .. [#uncertainty_characterization] G. J. Balas, A. K. Packard, and P. J. Seiler, + “Uncertain Model Set Calculation from Frequency Domain Data,” Springer eBooks, + pp. 89–105, Jan. 2009, doi: https://doi.org/10.1007/978-1-4419-0895-7_6. + """ # Convert frequency response data to expected type @@ -800,7 +776,7 @@ def _compute_optimal_weight_freq( return complex_response_weight_left_freq, complex_response_weight_right_freq -def fit_overbounding_uncertainty_weight( +def fit_uncertainty_weight( complex_response_uncertainty_weight: Union[ np.ndarray, control.FrequencyResponseData ], @@ -812,8 +788,7 @@ def fit_overbounding_uncertainty_weight( max_iter_bisection: int = 500, num_spec_constr: int = 500, ) -> control.StateSpace: - """ - Fit an overbounding stable and minimum-phase state-space uncertainty weight to + """Fit an overbounding stable and minimum-phase state-space uncertainty weight to frequency response data. Parameters @@ -867,16 +842,16 @@ def fit_overbounding_uncertainty_weight( ... uncertainty_models, ... ) >>> complex_response_weight_left, complex_response_weight_right = ( - ... dkpy.compute_optimal_uncertainty_weight_response( + ... dkpy.compute_uncertainty_weight_response( ... complex_response_residual_dict["multiplicative_input"], ... "diagonal", ... "diagonal", ... ) ... ) - >>> weight_left = dkpy.fit_overbounding_uncertainty_weight( + >>> weight_left = dkpy.fit_uncertainty_weight( ... complex_response_weight_left, omega, [4, 5] ... ) - >>> weight_right = dkpy.fit_overbounding_uncertainty_weight( + >>> weight_right = dkpy.fit_uncertainty_weight( ... complex_response_weight_right, omega, [3, 5] ... ) @@ -951,10 +926,26 @@ def fit_overbounding_uncertainty_weight( def _convert_frequency_response_data_to_array( - frequency_response: Union[ - np.ndarray, np.typing.ArrayLike, control.FrequencyResponseData - ], + frequency_response: Union[np.typing.ArrayLike, control.FrequencyResponseData], ) -> np.ndarray: + """Convert frequency response data into the expected form. + + Parameters + ---------- + frequency_response : Union[np.typing.ArrayLike, control.FrequencyResponseData], + Frequency response data in arbitrary form. + + Returns + ------- + np.ndarray + Frequency response data in expected array form. + + Raises + ------ + ValueError + The dimensions of the `frequency_response` input are incompatible with the + expected form. + """ if isinstance(frequency_response, control.FrequencyResponseData): complex_response = np.array(frequency_response.complex, dtype=complex) @@ -980,18 +971,34 @@ def _convert_frequency_response_data_to_array( def _convert_frequency_response_list_to_array( - frequency_response_list: Union[ - np.ndarray, np.typing.ArrayLike, control.FrequencyResponseList - ], + frequency_response_list: Union[np.typing.ArrayLike, control.FrequencyResponseList], ) -> np.ndarray: + """Convert list of frequency response data into the expected form. + + Parameters + ---------- + frequency_response_list : Union[np.typing.ArrayLike, control.FrequencyResponseData], + Frequency response data in arbitrary form. + + Returns + ------- + np.ndarray + Frequency response data list in expected array form. + + Raises + ------ + ValueError + The dimensions of the `frequency_response_list` input are incompatible with the + expected form. + """ if isinstance(frequency_response_list, control.FrequencyResponseList): complex_response_list = [] for frequency_response in frequency_response_list: complex_response = np.array(frequency_response.complex, dtype=complex) - # SISO `FrequencyResponseData` objects return 1D arrays for frequency response - # data and 3D arrays for MIMO systems. `dkpy` uses a 3D array in all cases, so - # the 1D array is converted into a 3D array. + # SISO `FrequencyResponseData` objects return 1D arrays for frequency + # response data and 3D arrays for MIMO systems. `dkpy` uses a 3D array in + # all cases, so the 1D array is converted into a 3D array. if complex_response.ndim == 1: complex_response = complex_response[None, None, :] @@ -1014,8 +1021,8 @@ def _convert_frequency_response_list_to_array( def plot_magnitude_response_uncertain_model_set( - complex_response_nom: np.ndarray, - complex_response_offnom_list: np.ndarray, + complex_response_nom: Union[np.ndarray, control.FrequencyResponseData], + complex_response_offnom_list: Union[np.ndarray, control.FrequencyResponseList], omega: np.ndarray, db: bool = True, hz: bool = False, @@ -1023,14 +1030,14 @@ def plot_magnitude_response_uncertain_model_set( plot_nom_kw: Dict[str, Any] = {}, plot_offnom_kw: Dict[str, Any] = {}, subplot_kw: Dict[str, Any] = {}, -) -> Tuple[Figure, Union[Axes, np.ndarray]]: +) -> Tuple[Figure, Union[Axes, np.ndarray], Legend]: """Plot magnitude response of nominal model and set of off-nominal models. Parameters ---------- - complex_response_nom : np.ndarray + complex_response_nom : Union[np.ndarray, control.FrequencyResponseData] Frequency response matrices over a grid of frequencies of the nominal system. - complex_response_offnom_list : np.ndarray + complex_response_offnom_list : Union[np.ndarray, control.FrequencyResponseList] Frequency response matrices over a grid of frequencies of the set of off-nominal systems. omega : np.ndarray @@ -1054,12 +1061,26 @@ def plot_magnitude_response_uncertain_model_set( Keyword arguments for the subplot. See [#subplot_kw]_ for more information on the subplot keywords. + Returns + ------- + Tuple[Figure, Union[Axes, np.ndarray], Legend] + Matplotlib Figure object, Axes object (or np.ndarray of Axes objects), and + Legend object. + References ---------- .. [#plot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html .. [#subplot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplots.html """ + # Convert frequency response data to expected type + complex_response_nom = _convert_frequency_response_data_to_array( + complex_response_nom + ) + complex_response_offnom_list = _convert_frequency_response_list_to_array( + complex_response_offnom_list + ) + # System paramters num_offnom = complex_response_offnom_list.shape[0] num_outputs = complex_response_offnom_list.shape[2] @@ -1116,19 +1137,19 @@ def plot_magnitude_response_uncertain_model_set( ax[-1, idx_input].set_xlabel("$f$ (Hz)" if hz else r"$\omega$ (rad/s)") handles, labels = ax[0, 0].get_legend_handles_labels() legend_dict = dict(zip(labels, handles)) - fig.legend( + legend = fig.legend( labels=legend_dict.keys(), handles=legend_dict.values(), loc="outside lower center", ncol=2, ) - return fig, ax + return fig, ax, legend def plot_phase_response_uncertain_model_set( - complex_response_nom: np.ndarray, - complex_response_offnom_list: np.ndarray, + complex_response_nom: Union[np.ndarray, control.FrequencyResponseData], + complex_response_offnom_list: Union[np.ndarray, control.FrequencyResponseList], omega: np.ndarray, deg: bool = True, hz: bool = False, @@ -1136,14 +1157,14 @@ def plot_phase_response_uncertain_model_set( plot_nom_kw: Dict[str, Any] = {}, plot_offnom_kw: Dict[str, Any] = {}, subplot_kw: Dict[str, Any] = {}, -) -> Tuple[Figure, Union[Axes, np.ndarray]]: +) -> Tuple[Figure, Union[Axes, np.ndarray], Legend]: """Plot phase response of nominal model and set of off-nominal models. Parameters ---------- - complex_response_nom : np.ndarray + complex_response_nom : Union[np.ndarray, control.FrequencyResponseData] Frequency response matrices over a grid of frequencies of the nominal system. - complex_response_offnom_list : np.ndarray + complex_response_offnom_list : Union[np.ndarray, control.FrequencyResponseList] Frequency response matrices over a grid of frequencies of the set of off-nominal systems. omega : np.ndarray @@ -1167,12 +1188,26 @@ def plot_phase_response_uncertain_model_set( Keyword arguments for the subplot. See [#subplot_kw]_ for more information on the subplot keywords. + Returns + ------- + Tuple[Figure, Union[Axes, np.ndarray], Legend] + Matplotlib Figure object, Axes object (or np.ndarray of Axes objects), and + Legend object. + References ---------- .. [#plot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html .. [#subplot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplots.html """ + # Convert frequency response data to expected type + complex_response_nom = _convert_frequency_response_data_to_array( + complex_response_nom + ) + complex_response_offnom_list = _convert_frequency_response_list_to_array( + complex_response_offnom_list + ) + # System paramters num_offnom = complex_response_offnom_list.shape[0] num_outputs = complex_response_offnom_list.shape[2] @@ -1229,19 +1264,19 @@ def plot_phase_response_uncertain_model_set( ax[-1, idx_input].set_xlabel("$f$ (Hz)" if hz else r"$\omega$ (rad/s)") handles, labels = ax[0, 0].get_legend_handles_labels() legend_dict = dict(zip(labels, handles)) - fig.legend( + legend = fig.legend( labels=legend_dict.keys(), handles=legend_dict.values(), loc="outside lower center", ncol=2, ) - return fig, ax + return fig, ax, legend def plot_singular_value_response_uncertain_model_set( - complex_response_nom: np.ndarray, - complex_response_offnom_list: np.ndarray, + complex_response_nom: Union[np.ndarray, control.FrequencyResponseData], + complex_response_offnom_list: Union[np.ndarray, control.FrequencyResponseList], omega: np.ndarray, db: bool = True, hz: bool = False, @@ -1249,14 +1284,14 @@ def plot_singular_value_response_uncertain_model_set( plot_nom_kw: Dict[str, Any] = {}, plot_offnom_kw: Dict[str, Any] = {}, subplot_kw: Dict[str, Any] = {}, -) -> Tuple[Figure, Union[Axes, np.ndarray]]: +) -> Tuple[Figure, Axes, Legend]: """Plot singular value response of nominal model and set of off-nominal models. Parameters ---------- - complex_response_nom : np.ndarray + complex_response_nom : Union[np.ndarray, control.FrequencyResponseData] Frequency response matrices over a grid of frequencies of the nominal system. - complex_response_offnom_list : np.ndarray + complex_response_offnom_list : Union[np.ndarray, control.FrequencyResponseList] Frequency response matrices over a grid of frequencies of the set of off-nominal systems. omega : np.ndarray @@ -1280,12 +1315,26 @@ def plot_singular_value_response_uncertain_model_set( Keyword arguments for the subplot. See [#subplot_kw]_ for more information on the subplot keywords. + Returns + ------- + Tuple[Figure, Union[Axes, np.ndarray], Legend] + Matplotlib Figure object, Axes object (or np.ndarray of Axes objects), and + Legend object. + References ---------- .. [#plot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html .. [#subplot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.subplots.html """ + # Convert frequency response data to expected type + complex_response_nom = _convert_frequency_response_data_to_array( + complex_response_nom + ) + complex_response_offnom_list = _convert_frequency_response_list_to_array( + complex_response_offnom_list + ) + # System paramters num_offnom = complex_response_offnom_list.shape[0] @@ -1330,21 +1379,19 @@ def plot_singular_value_response_uncertain_model_set( # Plot settings if frequency_log_scale: ax.set_xscale("log") - ax.set_ylabel( - "Singular Value Magnitude (dB)" if db else " Singular Value Magnitude (-)" - ) + ax.set_ylabel("Magnitude (dB)" if db else "Magnitude (-)") ax.grid() ax.set_xlabel("$f$ (Hz)" if hz else r"$\omega$ (rad/s)") handles, labels = ax.get_legend_handles_labels() legend_dict = dict(zip(labels, handles)) - fig.legend( + legend = fig.legend( labels=legend_dict.keys(), handles=legend_dict.values(), loc="outside lower center", ncol=2, ) - return fig, ax + return fig, ax, legend def plot_singular_value_response_residual( @@ -1356,7 +1403,7 @@ def plot_singular_value_response_residual( plot_sval_max_kw: Dict[str, Any] = {}, plot_sval_kw: Dict[str, Any] = {}, subplot_kw: Dict[str, Any] = {}, -) -> Dict[str, Tuple[Figure, Union[Axes, np.ndarray]]]: +) -> Dict[str, Tuple[Figure, Axes, Legend]]: """Plot the singular value response of the uncertainty residuals for different unstructured uncertainty models on separate figures. @@ -1386,6 +1433,12 @@ def plot_singular_value_response_residual( Keyword arguments for the subplot. See [#subplot_kw]_ for more information on the subplot keywords. + Returns + ------- + Tuple[Figure, Union[Axes, np.ndarray], Legend] + Matplotlib Figure object, Axes object (or np.ndarray of Axes objects), and + Legend object. + References ---------- .. [#plot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html @@ -1459,13 +1512,11 @@ def plot_singular_value_response_residual( # Plot settings if frequency_log_scale: ax.set_xscale("log") - ax.set_ylabel( - "Singular Value Magnitude (dB)" if db else "Singular Value Magnitude (-)" - ) + ax.set_ylabel("Magnitude (dB)" if db else "Magnitude (-)") ax.grid() ax.set_xlabel("$f$ (Hz)" if hz else r"$\omega$ (rad/s)") handles, labels = ax.get_legend_handles_labels() - legend_dict = dict(zip(labels, handles)) + legend = legend_dict = dict(zip(labels, handles)) fig.legend( labels=legend_dict.keys(), handles=legend_dict.values(), @@ -1473,7 +1524,7 @@ def plot_singular_value_response_residual( ncol=2, ) - figure_dict[uncertainty_model_id] = (fig, ax) + figure_dict[uncertainty_model_id] = (fig, ax, legend) return figure_dict @@ -1486,7 +1537,7 @@ def plot_singular_value_response_residual_comparison( frequency_log_scale: bool = True, plot_sval_max_kw: Dict[str, Any] = {}, subplot_kw: Dict[str, Any] = {}, -) -> Tuple[Figure, Union[Axes, np.ndarray]]: +) -> Tuple[Figure, Axes, Legend]: """Plot the maximum singular value response of the uncertainty residuals for different unstructured uncertainty models on the same figure for comparision of the various uncertainty models. @@ -1514,6 +1565,12 @@ def plot_singular_value_response_residual_comparison( Keyword arguments for the subplot. See [#subplot_kw]_ for more information on the subplot keywords. + Returns + ------- + Tuple[Figure, Union[Axes, np.ndarray], Legend] + Matplotlib Figure object, Axes object (or np.ndarray of Axes objects), and + Legend object. + References ---------- .. [#plot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html @@ -1571,21 +1628,19 @@ def plot_singular_value_response_residual_comparison( # Plot settings if frequency_log_scale: ax.set_xscale("log") - ax.set_ylabel( - "Singular Value Magnitude (dB)" if db else "Singular Value Magnitude (-)" - ) + ax.set_ylabel("Magnitude (dB)" if db else "Magnitude (-)") ax.grid() ax.set_xlabel("$f$ (Hz)" if hz else r"$\omega$ (rad/s)") handles, labels = ax.get_legend_handles_labels() legend_dict = dict(zip(labels, handles)) - fig.legend( + legend = fig.legend( labels=legend_dict.keys(), handles=legend_dict.values(), loc="outside lower center", ncol=3, ) - return fig, ax + return fig, ax, legend def plot_magnitude_response_uncertainty_weight( @@ -1600,7 +1655,7 @@ def plot_magnitude_response_uncertainty_weight( plot_response_kw: Dict[str, Any] = {}, plot_response_fit_kw: Dict[str, Any] = {}, subplot_kw: Dict[str, Any] = {}, -) -> Tuple[Figure, Union[Axes, np.ndarray]]: +) -> Tuple[Figure, Union[Axes, np.ndarray], Legend]: """Plot the diagonal elements of the optimal left and right uncertainty weight frequency responses. Optionally, the fitted overbounding left and right uncertainty weights can also be displayed. @@ -1638,6 +1693,12 @@ def plot_magnitude_response_uncertainty_weight( Keyword arguments for the subplot. See [#subplot_kw]_ for more information on the subplot keywords. + Returns + ------- + Tuple[Figure, Union[Axes, np.ndarray], Legend] + Matplotlib Figure object, Axes object (or np.ndarray of Axes objects), and + Legend object. + References ---------- .. [#plot_kw] https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html @@ -1754,11 +1815,11 @@ def plot_magnitude_response_uncertainty_weight( fig.delaxes(ax_row_col) handles, labels = ax[0, 0].get_legend_handles_labels() legend_dict = dict(zip(labels, handles)) - fig.legend( + legend = fig.legend( labels=legend_dict.keys(), handles=legend_dict.values(), loc="outside lower center", ncol=2, ) - return fig, ax + return fig, ax, legend diff --git a/src/dkpy/utilities.py b/src/dkpy/utilities.py index 4495f97..23f67a2 100644 --- a/src/dkpy/utilities.py +++ b/src/dkpy/utilities.py @@ -4,6 +4,7 @@ "example_scherer1997_p907", "example_skogestad2006_p325", "example_mackenroth2004_p430", + "example_aircraft_actuator_uncertainty", "example_multimodel_uncertainty", "_ensure_tf", "_tf_close_coeff", @@ -322,6 +323,96 @@ def example_mackenroth2004_p430(): return out +def example_aircraft_actuator_uncertainty(): + """Add uncertain frequency response of aircaft actuator from [M04]_, Section 14.1 + (p. 430). + """ + # Actuator model + A_act = np.array( + [[0, 1, 0, 0], [-1600, -56, 0, 0], [0, 0, 0, 1], [0, 0, -1600, -56]] + ) + B_act = np.array([[0, 0], [1600, 0], [0, 0], [0, 1600]]) + C_act = np.eye(4) + D_act = np.zeros((4, 2)) + actuator = control.StateSpace(A_act, B_act, C_act, D_act, name="actuator") + actuator.set_inputs(["zeta_unc", "xi_unc"]) + actuator.set_outputs(["zeta", "rate_zeta", "xi", "rate_xi"]) + + # Input multiplicative uncertainty weight + weight_unc_channel = control.TransferFunction([0.25, 0.05], [0.125, 1]) + weight_unc = control.append( + weight_unc_channel, + weight_unc_channel, + name="weight_unc", + ) + weight_unc.set_inputs(["zeta_c", "xi_c"]) + weight_unc.set_outputs(weight_unc.noutputs, "y_del") + + # Uncertainty summation junction + sum_uncertainty = control.StateSpace([], [], [], [[1, 0, 1, 0], [0, 1, 0, 1]]) + sum_uncertainty.set_inputs(["zeta_c", "xi_c", "u_del[0]", "u_del[1]"]) + sum_uncertainty.set_outputs(["zeta_unc", "xi_unc"]) + + # Generate off-nominal models with different perturbations + delta_block_list = [] + + # Constant gain perturbation + num_offnom_gain = 20 + for gain_unc in np.linspace(-1, 1, num_offnom_gain): + delta = control.StateSpace([], [], [], [gain_unc]) + delta_block = control.append(delta, delta) + delta_block.set_inputs(delta_block.ninputs, "y_del") + delta_block.set_outputs(delta_block.noutputs, "u_del") + delta_block_list.append(delta_block) + + # Phase perturbation + a_min = 0.01 + a_max = 10 + num_offnom_phase = 20 + for a in np.linspace(a_min, a_max, num_offnom_phase): + delta_block = control.append( + control.tf([1 / a, -1], [1 / a, 1]), + control.tf([1 / a, -1], [1 / a, 1]), + name="delta_block", + ) + delta_block.set_inputs(delta_block.ninputs, "y_del") + delta_block.set_outputs(delta_block.noutputs, "u_del") + delta_block_list.append(delta_block) + + # Off-nominal actuator list + actuator_offnom_list = [] + for delta_block in delta_block_list: + actuator_offnom = control.interconnect( + syslist=[ + actuator, + delta_block, + weight_unc, + sum_uncertainty, + ], + inplist=["zeta_c", "xi_c"], + outlist=["zeta", "rate_zeta", "xi", "rate_xi"], + name="closed_loop_sim", + ) + actuator_offnom_list.append(actuator_offnom) + + # Frequency response + omega_min = 0.01 + omega_max = 100 + num_omega = 100 + omega = np.logspace(np.log10(omega_min), np.log10(omega_max), num_omega) + + frequency_response_actuator = control.frequency_response(actuator, omega) + frequency_response_actuator_offnom_list = control.frequency_response( + actuator_offnom_list, omega + ) + + return { + "response_actuator_nominal": frequency_response_actuator, + "response_actuator_offnominal": frequency_response_actuator_offnom_list, + "omega": omega, + } + + def example_multimodel_uncertainty(): """Generate uncertain models using parameter variation.""" diff --git a/tests/test_uncertainty_characterization.py b/tests/test_uncertainty_characterization.py index 963eeb2..390053c 100644 --- a/tests/test_uncertainty_characterization.py +++ b/tests/test_uncertainty_characterization.py @@ -133,7 +133,7 @@ def test_compute_uncertainty_residual_multiplicative_input_freq( self, complex_response_nom_freq, complex_response_offnom_freq ): """Test ValueError of - :func:`_test_compute_uncertainty_residual_multiplicative_input_freq`. + :func:`_compute_uncertainty_residual_multiplicative_input_freq`. """ with pytest.raises(ValueError): @@ -154,7 +154,7 @@ def test_compute_uncertainty_residual_multiplicative_output_freq( self, complex_response_nom_freq, complex_response_offnom_freq ): """Test ValueError of - :func:`_test_compute_uncertainty_residual_multiplicative_output_freq`. + :func:`_compute_uncertainty_residual_multiplicative_output_freq`. """ with pytest.raises(ValueError): residual_freq = dkpy.uncertainty_characterization._compute_uncertainty_residual_multiplicative_output_freq( @@ -178,7 +178,7 @@ def test_compute_uncertainty_residual_inverse_additive_freq( self, complex_response_nom_freq, complex_response_offnom_freq ): """Test ValueError of - :func:`_test_compute_uncertainty_residual_inverse_additive_freq`. + :func:`_compute_uncertainty_residual_inverse_additive_freq`. """ with pytest.raises(ValueError): residual_freq = dkpy.uncertainty_characterization._compute_uncertainty_residual_inverse_additive_freq( @@ -198,7 +198,7 @@ def test_compute_uncertainty_residual_inverse_multiplicative_input_freq( self, complex_response_nom_freq, complex_response_offnom_freq ): """Test ValueError of - :func:`_test_compute_uncertainty_residual_inverse_multiplicative_input_freq`. + :func:`_compute_uncertainty_residual_inverse_multiplicative_input_freq`. """ with pytest.raises(ValueError): residual_freq = dkpy.uncertainty_characterization._compute_uncertainty_residual_inverse_multiplicative_input_freq( @@ -218,7 +218,7 @@ def test_compute_uncertainty_residual_inverse_multiplicative_output_freq( self, complex_response_nom_freq, complex_response_offnom_freq ): """Test ValueError of - :func:`_test_compute_uncertainty_residual_inverse_multiplicative_output_freq`. + :func:`_compute_uncertainty_residual_inverse_multiplicative_output_freq`. """ with pytest.raises(ValueError): residual_freq = dkpy.uncertainty_characterization._compute_uncertainty_residual_inverse_multiplicative_output_freq( @@ -226,8 +226,8 @@ def test_compute_uncertainty_residual_inverse_multiplicative_output_freq( ) -class TestComputeOptimalUncertaintyWeightResponse: - """Test :func:`compute_optimal_uncertainty_weight_response`.""" +class TestComputeUncertaintyWeightResponse: + """Test :func:`compute_uncertainty_weight_response`.""" @pytest.mark.parametrize( "sys_nom, sys_offnom_list, omega, weight_left_structure, weight_right_structure", @@ -589,7 +589,7 @@ class TestComputeOptimalUncertaintyWeightResponse: ), ], ) - def test_compute_optimal_uncertainty_weight_response( + def test_compute_uncertainty_weight_response( self, ndarrays_regression, sys_nom, @@ -598,7 +598,7 @@ def test_compute_optimal_uncertainty_weight_response( weight_left_structure, weight_right_structure, ): - """Regression test :func:`compute_optimal_uncertainty_weight_response`.""" + """Regression test :func:`compute_uncertainty_weight_response`.""" # Frequency response of systems frequency_response_nom = control.frequency_response( sys_nom, omega, squeeze=False @@ -627,7 +627,7 @@ def test_compute_optimal_uncertainty_weight_response( # Optimal uncertainty weight complex_response_weight_left, complex_response_weight_right = ( - dkpy.compute_optimal_uncertainty_weight_response( + dkpy.compute_uncertainty_weight_response( complex_response_residual_dict["multiplicative_input"], weight_left_structure, weight_right_structure, @@ -645,8 +645,8 @@ def test_compute_optimal_uncertainty_weight_response( ) -class TestFitOverboundingUncertaintyWeight: - """Test :func:`fit_overbounding_uncertainty_weight`.""" +class TestFitUncertaintyWeight: + """Test :func:`fit_uncertainty_weight`.""" @pytest.mark.parametrize( "sys_nom, sys_offnom_list, omega, weight_left_structure, weight_right_structure, fit_order", @@ -823,7 +823,7 @@ class TestFitOverboundingUncertaintyWeight: ), ], ) - def test_fit_overbounding_uncertainty_weight( + def test_fit_uncertainty_weight( self, ndarrays_regression, sys_nom, @@ -833,7 +833,7 @@ def test_fit_overbounding_uncertainty_weight( weight_right_structure, fit_order, ): - """Regression test :func:`test_fit_overbounding_uncertainty_weight`.""" + """Regression test :func:`fit_uncertainty_weight`.""" # Frequency response of systems frequency_response_nom = control.frequency_response( sys_nom, omega, squeeze=False @@ -862,7 +862,7 @@ def test_fit_overbounding_uncertainty_weight( # Optimal uncertainty weight complex_response_weight_left, complex_response_weight_right = ( - dkpy.compute_optimal_uncertainty_weight_response( + dkpy.compute_uncertainty_weight_response( complex_response_residual_dict["multiplicative_input"], weight_left_structure, weight_right_structure, @@ -870,10 +870,10 @@ def test_fit_overbounding_uncertainty_weight( ) # Overbounding transfer function fit - weight_left_fit = dkpy.fit_overbounding_uncertainty_weight( + weight_left_fit = dkpy.fit_uncertainty_weight( complex_response_weight_left, omega, fit_order ) - weight_right_fit = dkpy.fit_overbounding_uncertainty_weight( + weight_right_fit = dkpy.fit_uncertainty_weight( complex_response_weight_right, omega, fit_order ) diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom10_sys_offnom_list10_omega10_diagonal_diagonal_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom10_sys_offnom_list10_omega10_diagonal_diagonal_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom10_sys_offnom_list10_omega10_diagonal_diagonal_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom10_sys_offnom_list10_omega10_diagonal_diagonal_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom11_sys_offnom_list11_omega11_diagonal_identity_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom11_sys_offnom_list11_omega11_diagonal_identity_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom11_sys_offnom_list11_omega11_diagonal_identity_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom11_sys_offnom_list11_omega11_diagonal_identity_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom12_sys_offnom_list12_omega12_identity_diagonal_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom12_sys_offnom_list12_omega12_identity_diagonal_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom12_sys_offnom_list12_omega12_identity_diagonal_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom12_sys_offnom_list12_omega12_identity_diagonal_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom13_sys_offnom_list13_omega13_scalar_identity_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom13_sys_offnom_list13_omega13_scalar_identity_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom13_sys_offnom_list13_omega13_scalar_identity_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom13_sys_offnom_list13_omega13_scalar_identity_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom14_sys_offnom_list14_omega14_identity_scalar_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom14_sys_offnom_list14_omega14_identity_scalar_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom14_sys_offnom_list14_omega14_identity_scalar_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom14_sys_offnom_list14_omega14_identity_scalar_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom1_sys_offnom_list1_omega1_diagonal_identity_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom1_sys_offnom_list1_omega1_diagonal_identity_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom1_sys_offnom_list1_omega1_diagonal_identity_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom1_sys_offnom_list1_omega1_diagonal_identity_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom2_sys_offnom_list2_omega2_identity_diagonal_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom2_sys_offnom_list2_omega2_identity_diagonal_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom2_sys_offnom_list2_omega2_identity_diagonal_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom2_sys_offnom_list2_omega2_identity_diagonal_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom3_sys_offnom_list3_omega3_scalar_identity_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom3_sys_offnom_list3_omega3_scalar_identity_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom3_sys_offnom_list3_omega3_scalar_identity_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom3_sys_offnom_list3_omega3_scalar_identity_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom4_sys_offnom_list4_omega4_identity_scalar_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom4_sys_offnom_list4_omega4_identity_scalar_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom4_sys_offnom_list4_omega4_identity_scalar_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom4_sys_offnom_list4_omega4_identity_scalar_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom5_sys_offnom_list5_omega5_diagonal_diagonal_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom5_sys_offnom_list5_omega5_diagonal_diagonal_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom5_sys_offnom_list5_omega5_diagonal_diagonal_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom5_sys_offnom_list5_omega5_diagonal_diagonal_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom6_sys_offnom_list6_omega6_diagonal_identity_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom6_sys_offnom_list6_omega6_diagonal_identity_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom6_sys_offnom_list6_omega6_diagonal_identity_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom6_sys_offnom_list6_omega6_diagonal_identity_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom7_sys_offnom_list7_omega7_identity_diagonal_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom7_sys_offnom_list7_omega7_identity_diagonal_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom7_sys_offnom_list7_omega7_identity_diagonal_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom7_sys_offnom_list7_omega7_identity_diagonal_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom8_sys_offnom_list8_omega8_scalar_identity_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom8_sys_offnom_list8_omega8_scalar_identity_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom8_sys_offnom_list8_omega8_scalar_identity_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom8_sys_offnom_list8_omega8_scalar_identity_.npz diff --git a/tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom9_sys_offnom_list9_omega9_identity_scalar_.npz b/tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom9_sys_offnom_list9_omega9_identity_scalar_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_compute_optimal_uncertainty_weight_response_sys_nom9_sys_offnom_list9_omega9_identity_scalar_.npz rename to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom9_sys_offnom_list9_omega9_identity_scalar_.npz diff --git a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_4_.npz b/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_4_.npz deleted file mode 100644 index 9d67de8..0000000 Binary files a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_4_.npz and /dev/null differ diff --git a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_4_.npz b/tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_4_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_4_.npz rename to tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_4_.npz diff --git a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom1_sys_offnom_list1_omega1_diagonal_diagonal_4_.npz b/tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom1_sys_offnom_list1_omega1_diagonal_diagonal_4_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom1_sys_offnom_list1_omega1_diagonal_diagonal_4_.npz rename to tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom1_sys_offnom_list1_omega1_diagonal_diagonal_4_.npz diff --git a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom2_sys_offnom_list2_omega2_diagonal_diagonal_4_.npz b/tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom2_sys_offnom_list2_omega2_diagonal_diagonal_4_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom2_sys_offnom_list2_omega2_diagonal_diagonal_4_.npz rename to tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom2_sys_offnom_list2_omega2_diagonal_diagonal_4_.npz diff --git a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_fit_order3_.npz b/tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_fit_order3_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_fit_order3_.npz rename to tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom3_sys_offnom_list3_omega3_diagonal_diagonal_fit_order3_.npz diff --git a/tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom4_sys_offnom_list4_omega4_diagonal_diagonal_fit_order4_.npz b/tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom4_sys_offnom_list4_omega4_diagonal_diagonal_fit_order4_.npz similarity index 100% rename from tests/test_uncertainty_characterization/test_fit_overbounding_uncertainty_weight_sys_nom4_sys_offnom_list4_omega4_diagonal_diagonal_fit_order4_.npz rename to tests/test_uncertainty_characterization/test_fit_uncertainty_weight_sys_nom4_sys_offnom_list4_omega4_diagonal_diagonal_fit_order4_.npz