class_docstr: Seasonality Layer that uses automatically determines the seasonality of your data. Can be used directly on
  any model that implements `SeasonalityModel` class. The algorithmic idea is from the
  `theta method <https://github.com/Mcompetitions/M4-methods/blob/master/4Theta%20method.R>`__. We find a set of
  multiple candidate seasonalites, and we return the best one(s) based on the `PeriodicityStrategy`.
docstr: Helper method to detect the seasonality of a time series.
  
  :param x: The numpy array of values whose seasonality we want to detect. Must be univariate & flattened.
  :param periodicity_strategy: Strategy to choose the seasonality if multiple candidates are detected.
  :param pval: p-value for deciding whether a detected seasonality is statistically significant.
  :param max_lag: max lag considered for seasonality detection.
docstr_tok: ["Helper","method","to","detect","the","seasonality","of","a","time","series",".",":","param","x",":","The","numpy","array","of","values","whose","seasonality","we","want","to","detect",".","Must","be","univariate","&","flattened",".",":","param","periodicity_strategy",":","Strategy","to","choose","the","seasonality","if","multiple","candidates","are","detected",".",":","param","pval",":","p-value","for","deciding","whether","a","detected","seasonality","is","statistically","significant",".",":","param","max_lag",":","max","lag","considered","for","seasonality","detection","."]
code: def detect_seasonality(
      x: np.array,
      max_lag: int = None,
      pval: float = 0.05,
      periodicity_strategy: PeriodicityStrategy = PeriodicityStrategy.ACF,
  ) -> List[int]:
      """
      Helper method to detect the seasonality of a time series.
  
      :param x: The numpy array of values whose seasonality we want to detect. Must be univariate & flattened.
      :param periodicity_strategy: Strategy to choose the seasonality if multiple candidates are detected.
      :param pval: p-value for deciding whether a detected seasonality is statistically significant.
      :param max_lag: max lag considered for seasonality detection.
      """
      # Use the residuals of an ETS model fit to the data, to handle any trend. This makes the ACF more robust.
      # For each candidate seasonality, the ACF we assign is the higher of the raw ACF and residual ACF.
      candidate2score = {}
      y = (
          x
          if len(x) < 10
          else ETSModel(x, error="add", trend="add")
          .fit(disp=False)
          .resid
      )
      for x in [x] if x is y else [x, y]:
          # compute max lag & ACF function
          max_lag = (
              max(min(int(10 * np.log10(len(x))), len(x) - 1), 40)
              if max_lag is None
              else max_lag
          )
          xacf = sm.tsa.acf(x, nlags=max_lag, fft=False)
          xacf[np.isnan(xacf)] = 0
  
          # select the local maximum points with acf > 0, and smaller than 1/2 the length of the time series
          xacf = xacf[: np.ceil(len(x) / 2).astype(int)]
          candidates = np.intersect1d(
              np.where(xacf > 0), argrelmax(xacf)[0]
          )
  
          if len(candidates) > 0:
              # filter out potential harmonics by applying peak-finding on the peaks of the ACF
              if len(candidates) > 1:
                  candidates_idx = []
                  if xacf[candidates[0]] > xacf[candidates[1]]:
                      candidates_idx += [0]
                  candidates_idx += argrelmax(xacf[candidates])[
                      0
                  ].tolist()
                  if xacf[candidates[-1]] > xacf[candidates[-2]]:
                      candidates_idx += [-1]
                  candidates = candidates[candidates_idx]
  
              # statistical test if ACF is significant with respect to a normal distribution
              xacf = xacf[1:]
              xacf_var = np.cumsum(
                  np.concatenate(([1], 2 * xacf[:-1] ** 2))
              ) / len(x)
              z_scores = xacf / np.sqrt(xacf_var)
              candidates = candidates[
                  z_scores[candidates - 1] > norm.ppf(1 - pval / 2)
              ]
              for c in candidates.tolist():
                  candidate2score[c] = max(
                      candidate2score.get(c, -np.inf), z_scores[c - 1]
                  )
  
      # sort the candidates by z-score and choose the desired candidates based on periodicity strategy
      candidates = sorted(
          candidate2score.keys(),
          key=lambda c: candidate2score[c],
          reverse=True,
      )
      for c in candidates:
          logger.info(
              f"Detected seas = {c:3d} with z-score = {candidate2score[c]:5.2f}."
          )
      if len(candidates) == 0:
          candidates = [1]
      if periodicity_strategy is PeriodicityStrategy.ACF:
          candidates = [candidates[0]]
      elif periodicity_strategy is PeriodicityStrategy.Min:
          candidates = [min(candidates)]
      elif periodicity_strategy is PeriodicityStrategy.Max:
          candidates = [max(candidates)]
      elif periodicity_strategy is PeriodicityStrategy.All:
          candidates = candidates
      else:
          raise ValueError(
              f"Periodicity strategy {periodicity_strategy} not supported."
          )
      return candidates
  
imports: from abc import abstractmethod
  from enum import Enum, auto
  import logging
  from typing import Any, Iterator, List, Optional, Tuple, Union
  import numpy
  from scipy.signal import argrelmax
  from scipy.stats import norm
  import statsmodels.api
  from statsmodels.tsa.exponential_smoothing.ets import ETSModel
  from merlion.models.automl.base import AutoMLMixIn
  from merlion.models.base import ModelBase
  from merlion.models.layers import LayeredModelConfig
  from merlion.transform.resample import TemporalResample
  from merlion.utils import TimeSeries, UnivariateTimeSeries
  from merlion.utils.misc import AutodocABCMeta
label_desc: Use image node_id 7 for calling the SeasonalityLayer obj's underlying member method code with example usage: obj.detect_seasonality(x, max_lag, pval, periodicity_strategy) and returns: candidates