• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

python-control / python-control / 13107996232

03 Feb 2025 06:53AM UTC coverage: 94.731% (+0.02%) from 94.709%
13107996232

push

github

web-flow
Merge pull request #1094 from murrayrm/userguide-22Dec2024

Updated user documentation (User Guide, Reference Manual)

9673 of 10211 relevant lines covered (94.73%)

8.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.28
control/bdalg.py
1
# bdalg.py - block diagram algebra
2
#
3
# Initial author: Richard M. Murray
4
# Creation date: 24 May 09
5
# Pre-2014 revisions: Kevin K. Chen, Dec 2010
6
# Use `git shortlog -n -s bdalg.py` for full list of contributors
7

8
"""Block diagram algebra.
9

10
This module contains some standard block diagram algebra, including
11
series, parallel, and feedback functions.
12

13
"""
14

15
from functools import reduce
9✔
16
from warnings import warn
9✔
17

18
import numpy as np
9✔
19

20
from . import frdata as frd
9✔
21
from . import statesp as ss
9✔
22
from . import xferfcn as tf
9✔
23
from .iosys import InputOutputSystem
9✔
24

25
__all__ = ['series', 'parallel', 'negate', 'feedback', 'append', 'connect',
9✔
26
           'combine_tf', 'split_tf']
27

28

29
def series(*sys, **kwargs):
9✔
30
    """series(sys1, sys2[, ..., sysn])
31

32
    Series connection of I/O systems.
33

34
    Generates a new system ``[sysn * ... *] sys2 * sys1``.
35

36
    Parameters
37
    ----------
38
    sys1, sys2, ..., sysn : scalar, array, or `InputOutputSystem`
39
        I/O systems to combine.
40

41
    Returns
42
    -------
43
    out : `InputOutputSystem`
44
        Series interconnection of the systems.
45

46
    Other Parameters
47
    ----------------
48
    inputs, outputs : str, or list of str, optional
49
        List of strings that name the individual signals.  If not given,
50
        signal names will be of the form 's[i]' (where 's' is one of 'u,
51
        or 'y'). See `InputOutputSystem` for more information.
52
    states : str, or list of str, optional
53
        List of names for system states.  If not given, state names will be
54
        of the form 'x[i]' for interconnections of linear systems or
55
        '<subsys_name>.<state_name>' for interconnected nonlinear systems.
56
    name : string, optional
57
        System name (used for specifying signals). If unspecified, a generic
58
        name 'sys[id]' is generated with a unique integer id.
59

60
    Raises
61
    ------
62
    ValueError
63
        If `sys2.ninputs` does not equal `sys1.noutputs` or if `sys1.dt` is
64
        not compatible with `sys2.dt`.
65

66
    See Also
67
    --------
68
    append, feedback, interconnect, negate, parallel
69

70
    Notes
71
    -----
72
    This function is a wrapper for the __mul__ function in the appropriate
73
    `NonlinearIOSystem`, `StateSpace`, `TransferFunction`, or other I/O
74
    system class.  The output type is the type of `sys1` unless a more
75
    general type is required based on type type of `sys2`.
76

77
    If both systems have a defined timebase (`dt` = 0 for continuous time,
78
    `dt` > 0 for discrete time), then the timebase for both systems must
79
    match.  If only one of the system has a timebase, the return
80
    timebase will be set to match it.
81

82
    Examples
83
    --------
84
    >>> G1 = ct.rss(3)
85
    >>> G2 = ct.rss(4)
86
    >>> G = ct.series(G1, G2) # Same as sys3 = sys2 * sys1
87
    >>> G.ninputs, G.noutputs, G.nstates
88
    (1, 1, 7)
89

90
    >>> G1 = ct.rss(2, inputs=2, outputs=3)
91
    >>> G2 = ct.rss(3, inputs=3, outputs=1)
92
    >>> G = ct.series(G1, G2) # Same as sys3 = sys2 * sys1
93
    >>> G.ninputs, G.noutputs, G.nstates
94
    (2, 1, 5)
95

96
    """
97
    sys = reduce(lambda x, y: y * x, sys[1:], sys[0])
9✔
98
    sys.update_names(**kwargs)
9✔
99
    return sys
9✔
100

101

102
def parallel(*sys, **kwargs):
9✔
103
    r"""parallel(sys1, sys2[, ..., sysn])
104

105
    Parallel connection of I/O systems.
106

107
    Generates a parallel connection ``sys1 + sys2 [+ ...  + sysn]``.
108

109
    Parameters
110
    ----------
111
    sys1, sys2, ..., sysn : scalar, array, or `InputOutputSystem`
112
        I/O systems to combine.
113

114
    Returns
115
    -------
116
    out : `InputOutputSystem`
117
        Parallel interconnection of the systems.
118

119
    Other Parameters
120
    ----------------
121
    inputs, outputs : str, or list of str, optional
122
        List of strings that name the individual signals.  If not given,
123
        signal names will be of the form 's[i'` (where 's' is one of 'u',
124
        or 'y'). See `InputOutputSystem` for more information.
125
    states : str, or list of str, optional
126
        List of names for system states.  If not given, state names will be
127
        of the form 'x[i]' for interconnections of linear systems or
128
        '<subsys_name>.<state_name>' for interconnected nonlinear systems.
129
    name : string, optional
130
        System name (used for specifying signals). If unspecified, a generic
131
        name 'sys[id]' is generated with a unique integer id.
132

133
    Raises
134
    ------
135
    ValueError
136
        If `sys1` and `sys2` do not have the same numbers of inputs and
137
        outputs.
138

139
    See Also
140
    --------
141
    append, feedback, interconnect, negate, series
142

143
    Notes
144
    -----
145
    This function is a wrapper for the __add__ function in the
146
    `StateSpace` and `TransferFunction` classes.  The output type is usually
147
    the type of `sys1`.  If `sys1` is a scalar, then the output type is
148
    the type of `sys2`.
149

150
    If both systems have a defined timebase (`dt` = 0 for continuous time,
151
    `dt` > 0 for discrete time), then the timebase for both systems must
152
    match.  If only one of the system has a timebase, the return
153
    timebase will be set to match it.
154

155
    Examples
156
    --------
157
    >>> G1 = ct.rss(3)
158
    >>> G2 = ct.rss(4)
159
    >>> G = ct.parallel(G1, G2) # Same as sys3 = sys1 + sys2
160
    >>> G.ninputs, G.noutputs, G.nstates
161
    (1, 1, 7)
162

163
    >>> G1 = ct.rss(3, inputs=3, outputs=4)
164
    >>> G2 = ct.rss(4, inputs=3, outputs=4)
165
    >>> G = ct.parallel(G1, G2)  # Add another system
166
    >>> G.ninputs, G.noutputs, G.nstates
167
    (3, 4, 7)
168

169
    """
170
    sys = reduce(lambda x, y: x + y, sys[1:], sys[0])
9✔
171
    sys.update_names(**kwargs)
9✔
172
    return sys
9✔
173

174
def negate(sys, **kwargs):
9✔
175
    """Return the negative of a system.
176

177
    Parameters
178
    ----------
179
    sys : scalar, array, or `InputOutputSystem`
180
        I/O systems to negate.
181

182
    Returns
183
    -------
184
    out : `InputOutputSystem`
185
        Negated system.
186

187
    Other Parameters
188
    ----------------
189
    inputs, outputs : str, or list of str, optional
190
        List of strings that name the individual signals.  If not given,
191
        signal names will be of the form 's[i]' (where 's' is one of 'u',
192
        or 'y'). See `InputOutputSystem` for more information.
193
    states : str, or list of str, optional
194
        List of names for system states.  If not given, state names will be
195
        of of the form 'x[i]' for interconnections of linear systems or
196
        '<subsys_name>.<state_name>' for interconnected nonlinear systems.
197
    name : string, optional
198
        System name (used for specifying signals). If unspecified, a generic
199
        name 'sys[id]' is generated with a unique integer id.
200

201
    See Also
202
    --------
203
    append, feedback, interconnect, parallel, series
204

205
    Notes
206
    -----
207
    This function is a wrapper for the __neg__ function in the `StateSpace`
208
    and `TransferFunction` classes.  The output type is the same as the
209
    input type.
210

211
    Examples
212
    --------
213
    >>> G = ct.tf([2], [1, 1])
214
    >>> G.dcgain()
215
    np.float64(2.0)
216

217
    >>> Gn = ct.negate(G) # Same as sys2 = -sys1.
218
    >>> Gn.dcgain()
219
    np.float64(-2.0)
220

221
    """
222
    sys = -sys
9✔
223
    sys.update_names(**kwargs)
9✔
224
    return sys
9✔
225

226
#! TODO: expand to allow sys2 default to work in MIMO case?
227
def feedback(sys1, sys2=1, sign=-1, **kwargs):
9✔
228
    """Feedback interconnection between two I/O systems.
229

230
    Parameters
231
    ----------
232
    sys1, sys2 : scalar, array, or `InputOutputSystem`
233
        I/O systems to combine.
234
    sign : scalar, optional
235
        The sign of feedback.  `sign=-1` indicates negative feedback
236
        (default), and `sign=1` indicates positive feedback.
237

238
    Returns
239
    -------
240
    out : `InputOutputSystem`
241
        Feedback interconnection of the systems.
242

243
    Other Parameters
244
    ----------------
245
    inputs, outputs : str, or list of str, optional
246
        List of strings that name the individual signals.  If not given,
247
        signal names will be of the form 's[i]' (where 's' is one of 'u',
248
        or 'y'). See `InputOutputSystem` for more information.
249
    states : str, or list of str, optional
250
        List of names for system states.  If not given, state names will be
251
        of of the form 'x[i]' for interconnections of linear systems or
252
        '<subsys_name>.<state_name>' for interconnected nonlinear systems.
253
    name : string, optional
254
        System name (used for specifying signals). If unspecified, a generic
255
        name 'sys[id]' is generated with a unique integer id.
256

257
    Raises
258
    ------
259
    ValueError
260
        If `sys1` does not have as many inputs as `sys2` has outputs, or if
261
        `sys2` does not have as many inputs as `sys1` has outputs.
262
    NotImplementedError
263
        If an attempt is made to perform a feedback on a MIMO `TransferFunction`
264
        object.
265

266
    See Also
267
    --------
268
    append, interconnect, negate, parallel, series
269

270
    Notes
271
    -----
272
    This function is a wrapper for the `feedback` function in the I/O
273
    system classes.  It calls sys1.feedback if `sys1` is an I/O system
274
    object.  If `sys1` is a scalar, then it is converted to `sys2`'s type,
275
    and the corresponding feedback function is used.
276

277
    Examples
278
    --------
279
    >>> G = ct.rss(3, inputs=2, outputs=5)
280
    >>> C = ct.rss(4, inputs=5, outputs=2)
281
    >>> T = ct.feedback(G, C, sign=1)
282
    >>> T.ninputs, T.noutputs, T.nstates
283
    (2, 5, 7)
284

285
    """
286
    # Allow anything with a feedback function to call that function
287
    # TODO: rewrite to allow __rfeedback__
288
    try:
9✔
289
        return sys1.feedback(sys2, sign, **kwargs)
9✔
290
    except (AttributeError, TypeError):
9✔
291
        pass
9✔
292

293
    # Check for correct input types
294
    if not isinstance(sys1, (int, float, complex, np.number, np.ndarray,
9✔
295
                             InputOutputSystem)):
296
        raise TypeError("sys1 must be an I/O system, scalar, or array")
9✔
297
    elif not isinstance(sys2, (int, float, complex, np.number, np.ndarray,
9✔
298
                               InputOutputSystem)):
299
        raise TypeError("sys2 must be an I/O system, scalar, or array")
9✔
300

301
    # If sys1 is a scalar or ndarray, use the type of sys2 to figure
302
    # out how to convert sys1, using transfer functions whenever possible.
303
    if isinstance(sys1, (int, float, complex, np.number, np.ndarray)):
9✔
304
        if isinstance(sys2, (int, float, complex, np.number, np.ndarray,
9✔
305
                             tf.TransferFunction)):
306
            sys1 = tf._convert_to_transfer_function(sys1)
9✔
307
        elif isinstance(sys2, frd.FrequencyResponseData):
9✔
308
            sys1 = frd._convert_to_frd(sys1, sys2.omega)
9✔
309
        else:
310
            sys1 = ss._convert_to_statespace(sys1)
9✔
311

312
    sys = sys1.feedback(sys2, sign)
9✔
313
    sys.update_names(**kwargs)
9✔
314
    return sys
9✔
315

316
def append(*sys, **kwargs):
9✔
317
    """append(sys1, sys2[, ..., sysn])
318

319
    Group LTI models by appending their inputs and outputs.
320

321
    Forms an augmented system model, and appends the inputs and
322
    outputs together.
323

324
    Parameters
325
    ----------
326
    sys1, sys2, ..., sysn : scalar, array, or `LTI`
327
        I/O systems to combine.
328

329
    Returns
330
    -------
331
    out : `LTI`
332
        Combined system, with input/output vectors consisting of all
333
        input/output vectors appended. Specific type returned is the type of
334
        the first argument.
335

336
    Other Parameters
337
    ----------------
338
    inputs, outputs : str, or list of str, optional
339
        List of strings that name the individual signals.  If not given,
340
        signal names will be of the form 's[i]' (where 's' is one of 'u',
341
        or 'y'). See `InputOutputSystem` for more information.
342
    states : str, or list of str, optional
343
        List of names for system states.  If not given, state names will be
344
        of of the form 'x[i]' for interconnections of linear systems or
345
        '<subsys_name>.<state_name>' for interconnected nonlinear systems.
346
    name : string, optional
347
        System name (used for specifying signals). If unspecified, a generic
348
        name 'sys[id]' is generated with a unique integer id.
349

350
    See Also
351
    --------
352
    interconnect, feedback, negate, parallel, series
353

354
    Examples
355
    --------
356
    >>> G1 = ct.rss(3)
357
    >>> G2 = ct.rss(4)
358
    >>> G = ct.append(G1, G2)
359
    >>> G.ninputs, G.noutputs, G.nstates
360
    (2, 2, 7)
361

362
    >>> G1 = ct.rss(3, inputs=2, outputs=4)
363
    >>> G2 = ct.rss(4, inputs=1, outputs=4)
364
    >>> G = ct.append(G1, G2)
365
    >>> G.ninputs, G.noutputs, G.nstates
366
    (3, 8, 7)
367

368
    """
369
    s1 = sys[0]
9✔
370
    for s in sys[1:]:
9✔
371
        s1 = s1.append(s)
9✔
372
    s1.update_names(**kwargs)
9✔
373
    return s1
9✔
374

375
def connect(sys, Q, inputv, outputv):
9✔
376
    """Index-based interconnection of an LTI system.
377

378
    .. deprecated:: 0.10.0
379
        `connect` will be removed in a future version of python-control.
380
        Use `interconnect` instead, which works with named signals.
381

382
    The system `sys` is a system typically constructed with `append`, with
383
    multiple inputs and outputs.  The inputs and outputs are connected
384
    according to the interconnection matrix `Q`, and then the final inputs and
385
    outputs are trimmed according to the inputs and outputs listed in `inputv`
386
    and `outputv`.
387

388
    NOTE: Inputs and outputs are indexed starting at 1 and negative values
389
    correspond to a negative feedback interconnection.
390

391
    Parameters
392
    ----------
393
    sys : `InputOutputSystem`
394
        System to be connected.
395
    Q : 2D array
396
        Interconnection matrix. First column gives the input to be connected.
397
        The second column gives the index of an output that is to be fed into
398
        that input. Each additional column gives the index of an additional
399
        input that may be optionally added to that input. Negative
400
        values mean the feedback is negative. A zero value is ignored. Inputs
401
        and outputs are indexed starting at 1 to communicate sign information.
402
    inputv : 1D array
403
        List of final external inputs, indexed starting at 1.
404
    outputv : 1D array
405
        List of final external outputs, indexed starting at 1.
406

407
    Returns
408
    -------
409
    out : `InputOutputSystem`
410
        Connected and trimmed I/O system.
411

412
    See Also
413
    --------
414
    append, feedback, interconnect, negate, parallel, series
415

416
    Notes
417
    -----
418
    The `interconnect` function allows the use of named signals and
419
    provides an alternative method for interconnecting multiple systems.
420

421
    Examples
422
    --------
423
    >>> G = ct.rss(7, inputs=2, outputs=2)
424
    >>> K = [[1, 2], [2, -1]]  # negative feedback interconnection
425
    >>> T = ct.connect(G, K, [2], [1, 2])
426
    >>> T.ninputs, T.noutputs, T.nstates
427
    (1, 2, 7)
428

429
    """
430
    # TODO: maintain `connect` for use in MATLAB submodule (?)
431
    warn("connect() is deprecated; use interconnect()", FutureWarning)
9✔
432

433
    inputv, outputv, Q = \
9✔
434
        np.atleast_1d(inputv), np.atleast_1d(outputv), np.atleast_1d(Q)
435
    # check indices
436
    index_errors = (inputv - 1 > sys.ninputs) | (inputv < 1)
9✔
437
    if np.any(index_errors):
9✔
438
        raise IndexError(
9✔
439
            "inputv index %s out of bounds" % inputv[np.where(index_errors)])
440
    index_errors = (outputv - 1 > sys.noutputs) | (outputv < 1)
9✔
441
    if np.any(index_errors):
9✔
442
        raise IndexError(
9✔
443
            "outputv index %s out of bounds" % outputv[np.where(index_errors)])
444
    index_errors = (Q[:,0:1] - 1 > sys.ninputs) | (Q[:,0:1] < 1)
9✔
445
    if np.any(index_errors):
9✔
446
        raise IndexError(
9✔
447
            "Q input index %s out of bounds" % Q[np.where(index_errors)])
448
    index_errors = (np.abs(Q[:,1:]) - 1 > sys.noutputs)
9✔
449
    if np.any(index_errors):
9✔
450
        raise IndexError(
9✔
451
            "Q output index %s out of bounds" % Q[np.where(index_errors)])
452

453
    # first connect
454
    K = np.zeros((sys.ninputs, sys.noutputs))
9✔
455
    for r in np.array(Q).astype(int):
9✔
456
        inp = r[0]-1
9✔
457
        for outp in r[1:]:
9✔
458
            if outp < 0:
9✔
459
                K[inp,-outp-1] = -1.
9✔
460
            elif outp > 0:
9✔
461
                K[inp,outp-1] = 1.
9✔
462
    sys = sys.feedback(np.array(K), sign=1)
9✔
463

464
    # now trim
465
    Ytrim = np.zeros((len(outputv), sys.noutputs))
9✔
466
    Utrim = np.zeros((sys.ninputs, len(inputv)))
9✔
467
    for i,u in enumerate(inputv):
9✔
468
        Utrim[u-1,i] = 1.
9✔
469
    for i,y in enumerate(outputv):
9✔
470
        Ytrim[i,y-1] = 1.
9✔
471

472
    return Ytrim * sys * Utrim
9✔
473

474
def combine_tf(tf_array, **kwargs):
9✔
475
    """Combine array of transfer functions into MIMO transfer function.
476

477
    Parameters
478
    ----------
479
    tf_array : list of list of `TransferFunction` or array_like
480
        Transfer matrix represented as a two-dimensional array or
481
        list-of-lists containing `TransferFunction` objects. The
482
        `TransferFunction` objects can have multiple outputs and inputs, as
483
        long as the dimensions are compatible.
484

485
    Returns
486
    -------
487
    `TransferFunction`
488
        Transfer matrix represented as a single MIMO `TransferFunction` object.
489

490
    Other Parameters
491
    ----------------
492
    inputs, outputs : str, or list of str, optional
493
        List of strings that name the individual signals.  If not given,
494
        signal names will be of the form 's[i]' (where 's' is one of 'u',
495
        or 'y'). See `InputOutputSystem` for more information.
496
    name : string, optional
497
        System name (used for specifying signals). If unspecified, a generic
498
        name 'sys[id]' is generated with a unique integer id.
499

500
    Raises
501
    ------
502
    ValueError
503
        If timebase of transfer functions do not match.
504
    ValueError
505
        If `tf_array` has incorrect dimensions.
506
    ValueError
507
        If the transfer functions in a row have mismatched output or input
508
        dimensions.
509

510
    Examples
511
    --------
512
    Combine two transfer functions:
513

514
    >>> s = ct.tf('s')
515
    >>> ct.combine_tf(
516
    ...     [[1 / (s + 1)],
517
    ...      [s / (s + 2)]],
518
    ...     name='G'
519
    ... )
520
    TransferFunction(
521
    [[array([1])],
522
     [array([1, 0])]],
523
    [[array([1, 1])],
524
     [array([1, 2])]],
525
    name='G', outputs=2, inputs=1)
526

527
    Combine NumPy arrays with transfer functions:
528

529
    >>> ct.combine_tf(
530
    ...     [[np.eye(2), np.zeros((2, 1))],
531
    ...      [np.zeros((1, 2)), ct.tf([1], [1, 0])]],
532
    ...     name='G'
533
    ... )
534
    TransferFunction(
535
    [[array([1.]), array([0.]), array([0.])],
536
     [array([0.]), array([1.]), array([0.])],
537
     [array([0.]), array([0.]), array([1])]],
538
    [[array([1.]), array([1.]), array([1.])],
539
     [array([1.]), array([1.]), array([1.])],
540
     [array([1.]), array([1.]), array([1, 0])]],
541
    name='G', outputs=3, inputs=3)
542

543
    """
544
    # Find common timebase or raise error
545
    dt_list = []
9✔
546
    try:
9✔
547
        for row in tf_array:
9✔
548
            for tfn in row:
9✔
549
                dt_list.append(getattr(tfn, "dt", None))
9✔
550
    except OSError:
9✔
551
        raise ValueError("`tf_array` has too few dimensions.")
9✔
552
    dt_set = set(dt_list)
9✔
553
    dt_set.discard(None)
9✔
554
    if len(dt_set) > 1:
9✔
555
        raise ValueError("Time steps of transfer functions are "
9✔
556
                         f"mismatched: {dt_set}")
557
    elif len(dt_set) == 0:
9✔
558
        dt = None
9✔
559
    else:
560
        dt = dt_set.pop()
9✔
561
    # Convert all entries to transfer function objects
562
    ensured_tf_array = []
9✔
563
    for row in tf_array:
9✔
564
        ensured_row = []
9✔
565
        for tfn in row:
9✔
566
            ensured_row.append(_ensure_tf(tfn, dt))
9✔
567
        ensured_tf_array.append(ensured_row)
9✔
568
    # Iterate over
569
    num = []
9✔
570
    den = []
9✔
571
    for row_index, row in enumerate(ensured_tf_array):
9✔
572
        for j_out in range(row[0].noutputs):
9✔
573
            num_row = []
9✔
574
            den_row = []
9✔
575
            for col in row:
9✔
576
                if col.noutputs != row[0].noutputs:
9✔
577
                    raise ValueError(
9✔
578
                        "Mismatched number of transfer function outputs in "
579
                        f"row {row_index}."
580
                    )
581
                for j_in in range(col.ninputs):
9✔
582
                    num_row.append(col.num_array[j_out, j_in])
9✔
583
                    den_row.append(col.den_array[j_out, j_in])
9✔
584
            num.append(num_row)
9✔
585
            den.append(den_row)
9✔
586
    for row_index, row in enumerate(num):
9✔
587
        if len(row) != len(num[0]):
9✔
588
            raise ValueError(
9✔
589
                "Mismatched number transfer function inputs in row "
590
                f"{row_index} of numerator."
591
            )
592
    for row_index, row in enumerate(den):
9✔
593
        if len(row) != len(den[0]):
9✔
594
            raise ValueError(
×
595
                "Mismatched number transfer function inputs in row "
596
                f"{row_index} of denominator."
597
            )
598
    return tf.TransferFunction(num, den, dt=dt, **kwargs)
9✔
599

600

601

602
def split_tf(transfer_function):
9✔
603
    """Split MIMO transfer function into SISO transfer functions.
604

605
    System and signal names for the array of SISO transfer functions are
606
    copied from the MIMO system.
607

608
    Parameters
609
    ----------
610
    transfer_function : `TransferFunction`
611
        MIMO transfer function to split.
612

613
    Returns
614
    -------
615
    ndarray
616
        NumPy array of SISO transfer functions.
617

618
    Examples
619
    --------
620
    Split a MIMO transfer function:
621

622
    >>> G = ct.tf(
623
    ...     [ [[87.8], [-86.4]],
624
    ...       [[108.2], [-109.6]] ],
625
    ...     [ [[1, 1], [1, 1]],
626
    ...       [[1, 1], [1, 1]],   ],
627
    ...     name='G'
628
    ... )
629
    >>> ct.split_tf(G)
630
    array([[TransferFunction(
631
            array([87.8]),
632
            array([1, 1]),
633
            name='G', outputs=1, inputs=1), TransferFunction(
634
                                            array([-86.4]),
635
                                            array([1, 1]),
636
                                            name='G', outputs=1, inputs=1)],
637
           [TransferFunction(
638
            array([108.2]),
639
            array([1, 1]),
640
            name='G', outputs=1, inputs=1), TransferFunction(
641
                                            array([-109.6]),
642
                                            array([1, 1]),
643
                                            name='G', outputs=1, inputs=1)]],
644
          dtype=object)
645

646
    """
647
    tf_split_lst = []
9✔
648
    for i_out in range(transfer_function.noutputs):
9✔
649
        row = []
9✔
650
        for i_in in range(transfer_function.ninputs):
9✔
651
            row.append(
9✔
652
                tf.TransferFunction(
653
                    transfer_function.num_array[i_out, i_in],
654
                    transfer_function.den_array[i_out, i_in],
655
                    dt=transfer_function.dt,
656
                    inputs=transfer_function.input_labels[i_in],
657
                    outputs=transfer_function.output_labels[i_out],
658
                    name=transfer_function.name
659
                )
660
            )
661
        tf_split_lst.append(row)
9✔
662
    return np.array(tf_split_lst, dtype=object)
9✔
663

664
def _ensure_tf(arraylike_or_tf, dt=None):
9✔
665
    """Convert an array_like to a transfer function.
666

667
    Parameters
668
    ----------
669
    arraylike_or_tf : `TransferFunction` or array_like
670
        Array-like or transfer function.
671
    dt : None, True or float, optional
672
        System timebase. 0 (default) indicates continuous time, True
673
        indicates discrete time with unspecified sampling time, positive
674
        number is discrete time with specified sampling time, None
675
        indicates unspecified timebase (either continuous or discrete
676
        time). If None, timebase is not validated.
677

678
    Returns
679
    -------
680
    `TransferFunction`
681
        Transfer function.
682

683
    Raises
684
    ------
685
    ValueError
686
        If input cannot be converted to a transfer function.
687
    ValueError
688
        If the timebases do not match.
689

690
    """
691
    # If the input is already a transfer function, return it right away
692
    if isinstance(arraylike_or_tf, tf.TransferFunction):
9✔
693
        # If timebases don't match, raise an exception
694
        if (dt is not None) and (arraylike_or_tf.dt != dt):
9✔
695
            raise ValueError(
9✔
696
                f"`arraylike_or_tf.dt={arraylike_or_tf.dt}` does not match "
697
                f"argument `dt={dt}`."
698
            )
699
        return arraylike_or_tf
9✔
700
    if np.ndim(arraylike_or_tf) > 2:
9✔
701
        raise ValueError(
9✔
702
            "Array-like must have less than two dimensions to be converted "
703
            "into a transfer function."
704
        )
705
    # If it's not, then convert it to a transfer function
706
    arraylike_3d = np.atleast_3d(arraylike_or_tf)
9✔
707
    try:
9✔
708
        tfn = tf.TransferFunction(
9✔
709
            arraylike_3d,
710
            np.ones_like(arraylike_3d),
711
            dt,
712
        )
713
    except TypeError:
9✔
714
        raise ValueError(
9✔
715
            "`arraylike_or_tf` must only contain array_likes or transfer "
716
            "functions."
717
        )
718
    return tfn
9✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc