Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9c53287
Implement object-oriented uncertainty block structure in source code
timeverettadams May 13, 2025
dd48ded
Implement object-oriented uncertainty blocks in tests and examples
timeverettadams May 14, 2025
8f47d5f
Add UncertaintyBlock parent class
timeverettadams May 14, 2025
d59e62c
Add MATLAB block structure to OOP block structure conversion and bloc…
timeverettadams May 27, 2025
de98ddc
Fix current doctests
timeverettadams May 27, 2025
fce826e
Update Sphinx documentation and bump version number and date
timeverettadams May 27, 2025
89070c9
Implement UncertaintyBlock as an abstract class
timeverettadams Jun 25, 2025
bbd8f43
Create an UncertaintyBlockStructure class to create common interface …
timeverettadams Jun 26, 2025
a90009c
Add architecture overview documentation and update examples with new …
timeverettadams Jul 3, 2025
916fbb5
Remove flaky doctest
sdahdah Jul 9, 2025
64cba3f
Merge branch 'main' into 36-implement-oop-block_structure-approach-an…
sdahdah Jul 9, 2025
e16e181
Revert to list of UncertaintyBlock and MATLAB format for uncertainty …
timeverettadams Jul 16, 2025
c47802a
Update unit tests as control.append returns same object type as inputs
timeverettadams Jul 16, 2025
dfbb9e7
Add name to conf.py
timeverettadams Jul 16, 2025
6e6eed4
Substitute utilities._tf_combine and utilities._tf_split for python-c…
timeverettadams Jul 16, 2025
848baa9
Tweak doctests
timeverettadams Jul 16, 2025
e9ddca2
Decrease numerical precision of doctests
timeverettadams Jul 21, 2025
2347d6b
Add np.ndarray in block_structure type hints and remove transfer func…
timeverettadams Jul 24, 2025
58475bf
Update uncertainty structure description in README.rst example
timeverettadams Jul 24, 2025
7cd241e
Update version numbers
timeverettadams Jul 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ authors:
orcid: "https://orcid.org/0000-0002-1987-9268"
affiliation: "McGill University"
title: "decargroup/dkpy"
version: v0.1.8
version: v0.1.9
url: "https://github.com/decargroup/dkpy"
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,12 @@ If you use this software in your research, please cite it as below or see

.. code-block:: bibtex

@software{dahdah_dkpy_2024,
@software{dahdah_dkpy_2025,
title={{decargroup/dkpy}},
doi={10.5281/zenodo.14511244},
url={https://github.com/decargroup/dkpy},
publisher={Zenodo},
author={Steven Dahdah and James Richard Forbes},
version = {{v0.1.8}},
year={2024},
version = {{v0.1.9}},
year={2025},
}
4 changes: 2 additions & 2 deletions doc/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
project = "dkpy"
copyright = "2024, Steven Dahdah and James Richard Forbes"
author = "Steven Dahdah and James Richard Forbes"
version = "0.1.8"
release = "0.1.8"
version = "0.1.9"
release = "0.1.9"

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
Expand Down
17 changes: 17 additions & 0 deletions doc/dkpy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ selecting the order in :func:`DScaleFit.fit`.

dkpy.DScaleFitSlicot

Uncertainty Block Structure
===========================

Supported uncertainty block elements are provided below. The prefered method for
specifying the uncertainty block description is via :class:`UncertaintyBlock` objects.
An additional method for specifying the uncertainty block description is the MATLAB
two column array format (see `MATLAB documentation <https://www.mathworks.com/help/robust/ref/mussv.html>`)
for users that are more comfortable with this format.

.. autosummary::
:toctree: _autosummary/

dkpy.UncertaintyBlock
dkpy.RealDiagonalBlock
dkpy.ComplexDiagonalBlock
dkpy.ComplexFullBlock

Extending ``dkpy``
==================

Expand Down
7 changes: 6 additions & 1 deletion examples/1_example_dk_iter_fixed_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ def example_dk_iter_fixed_order():
)

omega = np.logspace(-3, 3, 61)
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
# block_structure = np.array([[1, 1], [1, 1], [2, 2]]) # Alternative MATLAB descr.
block_structure = [
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(2, 2),
]
K, N, mu, iter_results, info = dk_iter.synthesize(
eg["P"],
eg["n_y"],
Expand Down
7 changes: 6 additions & 1 deletion examples/2_example_dk_iter_list_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ def example_dk_iter_list_order():
)

omega = np.logspace(-3, 3, 61)
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
# block_structure = np.array([[1, 1], [1, 1], [2, 2]]) # Alternative MATLAB descr.
block_structure = [
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(2, 2),
]
K, N, mu, iter_results, info = dk_iter.synthesize(
eg["P"],
eg["n_y"],
Expand Down
7 changes: 6 additions & 1 deletion examples/3_example_dk_iter_auto_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ def example_dk_iter_auto_order():
)

omega = np.logspace(-3, 3, 61)
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
# block_structure = np.array([[1, 1], [1, 1], [2, 2]]) # Alternative MATLAB descr.
block_structure = [
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(2, 2),
]
K, N, mu, iter_results, info = dk_iter.synthesize(
eg["P"],
eg["n_y"],
Expand Down
7 changes: 6 additions & 1 deletion examples/4_example_dk_iter_interactive.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ def example_dk_iter_interactive():
)

omega = np.logspace(-3, 3, 61)
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
# block_structure = np.array([[1, 1], [1, 1], [2, 2]]) # Alternative MATLAB descr.
block_structure = [
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(2, 2),
]
K, N, mu, iter_results, info = dk_iter.synthesize(
eg["P"],
eg["n_y"],
Expand Down
7 changes: 6 additions & 1 deletion examples/5_example_dk_iteration_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ def example_dk_iter_custom():
)

omega = np.logspace(-3, 3, 61)
block_structure = np.array([[1, 1], [1, 1], [2, 2]])
# block_structure = np.array([[1, 1], [1, 1], [2, 2]]) # Alternative MATLAB descr.
block_structure = [
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(1, 1),
dkpy.ComplexFullBlock(2, 2),
]
K, N, mu, iter_results, info = dk_iter.synthesize(
eg["P"],
eg["n_y"],
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "dkpy"
version = "0.1.8"
version = "0.1.9"
dependencies = [
"numpy>=1.21.0",
"scipy>=1.7.0",
Expand Down
1 change: 1 addition & 0 deletions src/dkpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from .dk_iteration import *
from .d_scale_fit import *
from .structured_singular_value import *
from .uncertainty_structure import *
from .utilities import *
62 changes: 37 additions & 25 deletions src/dkpy/d_scale_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
]

import abc
from typing import Optional, Tuple, Union
from typing import Optional, Tuple, Union, Sequence
import warnings

import control
Expand All @@ -15,6 +15,13 @@
import slycot

from . import utilities
from .uncertainty_structure import (
UncertaintyBlock,
RealDiagonalBlock,
ComplexDiagonalBlock,
ComplexFullBlock,
_convert_matlab_block_structure,
)


class DScaleFit(metaclass=abc.ABCMeta):
Expand All @@ -26,7 +33,7 @@ def fit(
omega: np.ndarray,
D_omega: np.ndarray,
order: Union[int, np.ndarray] = 0,
block_structure: Optional[np.ndarray] = None,
block_structure: Optional[Union[Sequence[UncertaintyBlock], np.ndarray]] = None,
) -> Tuple[control.StateSpace, control.StateSpace]:
"""Fit D-scale magnitudes.

Expand All @@ -39,10 +46,8 @@ def fit(
dimension.
order : Union[int, np.ndarray]
Transfer function order to fit. Can be specified per-entry.
block_structure : np.ndarray
2D array with 2 columns and as many rows as uncertainty blocks
in Delta. The columns represent the number of rows and columns in
each uncertainty block. See [#mussv]_.
block_structure : Union[Sequence[UncertaintyBlock], np.ndarray]
Uncertainty block structure description.

Returns
-------
Expand Down Expand Up @@ -80,24 +85,24 @@ class DScaleFitSlicot(DScaleFit):
... )
>>> D, D_inv = dkpy.DScaleFitSlicot().fit(omega, D_omega, 2, block_structure)
>>> print(control.ss2tf(D[0, 0]))
<TransferFunction>: sys[3641]$indexed$converted
<TransferFunction>: sys[4066]$indexed$converted
Inputs (1): ['u[0]']
Outputs (1): ['y[0]']
<BLANKLINE>
<BLANKLINE>
0.0157 s^2 + 0.257 s + 0.1391
-----------------------------
s^2 + 0.9658 s + 0.01424
0.01571 s^2 + 0.257 s + 0.1391
------------------------------
s^2 + 0.9657 s + 0.01424
<BLANKLINE>
>>> print(control.ss2tf(D[1, 1]))
<TransferFunction>: sys[3641]$indexed$converted
<TransferFunction>: sys[4066]$indexed$converted
Inputs (1): ['u[1]']
Outputs (1): ['y[1]']
<BLANKLINE>
<BLANKLINE>
0.01573 s^2 + 0.2574 s + 0.1394
-------------------------------
s^2 + 0.9663 s + 0.01425
s^2 + 0.9663 s + 0.01425
<BLANKLINE>
"""

Expand All @@ -106,8 +111,10 @@ def fit(
omega: np.ndarray,
D_omega: np.ndarray,
order: Union[int, np.ndarray] = 0,
block_structure: Optional[np.ndarray] = None,
block_structure: Optional[Union[Sequence[UncertaintyBlock], np.ndarray]] = None,
) -> Tuple[control.StateSpace, control.StateSpace]:
if isinstance(block_structure, np.ndarray):
block_structure = _convert_matlab_block_structure(block_structure)
# Get mask
if block_structure is None:
mask = -1 * np.ones((D_omega.shape[0], D_omega.shape[1]), dtype=int)
Expand Down Expand Up @@ -155,18 +162,18 @@ def fit(
return ss, ss_inv


def _mask_from_block_structure(block_structure: np.ndarray) -> np.ndarray:
def _mask_from_block_structure(
block_structure: Sequence[UncertaintyBlock],
) -> np.ndarray:
"""Create a mask from a specified block structure.

Entries known to be zero are set to 0. Entries known to be one are set to
1. Entries to be fit numerically are set to -1.

Parameters
----------
block_structure : np.ndarray
2D array with 2 columns and as many rows as uncertainty blocks
in Delta. The columns represent the number of rows and columns in
each uncertainty block. See [#mussv]_.
block_structure : Sequence[UncertaintyBlock]
Sequence of uncertainty block objects.

Returns
-------
Expand All @@ -178,19 +185,24 @@ def _mask_from_block_structure(block_structure: np.ndarray) -> np.ndarray:
----------
.. [#mussv] https://www.mathworks.com/help/robust/ref/mussv.html
"""
num_blocks = len(block_structure)
X_lst = []
for i in range(block_structure.shape[0]):
if block_structure[i, 0] <= 0:
for i in range(num_blocks):
# Uncertainty block
block = block_structure[i]
# Square uncertainty block condition
is_block_square = block.num_inputs == block.num_outputs
if isinstance(block, RealDiagonalBlock):
raise NotImplementedError("Real perturbations are not yet supported.")
if block_structure[i, 1] <= 0:
if isinstance(block, ComplexDiagonalBlock):
raise NotImplementedError("Diagonal perturbations are not yet supported.")
if block_structure[i, 0] != block_structure[i, 1]:
if isinstance(block, ComplexFullBlock) and (not is_block_square):
raise NotImplementedError("Nonsquare perturbations are not yet supported.")
# Set last scaling to identity
if i == block_structure.shape[0] - 1:
X_lst.append(np.eye(block_structure[i, 0], dtype=int))
if i == num_blocks - 1:
X_lst.append(np.eye(block.num_inputs, dtype=int))
else:
X_lst.append(-1 * np.eye(block_structure[i, 0], dtype=int))
X_lst.append(-1 * np.eye(block.num_inputs, dtype=int))
X = scipy.linalg.block_diag(*X_lst)
return X

Expand Down
Loading
Loading