code: def _hkt(eivals, timescales, normalization, normalized_laplacian):
      
      nv = eivals.shape[0]
      hkt = np.zeros(timescales.shape)
      for idx, t in enumerate(timescales):
          hkt[idx] = np.sum(np.exp(-t * eivals))
      if isinstance(normalization, np.ndarray):
          return hkt / normalization
      if normalization == 'empty' or normalization == True:
          return hkt / nv
      if normalization == 'complete':
          if normalized_laplacian:
              return hkt / (1 + (nv - 1) * np.exp(-timescales))
          else:
              return hkt / (1 + nv * np.exp(-nv * timescales))
      return hkt
docs: Computes heat kernel trace from given eigenvalues, timescales, and normalization.
  
      For precise definition, please refer to "NetLSD: Hearing the Shape of a Graph" by A. Tsitsulin, D. Mottin, P. Karras, A. Bronstein, E. Müller. Published at KDD'18.
      
      Parameters
      ----------
      eivals : numpy.ndarray
          Eigenvalue vector
      timescales : numpy.ndarray
          Vector of discrete timesteps for the kernel computation
      normalization : str or numpy.ndarray
          Either 'empty', 'complete' or None.
          If None or any ther value, return unnormalized heat kernel trace.
          For the details how 'empty' and 'complete' are computed, please refer to the paper.
          If np.ndarray, they are treated as exact normalization constants
      normalized_laplacian: bool
          Defines whether the eigenvalues came from the normalized Laplacian. It only affects 'complete' normalization.
  
      Returns
      -------
      numpy.ndarray
          Heat kernel trace signature