2020import scipy
2121from matplotlib import pyplot as plt
2222
23- from typing import List , Optional , Union , Tuple , Dict , Callable , Set , Any
23+ from typing import List , Optional , Union , Tuple , Dict , Callable , Set , Any , Literal
2424from matplotlib .figure import Figure
2525from matplotlib .axes import Axes
2626from matplotlib .legend import Legend
@@ -551,8 +551,8 @@ def _compute_uncertainty_residual_inverse_multiplicative_output_freq(
551551
552552def compute_uncertainty_weight_response (
553553 complex_response_residual_list : Union [np .ndarray , control .FrequencyResponseList ],
554- weight_left_structure : str ,
555- weight_right_structure : str ,
554+ weight_left_structure : Literal [ "full" , "diagonal" , "scalar" , "identity" ] ,
555+ weight_right_structure : Literal [ "full" , "diagonal" , "scalar" , "identity" ] ,
556556 solver_params : Optional [Dict [str , Any ]] = None ,
557557) -> Tuple [np .ndarray , np .ndarray ]:
558558 """Compute the optimal uncertainty weight frequency response.
@@ -566,11 +566,9 @@ def compute_uncertainty_weight_response(
566566 Frequency response of the residuals for which to compute the optimal uncertainty
567567 weights.
568568 weight_left_structure : str
569- Structure of the left uncertainty weight. Valid structures include: "diagonal",
570- "scalar", and "identity".
569+ Structure of the left uncertainty weight.
571570 weight_right_structure : str
572- Structure of the right uncertainty weight. Valid structures include: "diagonal",
573- "scalar", and "identity".
571+ Structure of the right uncertainty weight.
574572 solver_param : Dict[str, Any]
575573 Keyword arguments for the convex optimization solver. See [#cvxpy_solver]_ for
576574 more information.
@@ -650,8 +648,8 @@ def compute_uncertainty_weight_response(
650648
651649def _compute_optimal_weight_freq (
652650 complex_residual_offnom_set_freq : np .ndarray ,
653- weight_left_structure : str ,
654- weight_right_structure : str ,
651+ weight_left_structure : Literal [ "full" , "diagonal" , "scalar" , "identity" ] ,
652+ weight_right_structure : Literal [ "full" , "diagonal" , "scalar" , "identity" ] ,
655653 solver_params : Optional [Dict [str , Any ]] = None ,
656654) -> Tuple [np .ndarray , np .ndarray ]:
657655 """Compute the optimal uncertainty weight at a given frequency.
@@ -661,14 +659,12 @@ def _compute_optimal_weight_freq(
661659 complex_residual_offnom_set_freq : np.ndarray
662660 Frequency response matrix of the off-nominal models at a given frequency.
663661 weight_left_structure : str
664- Structure of the left uncertainty weight. Valid structures include: "diagonal",
665- "scalar", and "identity".
662+ Structure of the left uncertainty weight.
666663 weight_right_structure : str
667- Structure of the right uncertainty weight. Valid structures include: "diagonal",
668- "scalar", and "identity".
669- solver_param : Dict[str, Any]
670- Keyword arguments for the convex optimization solver. See [#cvxpy_solver]_ for
671- more information.
664+ Structure of the right uncertainty weight.
665+ solver_params : Dict[str, Any]
666+ Solver parameters for the optimization problem. These are keyword arguments for
667+ `cvxpy.Problem.solve()` [#cvxpy_solve]_.
672668
673669 Returns
674670 -------
@@ -696,49 +692,51 @@ def _compute_optimal_weight_freq(
696692 )
697693
698694 # System parameters
699- num_left = complex_residual_offnom_set_freq .shape [1 ]
700- num_right = complex_residual_offnom_set_freq .shape [2 ]
701- num_offnom = complex_residual_offnom_set_freq .shape [0 ]
695+ nbr_left = complex_residual_offnom_set_freq .shape [1 ]
696+ nbr_right = complex_residual_offnom_set_freq .shape [2 ]
697+ nbr_offnom = complex_residual_offnom_set_freq .shape [0 ]
702698
703699 # Generate left weight variable
704- if weight_left_structure == "diagonal" :
705- L = cvxpy .Variable ((num_left , num_left ), diag = True )
700+ if weight_left_structure == "full" :
701+ L = cvxpy .Variable ((nbr_left , nbr_left ))
702+ elif weight_left_structure == "diagonal" :
703+ L = cvxpy .Variable ((nbr_left , nbr_left ), diag = True )
706704 elif weight_left_structure == "scalar" :
707705 L_scalar = cvxpy .Variable ()
708- L = L_scalar * scipy .sparse .eye_array (num_left )
706+ L = L_scalar * scipy .sparse .eye_array (nbr_left )
709707 elif weight_left_structure == "identity" :
710708 L = cvxpy .Parameter (
711- shape = (num_left , num_left ), value = np .eye (num_left ), diag = True
709+ shape = (nbr_left , nbr_left ), value = np .eye (nbr_left ), diag = True
712710 )
713711 else :
714712 raise ValueError (
715- f'"{ weight_left_structure } " is not a valid value for '
716- '`weight_right_structure`. It must take a value of either "diagonal" '
717- '"scalar", or "identity".'
713+ "Invalid `weight_left_structure`. Expected `full`, `diagonal`, `scalar`, "
714+ f"or `identity`. (Got { weight_left_structure } )."
718715 )
719716
720717 # Generate right weight variable
721- if weight_right_structure == "diagonal" :
722- R = cvxpy .Variable ((num_right , num_right ), diag = True )
718+ if weight_right_structure == "full" :
719+ R = cvxpy .Variable ((nbr_right , nbr_right ))
720+ elif weight_right_structure == "diagonal" :
721+ R = cvxpy .Variable ((nbr_right , nbr_right ), diag = True )
723722 elif weight_right_structure == "scalar" :
724723 R_scalar = cvxpy .Variable ()
725- R = R_scalar * scipy .sparse .eye_array (num_right )
724+ R = R_scalar * scipy .sparse .eye_array (nbr_right )
726725 elif weight_right_structure == "identity" :
727726 R = cvxpy .Parameter (
728- shape = (num_right , num_right ), value = np .eye (num_right ), diag = True
727+ shape = (nbr_right , nbr_right ), value = np .eye (nbr_right ), diag = True
729728 )
730729 else :
731730 raise ValueError (
732- f'"{ weight_right_structure } " is not a valid value for '
733- '`weight_right_structure`. It must take a value of either "diagonal" '
734- '"scalar", or "identity".'
731+ "Invalid `weight_right_structure`. Expected `full`, `diagonal`, `scalar`, "
732+ f"or `identity`. (Got { weight_right_structure } )."
735733 )
736734
737735 # Generate optimal uncertainty weight constraints
738736 constraint_freq_list = []
739- for idx_offnom in range (num_offnom ):
737+ for idx_offnom in range (nbr_offnom ):
740738 E_k = cvxpy .Parameter (
741- shape = (num_left , num_right ),
739+ shape = (nbr_left , nbr_right ),
742740 value = complex_residual_offnom_set_freq [idx_offnom , :, :],
743741 complex = True ,
744742 )
@@ -761,19 +759,20 @@ def _compute_optimal_weight_freq(
761759 problem .solve (** solver_params )
762760
763761 # Extract left weight
764- if weight_left_structure == "identity" :
762+ if weight_left_structure == "identity" or weight_left_structure == "full" :
765763 L_value = np .array (L .value )
766764 else :
767765 L_value = np .array (L .value .toarray ())
768- complex_response_weight_left_freq = np .sqrt (L_value )
766+ weight_left_opt = np .linalg .cholesky (L_value )
767+
769768 # Extract right weight
770- if weight_right_structure == "identity" :
769+ if weight_right_structure == "identity" or weight_right_structure == "full" :
771770 R_value = np .array (R .value )
772771 else :
773772 R_value = np .array (R .value .toarray ())
774- complex_response_weight_right_freq = np .sqrt (R_value )
773+ weight_right_opt = np .linalg . cholesky (R_value )
775774
776- return complex_response_weight_left_freq , complex_response_weight_right_freq
775+ return weight_left_opt , weight_right_opt
777776
778777
779778def fit_uncertainty_weight (
0 commit comments