docs: def mi_chain_rule(X, y):
      '''
      Decompose the information between all X and y according to the chain rule and return all the terms in the chain rule.
  
      Inputs:
      -------
          X:          iterable of iterables. You should be able to compute [mi(x, y) for x in X]
  
          y:          iterable of symbols
  
      output:
      -------
          ndarray:    terms of chaing rule
  
      Implemenation notes:
          I(X; y) = I(x0, x1, ..., xn; y)
                  = I(x0; y) + I(x1;y | x0) + I(x2; y | x0, x1) + ... + I(xn; y | x0, x1, ..., xn-1)
      '''
  
      # allocate ndarray output
      chain = np.zeros(len(X))
  
      # first term in the expansion is not a conditional information, but the information between the first x and y
      chain[0] = mi(X[0], y)
  
      for i in range(1, len(X)):
          chain[i] = cond_mi(X[i], y, X[:i])
  
      return chain