code: def validate_pd(
      causal_graph: DirectedGraph,
      data: pd.DataFrame,
      p_values_memory: Optional[_PValuesMemory] = None,
      n_pairs: int = -1,
      independence_test: Callable[
          [np.ndarray, np.ndarray], float
      ] = kernel_based,
      significance_level: float = 0.05,
      adjacent_only: bool = False,
      n_jobs: Optional[int] = None,
  ) -> Dict[str, Union[int, Dict[tuple, float]]]:
      """
      Validate pairwise dependencies (pd) for a given causal graph and data. Test for each node if it is statistically
      dependent of all its ancestors.
  
      :param causal_graph: A directed acyclic graph (DAG).
      :param data: Observations of variables in the DAG.
      :param p_values_memory: _PValuesMemory object, where results of previously performed tests are stored.
      :param n_pairs: Evaluate dependencies for n_pairs <= all pairs in the DAG. If n_pairs=-1, evaluate dependencies for
          all (ancestor, node) pairs (default).
      :param independence_test: Independence test to use for checking pairwise dependencies.
      :param significance_level: Significance level for independence tests.
      :param adjacent_only: Only test adjacent node pairs.
      :param n_jobs: Number of jobs to use for parallel execution of (conditional) independence tests.
      :return: Summary dict: {n_violations: int, n_tests: int, p_values: {(ancestor, node): float, ...}}
      """
      n_jobs = config.default_n_jobs if n_jobs is None else n_jobs
  
      if p_values_memory is None:
          p_values_memory = _PValuesMemory()
  
      pairs = [
          (ancestor, node)
          for node in causal_graph.nodes
          for ancestor in nx.ancestors(causal_graph, node)
      ]
      if adjacent_only:
          pairs = [
              (ancestor, node)
              for (ancestor, node) in pairs
              if ancestor
              in get_ordered_predecessors(causal_graph, node)
          ]
  
      if n_pairs < 0:
          n_pairs = len(pairs)
  
      if n_pairs > len(pairs):
          raise ValueError(
              f"n_pairs ({n_pairs}) > number of pairs in the DAG ({len(pairs)})"
          )
  
      validation_summary = {
          FalsifyConst.METHOD: FalsifyConst.VALIDATE_PD,
          FalsifyConst.N_VIOLATIONS: 0,
          FalsifyConst.N_TESTS: n_pairs,
          FalsifyConst.P_VALUES: dict(),
      }
      pair_idxs = np.random.choice(
          len(pairs), size=n_pairs, replace=False
      )
  
      # Find out which tests to do
      to_test = []
      for pair_idx in pair_idxs:
          ancestor, node = pairs[pair_idx]
          if not (ancestor, node) in p_values_memory:
              to_test.append((ancestor, node))
              p_values_memory.add_p_value(
                  None, ancestor, node
              )  # Placeholder
  
      # Parallelize over tests
      random_seeds = np.random.randint(
          np.iinfo(np.int32).max, size=len(to_test)
      )
      p_values = Parallel(n_jobs=n_jobs)(
          delayed(_compute_p_value)(
              data=data,
              X=ancestor,
              Y=node,
              Z=None,
              independence_test=independence_test,
              conditional_independence_test=None,
              seed=int(seed),
          )
          for (ancestor, node), seed in zip(to_test, random_seeds)
      )
  
      # Gather results
      for i, (ancestor, node) in enumerate(to_test):
          p_values_memory.add_p_value(p_values[i], ancestor, node)
  
      # Summarize
      for pair_idx in pair_idxs:
          ancestor, node = pairs[pair_idx]
          p_value = p_values_memory.get_p_value(ancestor, node)
          if p_value is not None:
              validation_summary[FalsifyConst.P_VALUES][
                  (node, ancestor)
              ] = (p_value, p_value > significance_level)
              if p_value > significance_level:
                  validation_summary[FalsifyConst.N_VIOLATIONS] += 1
  
      return validation_summary
  
imports: import warnings
  from dataclasses import dataclass, field
  from enum import Enum, auto
  from functools import partial
  from inspect import getfullargspec
  from itertools import permutations
  from typing import Any, Callable, Dict, FrozenSet, List, Optional, Set, Tuple, Union
  import matplotlib.colors
  import matplotlib.pyplot
  import networkx
  import numpy
  import pandas
  from joblib import Parallel, delayed
  from tqdm import tqdm
  import dowhy.gcm.config
  from dowhy.gcm.independence_test import kernel_based
  from dowhy.gcm.util import plot
  from dowhy.gcm.util.general import set_random_seed
  from dowhy.graph import DirectedGraph, get_ordered_predecessors
label_desc: Use image node_id 3 for calling a global function with example usage: validate_pd(causal_graph, data, p_values_memory, n_pairs, independence_test, significance_level, adjacent_only, n_jobs) and returns: validation_summary