Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
112 changes: 112 additions & 0 deletions src/dkpy/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
"_ensure_tf",
"_tf_close_coeff",
"_tf_combine",
"_tf_split",
"_tf_eye",
"_tf_zeros",
"_tf_ones",
"_auto_lmi_strictness",
]

Expand Down Expand Up @@ -192,6 +195,55 @@ def _tf_combine(
return G_tf


def _tf_split(tf: control.TransferFunction) -> np.ndarray:
"""Split MIMO transfer function into NumPy array of SISO tranfer functions.

Parameters
----------
tf : control.TransferFunction
MIMO transfer function to split.

Returns
-------
np.ndarray
NumPy array of SISO transfer functions.

Examples
--------
Split a MIMO transfer function

>>> G = control.TransferFunction(
... [
... [[87.8], [-86.4]],
... [[108.2], [-109.6]],
... ],
... [
... [[1, 1], [1, 1]],
... [[1, 1], [1, 1]],
... ],
... )
>>> dkpy._tf_split(G)
array([[TransferFunction(array([87.8]), array([1, 1])),
TransferFunction(array([-86.4]), array([1, 1]))],
[TransferFunction(array([108.2]), array([1, 1])),
TransferFunction(array([-109.6]), array([1, 1]))]], dtype=object)
"""
tf_split_lst = []
for i_out in range(tf.noutputs):
row = []
for i_in in range(tf.ninputs):
row.append(
control.TransferFunction(
tf.num[i_out][i_in],
tf.den[i_out][i_in],
dt=tf.dt,
)
)
tf_split_lst.append(row)
tf_split = np.array(tf_split_lst, dtype=object)
return tf_split


def _tf_eye(
n: int,
dt: Union[None, bool, float] = None,
Expand Down Expand Up @@ -219,6 +271,66 @@ def _tf_eye(
return eye


def _tf_zeros(
m: int,
n: int,
dt: Union[None, bool, float] = None,
) -> control.TransferFunction:
"""Transfer function matrix of zeros.

Parameters
----------
m : int
First dimension.
n : int
Second dimension.
dt : Union[None, bool, float]
Timestep (s). Based on the ``control`` package, ``True`` indicates a
discrete-time system with unspecified timestep, ``0`` indicates a
continuous-time system, and ``None`` indicates a continuous- or
discrete-time system with unspecified timestep.

Returns
-------
control.TransferFunction
Identity transfer matrix.
"""
num = np.zeros((m, n, 1))
den = np.ones((m, n, 1))
zeros = control.TransferFunction(num, den, dt=dt)
return zeros


def _tf_ones(
m: int,
n: int,
dt: Union[None, bool, float] = None,
) -> control.TransferFunction:
"""Transfer matrix of ones.

Parameters
----------
m : int
First dimension.
n : int
Second dimension.
dt : Union[None, bool, float]
Timestep (s). Based on the ``control`` package, ``True`` indicates a
discrete-time system with unspecified timestep, ``0`` indicates a
continuous-time system, and ``None`` indicates a continuous- or
discrete-time system with unspecified timestep.

Returns
-------
control.TransferFunction
Identity transfer matrix.
"""
num = np.ones((m, n, 1))
den = np.ones((m, n, 1))
zeros = control.TransferFunction(num, den, dt=dt)
return zeros


def _auto_lmi_strictness(
solver_params: Dict[str, Any],
scale: float = 10,
Expand Down
176 changes: 172 additions & 4 deletions tests/test_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,8 @@ def test_error_ensure(self, arraylike_or_tf, dt, exception):
dkpy._ensure_tf(arraylike_or_tf, dt)


class TestTfCombine:
"""Test :func:`_tf_combine`."""
class TestTfCombineSplit:
"""Test :func:`_tf_combine` and :func:`_tf_split`."""

@pytest.mark.parametrize(
"tf_array, tf",
Expand Down Expand Up @@ -362,6 +362,102 @@ def test_combine(self, tf_array, tf):
tf_combined = dkpy._tf_combine(tf_array)
assert dkpy._tf_close_coeff(tf_combined, tf)

@pytest.mark.parametrize(
"tf_array, tf",
[
(
np.array(
[
[control.TransferFunction([1], [1, 1])],
],
dtype=object,
),
control.TransferFunction(
[
[[1]],
],
[
[[1, 1]],
],
),
),
(
np.array(
[
[control.TransferFunction([1], [1, 1])],
[control.TransferFunction([2], [1, 0])],
],
dtype=object,
),
control.TransferFunction(
[
[[1]],
[[2]],
],
[
[[1, 1]],
[[1, 0]],
],
),
),
(
np.array(
[
[control.TransferFunction([1], [1, 1], dt=1)],
[control.TransferFunction([2], [1, 0], dt=1)],
],
dtype=object,
),
control.TransferFunction(
[
[[1]],
[[2]],
],
[
[[1, 1]],
[[1, 0]],
],
dt=1,
),
),
(
np.array(
[
[control.TransferFunction([2], [1], dt=0.1)],
[control.TransferFunction([2], [1, 0], dt=0.1)],
],
dtype=object,
),
control.TransferFunction(
[
[[2]],
[[2]],
],
[
[[1]],
[[1, 0]],
],
dt=0.1,
),
),
],
)
def test_split(self, tf_array, tf):
"""Test splitting transfer functions."""
tf_split = dkpy._tf_split(tf)
# Test entry-by-entry
for i in range(tf_split.shape[0]):
for j in range(tf_split.shape[1]):
assert dkpy._tf_close_coeff(
tf_split[i, j],
tf_array[i, j],
)
# Test combined
assert dkpy._tf_close_coeff(
dkpy._tf_combine(tf_split),
dkpy._tf_combine(tf_array),
)

@pytest.mark.parametrize(
"tf_array, exception",
[
Expand Down Expand Up @@ -431,7 +527,7 @@ class TestTfEye:
),
),
(
2,
3,
1e-3,
control.TransferFunction(
[
Expand All @@ -452,7 +548,79 @@ class TestTfEye:
def test_tf_eye(self, n, dt, tf_exp):
"""Test :func:`_tf_eye`."""
tf = dkpy._tf_eye(n, dt)
assert dkpy._tf_close_coeff(tf, tf)
assert dkpy._tf_close_coeff(tf, tf_exp)


class TestTfZeros:
"""Test :func:`_tf_zeros`."""

@pytest.mark.parametrize(
"m, n, dt, tf_exp",
[
(
1,
1,
None,
control.TransferFunction([0], [1], dt=None),
),
(
2,
3,
None,
control.TransferFunction(
[
[[0], [0], [0]],
[[0], [0], [0]],
],
[
[[1], [1], [1]],
[[1], [1], [1]],
],
dt=None,
),
),
],
)
def test_tf_zeros(self, m, n, dt, tf_exp):
"""Test :func:`_tf_zeros`."""
tf = dkpy._tf_zeros(m, n, dt)
assert dkpy._tf_close_coeff(tf, tf_exp)


class TestTfOnes:
"""Test :func:`_tf_ones`."""

@pytest.mark.parametrize(
"m, n, dt, tf_exp",
[
(
1,
1,
None,
control.TransferFunction([1], [1], dt=None),
),
(
2,
3,
None,
control.TransferFunction(
[
[[1], [1], [1]],
[[1], [1], [1]],
],
[
[[1], [1], [1]],
[[1], [1], [1]],
],
dt=None,
),
),
],
)
def test_tf_ones(self, m, n, dt, tf_exp):
"""Test :func:`_tf_ones`."""
tf = dkpy._tf_ones(m, n, dt)
assert dkpy._tf_close_coeff(tf, tf_exp)


class TestAutoLmiStrictness:
Expand Down
Loading