22from pydmd .bopdmd import BOPDMD
33from .utils import compute_rank , compute_svd
44import copy
5- from sklearn .cluster import KMeans
5+ from sklearn .cluster import MiniBatchKMeans
66from sklearn .metrics import silhouette_score
77import matplotlib .pyplot as plt
88import xarray as xr
@@ -258,7 +258,6 @@ def build_windows(data, window_length, step_size, integer_windows=False):
258258 :type integer_windows: bool
259259 :return:
260260 """
261-
262261 if integer_windows :
263262 n_split = np .floor (data .shape [1 ] / window_length ).astype (int )
264263 else :
@@ -329,11 +328,9 @@ def _build_initizialization(self):
329328 :return: First guess of eigenvalues
330329 :rtype: numpy.ndarray or None
331330 """
332- # If not initial values are provided return None by default.
333- init_alpha = None
334331 # User provided initial eigenvalues.
335332 if self ._initialize_artificially and self ._init_alpha is not None :
336- init_alpha = self ._init_alpha
333+ return self ._init_alpha
337334 # Initial eigenvalue guesses from kmeans clustering.
338335 elif (
339336 self ._initialize_artificially
@@ -347,6 +344,7 @@ def _build_initizialization(self):
347344 init_alpha = init_alpha * np .tile (
348345 [1 , - 1 ], int (self ._svd_rank / self ._n_components )
349346 )
347+ return init_alpha
350348 # The user accidentally provided both methods of initializing the eigenvalues.
351349 elif (
352350 self ._initialize_artificially
@@ -356,8 +354,9 @@ def _build_initizialization(self):
356354 raise ValueError (
357355 "Only one of `init_alpha` and `cluster_centroids` can be provided"
358356 )
359-
360- return init_alpha
357+ # If not initial values are provided return None by default.
358+ else :
359+ return None
361360
362361 def fit (
363362 self ,
@@ -516,7 +515,9 @@ def fit(
516515 self ._amplitudes_array [
517516 k , : optdmd .eigs .shape [0 ]
518517 ] = optdmd .amplitudes
519- self ._window_means_array [k ] = c .flatten ()
518+ self ._window_means_array [k ] = np .mean (
519+ data_window , 1 , keepdims = True
520+ ).flatten ()
520521 self ._time_array [k ] = original_time_window
521522
522523 # Reset optdmd between iterations
@@ -549,16 +550,14 @@ def get_window_indices(self, k):
549550 if k == self ._n_slides - 1 and self ._non_integer_n_slide :
550551 return slice (- self ._window_length , None )
551552 else :
552- return slice (
553- sample_start , sample_start + self ._window_length
554- )
553+ return slice (sample_start , sample_start + self ._window_length )
555554
556555 def cluster_omega (
557556 self ,
558557 n_components ,
559558 kmeans_kwargs = None ,
560559 transform_method = None ,
561- method = "KMeans" ,
560+ method = MiniBatchKMeans ,
562561 ):
563562 """Clusters fitted eigenvalues into frequency bands by the imaginary component.
564563
@@ -567,35 +566,33 @@ def cluster_omega(
567566 :type method: str
568567 :param n_components: Hyperparameter for k-means clustering, number of clusters.
569568 :type n_components: int
570- :param kmeans_kwargs: Arguments for KMeans clustering.
569+ :param kmeans_kwargs: Arguments for KMeans clustering. The default is
570+ random_state = 0.
571571 :type kmeans_kwargs: dict
572572 :param transform_method: How to transform omega. See docstring for valid options.
573573 :type transform_method: str or NoneType
574574 :return:
575575 """
576576 # Reshape the omega array into a 1d array
577- omega_array = self .omega_array
578- n_slides = omega_array .shape [0 ]
579- svd_rank = omega_array .shape [1 ]
580- omega_rshp = omega_array .reshape (n_slides * svd_rank )
577+ n_slides = self .omega_array .shape [0 ]
578+ svd_rank = self .omega_array .shape [1 ]
579+ omega_rshp = self .omega_array .reshape (n_slides * svd_rank )
581580 omega_transform = self .transform_omega (
582581 omega_rshp , transform_method = transform_method
583582 )
584583
585584 if kmeans_kwargs is None :
585+ kmeans_kwargs = {}
586586 random_state = 0
587- kmeans_kwargs = {
588- "random_state" : random_state ,
589- }
590- if method == "KMeans" :
591- clustering = KMeans (n_clusters = n_components , ** kmeans_kwargs )
592- elif method == "KMediods" :
593- from sklearn_extra .cluster import KMedoids
594-
595- clustering = KMedoids (n_clusters = n_components , ** kmeans_kwargs )
596- else :
587+ kmeans_kwargs ["random_state" ] = kmeans_kwargs .get (
588+ "random_state" , random_state
589+ )
590+ clustering = method (n_clusters = n_components , ** kmeans_kwargs )
591+ if not hasattr (clustering , "fit_predict" ) and callable (
592+ getattr (clustering , "fit_predict" )
593+ ):
597594 raise ValueError (
598- "Unrecognized clustering method {}." . format ( method )
595+ "Clustering method must have `fit_predict()` method."
599596 )
600597
601598 omega_classes = clustering .fit_predict (np .atleast_2d (omega_transform ).T )
@@ -615,9 +612,7 @@ def cluster_omega(
615612 self ._transform_method = transform_method
616613 self ._n_components = n_components
617614
618- return self
619-
620- def transform_omega (self , omega_array , transform_method = None ):
615+ def transform_omega (self , omega_array , transform_method = "absolute" ):
621616 """Transform omega, primarily for clustering.
622617 Options for transforming omega are:
623618 "period": :math:`\\ frac{1}{\\ omega}`
@@ -633,18 +628,14 @@ def transform_omega(self, omega_array, transform_method=None):
633628 :rtype: numpy.ndarray
634629 """
635630 # Apply a transformation to omega to improve frequency band separation
636- if transform_method is None or transform_method == "absolute" :
631+ if transform_method == "absolute" :
637632 omega_transform = np .abs (omega_array .imag .astype ("float" ))
638633 self ._omega_label = r"$|\omega|$"
639634 self ._hist_kwargs = {"bins" : 64 }
640635 # Outstanding question: should this be the complex conjugate or
641636 # the imaginary component squared?
642637 elif transform_method == "square_frequencies" :
643- # omega_transform = (np.conj(omega_array) * omega_array).real.astype(
644- # "float"
645- # )
646638 omega_transform = (omega_array .imag ** 2 ).real .astype ("float" )
647-
648639 self ._omega_label = r"$|\omega|^{2}$"
649640 self ._hist_kwargs = {"bins" : 64 }
650641 elif transform_method == "log10" :
@@ -654,17 +645,9 @@ def transform_omega(self, omega_array, transform_method=None):
654645 omega_transform [~ np .isfinite (omega_transform )] = zero_imputer
655646 self ._omega_label = r"$log_{10}(|\omega|)$"
656647 self ._hist_kwargs = {"bins" : 64 }
657- # {
658- # "bins": np.linspace(
659- # np.min(np.log10(omega_transform[omega_transform > 0])),
660- # np.max(np.log10(omega_transform[omega_transform > 0])),
661- # 64,
662- # )
663- # }
664648 elif transform_method == "period" :
665649 omega_transform = 1 / np .abs (omega_array .imag .astype ("float" ))
666650 self ._omega_label = "Period"
667- # @ToDo: Specify bins like in log10 transform
668651 self ._hist_kwargs = {"bins" : 64 }
669652 else :
670653 raise ValueError (
@@ -716,7 +699,7 @@ def cluster_hyperparameter_sweep(
716699 )
717700
718701 for nind , n in enumerate (n_components_range ):
719- _ = self .cluster_omega (
702+ self .cluster_omega (
720703 n_components = n , transform_method = transform_method
721704 )
722705
@@ -872,11 +855,12 @@ def scale_reconstruction(
872855 (self ._n_components , self ._n_data_vars , self ._window_length )
873856 )
874857 for j in np .unique (self ._omega_classes ):
858+ class_index = classification == j
875859 xr_sep_window [j ] = np .linalg .multi_dot (
876860 [
877- w [:, classification == j ],
878- np .diag (b [classification == j ]),
879- np .exp (omega [classification == j ] * t ),
861+ w [:, class_index ],
862+ np .diag (b [class_index ]),
863+ np .exp (omega [class_index ] * t ),
880864 ]
881865 ).real
882866
@@ -1222,7 +1206,6 @@ def plot_time_series(
12221206 scale_reconstruction_kwargs = {}
12231207 xr_sep = self .scale_reconstruction (** scale_reconstruction_kwargs )
12241208
1225- # ToDo: Make these kwargs adjustable inputs.
12261209 fig , axes = plt .subplots (
12271210 nrows = self .n_components + 2 ,
12281211 sharex = True ,
0 commit comments