Skip to content

Commit 98c82c6

Browse files
Add ability to specify unstructured/full uncertainty weight in optimal uncertainty weight response.
1 parent eb4cb77 commit 98c82c6

File tree

19 files changed

+45
-117
lines changed

19 files changed

+45
-117
lines changed

src/dkpy/uncertainty_characterization.py

Lines changed: 39 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import scipy
2121
from 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
2424
from matplotlib.figure import Figure
2525
from matplotlib.axes import Axes
2626
from matplotlib.legend import Legend
@@ -551,8 +551,8 @@ def _compute_uncertainty_residual_inverse_multiplicative_output_freq(
551551

552552
def 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

651649
def _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

779778
def fit_uncertainty_weight(

tests/test_uncertainty_characterization.py

Lines changed: 6 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ class TestComputeUncertaintyWeightResponse:
241241
control.TransferFunction([1], [0.7, 1]),
242242
],
243243
np.logspace(-2, 2, 100),
244-
"diagonal",
245-
"diagonal",
244+
"full",
245+
"full",
246246
),
247247
(
248248
control.TransferFunction([1], [0.5, 1]),
@@ -254,18 +254,6 @@ class TestComputeUncertaintyWeightResponse:
254254
],
255255
np.logspace(-2, 2, 100),
256256
"diagonal",
257-
"identity",
258-
),
259-
(
260-
control.TransferFunction([1], [0.5, 1]),
261-
[
262-
control.TransferFunction([1], [0.3, 1]),
263-
control.TransferFunction([1], [0.4, 1]),
264-
control.TransferFunction([1], [0.6, 1]),
265-
control.TransferFunction([1], [0.7, 1]),
266-
],
267-
np.logspace(-2, 2, 100),
268-
"identity",
269257
"diagonal",
270258
),
271259
(
@@ -301,8 +289,8 @@ class TestComputeUncertaintyWeightResponse:
301289
control.TransferFunction([1], [1, 2 * 0.9 * 0.7, 0.7**2]),
302290
],
303291
np.logspace(-1, 1, 100),
304-
"diagonal",
305-
"diagonal",
292+
"full",
293+
"full",
306294
),
307295
(
308296
control.TransferFunction([1], [1, 2 * 0.5 * 1, 1**2]),
@@ -314,18 +302,6 @@ class TestComputeUncertaintyWeightResponse:
314302
],
315303
np.logspace(-1, 1, 100),
316304
"diagonal",
317-
"identity",
318-
),
319-
(
320-
control.TransferFunction([1], [1, 2 * 0.5 * 1, 1**2]),
321-
[
322-
control.TransferFunction([1], [1, 2 * 0.3 * 1.5, 1.5**2]),
323-
control.TransferFunction([1], [1, 2 * 0.7 * 1.3, 1.3**2]),
324-
control.TransferFunction([1], [1, 2 * 0.2 * 0.9, 0.9**2]),
325-
control.TransferFunction([1], [1, 2 * 0.9 * 0.7, 0.7**2]),
326-
],
327-
np.logspace(-1, 1, 100),
328-
"identity",
329305
"diagonal",
330306
),
331307
(
@@ -396,8 +372,8 @@ class TestComputeUncertaintyWeightResponse:
396372
),
397373
],
398374
np.logspace(-1, 1.5, 100),
399-
"diagonal",
400-
"diagonal",
375+
"full",
376+
"full",
401377
),
402378
(
403379
control.TransferFunction(
@@ -444,53 +420,6 @@ class TestComputeUncertaintyWeightResponse:
444420
],
445421
np.logspace(-1, 1.5, 100),
446422
"diagonal",
447-
"identity",
448-
),
449-
(
450-
control.TransferFunction(
451-
[
452-
[[1], [3]],
453-
[[2], [1]],
454-
],
455-
[
456-
[[1, 2 * 0.5 * 1, 1**2], [0.5, 1]],
457-
[[1, 1], [1, 2 * 0.3 * 5, 5**2]],
458-
],
459-
),
460-
[
461-
control.TransferFunction(
462-
[
463-
[[1], [3.25]],
464-
[[1.9], [1]],
465-
],
466-
[
467-
[[1, 2 * 0.4 * 1.1, 1.1**2], [0.6, 1]],
468-
[[0.8, 1], [1, 2 * 0.4 * 4.2, 4.2**2]],
469-
],
470-
),
471-
control.TransferFunction(
472-
[
473-
[[1.1], [2.85]],
474-
[[1.7], [0.9]],
475-
],
476-
[
477-
[[1, 2 * 0.45 * 0.9, 0.9**2], [0.55, 1]],
478-
[[0.95, 1], [1, 2 * 0.8 * 5.5, 5.5**2]],
479-
],
480-
),
481-
control.TransferFunction(
482-
[
483-
[[1], [3.05]],
484-
[[1.7], [1.0]],
485-
],
486-
[
487-
[[1, 2 * 0.5 * 1.3, 1.3**2], [0.42, 1]],
488-
[[1, 1], [1, 2 * 0.9 * 5.25, 5.25**2]],
489-
],
490-
),
491-
],
492-
np.logspace(-1, 1.5, 100),
493-
"identity",
494423
"diagonal",
495424
),
496425
(

tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom0_sys_offnom_list0_omega0_diagonal_diagonal_.npz renamed to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom0_sys_offnom_list0_omega0_full_full_.npz

File renamed without changes.

tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom13_sys_offnom_list13_omega13_scalar_identity_.npz renamed to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom10_sys_offnom_list10_omega10_scalar_identity_.npz

File renamed without changes.

tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom14_sys_offnom_list14_omega14_identity_scalar_.npz renamed to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom11_sys_offnom_list11_omega11_identity_scalar_.npz

File renamed without changes.

tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom1_sys_offnom_list1_omega1_diagonal_identity_.npz renamed to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom2_sys_offnom_list2_omega2_scalar_identity_.npz

File renamed without changes.

tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom2_sys_offnom_list2_omega2_identity_diagonal_.npz renamed to tests/test_uncertainty_characterization/test_compute_uncertainty_weight_response_sys_nom3_sys_offnom_list3_omega3_identity_scalar_.npz

File renamed without changes.

0 commit comments

Comments
 (0)