@@ -729,7 +729,7 @@ public ExpectationMaximizationResult ExpectationMaximization(
729729 scalarType : _scalarType ,
730730 device : _device ) ;
731731
732- // Compute log likelihood (avoid creating intermediate tensors)
732+ // Compute log likelihood
733733 var llSumDouble = filteredState . LogLikelihood . sum ( )
734734 . to_type ( ScalarType . Float64 ) . item < double > ( ) ;
735735 var filteredLogLikelihoodSum = logLikelihoodConst + 0.5 * llSumDouble ;
@@ -766,7 +766,7 @@ public ExpectationMaximizationResult ExpectationMaximization(
766766 var S11 = smoothedState . S11 . sum ( [ 0 ] ) ;
767767 var S10 = smoothedState . S10 . sum ( [ 0 ] ) ;
768768
769- // Replace einsum with faster matmul
769+ // Compute cross-correlation between observations and smoothed states
770770 var crossCorrelationObservations = observationT . matmul ( smoothedState . SmoothedMean ) ;
771771
772772 // Update parameters
@@ -949,18 +949,19 @@ public static StochasticSubspaceIdentificationResult StochasticSubspaceIdentific
949949
950950 var timeBins = observations . size ( 0 ) ;
951951 var numObs = observations . size ( 1 ) ;
952+ var centered = observations - observations . mean ( [ 0 ] , keepdim : true ) ;
952953
953954 // Build Hankel matrices from observations
954955 var numCols = ( int ) ( timeBins - 2 * maxLag + 1 ) ;
955956
956957 if ( numCols <= 0 )
957958 throw new ArgumentException ( $ "Number of time bins ({ timeBins } ) must be greater than 2*maxLag ({ 2 * maxLag } ) for subspace identification.") ;
958959
959- var stride = observations . stride ( ) ;
960- var pastView = observations . as_strided ( [ maxLag , numCols , numObs ] , [ stride [ 0 ] , stride [ 0 ] , stride [ 1 ] ] ) ;
960+ var stride = centered . stride ( ) ;
961+ var pastView = centered . as_strided ( [ maxLag , numCols , numObs ] , [ stride [ 0 ] , stride [ 0 ] , stride [ 1 ] ] ) ;
961962 var past = pastView . permute ( 0 , 2 , 1 ) . reshape ( maxLag * numObs , numCols ) ;
962963
963- var futureView = observations . narrow ( 0 , maxLag , timeBins - maxLag )
964+ var futureView = centered . narrow ( 0 , maxLag , timeBins - maxLag )
964965 . as_strided ( [ maxLag , numCols , numObs ] , [ stride [ 0 ] , stride [ 0 ] , stride [ 1 ] ] ) ;
965966 var future = futureView . permute ( 0 , 2 , 1 ) . reshape ( maxLag * numObs , numCols ) ;
966967
@@ -973,8 +974,7 @@ public static StochasticSubspaceIdentificationResult StochasticSubspaceIdentific
973974
974975 // Compute the effective rank
975976 var effectiveRank = ( S > ( threshold * S [ 0 ] ) ) . to_type ( ScalarType . Int64 ) . sum ( ) . item < long > ( ) ;
976-
977- var effectiveStates = Math . Min ( effectiveRank , targetNumStates ?? effectiveRank ) ;
977+ var effectiveStates = Math . Max ( Math . Min ( effectiveRank , targetNumStates ?? effectiveRank ) , 1 ) ;
978978
979979 var Ur = U [ TensorIndex . Colon , TensorIndex . Slice ( 0 , effectiveStates ) ] ;
980980 var SrSqrt = S [ TensorIndex . Slice ( 0 , effectiveStates ) ] . diag ( ) . sqrt ( ) ;
@@ -999,13 +999,13 @@ public static StochasticSubspaceIdentificationResult StochasticSubspaceIdentific
999999
10001000 // Estimate noise covariances using residuals
10011001 var stateResiduals = statesNext - transitionMatrix . matmul ( statesShifted ) ;
1002- var processNoiseCovariance = WrappedTensorDisposeScope ( ( ) => stateResiduals . matmul ( stateResiduals . mT ) / ( numCols - 1 ) ) ;
1002+ var processNoiseCovariance = WrappedTensorDisposeScope ( ( ) => EnsureSymmetric ( stateResiduals . matmul ( stateResiduals . mT ) / ( numCols - 1 ) ) ) ;
10031003
10041004 // Compute the observation residuals
10051005 var observationPredictions = measurementFunction . matmul ( states ) ;
1006- var observationWindow = observations [ TensorIndex . Slice ( maxLag , maxLag + numCols ) ] . mT ;
1006+ var observationWindow = centered [ TensorIndex . Slice ( maxLag , maxLag + numCols ) ] . mT ;
10071007 var observationResiduals = observationWindow - observationPredictions ;
1008- var measurementNoiseCovariance = WrappedTensorDisposeScope ( ( ) => observationResiduals . matmul ( observationResiduals . mT ) / numCols ) ;
1008+ var measurementNoiseCovariance = WrappedTensorDisposeScope ( ( ) => EnsureSymmetric ( observationResiduals . matmul ( observationResiduals . mT ) / numCols ) ) ;
10091009
10101010 // Initial state estimates
10111011 var initialMean = states [ TensorIndex . Colon , 0 ] ;
0 commit comments