<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def grad(self, X, *params):
 """ 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. """

        # 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)