Skip to content

Commit cf5a0ab

Browse files
committed
Implement SS __rmul__ and add __mul__ unit tests
1 parent 525e245 commit cf5a0ab

2 files changed

Lines changed: 166 additions & 7 deletions

File tree

‎control/statesp.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,10 @@ def __mul__(self, other):
671671

672672
elif isinstance(other, np.ndarray):
673673
other = np.atleast_2d(other)
674+
# Special case for SISO transfer function
675+
if self.issiso():
676+
self = bdalg.append(*([self] * other.shape[0]))
677+
# Dimension check after broadcasting
674678
if self.ninputs != other.shape[0]:
675679
raise ValueError("array has incompatible shape")
676680
A, C = self.A, self.C
@@ -727,8 +731,15 @@ def __rmul__(self, other):
727731
return StateSpace(self.A, B, self.C, D, self.dt)
728732

729733
elif isinstance(other, np.ndarray):
730-
C = np.atleast_2d(other) @ self.C
731-
D = np.atleast_2d(other) @ self.D
734+
other = np.atleast_2d(other)
735+
# Special case for SISO transfer function
736+
if self.issiso():
737+
self = bdalg.append(*([self] * other.shape[1]))
738+
# Dimension check after broadcasting
739+
if self.noutputs != other.shape[1]:
740+
raise ValueError("array has incompatible shape")
741+
C = other @ self.C
742+
D = other @ self.D
732743
return StateSpace(self.A, self.B, C, D, self.dt)
733744

734745
if not isinstance(other, StateSpace):

‎control/tests/statesp_test.py‎

Lines changed: 153 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
from control.lti import evalfr
2121
from control.statesp import StateSpace, _convert_to_statespace, tf2ss, \
2222
_statesp_defaults, _rss_generate, linfnorm, ss, rss, drss
23-
from control.xferfcn import TransferFunction, ss2tf
23+
from control.xferfcn import TransferFunction, ss2tf, _tf_close_coeff
2424

2525
from .conftest import editsdefaults, slycotonly
2626

@@ -319,11 +319,159 @@ def test_multiply_ss(self, sys222, sys322):
319319
np.testing.assert_array_almost_equal(sys.C, C)
320320
np.testing.assert_array_almost_equal(sys.D, D)
321321

322-
def test_multiply_mimo_siso(self):
323-
assert False
322+
@pytest.mark.parametrize(
323+
"left, right, expected",
324+
[
325+
(
326+
TransferFunction([2], [1, 0]),
327+
TransferFunction(
328+
[
329+
[[2], [1]],
330+
[[-1], [4]],
331+
],
332+
[
333+
[[10, 1], [20, 1]],
334+
[[20, 1], [30, 1]],
335+
],
336+
),
337+
TransferFunction(
338+
[
339+
[[4], [2]],
340+
[[-2], [8]],
341+
],
342+
[
343+
[[10, 1, 0], [20, 1, 0]],
344+
[[20, 1, 0], [30, 1, 0]],
345+
],
346+
),
347+
),
348+
(
349+
TransferFunction(
350+
[
351+
[[2], [1]],
352+
[[-1], [4]],
353+
],
354+
[
355+
[[10, 1], [20, 1]],
356+
[[20, 1], [30, 1]],
357+
],
358+
),
359+
TransferFunction([2], [1, 0]),
360+
TransferFunction(
361+
[
362+
[[4], [2]],
363+
[[-2], [8]],
364+
],
365+
[
366+
[[10, 1, 0], [20, 1, 0]],
367+
[[20, 1, 0], [30, 1, 0]],
368+
],
369+
),
370+
),
371+
(
372+
TransferFunction([2], [1, 0]),
373+
np.eye(3),
374+
TransferFunction(
375+
[
376+
[[2], [0], [0]],
377+
[[0], [2], [0]],
378+
[[0], [0], [2]],
379+
],
380+
[
381+
[[1, 0], [1], [1]],
382+
[[1], [1, 0], [1]],
383+
[[1], [1], [1, 0]],
384+
],
385+
),
386+
),
387+
]
388+
)
389+
def test_mul_mimo_siso(self, left, right, expected):
390+
result = tf2ss(left).__mul__(right)
391+
assert _tf_close_coeff(
392+
expected.minreal(),
393+
ss2tf(result).minreal(),
394+
)
324395

325-
def test_divide_mimo_siso(self):
326-
assert False
396+
@pytest.mark.parametrize(
397+
"left, right, expected",
398+
[
399+
(
400+
TransferFunction([2], [1, 0]),
401+
TransferFunction(
402+
[
403+
[[2], [1]],
404+
[[-1], [4]],
405+
],
406+
[
407+
[[10, 1], [20, 1]],
408+
[[20, 1], [30, 1]],
409+
],
410+
),
411+
TransferFunction(
412+
[
413+
[[4], [2]],
414+
[[-2], [8]],
415+
],
416+
[
417+
[[10, 1, 0], [20, 1, 0]],
418+
[[20, 1, 0], [30, 1, 0]],
419+
],
420+
),
421+
),
422+
(
423+
TransferFunction(
424+
[
425+
[[2], [1]],
426+
[[-1], [4]],
427+
],
428+
[
429+
[[10, 1], [20, 1]],
430+
[[20, 1], [30, 1]],
431+
],
432+
),
433+
TransferFunction([2], [1, 0]),
434+
TransferFunction(
435+
[
436+
[[4], [2]],
437+
[[-2], [8]],
438+
],
439+
[
440+
[[10, 1, 0], [20, 1, 0]],
441+
[[20, 1, 0], [30, 1, 0]],
442+
],
443+
),
444+
),
445+
(
446+
np.eye(3),
447+
TransferFunction([2], [1, 0]),
448+
TransferFunction(
449+
[
450+
[[2], [0], [0]],
451+
[[0], [2], [0]],
452+
[[0], [0], [2]],
453+
],
454+
[
455+
[[1, 0], [1], [1]],
456+
[[1], [1, 0], [1]],
457+
[[1], [1], [1, 0]],
458+
],
459+
),
460+
),
461+
]
462+
)
463+
def test_rmul_mimo_siso(self, left, right, expected):
464+
result = tf2ss(right).__rmul__(left)
465+
assert _tf_close_coeff(
466+
expected.minreal(),
467+
ss2tf(result).minreal(),
468+
)
469+
470+
# def test_truediv_mimo_siso(self, left, right, expected):
471+
# assert False
472+
#
473+
# def test_rtruediv_mimo_siso(self, left, right, expected):
474+
# assert False
327475

328476
@pytest.mark.parametrize("k", [2, -3.141, np.float32(2.718), np.array([[4.321], [5.678]])])
329477
def test_truediv_ss_scalar(self, sys322, k):

0 commit comments

Comments
 (0)