1717__email__ = "murray@cds.caltech.edu"
1818
1919import numpy as np
20+ import scipy as sp
21+ from math import sqrt
2022
2123from .iosys import InputOutputSystem , NonlinearIOSystem
2224from .lti import LTI , isctime , isdtime
2325from .mateqn import care , dare , _check_shape
2426from .statesp import StateSpace , _ssmatrix
2527from .exception import ControlArgument , ControlNotImplemented
2628
27- __all__ = ['lqe' ,'dlqe' , 'create_estimator_iosystem' ]
29+ __all__ = ['lqe' ,'dlqe' , 'create_estimator_iosystem' , 'white_noise' ,
30+ 'correlation' ]
2831
2932
3033# contributed by Sawyer B. Fuller <minster@uw.edu>
@@ -409,18 +412,18 @@ def create_estimator_iosystem(
409412
410413 else :
411414 # Create an I/O system for the state feedback gains
415+ # Note: reshape various vectors into column vectors for legacy matrix
412416 def _estim_update (t , x , u , params ):
413417 # See if we are estimating or predicting
414418 correct = params .get ('correct' , True )
415419
416- # Get the state of the estimator
417- x = np .array (x ) # bug fix for python-control 0.9.1
418- xhat = x [0 :sys .nstates ]
420+ # Get the state of the estimator
421+ xhat = x [0 :sys .nstates ].reshape (- 1 , 1 )
419422 P = x [sys .nstates :].reshape (sys .nstates , sys .nstates )
420423
421424 # Extract the inputs to the estimator
422- y = u [0 :C .shape [0 ]]
423- u = u [C .shape [0 ]:]
425+ y = u [0 :C .shape [0 ]]. reshape ( - 1 , 1 )
426+ u = u [C .shape [0 ]:]. reshape ( - 1 , 1 )
424427
425428 # Compute the optimal again
426429 Reps_inv = np .linalg .inv (RN + C @ P @ C .T )
@@ -437,7 +440,7 @@ def _estim_update(t, x, u, params):
437440 dP -= A @ P @ C .T @ Reps_inv @ C @ P @ A .T
438441
439442 # Return the update
440- return np .hstack ([dxhat , dP .reshape (- 1 )])
443+ return np .hstack ([dxhat . reshape ( - 1 ) , dP .reshape (- 1 )])
441444
442445 def _estim_output (t , x , u , params ):
443446 return x [0 :sys .nstates ]
@@ -447,3 +450,79 @@ def _estim_output(t, x, u, params):
447450 _estim_update , _estim_output , states = state_labels + covariance_labels ,
448451 inputs = sys .output_labels + sys .input_labels , outputs = output_labels ,
449452 dt = sys .dt )
453+
454+
455+ def white_noise (T , Q , dt = 0 ):
456+ """Generate a white noise signal with specified intensity.
457+
458+ This function generates a (multi-variable) white noise signal of
459+ specified intensity as either a sampled continous time signal or a
460+ discrete time signal. A white noise signal along a 1D array
461+ of linearly spaced set of times T can be computing using
462+
463+ V = ct.white_noise(T, Q, dt)
464+
465+ where Q is a positive definite matrix providing the noise intensity.
466+
467+ In continuous time, the white noise signal is scaled such that the
468+ integral of the covariance over a sample period is Q, thus approximating
469+ a white noise signal. In discrete time, the white noise signal has
470+ covariance Q at each point in time (without any scaling based on the
471+ sample time).
472+
473+ """
474+ # Check the shape of the input arguments
475+ if len (T .shape ) != 1 :
476+ raise ValueError ("Time vector T must be 1D" )
477+ if len (Q .shape ) != 2 or Q .shape [0 ] != Q .shape [1 ]:
478+ raise ValueError ("Covariance matrix Q must be square" )
479+
480+ # Figure out the time increment
481+ if dt != 0 :
482+ # Discrete time system => white noise is not scaled
483+ dt = 1
484+ else :
485+ dt = T [1 ] - T [0 ]
486+
487+ # Make sure data points are equally spaced
488+ if not np .allclose (np .diff (T ), T [1 ] - T [0 ]):
489+ raise ValueError ("Time values must be equally spaced." )
490+
491+ # Generate independent white noise sources for each input
492+ W = np .array ([
493+ np .random .normal (0 , 1 / sqrt (dt ), T .size ) for i in range (Q .shape [0 ])])
494+
495+ # Return a linear combination of the noise sources
496+ return sp .linalg .sqrtm (Q ) @ W
497+
498+ def correlation (T , X , Y = None , dt = 0 , squeeze = True ):
499+ T = np .atleast_1d (T )
500+ X = np .atleast_2d (X )
501+ Y = np .atleast_2d (Y ) if Y is not None else X
502+
503+ # Check the shape of the input arguments
504+ if len (T .shape ) != 1 :
505+ raise ValueError ("Time vector T must be 1D" )
506+ if len (X .shape ) != 2 or len (Y .shape ) != 2 :
507+ raise ValueError ("Signals X and Y must be 2D arrays" )
508+ if T .shape [0 ] != X .shape [1 ] or T .shape [0 ] != Y .shape [1 ]:
509+ raise ValueError ("Signals X and Y must have same length as T" )
510+
511+ # Figure out the time increment
512+ if dt != 0 :
513+ raise NotImplementedError ("Discrete time systems not yet supported" )
514+ else :
515+ dt = T [1 ] - T [0 ]
516+
517+ # Make sure data points are equally spaced
518+ if not np .allclose (np .diff (T ), T [1 ] - T [0 ]):
519+ raise ValueError ("Time values must be equally spaced." )
520+
521+ # Compute the correlation matrix
522+ R = np .array (
523+ [[sp .signal .correlate (X [i ], Y [j ])
524+ for i in range (X .shape [0 ])] for j in range (Y .shape [0 ])]
525+ ) * dt / (T [- 1 ] - T [0 ])
526+ tau = sp .signal .correlation_lags (len (X [0 ]), len (Y [0 ])) * dt
527+
528+ return tau , R .squeeze () if squeeze else R
0 commit comments