tools/autograd/templates/Functions.cpp
// Generally speaking, fft's backward is ifft.
Generally speaking, fft's backward is ifft.
Generally speaking fft ' s backward is ifft .
Tensor fft_backward(const Tensor& self, const Tensor& grad, int64_t signal_ndim,
                    bool complex_input, bool complex_output,
                    bool inverse, IntList checked_signal_sizes,
                    bool normalized, bool onesided,
                    IntList output_sizes) {
  Tensor gI;
  if (!complex_input && complex_output) {
    if (onesided) {
       (onesided)
      int64_t zero_length = checked_signal_sizes[signal_ndim - 1] - grad.size(signal_ndim);
      auto complex_full_grad = grad;
      if (zero_length > 0) {
        std::vector<int64_t> zero_grad_shape(signal_ndim + 2);
        zero_grad_shape[0] = self.size(0);
        for (int64_t i = 1; i < signal_ndim; i++) {
          zero_grad_shape[i] = checked_signal_sizes[i - 1];
        }
        zero_grad_shape[signal_ndim] = zero_length;
        zero_grad_shape[signal_ndim + 1] = 2;
        complex_full_grad =  at::cat({ grad, at::zeros(zero_grad_shape, grad.type()) }, signal_ndim);
      }
      gI = _fft_with_size(complex_full_grad, signal_ndim,
                           true,  true,
                          !inverse, checked_signal_sizes, normalized,
                           false, complex_full_grad.sizes()).select(-1, 0);
    } else {
      gI = _fft_with_size(grad, signal_ndim,  true,
                           true, !inverse,
                          checked_signal_sizes, normalized,
                           false, grad.sizes()).select(-1, 0);
    }
  } else if (complex_input && !complex_output && onesided) {
    gI = _fft_with_size(grad, signal_ndim,  false,
                         true,  false,
                        checked_signal_sizes, normalized,  true,
                        self.sizes());
    int64_t double_length = checked_signal_sizes[signal_ndim - 1] - self.size(signal_ndim);
    if (double_length > 0) {  
      gI.narrow(signal_ndim, 1, double_length).mul_(2);
    }
  } else {
    gI = _fft_with_size(grad, signal_ndim, complex_output, complex_input,
                        !inverse, checked_signal_sizes, normalized, onesided,
                        self.sizes());
  }
  if (normalized) {
    return gI;
  } else {
    auto signal_numel = std::accumulate(checked_signal_sizes.begin(),
                    checked_signal_sizes.end(), 1, std::multiplies<int64_t>());
    if (!inverse) {
      return gI.mul_(static_cast<double>(signal_numel));
    } else {
      return gI.div_(static_cast<double>(signal_numel));
    }
  }
}
Tensor fft_backward ( const Tensor & self , const Tensor & grad , int64_t signal_ndim , bool complex_input , bool complex_output , bool inverse , IntList checked_signal_sizes , bool normalized , bool onesided , IntList output_sizes ) { Tensor gI ; if ( ! complex_input && complex_output ) { if ( onesided ) { int64_t zero_length = checked_signal_sizes [ signal_ndim - 1 ] - grad . size ( signal_ndim ) ; auto complex_full_grad = grad ; if ( zero_length > 0 ) { std :: vector < int64_t > zero_grad_shape ( signal_ndim + 2 ) ; zero_grad_shape [ 0 ] = self . size ( 0 ) ; for ( int64_t i = 1 ; i < signal_ndim ; i ++ ) { zero_grad_shape [ i ] = checked_signal_sizes [ i - 1 ] ; } zero_grad_shape [ signal_ndim ] = zero_length ; zero_grad_shape [ signal_ndim + 1 ] = 2 ; complex_full_grad = at :: cat ( { grad , at :: zeros ( zero_grad_shape , grad . type ( ) ) } , signal_ndim ) ; } gI = _fft_with_size ( complex_full_grad , signal_ndim , true , true , ! inverse , checked_signal_sizes , normalized , false , complex_full_grad . sizes ( ) ) . select ( -1 , 0 ) ; } else { gI = _fft_with_size ( grad , signal_ndim , true , true , ! inverse , checked_signal_sizes , normalized , false , grad . sizes ( ) ) . select ( -1 , 0 ) ; } } else if ( complex_input && ! complex_output && onesided ) { gI = _fft_with_size ( grad , signal_ndim , false , true , false , checked_signal_sizes , normalized , true , self . sizes ( ) ) ; int64_t double_length = checked_signal_sizes [ signal_ndim - 1 ] - self . size ( signal_ndim ) ; if ( double_length > 0 ) { gI . narrow ( signal_ndim , 1 , double_length ) . mul_ ( 2 ) ; } } else { gI = _fft_with_size ( grad , signal_ndim , complex_output , complex_input , ! inverse , checked_signal_sizes , normalized , onesided , self . sizes ( ) ) ; } if ( normalized ) { return gI ; } else { auto signal_numel = std :: accumulate ( checked_signal_sizes . begin ( ) , checked_signal_sizes . end ( ) , 1 , std :: multiplies < int64_t > ( ) ) ; if ( ! inverse ) { return gI . mul_ ( static_cast < double > ( signal_numel ) ) ; } else { return gI . div_ ( static_cast < double > ( signal_numel ) ) ; } } }
Generally speaking, fft's backward is ifft.
Generally speaking fft ' s backward is ifft .
// Forward is R2C // Do inverse C2C and project onto real plane because grad can be // asymmetrical so C2R can't be used. // Forward is R2C (onesided) // Think of onesided R2C rfft as //     1. view as complex numbers (fill complex dim with zeros) //     2. C2C fft //     3. discard half of results // So backward is //     1. fill the other half with zeros (with `zero_grad_shape` below) //        (C2C ifft only take twosided inputs so we need to fill here) //     2. inverse C2C ifft //     3. discard the complex dim /* complex_input */ /* complex_output */ /* onesided */ /* complex_input */ /* complex_output */ /* onesided */ // Forward is C2R (onesided) // Think of onesided C2R irfft as //    1. fill the other half by conjugate symmetry //    2. inverse C2C ifft //    3. discard the complex dimension // So backward is //    1. R2C rfft (essentially add dummy complex dimension, and dft) //    2. accumulate gradient by conjugate symmetry //       since rfft results follow conjugate symmetry, we only need to //       double some entries from onesided rfft results, i.e., the ones with //       their reflected indices also landing out of the onesided range. So //       consider the index of last dim: //           i.   idx = 0. //                Reflected to (N - 0) % N = 0. Not doubled. //           ii   0 < idx < floor(N/2) (last). //                N > N - idx > ceil(N/2) //                Reflected to () //           iii. idx = floor(N/2) = N/2 (last) when N even. //                Reflected to (N - N/2) % N = N/2. Not doubled. //           iv.  idx = floor(N/2) = (N-1)/2 (last) when N odd. //                Reflected to (N - (N-1)/2) % N = (N+1)/2. Doubled. //       Therefore, needs to double //           idx = 1, 2, ..., N/2 - 1     when N even //           idx = 1, 2, ..., (N-1)/2     when N odd //       that is //           idx = 1, 2, ..., N - (floor(N/2) + 1) //               = 1, 2, ..., N - onesided_length /* complex_input */ /* complex_output */ /* inverse */ /* onesided */ // also covers case when signal size is zero // If normalized, backward is exactly calling fft with inversed argument as // the forward because both are unitary. // If not normalized, in backward, we need to upscale or downscale gI basing // on whether the forward is an inverse fft.