2222from .timeresp import TimeResponseData
2323
2424__all__ = ['hankel_singular_values' , 'balanced_reduction' , 'model_reduction' ,
25- 'minimal_realization' , 'eigensys_realization' , 'markov' , 'hsvd' ,
26- 'balred' , 'modred' , 'minreal' , 'era' ]
25+ 'minimal_realization' , 'eigensys_realization' ,
26+ 'observer_kalman_identification' , 'markov' , 'hsvd' , 'balred' ,
27+ 'modred' , 'minreal' , 'era' , 'okid' ]
2728
2829
2930# Hankel Singular Value Decomposition
@@ -724,9 +725,232 @@ def markov(*args, m=None, transpose=False, dt=None, truncate=False):
724725 # Return the first m Markov parameters
725726 return H if not transpose else np .transpose (H )
726727
728+ def observer_kalman_identification (* args , m = None , transpose = False , dt = True , truncate = False ):
729+ """observer_kalman_identification(Y, U, [, m])
730+
731+ Calculate the first `m` Markov parameters [D CB CAB ...]
732+ from data.
733+
734+ This function computes the Markov parameters for a discrete time system
735+
736+ .. math::
737+
738+ x[k+1] &= A x[k] + B u[k] \\ \\
739+ y[k] &= C x[k] + D u[k]
740+
741+ given data for u and y. The algorithm assumes that that C A^k B = 0 for
742+ k > m-2 (see [1]_, [2]_). Note that the problem is ill-posed if the length of
743+ the input data is less than the desired number of Markov parameters (a
744+ warning message is generated in this case).
745+
746+ The function can be called with either 1, 2 or 3 arguments:
747+
748+ * ``H = observer_kalman_identification(data)``
749+ * ``H = observer_kalman_identification(data, m)``
750+ * ``H = observer_kalman_identification(Y, U)``
751+ * ``H = observer_kalman_identification(Y, U, m)``
752+
753+ where `data` is an `TimeResponseData` object, and `Y`, `U`, are 1D or 2D
754+ array and m is an integer.
755+
756+ Parameters
757+ ----------
758+ Y : array_like
759+ Output data. If the array is 1D, the system is assumed to be
760+ single input. If the array is 2D and transpose=False, the columns
761+ of `Y` are taken as time points, otherwise the rows of `Y` are
762+ taken as time points.
763+ U : array_like
764+ Input data, arranged in the same way as `Y`.
765+ data : TimeResponseData
766+ Response data from which the Markov parameters where estimated.
767+ Input and output data must be 1D or 2D array.
768+ m : int, optional
769+ Number of Markov parameters to output. Defaults to len(U).
770+ dt : True of float, optional
771+ True indicates discrete time with unspecified sampling time and a
772+ positive float is discrete time with the specified sampling time.
773+ It can be used to scale the Markov parameters in order to match
774+ the unit-area impulse response of python-control. Default is True
775+ for array_like and dt=data.time[1]-data.time[0] for
776+ TimeResponseData as input.
777+ truncate : bool, optional
778+ Do not use first m equation for least least squares. Default is False.
779+ transpose : bool, optional
780+ Assume that input data is transposed relative to the standard
781+ :ref:`time-series-convention`. For TimeResponseData this parameter
782+ is ignored. Default is False.
783+
784+ Returns
785+ -------
786+ H : ndarray
787+ First m Markov parameters, [D CB CAB ...].
788+
789+ See Also
790+ --------
791+ markov
792+
793+ Notes
794+ -----
795+ The :func:`~control.markov` command estimates the Markov parameters directly, which can be hard for slightly damped systems.
796+ The :func:`~control.observer_kalman_identification` command uses a Kalman filter, which is better suited for slightly damped systems.
797+
798+ References
799+ ----------
800+ .. [1] J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman,
801+ Identification of observer/Kalman filter Markov parameters - Theory
802+ and experiments. Journal of Guidance Control and Dynamics, 16(2),
803+ 320-329, 2012. http://doi.org/10.2514/3.21006
804+
805+ .. [2] J.-N. Juang, Applied System Identification, 1994
806+
807+ Examples
808+ --------
809+ >>> T = np.linspace(0, 10, 100)
810+ >>> U = np.ones((1, 100))
811+ >>> T, Y = ct.forced_response(ct.tf([1], [1, 0.5], True), T, U)
812+ >>> H = ct.okid(Y, U, 3, transpose=False)
813+
814+ """
815+ # Convert input parameters to 2D arrays (if they aren't already)
816+
817+ # Get the system description
818+ if len (args ) < 1 :
819+ raise ControlArgument ("not enough input arguments" )
820+
821+ if isinstance (args [0 ], TimeResponseData ):
822+ data = args [0 ]
823+ Umat = np .array (data .inputs , ndmin = 2 )
824+ Ymat = np .array (data .outputs , ndmin = 2 )
825+ if dt is None :
826+ dt = data .time [1 ] - data .time [0 ]
827+ if not np .allclose (np .diff (data .time ), dt ):
828+ raise ValueError ("response time values must be equally "
829+ "spaced." )
830+ transpose = data .transpose
831+ if data .transpose and not data .issiso :
832+ Umat , Ymat = np .transpose (Umat ), np .transpose (Ymat )
833+ if len (args ) == 2 :
834+ m = args [1 ]
835+ elif len (args ) > 2 :
836+ raise ControlArgument ("too many positional arguments" )
837+ else :
838+ if len (args ) < 2 :
839+ raise ControlArgument ("not enough input arguments" )
840+ Umat = np .array (args [1 ], ndmin = 2 )
841+ Ymat = np .array (args [0 ], ndmin = 2 )
842+ if dt is None :
843+ dt = True
844+ if transpose :
845+ Umat , Ymat = np .transpose (Umat ), np .transpose (Ymat )
846+ if len (args ) == 3 :
847+ m = args [2 ]
848+ elif len (args ) > 3 :
849+ raise ControlArgument ("too many positional arguments" )
850+
851+ # Make sure the number of time points match
852+ if Umat .shape [1 ] != Ymat .shape [1 ]:
853+ raise ControlDimension (
854+ "Input and output data are of differnent lengths" )
855+ l = Umat .shape [1 ]
856+
857+ # If number of desired parameters was not given, set to size of input data
858+ if m is None :
859+ m = l
860+
861+ # Paper equation 8, Page 8
862+ # There is a mistake in the paper, but it is right in the book
863+ t = 0
864+ if truncate :
865+ t = m
866+
867+ # The okid in the paper estimates `m + 1` Markov parameters
868+ # Change to `m` to match control.markov,
869+ # TODO:What is the best way to match control.markov
870+ # m = m - 1
871+
872+ q = Ymat .shape [0 ] # number of outputs
873+ p = Umat .shape [0 ] # number of inputs
874+
875+ # Make sure there is enough data to compute parameters
876+ if m * p > (l - t ):
877+ warnings .warn ("Not enough data for requested number of parameters" )
878+
879+ # the algorithm - Construct a matrix of control virtual inputs to invert
880+ #
881+ # (q,l) = (q,(p+q)*m+p) @ ((p+q)*m+p,l)
882+ # YY.T = Ybar @ VV.T
883+ #
884+ # This algorithm sets up the following problem and solves it for
885+ # the observer Markov parameters
886+ #
887+ # (l,q) = (l,(p+q)*m+p) @ ((p+q)*m+p,q)
888+ # YY = VV @ Ybar.T
889+ #
890+ # truncated version t=m, do not use first m equation
891+ #
892+ # Note: This algorithm assumes C A^{j} B = 0
893+ # for j > m-2. See equation (3) in
894+ #
895+ # J.-N. Juang, M. Phan, L. G. Horta, and R. W. Longman, Identification
896+ # of observer/Kalman filter Markov parameters - Theory and
897+ # experiments. Journal of Guidance Control and Dynamics, 16(2),
898+ # 320-329, 2012. http://doi.org/10.2514/3.21006
899+ #
900+
901+ Vmat = np .concatenate ((Umat , Ymat ),axis = 0 )
902+
903+ VVT = np .zeros (((p + q )* m + p , l ))
904+
905+ VVT [:p , :] = Umat
906+ for i in range (m ):
907+ # Shift previous column down and keep zeros at the top
908+ VVT [(p + q )* i + p :(p + q )* (i + 1 )+ p , i + 1 :] = Vmat [:, :l - i - 1 ]
909+
910+ YY = Ymat [:, t :].T
911+ VV = VVT [:, t :].T
912+
913+ # Solve for the observer Markov parameters from YY = VV @ Ybar.T
914+ YbarT , _ , _ , _ = np .linalg .lstsq (VV , YY , rcond = None )
915+ Ybar = YbarT .T
916+
917+ # Paper equation 11, Page 9
918+ D = Ybar [:, :p ]
919+
920+ Ybar_r = Ybar [:, p :].reshape (q , m , p + q ) # output, time*v_input -> output, time, v_input
921+ Ybar_r = Ybar_r .transpose (0 , 2 , 1 ) # output, v_input, time
922+
923+ Ybar1 = Ybar_r [:, :p , :] # select observer Markov parameters generated by input
924+ Ybar2 = Ybar_r [:, p :, :] # select observer Markov parameters generated by output
925+
926+ # Using recursive substitution to solve for Markov parameters
927+ Y = np .zeros ((q , p , m ))
928+ # Paper equation 12, Page 9
929+ Y [:, :, 0 ] = Ybar1 [:, :, 0 ] + Ybar2 [:, :, 0 ] @ D
930+
931+ # Paper equation 15, Page 10
932+ for k in range (1 , m ):
933+ Y [:, :, k ] = Ybar1 [:, :, k ] + Ybar2 [:, :, k ] @ D
934+ for i in range (k - 1 ):
935+ Y [:, :, k ] += Ybar2 [:, :, i ] @ Y [:, :, k - i - 1 ]
936+
937+ # Paper equation 11, Page 9
938+ H = np .zeros ((q , p , m + 1 ))
939+ H [:, :, 0 ] = D
940+ H [:, :, 1 :] = Y [:, :, :]
941+ H = H / dt # scaling
942+
943+ # for siso return a 1D array instead of a 3D array
944+ if q == 1 and p == 1 :
945+ H = np .squeeze (H )
946+
947+ # Return the first m Markov parameters
948+ return H if not transpose else np .transpose (H )
949+
727950# Function aliases
728951hsvd = hankel_singular_values
729952balred = balanced_reduction
730953modred = model_reduction
731954minreal = minimal_realization
732955era = eigensys_realization
956+ okid = observer_kalman_identification
0 commit comments