code: def grad(self, X, *params):
          
          # Establish a few dimensions
          N = X.shape[0]
          D = self.get_dim(X)
          endinds = self.__base_locations(X)  # for the Padding indices
          args = list(params)
  
          # Generate structured gradients with appropriate zero padding
          def make_dPhi(i, g):
  
              # Pad the gradient with respect to the total basis dimensionality
              dPhi_dim = (N, D) if g.ndim < 3 else (N, D, g.shape[2])
              dPhi = np.zeros(dPhi_dim)
              dPhi[:, endinds[i]:endinds[i + 1]] = g
  
              return dPhi
  
          # Get gradients from each basis
          for i, base in enumerate(self.bases):
  
              # evaluate gradient and deal with multiple parameter gradients by
              # keeping track of the basis index
              g, args, sargs = base._grad_popargs(X, *args)
  
              for gg in atleast_tuple(g):
                  if len(gg) == 0:
                      continue
                  yield make_dPhi(i, gg)
docs: Return the gradient of the basis function for each parameter.
  
          Parameters
          ----------
          X : ndarray
              (N, d) array of observations where N is the number of samples, and
              d is the dimensionality of X.
          *params : optional
              parameter aguments, these are the parameters of the concatenated
              bases `in the order` they were concatenated.
  
          Returns
          -------
          list or ndarray :
              this will be a list of ndarrays if there are multiple parameters,
              or just an ndarray if there is a single parameter. The ndarrays can
              have more than two dimensions (i.e. tensors of rank > 2), depending
              on the dimensions of the basis function parameters. If there are
              *no* parameters, ``[]`` is returned.