Skip to content

Commit dd48ded

Browse files
Implement object-oriented uncertainty blocks in tests and examples
1 parent 9c53287 commit dd48ded

10 files changed

+85
-56
lines changed

examples/1_example_dk_iter_fixed_order.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ def example_dk_iter_fixed_order():
1919
)
2020

2121
omega = np.logspace(-3, 3, 61)
22-
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
22+
# block_structure = np.array([[1, 1], [1, 1], [2, 2]])
23+
block_structure = [
24+
dkpy.ComplexFullBlock(1, 1),
25+
dkpy.ComplexFullBlock(1, 1),
26+
dkpy.ComplexFullBlock(2, 2),
27+
]
2328
K, N, mu, iter_results, info = dk_iter.synthesize(
2429
eg["P"],
2530
eg["n_y"],

examples/2_example_dk_iter_list_order.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ def example_dk_iter_list_order():
2323
)
2424

2525
omega = np.logspace(-3, 3, 61)
26-
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
26+
block_structure = [
27+
dkpy.ComplexFullBlock(1, 1),
28+
dkpy.ComplexFullBlock(1, 1),
29+
dkpy.ComplexFullBlock(2, 2),
30+
]
2731
K, N, mu, iter_results, info = dk_iter.synthesize(
2832
eg["P"],
2933
eg["n_y"],

examples/3_example_dk_iter_auto_order.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ def example_dk_iter_auto_order():
4545

4646
omega = np.logspace(-3, 3, 61)
4747
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
48+
block_structure = [
49+
dkpy.ComplexFullBlock(1, 1),
50+
dkpy.ComplexFullBlock(1, 1),
51+
dkpy.ComplexFullBlock(2, 2),
52+
]
4853
K, N, mu, iter_results, info = dk_iter.synthesize(
4954
eg["P"],
5055
eg["n_y"],

examples/4_example_dk_iter_interactive.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ def example_dk_iter_interactive():
3737
)
3838

3939
omega = np.logspace(-3, 3, 61)
40-
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
40+
block_structure = [
41+
dkpy.ComplexFullBlock(1, 1),
42+
dkpy.ComplexFullBlock(1, 1),
43+
dkpy.ComplexFullBlock(2, 2),
44+
]
4145
K, N, mu, iter_results, info = dk_iter.synthesize(
4246
eg["P"],
4347
eg["n_y"],

examples/5_example_dk_iteration_custom.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ def example_dk_iter_custom():
7070
)
7171

7272
omega = np.logspace(-3, 3, 61)
73-
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
73+
block_structure = [
74+
dkpy.ComplexFullBlock(1, 1),
75+
dkpy.ComplexFullBlock(1, 1),
76+
dkpy.ComplexFullBlock(2, 2),
77+
]
7478
K, N, mu, iter_results, info = dk_iter.synthesize(
7579
eg["P"],
7680
eg["n_y"],

src/dkpy/d_scale_fit.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
]
77

88
import abc
9-
from typing import Optional, Tuple, Union, List
9+
from typing import Optional, Tuple, Union, List, Sequence
1010
import warnings
1111

1212
import control
@@ -31,7 +31,9 @@ def fit(
3131
omega: np.ndarray,
3232
D_omega: np.ndarray,
3333
order: Union[int, np.ndarray] = 0,
34-
block_structure: Optional[np.ndarray] = None,
34+
block_structure: Optional[
35+
Sequence[Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]]
36+
] = None,
3537
) -> Tuple[control.StateSpace, control.StateSpace]:
3638
"""Fit D-scale magnitudes.
3739
@@ -44,7 +46,7 @@ def fit(
4446
dimension.
4547
order : Union[int, np.ndarray]
4648
Transfer function order to fit. Can be specified per-entry.
47-
block_structure : np.ndarray
49+
block_structure : Sequence[RealDiagonalBlock | ComplexDiagonalBlock | ComplexFullBlock]
4850
2D array with 2 columns and as many rows as uncertainty blocks
4951
in Delta. The columns represent the number of rows and columns in
5052
each uncertainty block. See [#mussv]_.
@@ -112,7 +114,7 @@ def fit(
112114
D_omega: np.ndarray,
113115
order: Union[int, np.ndarray] = 0,
114116
block_structure: Optional[
115-
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
117+
List[Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]]
116118
] = None,
117119
) -> Tuple[control.StateSpace, control.StateSpace]:
118120
# Get mask
@@ -192,12 +194,15 @@ def _mask_from_block_structure(
192194
num_blocks = len(block_structure)
193195
X_lst = []
194196
for i in range(num_blocks):
197+
# Uncertainty block
195198
block = block_structure[i]
199+
# Square uncertainty block condition
200+
is_block_square = block.num_inputs == block.num_outputs
196201
if isinstance(block, RealDiagonalBlock):
197202
raise NotImplementedError("Real perturbations are not yet supported.")
198203
if isinstance(block, ComplexDiagonalBlock):
199204
raise NotImplementedError("Diagonal perturbations are not yet supported.")
200-
if isinstance(block, ComplexFullBlock) and (not block.is_square):
205+
if isinstance(block, ComplexFullBlock) and (not is_block_square):
201206
raise NotImplementedError("Nonsquare perturbations are not yet supported.")
202207
# Set last scaling to identity
203208
if i == num_blocks - 1:

src/dkpy/dk_iteration.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
import abc
1515
import logging
16-
from typing import Any, Dict, List, Optional, Tuple, Union
16+
from typing import Any, Dict, List, Optional, Tuple, Union, Sequence
1717

1818
import control
1919
import numpy as np
@@ -26,6 +26,11 @@
2626
structured_singular_value,
2727
utilities,
2828
)
29+
from .uncertainty_structure import (
30+
RealDiagonalBlock,
31+
ComplexDiagonalBlock,
32+
ComplexFullBlock,
33+
)
2934

3035

3136
class IterResult:
@@ -46,7 +51,9 @@ def __init__(
4651
mu_fit_omega: np.ndarray,
4752
D_fit_omega: np.ndarray,
4853
D_fit: control.StateSpace,
49-
block_structure: np.ndarray,
54+
block_structure: List[
55+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
56+
],
5057
):
5158
"""Instantiate :class:`IterResult`.
5259
@@ -91,7 +98,9 @@ def create_from_fit(
9198
K: control.StateSpace,
9299
D_fit: control.StateSpace,
93100
D_fit_inv: control.StateSpace,
94-
block_structure: np.ndarray,
101+
block_structure: List[
102+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
103+
],
95104
) -> "IterResult":
96105
"""Instantiate :class:`IterResult` from fit D-scales.
97106
@@ -204,7 +213,9 @@ def synthesize(
204213
n_y: int,
205214
n_u: int,
206215
omega: np.ndarray,
207-
block_structure: np.ndarray,
216+
block_structure: Sequence[
217+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
218+
],
208219
) -> Tuple[
209220
control.StateSpace,
210221
control.StateSpace,
@@ -341,7 +352,9 @@ def _get_fit_order(
341352
D_omega: np.ndarray,
342353
P: control.StateSpace,
343354
K: control.StateSpace,
344-
block_structure: np.ndarray,
355+
block_structure: List[
356+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
357+
],
345358
) -> Optional[Union[int, np.ndarray]]:
346359
"""Get D-scale fit order.
347360
@@ -440,7 +453,9 @@ def _get_fit_order(
440453
D_omega: np.ndarray,
441454
P: control.StateSpace,
442455
K: control.StateSpace,
443-
block_structure: np.ndarray,
456+
block_structure: List[
457+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
458+
],
444459
) -> Optional[Union[int, np.ndarray]]:
445460
if iteration < self.n_iterations:
446461
return self.fit_order
@@ -507,7 +522,9 @@ def _get_fit_order(
507522
D_omega: np.ndarray,
508523
P: control.StateSpace,
509524
K: control.StateSpace,
510-
block_structure: np.ndarray,
525+
block_structure: List[
526+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
527+
],
511528
) -> Optional[Union[int, np.ndarray]]:
512529
if iteration < len(self.fit_orders):
513530
return self.fit_orders[iteration]
@@ -589,7 +606,9 @@ def _get_fit_order(
589606
D_omega: np.ndarray,
590607
P: control.StateSpace,
591608
K: control.StateSpace,
592-
block_structure: np.ndarray,
609+
block_structure: List[
610+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
611+
],
593612
) -> Optional[Union[int, np.ndarray]]:
594613
# Check termination conditions
595614
if (self.max_iterations is not None) and (iteration >= self.max_iterations):
@@ -686,14 +705,16 @@ def __init__(
686705

687706
def _get_fit_order(
688707
self,
689-
iteration,
690-
omega,
691-
mu_omega,
692-
D_omega,
693-
P,
694-
K,
695-
block_structure,
696-
):
708+
iteration: int,
709+
omega: np.ndarray,
710+
mu_omega: np.ndarray,
711+
D_omega: np.ndarray,
712+
P: control.StateSpace,
713+
K: control.StateSpace,
714+
block_structure: List[
715+
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
716+
],
717+
) -> Optional[Union[int, np.ndarray]]:
697718
d_info = []
698719
for fit_order in range(self.max_fit_order + 1):
699720
D_fit, D_fit_inv = self.d_scale_fit.fit(

src/dkpy/structured_singular_value.py

Lines changed: 12 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import abc
99
import warnings
10-
from typing import Any, Dict, Optional, Tuple, Union, List
10+
from typing import Any, Dict, Optional, Tuple, Union, List, Sequence
1111

1212
import cvxpy
1313
import joblib
@@ -29,7 +29,7 @@ class StructuredSingularValue(metaclass=abc.ABCMeta):
2929
def compute_ssv(
3030
self,
3131
N_omega: np.ndarray,
32-
block_structure: List[
32+
block_structure: Sequence[
3333
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
3434
],
3535
) -> Tuple[np.ndarray, np.ndarray, Dict[str, Any]]:
@@ -39,22 +39,15 @@ def compute_ssv(
3939
----------
4040
N_omega : np.ndarray
4141
Closed-loop transfer function evaluated at each frequency.
42-
block_structure : np.ndarray
43-
2D array with 2 columns and as many rows as uncertainty blocks
44-
in Delta. The columns represent the number of rows and columns in
45-
each uncertainty block. See [#mussv]_.
46-
42+
block_structure : Sequence[RealDiagonalBlock | ComplexDiagonalBlock | ComplexFullBlock]
43+
Sequence of uncertainty block objects.
4744
Returns
4845
-------
4946
Tuple[np.ndarray, np.ndarray, Dict[str, Any]]
5047
Structured singular value at each frequency, D-scales at each
5148
frequency, and solution information. If the structured singular
5249
value cannot be computed, the first two elements of the tuple are
5350
``None``, but solution information is still returned.
54-
55-
References
56-
----------
57-
.. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html
5851
"""
5952
raise NotImplementedError()
6053

@@ -146,7 +139,7 @@ def __init__(
146139
def compute_ssv(
147140
self,
148141
N_omega: np.ndarray,
149-
block_structure: List[
142+
block_structure: Sequence[
150143
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
151144
],
152145
) -> Tuple[np.ndarray, np.ndarray, Dict[str, Any]]:
@@ -329,35 +322,32 @@ def _ssv_at_omega(
329322

330323

331324
def _variable_from_block_structure(
332-
block_structure: List[
325+
block_structure: Sequence[
333326
Union[RealDiagonalBlock, ComplexDiagonalBlock, ComplexFullBlock]
334327
],
335328
) -> cvxpy.Variable:
336329
"""Get optimization variable with specified block structure.
337330
338331
Parameters
339332
----------
340-
block_structure : np.ndarray
341-
2D array with 2 columns and as many rows as uncertainty blocks
342-
in Delta. The columns represent the number of rows and columns in
343-
each uncertainty block. See [#mussv]_.
333+
block_structure : Sequence[RealDiagonalBlock | ComplexDiagonalBlock | ComplexFullBlock]
334+
Sequence of uncertainty block objects.
344335
345336
Returns
346337
-------
347338
cvxpy.Variable
348339
CVXPY variable with specified block structure.
349-
350-
References
351-
----------
352-
.. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html
353340
"""
354341
num_blocks = len(block_structure)
355342
X_lst = []
356343
for i in range(num_blocks):
357344
row = []
358345
for j in range(num_blocks):
346+
# Uncertainty blocks
359347
block_i = block_structure[i]
360348
block_j = block_structure[j]
349+
# Square uncertainty block condition
350+
is_block_square = block_i.num_inputs == block_i.num_outputs
361351
if i == j:
362352
# If on the block diagonal, insert variable
363353
if isinstance(block_i, RealDiagonalBlock):
@@ -368,7 +358,7 @@ def _variable_from_block_structure(
368358
raise NotImplementedError(
369359
"Complex diagonal perturbations are not yet supported."
370360
)
371-
if isinstance(block_i, ComplexFullBlock) and (not block_i.is_square):
361+
if isinstance(block_i, ComplexFullBlock) and (not is_block_square):
372362
raise NotImplementedError(
373363
"Nonsquare perturbations are not yet supported."
374364
)

src/dkpy/uncertainty_structure.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ def __init__(self, num_channels: int):
1919
# Uncertainty block parameters
2020
self.num_inputs = num_channels
2121
self.num_outputs = num_channels
22-
self.is_square = True
2322

2423

2524
class ComplexDiagonalBlock:
@@ -40,7 +39,6 @@ def __init__(self, num_channels: int):
4039
# Uncertainty block parameters
4140
self.num_inputs = num_channels
4241
self.num_outputs = num_channels
43-
self.is_square = True
4442

4543

4644
class ComplexFullBlock:
@@ -63,4 +61,3 @@ def __init__(self, num_inputs: int, num_outputs: int):
6361
# Uncertainty block parameters
6462
self.num_inputs = num_inputs
6563
self.num_outputs = num_outputs
66-
self.is_square = self.num_inputs == self.num_outputs

0 commit comments

Comments
 (0)