code: def _learn_structure(
      X,
      sample_weight=None,
      algorithm="chow-liu",
      include_parents=None,
      exclude_parents=None,
      max_parents=None,
      pseudocount=0,
      penalty=None,
      root=0,
  ):
      """Learn the structure of a Bayesian network using data.
  
      This function will take in data, an algorithm, and parameters for that
      algorithm, and will return the learned structure. Currently supported
      algorithms are:
  
              - 'chow-liu': Learn a maximal spanning tree that is the most likely
                      tree given the data and weights.
              - 'exact': A dynamic programming solution to the exact BNSL task that
                      reduces the time from super-exponential to simply-exponential.
  
  
      Parameters
      ----------
      X: torch.tensor, shape=(n, d)
              The data to fit the structure too, where each row is a sample and
              each column corresponds to the associated variable.
  
      sample_weight: torch.tensor, shape=(n,)
              The weight of each sample as a positive double. If None, all items are
              weighted equally.
  
      algorithm: str
              The name of the algorithm to use for structure learning. Must be one
              of 'chow-liu' or 'exact'.
  
      include_parents: list or None
              A list of tuples where each inner tuple is made up of integers
              indicating the parents that must exist for that variable. For example,
              when passing in [(1,), (), ()], the first variable must have the second
              variable as a parent, and potentially others if learned, and the others
              do not have any parents that must be present. If None, no parents are
              forced. Default is None.
  
      exclude_parents: list or None
              A list of tuples where each inner tuple is made up of integers
              indicating the parents that cannot exist for that variable. For example,
              when passing in [(1,), (), ()], the first variable cannot have the
              second variable as a parent, and the other variables have no
              restrictions on it. If None, no parents are excluded. Default is None.
  
      max_parents: int or None
              The maximum number of parents a variable can have. If used, this means
              using the k-learn procedure. Can drastically speed up algorithms.
              If None, no max on parents. Default is None.
  
      pseudocount: double
              A pseudocount to add to each count. Default is 0.
  
      penalty: float or None, optional
              The weighting of the model complexity term in the objective function.
              Increasing this value will encourage sparsity whereas setting the value
              to 0 will result in an unregularized structure. Default is
              log2(|D|) / 2 where |D| is the sum of the weights of the data.
  
      root: int
              When using a tree-based algorithm, like Chow-Liu, sets the variable
              that is the root of the tree.
  
  
      Returns
      -------
      structure: tuple, shape=(d,)
              A tuple of tuples, where each inner tuple represents a dimension in
              the data and contains the parents of that dimension.
      """
  
      X = _check_parameter(
          _cast_as_tensor(X),
          "X",
          min_value=0,
          ndim=2,
          dtypes=(torch.int32, torch.int64),
      )
      sample_weight = _check_parameter(
          _cast_as_tensor(sample_weight), "X", min_value=0, ndim=1
      )
  
      if sample_weight is None:
          sample_weight = torch.ones(
              X.shape[0], dtype=torch.float32, device=X.device
          )
  
      if algorithm == "chow-liu":
          structure = _categorical_chow_liu(
              X,
              sample_weight=sample_weight,
              pseudocount=pseudocount,
              root=root,
          )
      elif algorithm == "exact":
          structure = _categorical_exact(
              X,
              sample_weight=sample_weight,
              include_parents=include_parents,
              exclude_parents=exclude_parents,
              max_parents=max_parents,
              pseudocount=pseudocount,
          )
  
      return structure
  
imports: import time
  import numpy
  import torch
  import itertools
  import networkx
  from ._utils import _cast_as_tensor
  from ._utils import _cast_as_parameter
  from ._utils import _update_parameter
  from ._utils import _check_parameter
  from ._utils import _reshape_weights
  from .distributions._distribution import Distribution
  from .distributions import Categorical
  from .distributions import JointCategorical
  from .distributions import ConditionalCategorical
  from .factor_graph import FactorGraph
label_desc: Use image node_id 2 for calling a global function with example usage: _learn_structure(X, sample_weight, algorithm, include_parents, exclude_parents, max_parents, pseudocount, penalty, root) and returns: structure