code: def _param_grad_helper(self,X,X2,target):
          
          if X2 is None: X2 = X
          FX = np.column_stack([f(X) for f in self.F])
          FX2 = np.column_stack([f(X2) for f in self.F])
          DER = np.zeros((self.n,self.n,self.n))
          for i in range(self.n):
              DER[i,i,i] = np.sqrt(self.weights[i])
          dw = self.variance * mdot(FX,DER,self.G_1,np.diag(np.sqrt(self.weights)),FX2.T)
          dv = mdot(FX,np.diag(np.sqrt(self.weights)),self.G_1,np.diag(np.sqrt(self.weights)),FX2.T)
          np.add(target[:,:,0],np.transpose(dv,(0,2,1)), target[:,:,0])
          np.add(target[:,:,1:],np.transpose(dw,(0,2,1)), target[:,:,1:])
docs: Return shape is NxMx(Ntheta)