code: def _parse_tree_data(model, input_bounds):
      """
      This function creates the data structures with the information required
      for creation of the variables, sets, and constraints in the pyomo
      reformulation of the linear model decision trees. Note that these data
      structures are attributes of the LinearTreeDefinition Class.
  
      Arguments:
          model -- Trained linear-tree model or dic containing linear-tree model
              summary (e.g. dict = model.summary())
  
      Returns:
          leaves - Dict containing the following information for each leaf:
              1) 'slope' - The slope of the fitted line at that leaf
              2) 'intercept' - The intercept of the line at that lead
              3) 'parent' - The parent split or node of that leaf
          splits - Dict containing the following information for each split:
              1) 'children' - The child nodes of that split
              2) 'col' - The variable(feature) to split on (beginning at 0)
              3) 'left_leaves' - All the leaves to the left of that split
              4) 'right_leaves' - All the leaves to the right of that split
              5) 'parent' - The parent node of the split. Node zero has no parent
              6) 'th' - The threshold of the split
              7) 'y_index' - Indices corresponding to Mistry et. al. y binary
                      variable
          vars_dict - Dict of tree inputs and their respective thresholds
  
      Raises:
              Exception: If input dict is not equal to model.summary()
              Exception: If input model is not a dict or linear-tree instance
      """
      # Create the initial leaves and splits dictionaries depending on the
      # instance of the model (can be either a LinearTreeRegressor or dict).
      # Include checks to ensure that the input dict is the model summary which
      # is obtained by calling the summary() method contained within the
      # linear-tree package (e.g. dict = model.summary())
      if (
          isinstance(model, lineartree.lineartree.LinearTreeRegressor)
          is True
      ):
          leaves = model.summary(only_leaves=True)
          splits = model.summary()
      elif isinstance(model, dict) is True:
          splits = model
          leaves = {}
          num_splits_in_model = 0
          count = 0
          # Checks to ensure that the input nested dictionary contains the
          # correct information
          for entry in model:
              if "children" not in model[entry].keys():
                  leaves[entry] = model[entry]
              else:
                  left_child = model[entry]["children"][0]
                  right_child = model[entry]["children"][1]
                  num_splits_in_model += 1
                  if (
                      left_child not in model.keys()
                      or right_child not in model.keys()
                  ):
                      count += 1
          if count > 0 or num_splits_in_model == 0:
              raise ValueError(
                  "Input dict must be the summary of the linear-tree model"
                  + " e.g. dict = model.summary()"
              )
      else:
          raise TypeError(
              "Model entry must be dict or linear-tree instance"
          )
  
      # This loop adds keys for the slopes and intercept and removes the leaf
      # keys in the splits dictionary
      for leaf in leaves:
          del splits[leaf]
          leaves[leaf]["slope"] = list(leaves[leaf]["models"].coef_)
          leaves[leaf]["intercept"] = leaves[leaf]["models"].intercept_
  
      # This loop creates an parent node id entry for each node in the tree
      for split in splits:
          left_child = splits[split]["children"][0]
          right_child = splits[split]["children"][1]
  
          if left_child in splits:
              splits[left_child]["parent"] = split
          else:
              leaves[left_child]["parent"] = split
  
          if right_child in splits:
              splits[right_child]["parent"] = split
          else:
              leaves[right_child]["parent"] = split
  
      # This loop creates an entry for the all the leaves to the left and right
      # of a split
      for split in splits:
          left_child = splits[split]["children"][0]
          right_child = splits[split]["children"][1]
  
          if left_child in splits:
              splits[split]["left_leaves"] = _find_all_children_leaves(
                  left_child, splits, leaves
              )
          else:
              splits[split]["left_leaves"] = [left_child]
  
          if right_child in splits:
              splits[split]["right_leaves"] = _find_all_children_leaves(
                  right_child, splits, leaves
              )
          else:
              splits[split]["right_leaves"] = [right_child]
  
      # For each variable that appears in the tree, go through all the splits
      # and record its splitting threshold
      splitting_thresholds = {}
      for split in splits:
          var = splits[split]["col"]
          splitting_thresholds[var] = {}
      for split in splits:
          var = splits[split]["col"]
          splitting_thresholds[var][split] = splits[split]["th"]
  
      # Make sure every nested dictionary in the splitting_thresholds dictionary
      # is sorted by value
      for var in splitting_thresholds:
          splitting_thresholds[var] = dict(
              sorted(
                  splitting_thresholds[var].items(), key=lambda x: x[1]
              )
          )
  
      # NOTE: Can eliminate if not implementing the Mistry et. al. formulations
      # Record the ordered indices of the binary variable y. The first index
      # is the splitting variable. The second index is its location in the
      # ordered dictionary of thresholds for that variable.
      for split in splits:
          var = splits[split]["col"]
          splits[split]["y_index"] = []
          splits[split]["y_index"].append(var)
          splits[split]["y_index"].append(
              list(splitting_thresholds[var]).index(split)
          )
  
      # For each leaf, create an empty dictionary that will store the lower
      # and upper bounds of each feature.
      for leaf in leaves:
          leaves[leaf]["bounds"] = {}
  
      leaf_ids = np.array(list(leaves.keys()))
      features = np.arange(0, len(leaves[leaf_ids[0]]["slope"]))
  
      # For each feature in each leaf, initialize lower and upper bounds to None
      for feat in features:
          for leaf in leaves:
              leaves[leaf]["bounds"][feat] = [None, None]
  
      # Finally, go through each split and assign it's threshold value as the
      # upper bound to all the leaves descending to the left of the split and
      # as the lower bound to all the leaves descending to the right.
      for split in splits:
          var = splits[split]["col"]
          for leaf in splits[split]["left_leaves"]:
              leaves[leaf]["bounds"][var][1] = splits[split]["th"]
  
          for leaf in splits[split]["right_leaves"]:
              leaves[leaf]["bounds"][var][0] = splits[split]["th"]
  
      leaves = _reassign_none_bounds(leaves, input_bounds)
  
      # We use the same formulations developed for gradient boosted linear trees
      # so we nest the leaves, splits, and thresholds attributes in a "one-tree"
      # tree.
      leaves = {0: leaves}
      splits = {0: splits}
      splitting_thresholds = {0: splitting_thresholds}
  
      return splits, leaves, splitting_thresholds
  
file_path: OMLT/src/omlt/linear_tree/lt_definition.py
imports: import numpy
  import lineartree
label_desc: Use image node_id 5 for calling a global function with example usage: _parse_tree_data(model, input_bounds) and returns: splits,  leaves,  splitting_thresholds