code: def estimate_kl_divergence_continuous_knn(
      X: np.ndarray,
      Y: np.ndarray,
      k: int = 1,
      remove_common_elements: bool = True,
      n_jobs: int = 1,
  ) -> float:
      """Estimates KL-Divergence using k-nearest neighbours (Wang et al., 2009).
  
      While, in theory, this handles multidimensional inputs, consider using estimate_kl_divergence_continuous_clf
      for data with more than one dimension.
  
      Q. Wang, S. R. Kulkarni, and S. Verdú,
      "Divergence estimation for multidimensional densities via k-nearest-neighbor distances",
      IEEE Transactions on Information Theory, vol. 55, no. 5, pp. 2392-2405, May 2009.
  
      :param X: (N_1,D) Sample drawn from distribution P_X
      :param Y: (N_2,D) Sample drawn from distribution P_Y
      :param k: Number of neighbors to consider.
      :param remove_common_elements: If true, common values in X and Y are removed. This would otherwise lead to
                                     a KNN distance of zero for these values if k is set to 1, which would cause a
                                     division by zero error.
      :param n_jobs: Number of parallel jobs used for the nearest neighbors model. -1 means it uses all available cores.
                     Note that in most applications, parallelizing this rather introduces more overhead, leading to a
                     slower runtime.
      return: Estimated value of D(P_X||P_Y).
      """
      X, Y = shape_into_2d(X, Y)
  
      if X.shape[1] != Y.shape[1]:
          raise RuntimeError(
              "Samples from X and Y need to have the same dimension, but X has dimension %d and Y has "
              "dimension %d." % (X.shape[1], Y.shape[1])
          )
  
      X = X.astype(np.float64)
      Y = Y.astype(np.float64)
  
      # Making sure that X and Y have no overlapping values, which would lead to a distance of 0 with k=1 and, thus, to
      # a division by zero.
      if remove_common_elements:
          X = setdiff2d(X, Y, assume_unique=True)
          if X.shape[0] < k + 1:
              # All elements are equal (or at least less than k samples are different)
              return 0
  
      n, m = X.shape[0], Y.shape[0]
      if n == 0:
          return 0
  
      d = float(X.shape[1])
  
      x_neighbourhood = NearestNeighbors(
          n_neighbors=k + 1, n_jobs=n_jobs
      ).fit(X)
      y_neighbourhood = NearestNeighbors(
          n_neighbors=k, n_jobs=n_jobs
      ).fit(Y)
  
      distances_x, _ = x_neighbourhood.kneighbors(X, n_neighbors=k + 1)
      distances_y, _ = y_neighbourhood.kneighbors(X, n_neighbors=k)
  
      rho = distances_x[:, -1]
      nu = distances_y[:, -1]
  
      result = np.sum((d / n) * np.log(nu / rho)) + np.log(m / (n - 1))
  
      if ~np.isfinite(result):
          raise RuntimeError(
              "Got a non-finite KL divergence! This can happen if both data sets have overlapping "
              "elements. Since these are normally removed by this method, double check whether the arrays "
              "are numeric."
          )
  
      if result < 0:
          result = 0
  
      return result
  
file_path: dowhy/dowhy/gcm/divergence.py
imports: from functools import partial
  from typing import Callable, Union
  import numpy
  from scipy.stats import entropy
  from sklearn.model_selection import KFold
  from sklearn.neighbors import NearestNeighbors
  from dowhy.gcm.auto import AssignmentQuality, select_model
  from dowhy.gcm.constant import EPS
  from dowhy.gcm.ml.classification import ClassificationModel, create_logistic_regression_classifier
  from dowhy.gcm.util.general import has_categorical, is_categorical, setdiff2d, shape_into_2d
label_desc: Use image node_id 2 for calling a global function with example usage: estimate_kl_divergence_continuous_knn(X, Y, k, remove_common_elements, n_jobs) and returns: result, int, int