|
44 | 44 | import numpy as np
|
45 | 45 | import warnings
|
46 | 46 | from .exception import ControlSlycot, ControlMIMONotImplemented, \
|
47 |
| - ControlDimension |
| 47 | + ControlArgument, ControlDimension |
48 | 48 | from .iosys import isdtime, isctime
|
49 | 49 | from .statesp import StateSpace
|
50 | 50 | from .statefbk import gram
|
| 51 | +from .timeresp import TimeResponseData |
51 | 52 |
|
52 |
| -__all__ = ['hsvd', 'balred', 'modred', 'era', 'markov', 'minreal'] |
| 53 | +__all__ = ['hsvd', 'balred', 'modred', 'era', 'markov', 'minreal', 'okid'] |
53 | 54 |
|
54 | 55 |
|
55 | 56 | # Hankel Singular Value Decomposition
|
@@ -556,3 +557,193 @@ def markov(Y, U, m=None, transpose=False):
|
556 | 557 |
|
557 | 558 | # Return the first m Markov parameters
|
558 | 559 | return H if transpose else np.transpose(H)
|
| 560 | + |
| 561 | +def okid(*args, m=None, transpose=False, dt=True, truncate=False): |
| 562 | + """okid(Y, U, [, m]) |
| 563 | +
|
| 564 | + Calculate the first `m+1` Markov parameters [D CB CAB ...] |
| 565 | + from data. |
| 566 | +
|
| 567 | + This function computes the Markov parameters for a discrete time system |
| 568 | +
|
| 569 | + .. math:: |
| 570 | +
|
| 571 | + x[k+1] &= A x[k] + B u[k] \\\\ |
| 572 | + y[k] &= C x[k] + D u[k] |
| 573 | +
|
| 574 | + given data for u and y. The algorithm assumes that that C A^k B = 0 for |
| 575 | + k > m-2 (see [1]_). Note that the problem is ill-posed if the length of |
| 576 | + the input data is less than the desired number of Markov parameters (a |
| 577 | + warning message is generated in this case). |
| 578 | +
|
| 579 | + The function can be called with either 1, 2 or 3 arguments: |
| 580 | +
|
| 581 | + * ``H = okid(data)`` |
| 582 | + * ``H = okid(data, m)`` |
| 583 | + * ``H = okid(Y, U)`` |
| 584 | + * ``H = okid(Y, U, m)`` |
| 585 | +
|
| 586 | + where `data` is an `TimeResponseData` object, and `Y`, `U`, are 1D or 2D |
| 587 | + array and m is an integer. |
| 588 | +
|
| 589 | + Parameters |
| 590 | + ---------- |
| 591 | + Y : array_like |
| 592 | + Output data. If the array is 1D, the system is assumed to be single |
| 593 | + input. If the array is 2D and transpose=False, the columns of `Y` |
| 594 | + are taken as time points, otherwise the rows of `Y` are taken as |
| 595 | + time points. |
| 596 | + U : array_like |
| 597 | + Input data, arranged in the same way as `Y`. |
| 598 | + data : TimeResponseData |
| 599 | + Response data from which the Markov parameters where estimated. |
| 600 | + Input and output data must be 1D or 2D array. |
| 601 | + m : int, optional |
| 602 | + Number of Markov parameters to output. Defaults to len(U). |
| 603 | + dt : True of float, optional |
| 604 | + True indicates discrete time with unspecified sampling time and a |
| 605 | + positive float is discrete time with the specified sampling time. |
| 606 | + It can be used to scale the Markov parameters in order to match |
| 607 | + the unit-area impulse response of python-control. Default is True |
| 608 | + truncate : bool, optional |
| 609 | + Do not use first m equation for least least squares. Default is False. |
| 610 | + transpose : bool, optional |
| 611 | + Assume that input data is transposed relative to the standard |
| 612 | + :ref:`time-series-convention`. For TimeResponseData this parameter |
| 613 | + is ignored. Default is False. |
| 614 | +
|
| 615 | + Returns |
| 616 | + ------- |
| 617 | + H : ndarray |
| 618 | + First m Markov parameters, [D CB CAB ...]. |
| 619 | +
|
| 620 | + References |
| 621 | + ---------- |
| 622 | + .. [1] J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, |
| 623 | + Identification of observer/Kalman filter Markov parameters - Theory |
| 624 | + and experiments. Journal of Guidance Control and Dynamics, 16(2), |
| 625 | + 320-329, 2012. http://doi.org/10.2514/3.21006 |
| 626 | +
|
| 627 | + Examples |
| 628 | + -------- |
| 629 | + >>> T = np.linspace(0, 10, 100) |
| 630 | + >>> U = np.ones((1, 100)) |
| 631 | + >>> T, Y = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U) |
| 632 | + >>> H = ct.okid(Y, U, 3, transpose=False) |
| 633 | +
|
| 634 | + """ |
| 635 | + # Convert input parameters to 2D arrays (if they aren't already) |
| 636 | + |
| 637 | + # Get the system description |
| 638 | + if (len(args) < 1): |
| 639 | + raise ControlArgument("not enough input arguments") |
| 640 | + |
| 641 | + if isinstance(args[0], TimeResponseData): |
| 642 | + Umat = np.array(args[0].inputs, ndmin=2) |
| 643 | + Ymat = np.array(args[0].outputs, ndmin=2) |
| 644 | + transpose = args[0].transpose |
| 645 | + if args[0].transpose and not args[0].issiso: |
| 646 | + Umat, Ymat = np.transpose(Umat), np.transpose(Ymat) |
| 647 | + if (len(args) == 2): |
| 648 | + m = args[1] |
| 649 | + elif (len(args) > 2): |
| 650 | + raise ControlArgument("too many positional arguments") |
| 651 | + else: |
| 652 | + if (len(args) < 2): |
| 653 | + raise ControlArgument("not enough input arguments") |
| 654 | + Umat = np.array(args[1], ndmin=2) |
| 655 | + Ymat = np.array(args[0], ndmin=2) |
| 656 | + if transpose: |
| 657 | + Umat, Ymat = np.transpose(Umat), np.transpose(Ymat) |
| 658 | + if (len(args) == 3): |
| 659 | + m = args[2] |
| 660 | + elif (len(args) > 3): |
| 661 | + raise ControlArgument("too many positional arguments") |
| 662 | + |
| 663 | + # Make sure the number of time points match |
| 664 | + if Umat.shape[1] != Ymat.shape[1]: |
| 665 | + raise ControlDimension( |
| 666 | + "Input and output data are of differnent lengths") |
| 667 | + l = Umat.shape[1] |
| 668 | + |
| 669 | + # If number of desired parameters was not given, set to size of input data |
| 670 | + if m is None: |
| 671 | + m = l |
| 672 | + |
| 673 | + t = 0 |
| 674 | + if truncate: |
| 675 | + t = m |
| 676 | + |
| 677 | + q = Ymat.shape[0] # number of outputs |
| 678 | + p = Umat.shape[0] # number of inputs |
| 679 | + |
| 680 | + # Make sure there is enough data to compute parameters |
| 681 | + if m*p > (l-t): |
| 682 | + warnings.warn("Not enough data for requested number of parameters") |
| 683 | + |
| 684 | + # the algorithm - Construct a matrix of control inputs to invert |
| 685 | + # |
| 686 | + # (q,l) = (q,(p+q)*m+p) @ ((p+q)*m+p,l) |
| 687 | + # YY.T = Ybar @ VV.T |
| 688 | + # |
| 689 | + # This algorithm sets up the following problem and solves it for |
| 690 | + # the Markov parameters |
| 691 | + # |
| 692 | + # (l,q) = (l,(p+q)*m+p) @ ((p+q)*m+p,q) |
| 693 | + # YY = VV @ Ybar.T |
| 694 | + # |
| 695 | + # truncated version t=m, do not use first m equation |
| 696 | + # |
| 697 | + # Note: This algorithm assumes C A^{j} B = 0 |
| 698 | + # for j > m-2. See equation (3) in |
| 699 | + # |
| 700 | + # J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification |
| 701 | + # of observer/Kalman filter Markov parameters - Theory and |
| 702 | + # experiments. Journal of Guidance Control and Dynamics, 16(2), |
| 703 | + # 320-329, 2012. http://doi.org/10.2514/3.21006 |
| 704 | + # |
| 705 | + |
| 706 | + Vmat = np.concatenate((Umat, Ymat),axis=0) |
| 707 | + |
| 708 | + VVT = np.zeros(((p + q)*m + p, l)) |
| 709 | + |
| 710 | + VVT[:p,:] = Umat |
| 711 | + for i in range(m): |
| 712 | + # Shift previous column down and keep zeros at the top |
| 713 | + VVT[(p+q)*i+p:(p+q)*(i+1)+p, i+1:] = Vmat[:, :l-i-1] |
| 714 | + |
| 715 | + YY = Ymat[:,t:].T |
| 716 | + VV = VVT[:,t:].T |
| 717 | + |
| 718 | + # Solve for the observer Markov parameters from YY = VV @ Ybar.T |
| 719 | + YbarT, _, _, _ = np.linalg.lstsq(VV, YY, rcond=None) |
| 720 | + Ybar = YbarT.T |
| 721 | + |
| 722 | + # Equation 11, Page 9 |
| 723 | + D = Ybar[:,:p] |
| 724 | + |
| 725 | + Ybar_r = Ybar[:,p:].reshape(q,m,p+q) # output, time*v_input -> output, time, v_input |
| 726 | + Ybar_r = Ybar_r.transpose(0,2,1) # output, v_input, time |
| 727 | + |
| 728 | + Ybar1 = Ybar_r[:,:p,:] # select observer Markov parameters generated by input |
| 729 | + Ybar2 = Ybar_r[:,p:,:] # select observer Markov parameters generated by output |
| 730 | + |
| 731 | + # Using recursive substitution to solve for Markov parameters |
| 732 | + Y = np.zeros((q,p,m)) |
| 733 | + # Equation 12, Page 9 |
| 734 | + Y[:,:,0] = Ybar1[:,:,0] + Ybar2[:,:,0]@D |
| 735 | + |
| 736 | + # Equation 15, Page 10 |
| 737 | + for k in range(1,m): |
| 738 | + Y[:,:,k] = Ybar1[:,:,k] + Ybar2[:,:,k]@D |
| 739 | + for i in range(k-1): |
| 740 | + Y[:,:,k] += Ybar2[:,:,i]@Y[:,:,k-i-1] |
| 741 | + |
| 742 | + # Equation 11, Page 9 |
| 743 | + H = np.zeros((q,p,m+1)) |
| 744 | + H[:,:,0] = D |
| 745 | + H[:,:,1:] = Y[:,:,:] |
| 746 | + H = H/dt # scaling |
| 747 | + |
| 748 | + # Return the first m Markov parameters |
| 749 | + return H if not transpose else np.transpose(H) |
0 commit comments