Home | History | Annotate | Download | only in kernels
      1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
      2 
      3 Licensed under the Apache License, Version 2.0 (the "License");
      4 you may not use this file except in compliance with the License.
      5 You may obtain a copy of the License at
      6 
      7     http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 Unless required by applicable law or agreed to in writing, software
     10 distributed under the License is distributed on an "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 See the License for the specific language governing permissions and
     13 limitations under the License.
     14 ==============================================================================*/
     15 
     16 #include "tensorflow/core/kernels/eigen_spatial_convolutions.h"
     17 #include "tensorflow/core/framework/types.h"
     18 #include "tensorflow/core/kernels/eigen_cuboid_convolution.h"
     19 #include "tensorflow/core/platform/test.h"
     20 
     21 namespace Eigen {
     22 
     23 #define EigenApprox(a, b) \
     24   { ASSERT_TRUE(std::abs(a - b) <= std::min(std::abs(a), std::abs(b)) * 1e-3); }
     25 static int ceil_div(int a, int b) { return (a + b - 1) / b; }
     26 
     27 TEST(EigenSpatialConvolutionsTest, Simple) {
     28   const int input_depth = 7;
     29   const int input_rows = 4;
     30   const int input_cols = 5;
     31   const int output_depth = 10;
     32   const int patch_rows = 3;
     33   const int patch_cols = 4;
     34   const int output_rows = input_rows;
     35   const int output_cols = input_cols;
     36 
     37   Tensor<float, 3> input(input_depth, input_rows, input_cols);
     38   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
     39   Tensor<float, 3> result(output_depth, output_rows, output_cols);
     40 
     41   input = input.constant(11.0f) + input.random();
     42   kernel = kernel.constant(2.0f) + kernel.random();
     43   result.setRandom();
     44 
     45   result = SpatialConvolution(input, kernel);
     46 
     47   EXPECT_EQ(result.dimension(0), output_depth);
     48   EXPECT_EQ(result.dimension(1), output_rows);
     49   EXPECT_EQ(result.dimension(2), output_cols);
     50 
     51   for (int od = 0; od < output_depth; ++od) {
     52     for (int i = 0; i < output_rows; ++i) {
     53       for (int j = 0; j < output_cols; ++j) {
     54         float expected = 0.0f;
     55         for (int c = 0; c < patch_cols; ++c) {
     56           for (int r = 0; r < patch_rows; ++r) {
     57             for (int id = 0; id < input_depth; ++id) {
     58               if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < output_rows &&
     59                   c - 1 + j < output_cols) {
     60                 expected +=
     61                     input(id, r - 1 + i, c - 1 + j) * kernel(od, id, r, c);
     62               }
     63             }
     64           }
     65         }
     66         EigenApprox(result(od, i, j), expected);
     67       }
     68     }
     69   }
     70 }
     71 
     72 TEST(EigenSpatialConvolutionsTest, SimpleRowMajor) {
     73   const int input_depth = 7;
     74   const int input_rows = 4;
     75   const int input_cols = 5;
     76   const int output_depth = 10;
     77   const int patch_rows = 3;
     78   const int patch_cols = 4;
     79   const int output_rows = input_rows;
     80   const int output_cols = input_cols;
     81 
     82   Tensor<float, 3, RowMajor> input(input_cols, input_rows, input_depth);
     83   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
     84                                     output_depth);
     85   Tensor<float, 3, RowMajor> result(output_cols, output_rows, output_depth);
     86   input = input.constant(11.0f) + input.random();
     87   kernel = kernel.constant(2.0f) + kernel.random();
     88   result.setRandom();
     89 
     90   result = SpatialConvolution(input, kernel);
     91 
     92   EXPECT_EQ(result.dimension(0), output_cols);
     93   EXPECT_EQ(result.dimension(1), output_rows);
     94   EXPECT_EQ(result.dimension(2), output_depth);
     95 
     96   for (int od = 0; od < output_depth; ++od) {
     97     for (int i = 0; i < output_rows; ++i) {
     98       for (int j = 0; j < output_cols; ++j) {
     99         float expected = 0.0f;
    100         for (int c = 0; c < patch_cols; ++c) {
    101           for (int r = 0; r < patch_rows; ++r) {
    102             for (int id = 0; id < input_depth; ++id) {
    103               if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < output_rows &&
    104                   c - 1 + j < output_cols) {
    105                 expected +=
    106                     input(c - 1 + j, r - 1 + i, id) * kernel(c, r, id, od);
    107               }
    108             }
    109           }
    110         }
    111         EigenApprox(result(j, i, od), expected);
    112       }
    113     }
    114   }
    115 }
    116 
    117 TEST(EigenSpatialConvolutionsTest, BatchedSpatialConvolution) {
    118   Tensor<float, 4> input(10, 5, 5, 13);
    119   Tensor<float, 4> kernel(7, 10, 3, 3);
    120   Tensor<float, 4> result(7, 5, 5, 13);
    121   input = input.constant(11.0f) + input.random();
    122   kernel = kernel.constant(2.0f) + kernel.random();
    123   result.setRandom();
    124 
    125   result = SpatialConvolution(input, kernel);
    126 
    127   EXPECT_EQ(result.dimension(0), 7);
    128   EXPECT_EQ(result.dimension(1), 5);
    129   EXPECT_EQ(result.dimension(2), 5);
    130 
    131   for (int b = 0; b < 13; ++b) {
    132     for (int od = 0; od < 7; ++od) {
    133       for (int i = 0; i < 5; ++i) {
    134         for (int j = 0; j < 5; ++j) {
    135           float expected = 0.0f;
    136           for (int c = 0; c < 3; ++c) {
    137             for (int r = 0; r < 3; ++r) {
    138               for (int id = 0; id < 10; ++id) {
    139                 if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < 5 &&
    140                     c - 1 + j < 5) {
    141                   expected +=
    142                       input(id, r - 1 + i, c - 1 + j, b) * kernel(od, id, r, c);
    143                 }
    144               }
    145             }
    146           }
    147           EigenApprox(result(od, i, j, b), expected);
    148         }
    149       }
    150     }
    151   }
    152 }
    153 
    154 TEST(EigenSpatialConvolutionsTest, BatchedSpatialConvolutionRowMajor) {
    155   Tensor<float, 4, RowMajor> input(13, 5, 5, 10);
    156   Tensor<float, 4, RowMajor> kernel(3, 3, 10, 7);
    157   Tensor<float, 4, RowMajor> result(13, 5, 5, 7);
    158   input = input.constant(11.0f) + input.random();
    159   kernel = kernel.constant(2.0f) + kernel.random();
    160   result.setRandom();
    161 
    162   result = SpatialConvolution(input, kernel);
    163 
    164   EXPECT_EQ(result.dimension(1), 5);
    165   EXPECT_EQ(result.dimension(2), 5);
    166   EXPECT_EQ(result.dimension(3), 7);
    167 
    168   for (int b = 0; b < 13; ++b) {
    169     for (int od = 0; od < 7; ++od) {
    170       for (int i = 0; i < 5; ++i) {
    171         for (int j = 0; j < 5; ++j) {
    172           float expected = 0.0f;
    173           for (int c = 0; c < 3; ++c) {
    174             for (int r = 0; r < 3; ++r) {
    175               for (int id = 0; id < 10; ++id) {
    176                 if (r - 1 + i >= 0 && c - 1 + j >= 0 && r - 1 + i < 5 &&
    177                     c - 1 + j < 5) {
    178                   expected +=
    179                       input(b, c - 1 + j, r - 1 + i, id) * kernel(c, r, id, od);
    180                 }
    181               }
    182             }
    183           }
    184           EigenApprox(result(b, j, i, od), expected);
    185         }
    186       }
    187     }
    188   }
    189 }
    190 
    191 TEST(EigenSpatialConvolutionsTest, ValidSpatialConvolution) {
    192   const int input_depth = 10;
    193   const int input_rows = 5;
    194   const int input_cols = 5;
    195   const int num_batches = 13;
    196   const int output_depth = 7;
    197   const int patch_rows = 4;
    198   const int patch_cols = 4;
    199   const int output_rows = input_rows - patch_rows + 1;
    200   const int output_cols = input_cols - patch_cols + 1;
    201 
    202   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
    203   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    204   Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
    205   input = input.constant(11.0f) + input.random();
    206   kernel = kernel.constant(2.0f) + kernel.random();
    207   result.setRandom();
    208 
    209   // Apply a spatial convolution using a 4x4 kernel, valid padding, and a stride
    210   // of 1.
    211   const int stride = 1;
    212   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
    213 
    214   EXPECT_EQ(result.dimension(0), output_depth);
    215   EXPECT_EQ(result.dimension(1), output_rows);
    216   EXPECT_EQ(result.dimension(2), output_cols);
    217   EXPECT_EQ(result.dimension(3), num_batches);
    218 
    219   for (int b = 0; b < num_batches; ++b) {
    220     for (int od = 0; od < output_depth; ++od) {
    221       for (int i = 0; i < output_rows; ++i) {
    222         for (int j = 0; j < output_cols; ++j) {
    223           float expected = 0.0f;
    224           for (int c = 0; c < patch_cols; ++c) {
    225             for (int r = 0; r < patch_rows; ++r) {
    226               for (int id = 0; id < input_depth; ++id) {
    227                 expected += input(id, r + i, c + j, b) * kernel(od, id, r, c);
    228               }
    229             }
    230           }
    231           if (result(od, i, j, b) != expected) {
    232             std::cout << "at od=" << od << " b=" << b << " i=" << i
    233                       << " j=" << j << " " << result(od, i, j, b) << " vs "
    234                       << expected << std::endl;
    235           }
    236           EigenApprox(result(od, i, j, b), expected);
    237         }
    238       }
    239     }
    240   }
    241 }
    242 
    243 TEST(EigenSpatialConvolutionsTest, ValidSpatialConvolutionUnequalStrides) {
    244   const int input_depth = 10;
    245   const int input_rows = 5;
    246   const int input_cols = 5;
    247   const int num_batches = 13;
    248   const int output_depth = 7;
    249   const int patch_rows = 4;
    250   const int patch_cols = 4;
    251 
    252   const int row_stride = 1;
    253   const int col_stride = 2;
    254   const int output_rows = 2;
    255   const int output_cols = 1;
    256 
    257   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
    258   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    259   Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
    260   input = input.constant(11.0f) + input.random();
    261   kernel = kernel.constant(2.0f) + kernel.random();
    262   result.setRandom();
    263 
    264   // Apply a spatial convolution using a 4x4 kernel, valid padding, and a stride
    265   // of 1.
    266   result =
    267       SpatialConvolution(input, kernel, row_stride, col_stride, PADDING_VALID);
    268 
    269   EXPECT_EQ(result.dimension(0), output_depth);
    270   EXPECT_EQ(result.dimension(1), output_rows);
    271   EXPECT_EQ(result.dimension(2), output_cols);
    272   EXPECT_EQ(result.dimension(3), num_batches);
    273   if (true) return;
    274 
    275   for (int b = 0; b < num_batches; ++b) {
    276     for (int od = 0; od < output_depth; ++od) {
    277       for (int i = 0; i < output_rows; ++i) {
    278         for (int j = 0; j < output_cols; ++j) {
    279           float expected = 0.0f;
    280           for (int c = 0; c < patch_cols; ++c) {
    281             for (int r = 0; r < patch_rows; ++r) {
    282               for (int id = 0; id < input_depth; ++id) {
    283                 expected +=
    284                     input(id, r + row_stride * i, c + col_stride * j, b) *
    285                     kernel(od, id, r, c);
    286               }
    287             }
    288           }
    289           if (result(od, i, j, b) != expected) {
    290             std::cout << "at od=" << od << " b=" << b << " i=" << i
    291                       << " j=" << j << " " << result(od, i, j, b) << " vs "
    292                       << expected << std::endl;
    293           }
    294           EigenApprox(result(od, i, j, b), expected);
    295         }
    296       }
    297     }
    298   }
    299 }
    300 
    301 TEST(EigenSpatialConvolutionsTest, ValidSpatialConvolutionRowMajor) {
    302   const int input_depth = 10;
    303   const int input_rows = 5;
    304   const int input_cols = 5;
    305   const int num_batches = 13;
    306   const int output_depth = 7;
    307   const int patch_rows = 4;
    308   const int patch_cols = 4;
    309   const int output_rows = input_rows - patch_rows + 1;
    310   const int output_cols = input_cols - patch_cols + 1;
    311 
    312   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
    313                                    input_depth);
    314   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    315                                     output_depth);
    316   Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows,
    317                                     output_depth);
    318 
    319   input = input.constant(11.0f) + input.random();
    320   kernel = kernel.constant(2.0f) + kernel.random();
    321   result.setRandom();
    322 
    323   // Apply a spatial convolution using a 4x4 kernel, valid padding, and a stride
    324   // of 1.
    325   const int stride = 1;
    326   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
    327 
    328   EXPECT_EQ(result.dimension(0), num_batches);
    329   EXPECT_EQ(result.dimension(1), output_cols);
    330   EXPECT_EQ(result.dimension(2), output_rows);
    331   EXPECT_EQ(result.dimension(3), output_depth);
    332 
    333   for (int b = 0; b < num_batches; ++b) {
    334     for (int od = 0; od < output_depth; ++od) {
    335       for (int i = 0; i < output_rows; ++i) {
    336         for (int j = 0; j < output_cols; ++j) {
    337           float expected = 0.0f;
    338           for (int c = 0; c < patch_rows; ++c) {
    339             for (int r = 0; r < patch_cols; ++r) {
    340               for (int id = 0; id < input_depth; ++id) {
    341                 expected += input(b, c + j, r + i, id) * kernel(c, r, id, od);
    342               }
    343             }
    344           }
    345           if (result(b, j, i, od) != expected) {
    346             std::cout << "at od=" << od << " b=" << b << " i=" << i
    347                       << " j=" << j << " " << result(b, j, i, od) << " vs "
    348                       << expected << std::endl;
    349           }
    350           EigenApprox(result(b, j, i, od), expected);
    351         }
    352       }
    353     }
    354   }
    355 }
    356 
    357 TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolution) {
    358   const int input_depth = 10;
    359   const int input_rows = 5;
    360   const int input_cols = 5;
    361   const int num_batches = 13;
    362   const int output_depth = 7;
    363   const int patch_rows = 3;
    364   const int patch_cols = 3;
    365   const int output_rows = 2;
    366   const int output_cols = 2;
    367 
    368   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
    369   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    370   Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
    371   input = input.constant(11.0f) + input.random();
    372   kernel = kernel.constant(2.0f) + kernel.random();
    373   result.setRandom();
    374 
    375   // Apply a spatial convolution using a 3x3 kernel, valid padding, and a stride
    376   // of 2.
    377   int stride = 2;
    378   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
    379 
    380   EXPECT_EQ(result.dimension(0), output_depth);
    381   EXPECT_EQ(result.dimension(1), output_rows);
    382   EXPECT_EQ(result.dimension(2), output_cols);
    383   EXPECT_EQ(result.dimension(3), num_batches);
    384 
    385   for (int b = 0; b < num_batches; ++b) {
    386     for (int od = 0; od < output_depth; ++od) {
    387       for (int i = 0; i < output_rows; ++i) {
    388         for (int j = 0; j < output_cols; ++j) {
    389           float expected = 0.0f;
    390           for (int c = 0; c < patch_cols; ++c) {
    391             for (int r = 0; r < patch_rows; ++r) {
    392               for (int id = 0; id < input_depth; ++id) {
    393                 expected += input(id, r + stride * i, c + stride * j, b) *
    394                             kernel(od, id, r, c);
    395               }
    396             }
    397           }
    398           EigenApprox(result(od, i, j, b), expected);
    399         }
    400       }
    401     }
    402   }
    403 }
    404 
    405 TEST(EigenSpatialConvolutionsTest, KernelSmallerThanStride) {
    406   const int input_depth = 2;
    407   const int input_rows = 3;
    408   const int input_cols = 3;
    409   const int num_batches = 5;
    410   const int output_depth = 6;
    411   const int patch_rows = 1;
    412   const int patch_cols = 1;
    413   const int output_rows = 2;
    414   const int output_cols = 2;
    415 
    416   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
    417   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    418   Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
    419   input = input.constant(11.0f) + input.random();
    420   kernel = kernel.constant(2.0f) + kernel.random();
    421   result.setRandom();
    422 
    423   // Apply a spatial convolution using a 1x1 kernel, valid padding, and a stride
    424   // of 2.
    425   int stride = 2;
    426   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
    427 
    428   EXPECT_EQ(result.dimension(0), output_depth);
    429   EXPECT_EQ(result.dimension(1), output_rows);
    430   EXPECT_EQ(result.dimension(2), output_cols);
    431   EXPECT_EQ(result.dimension(3), num_batches);
    432 
    433   for (int b = 0; b < num_batches; ++b) {
    434     for (int od = 0; od < output_depth; ++od) {
    435       for (int i = 0; i < output_rows; ++i) {
    436         for (int j = 0; j < output_cols; ++j) {
    437           float expected = 0.0f;
    438           for (int c = 0; c < patch_cols; ++c) {
    439             for (int r = 0; r < patch_rows; ++r) {
    440               for (int id = 0; id < input_depth; ++id) {
    441                 expected += input(id, r + stride * i, c + stride * j, b) *
    442                             kernel(od, id, r, c);
    443               }
    444             }
    445           }
    446           EigenApprox(result(od, i, j, b), expected);
    447         }
    448       }
    449     }
    450   }
    451 }
    452 
    453 TEST(EigenSpatialConvolutionsTest, StridedSpatialConvolutionRowMajor) {
    454   const int input_depth = 10;
    455   const int input_rows = 5;
    456   const int input_cols = 5;
    457   const int num_batches = 13;
    458   const int output_depth = 7;
    459   const int patch_rows = 3;
    460   const int patch_cols = 3;
    461   const int output_rows = 2;
    462   const int output_cols = 2;
    463 
    464   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
    465                                    input_depth);
    466   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    467                                     output_depth);
    468   Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows,
    469                                     output_depth);
    470   input = input.constant(11.0f) + input.random();
    471   kernel = kernel.constant(2.0f) + kernel.random();
    472   result.setRandom();
    473 
    474   // Apply a spatial convolution using a 3x3 kernel, valid padding, and a stride
    475   // of 2.
    476   int stride = 2;
    477   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID);
    478 
    479   EXPECT_EQ(result.dimension(0), num_batches);
    480   EXPECT_EQ(result.dimension(1), output_cols);
    481   EXPECT_EQ(result.dimension(2), output_rows);
    482   EXPECT_EQ(result.dimension(3), output_depth);
    483 
    484   for (int b = 0; b < num_batches; ++b) {
    485     for (int od = 0; od < output_depth; ++od) {
    486       for (int i = 0; i < output_rows; ++i) {
    487         for (int j = 0; j < output_cols; ++j) {
    488           float expected = 0.0f;
    489           for (int c = 0; c < patch_cols; ++c) {
    490             for (int r = 0; r < patch_rows; ++r) {
    491               for (int id = 0; id < input_depth; ++id) {
    492                 expected += input(b, c + stride * j, r + stride * i, id) *
    493                             kernel(c, r, id, od);
    494               }
    495             }
    496           }
    497           EigenApprox(result(b, j, i, od), expected);
    498         }
    499       }
    500     }
    501   }
    502 }
    503 
    504 TEST(EigenSpatialConvolutionsTest, AtrousSpatial) {
    505   const int input_depth = 10;
    506   const int input_rows = 7;
    507   const int input_cols = 7;
    508   const int num_batches = 13;
    509   const int output_depth = 7;
    510   const int patch_rows = 3;
    511   const int patch_cols = 3;
    512   const int output_rows = 3;
    513   const int output_cols = 3;
    514 
    515   Tensor<float, 4> input(input_depth, input_rows, input_cols, num_batches);
    516   Tensor<float, 4> kernel(output_depth, input_depth, patch_rows, patch_cols);
    517   Tensor<float, 4> result(output_depth, output_rows, output_cols, num_batches);
    518   input = input.constant(11.0f) + input.random();
    519   kernel = kernel.constant(2.0f) + kernel.random();
    520   result.setRandom();
    521 
    522   // Apply a spatial convolution using a 3x3 kernel, valid padding
    523   // output (standard) stride 1, and input (atrous) stride of 2.
    524   int stride = 1;
    525   int in_stride = 2;
    526   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID,
    527                               in_stride, in_stride);
    528 
    529   EXPECT_EQ(result.dimension(0), output_depth);
    530   EXPECT_EQ(result.dimension(1), output_rows);
    531   EXPECT_EQ(result.dimension(2), output_cols);
    532   EXPECT_EQ(result.dimension(3), num_batches);
    533 
    534   for (int b = 0; b < num_batches; ++b) {
    535     for (int od = 0; od < output_depth; ++od) {
    536       for (int i = 0; i < output_rows; ++i) {
    537         for (int j = 0; j < output_cols; ++j) {
    538           float expected = 0.0f;
    539           for (int c = 0; c < patch_cols; ++c) {
    540             for (int r = 0; r < patch_rows; ++r) {
    541               for (int id = 0; id < input_depth; ++id) {
    542                 expected += input(id, in_stride * r + stride * i,
    543                                   in_stride * c + stride * j, b) *
    544                             kernel(od, id, r, c);
    545               }
    546             }
    547           }
    548           EigenApprox(result(od, i, j, b), expected);
    549         }
    550       }
    551     }
    552   }
    553 }
    554 
    555 TEST(EigenSpatialConvolutionsTest, AtrousSpatialRowMajor) {
    556   const int input_depth = 10;
    557   const int input_rows = 7;
    558   const int input_cols = 7;
    559   const int num_batches = 13;
    560   const int output_depth = 7;
    561   const int patch_rows = 3;
    562   const int patch_cols = 3;
    563   const int output_rows = 3;
    564   const int output_cols = 3;
    565 
    566   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
    567                                    input_depth);
    568   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    569                                     output_depth);
    570   Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows,
    571                                     output_depth);
    572   input = input.constant(11.0f) + input.random();
    573   kernel = kernel.constant(2.0f) + kernel.random();
    574   result.setRandom();
    575 
    576   // Apply a spatial convolution using a 3x3 kernel, valid padding
    577   // output (standard) stride 1, and input (atrous) stride of 2.
    578   int stride = 1;
    579   int in_stride = 2;
    580   result = SpatialConvolution(input, kernel, stride, stride, PADDING_VALID,
    581                               in_stride, in_stride);
    582 
    583   EXPECT_EQ(result.dimension(0), num_batches);
    584   EXPECT_EQ(result.dimension(1), output_cols);
    585   EXPECT_EQ(result.dimension(2), output_rows);
    586   EXPECT_EQ(result.dimension(3), output_depth);
    587 
    588   for (int b = 0; b < num_batches; ++b) {
    589     for (int od = 0; od < output_depth; ++od) {
    590       for (int i = 0; i < output_rows; ++i) {
    591         for (int j = 0; j < output_cols; ++j) {
    592           float expected = 0.0f;
    593           for (int c = 0; c < patch_cols; ++c) {
    594             for (int r = 0; r < patch_rows; ++r) {
    595               for (int id = 0; id < input_depth; ++id) {
    596                 expected += input(b, in_stride * c + stride * j,
    597                                   in_stride * r + stride * i, id) *
    598                             kernel(c, r, id, od);
    599               }
    600             }
    601           }
    602           EigenApprox(result(b, j, i, od), expected);
    603         }
    604       }
    605     }
    606   }
    607 }
    608 
    609 TEST(EigenSpatialConvolutionsTest, AtrousSpatialRowMajorUnequalStrides) {
    610   const int input_depth = 10;
    611   const int input_rows = 7;
    612   const int input_cols = 7;
    613   const int num_batches = 13;
    614   const int output_depth = 7;
    615   const int patch_rows = 3;
    616   const int patch_cols = 3;
    617   const int output_rows = 1;
    618   const int output_cols = 3;
    619 
    620   Tensor<float, 4, RowMajor> input(num_batches, input_cols, input_rows,
    621                                    input_depth);
    622   Tensor<float, 4, RowMajor> kernel(patch_cols, patch_rows, input_depth,
    623                                     output_depth);
    624   Tensor<float, 4, RowMajor> result(num_batches, output_cols, output_rows,
    625                                     output_depth);
    626   input = input.constant(11.0f) + input.random();
    627   kernel = kernel.constant(2.0f) + kernel.random();
    628   result.setRandom();
    629 
    630   // Apply a spatial convolution using a 3x3 kernel, valid padding
    631   // output (standard) stride 1, and input (atrous) stride of 2.
    632   int row_stride = 1;
    633   int col_stride = 2;
    634   int row_in_stride = 3;
    635   int col_in_stride = 1;
    636   result = SpatialConvolution(input, kernel, row_stride, col_stride,
    637                               PADDING_VALID, row_in_stride, col_in_stride);
    638 
    639   EXPECT_EQ(result.dimension(0), num_batches);
    640   EXPECT_EQ(result.dimension(1), output_cols);
    641   EXPECT_EQ(result.dimension(2), output_rows);
    642   EXPECT_EQ(result.dimension(3), output_depth);
    643 
    644   for (int b = 0; b < num_batches; ++b) {
    645     for (int od = 0; od < output_depth; ++od) {
    646       for (int i = 0; i < output_rows; ++i) {
    647         for (int j = 0; j < output_cols; ++j) {
    648           float expected = 0.0f;
    649           for (int c = 0; c < patch_cols; ++c) {
    650             for (int r = 0; r < patch_rows; ++r) {
    651               for (int id = 0; id < input_depth; ++id) {
    652                 expected += input(b, col_in_stride * c + col_stride * j,
    653                                   row_in_stride * r + row_stride * i, id) *
    654                             kernel(c, r, id, od);
    655               }
    656             }
    657           }
    658           EigenApprox(result(b, j, i, od), expected);
    659         }
    660       }
    661     }
    662   }
    663 }
    664 
    665 TEST(EigenSpatialConvolutionsTest, Cuboid) {
    666   const int in_channels = 10;
    667   const int in_depth = 5;
    668   const int in_rows = 8;
    669   const int in_cols = 7;
    670 
    671   const int kern_filters = 7;
    672   const int kern_depth = 3;
    673   const int kern_width = 4;
    674   const int kern_height = 4;
    675 
    676   const int out_depth = in_depth;
    677   const int out_height = in_rows;
    678   const int out_width = in_cols;
    679 
    680   Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols);
    681   Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height,
    682                           kern_width);
    683   Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width);
    684   input = input.constant(11.0f) + input.random();
    685   kernel = kernel.constant(2.0f) + kernel.random();
    686   result.setRandom();
    687 
    688   result = CuboidConvolution(input, kernel);
    689 
    690   EXPECT_EQ(result.dimension(0), kern_filters);
    691   EXPECT_EQ(result.dimension(1), out_depth);
    692   EXPECT_EQ(result.dimension(2), out_height);
    693   EXPECT_EQ(result.dimension(3), out_width);
    694 
    695   const int off_p = (kern_depth - 1) / 2;
    696   const int off_r = (kern_height - 1) / 2;
    697   const int off_c = (kern_width - 1) / 2;
    698 
    699   for (int od = 0; od < kern_filters; ++od) {
    700     for (int i = 0; i < out_depth; ++i) {
    701       for (int j = 0; j < out_height; ++j) {
    702         for (int k = 0; k < out_width; ++k) {
    703           float expected = 0.0f;
    704           for (int c = 0; c < kern_width; ++c) {
    705             for (int r = 0; r < kern_height; ++r) {
    706               for (int p = 0; p < kern_depth; ++p) {
    707                 for (int id = 0; id < in_channels; ++id) {
    708                   if (p - off_p + i >= 0 && r - off_r + j >= 0 &&
    709                       c - off_c + k >= 0 && p - off_p + i < in_depth &&
    710                       r - off_r + j < in_rows && c - off_c + k < in_cols) {
    711                     expected +=
    712                         input(id, p - off_p + i, r - off_r + j, c - off_c + k) *
    713                         kernel(od, id, p, r, c);
    714                   }
    715                 }
    716               }
    717             }
    718           }
    719           EigenApprox(result(od, i, j, k), expected);
    720         }
    721       }
    722     }
    723   }
    724 }
    725 
    726 TEST(EigenSpatialConvolutionsTest, CuboidRowMajor) {
    727   const int in_channels = 10;
    728   const int in_depth = 5;
    729   const int in_rows = 8;
    730   const int in_cols = 7;
    731 
    732   const int kern_filters = 7;
    733   const int kern_depth = 3;
    734   const int kern_width = 4;
    735   const int kern_height = 4;
    736 
    737   const int out_depth = in_depth;
    738   const int out_height = in_rows;
    739   const int out_width = in_cols;
    740 
    741   Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels);
    742   Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth,
    743                                     in_channels, kern_filters);
    744   Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth,
    745                                     kern_filters);
    746   input = input.constant(11.0f) + input.random();
    747   kernel = kernel.constant(2.0f) + kernel.random();
    748   result.setRandom();
    749 
    750   result = CuboidConvolution(input, kernel);
    751 
    752   EXPECT_EQ(result.dimension(3), kern_filters);
    753   EXPECT_EQ(result.dimension(2), out_depth);
    754   EXPECT_EQ(result.dimension(1), out_height);
    755   EXPECT_EQ(result.dimension(0), out_width);
    756 
    757   const int off_p = (kern_depth - 1) / 2;
    758   const int off_r = (kern_height - 1) / 2;
    759   const int off_c = (kern_width - 1) / 2;
    760 
    761   for (int od = 0; od < kern_filters; ++od) {
    762     for (int i = 0; i < out_depth; ++i) {
    763       for (int j = 0; j < out_height; ++j) {
    764         for (int k = 0; k < out_width; ++k) {
    765           float expected = 0.0f;
    766           for (int c = 0; c < kern_width; ++c) {
    767             for (int r = 0; r < kern_height; ++r) {
    768               for (int p = 0; p < kern_depth; ++p) {
    769                 for (int id = 0; id < in_channels; ++id) {
    770                   if (p - off_p + i >= 0 && r - off_r + j >= 0 &&
    771                       c - off_c + k >= 0 && p - off_p + i < in_depth &&
    772                       r - off_r + j < in_rows && c - off_c + k < in_cols) {
    773                     expected +=
    774                         input(c - off_c + k, r - off_r + j, p - off_p + i, id) *
    775                         kernel(c, r, p, id, od);
    776                   }
    777                 }
    778               }
    779             }
    780           }
    781           EigenApprox(result(k, j, i, od), expected);
    782         }
    783       }
    784     }
    785   }
    786 }
    787 
    788 TEST(EigenSpatialConvolutionsTest, ValidCuboid) {
    789   const int in_channels = 10;
    790   const int in_depth = 5;
    791   const int in_rows = 5;
    792   const int in_cols = 5;
    793 
    794   const int kern_filters = 7;
    795   const int kern_depth = 3;
    796   const int kern_width = 3;
    797   const int kern_height = 3;
    798 
    799   const int out_depth = 3;
    800   const int out_height = 3;
    801   const int out_width = 3;
    802 
    803   Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols);
    804   Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height,
    805                           kern_width);
    806   Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width);
    807   input = input.constant(11.0f) + input.random();
    808   kernel = kernel.constant(2.0f) + kernel.random();
    809   result.setRandom();
    810 
    811   result = CuboidConvolution(input, kernel, 1, 1, 1, PADDING_VALID);
    812 
    813   EXPECT_EQ(result.dimension(0), kern_filters);
    814   EXPECT_EQ(result.dimension(1), out_depth);
    815   EXPECT_EQ(result.dimension(2), out_height);
    816   EXPECT_EQ(result.dimension(3), out_width);
    817 
    818   for (int od = 0; od < kern_filters; ++od) {
    819     for (int i = 0; i < out_depth; ++i) {
    820       for (int j = 0; j < out_height; ++j) {
    821         for (int k = 0; k < out_width; ++k) {
    822           float expected = 0.0f;
    823           for (int c = 0; c < kern_width; ++c) {
    824             for (int r = 0; r < kern_height; ++r) {
    825               for (int p = 0; p < kern_depth; ++p) {
    826                 for (int id = 0; id < in_channels; ++id) {
    827                   expected +=
    828                       input(id, p + i, r + j, c + k) * kernel(od, id, p, r, c);
    829                 }
    830               }
    831             }
    832           }
    833           EigenApprox(result(od, i, j, k), expected);
    834         }
    835       }
    836     }
    837   }
    838 }
    839 
    840 TEST(EigenSpatialConvolutionsTest, ValidCuboidRowMajor) {
    841   const int in_channels = 10;
    842   const int in_depth = 5;
    843   const int in_rows = 5;
    844   const int in_cols = 5;
    845 
    846   const int kern_filters = 7;
    847   const int kern_depth = 3;
    848   const int kern_width = 3;
    849   const int kern_height = 3;
    850 
    851   const int out_depth = 3;
    852   const int out_height = 3;
    853   const int out_width = 3;
    854 
    855   Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels);
    856   Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth,
    857                                     in_channels, kern_filters);
    858   Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth,
    859                                     kern_filters);
    860   input = input.constant(11.0f) + input.random();
    861   kernel = kernel.constant(2.0f) + kernel.random();
    862   result.setRandom();
    863 
    864   result = CuboidConvolution(input, kernel, 1, 1, 1, PADDING_VALID);
    865 
    866   EXPECT_EQ(result.dimension(3), kern_filters);
    867   EXPECT_EQ(result.dimension(2), out_depth);
    868   EXPECT_EQ(result.dimension(1), out_height);
    869   EXPECT_EQ(result.dimension(0), out_width);
    870 
    871   for (int od = 0; od < kern_filters; ++od) {
    872     for (int i = 0; i < out_depth; ++i) {
    873       for (int j = 0; j < out_height; ++j) {
    874         for (int k = 0; k < out_width; ++k) {
    875           float expected = 0.0f;
    876           for (int c = 0; c < kern_width; ++c) {
    877             for (int r = 0; r < kern_height; ++r) {
    878               for (int p = 0; p < kern_depth; ++p) {
    879                 for (int id = 0; id < in_channels; ++id) {
    880                   expected +=
    881                       input(c + k, r + j, p + i, id) * kernel(c, r, p, id, od);
    882                 }
    883               }
    884             }
    885           }
    886           EigenApprox(result(k, j, i, od), expected);
    887         }
    888       }
    889     }
    890   }
    891 }
    892 
    893 TEST(EigenSpatialConvolutionsTest, BatchedCuboid) {
    894   const int batches = 2;
    895   const int in_channels = 10;
    896   const int in_depth = 5;
    897   const int in_rows = 8;
    898   const int in_cols = 7;
    899 
    900   const int kern_filters = 7;
    901   const int kern_depth = 3;
    902   const int kern_width = 4;
    903   const int kern_height = 4;
    904 
    905   const int out_depth = in_depth;
    906   const int out_height = in_rows;
    907   const int out_width = in_cols;
    908 
    909   Tensor<float, 5> input(in_channels, in_depth, in_rows, in_cols, batches);
    910   Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height,
    911                           kern_width);
    912   Tensor<float, 5> result(kern_filters, out_depth, out_height, out_width,
    913                           batches);
    914   input = input.constant(11.0f) + input.random();
    915   kernel = kernel.constant(2.0f) + kernel.random();
    916   result.setRandom();
    917 
    918   result = CuboidConvolution(input, kernel);
    919 
    920   EXPECT_EQ(result.dimension(0), kern_filters);
    921   EXPECT_EQ(result.dimension(1), out_depth);
    922   EXPECT_EQ(result.dimension(2), out_height);
    923   EXPECT_EQ(result.dimension(3), out_width);
    924   EXPECT_EQ(result.dimension(4), batches);
    925 
    926   const int off_p = (kern_depth - 1) / 2;
    927   const int off_r = (kern_height - 1) / 2;
    928   const int off_c = (kern_width - 1) / 2;
    929 
    930   for (int b = 0; b < batches; b++) {
    931     for (int od = 0; od < kern_filters; ++od) {
    932       for (int i = 0; i < out_depth; ++i) {
    933         for (int j = 0; j < out_height; ++j) {
    934           for (int k = 0; k < out_width; ++k) {
    935             float expected = 0.0f;
    936             for (int c = 0; c < kern_width; ++c) {
    937               for (int r = 0; r < kern_height; ++r) {
    938                 for (int p = 0; p < kern_depth; ++p) {
    939                   for (int id = 0; id < in_channels; ++id) {
    940                     if (p - off_p + i >= 0 && r - off_r + j >= 0 &&
    941                         c - off_c + k >= 0 && p - off_p + i < in_depth &&
    942                         r - off_r + j < in_rows && c - off_c + k < in_cols) {
    943                       expected += input(id, p - off_p + i, r - off_r + j,
    944                                         c - off_c + k, b) *
    945                                   kernel(od, id, p, r, c);
    946                     }
    947                   }
    948                 }
    949               }
    950             }
    951             EigenApprox(result(od, i, j, k, b), expected);
    952           }
    953         }
    954       }
    955     }
    956   }
    957 }
    958 
    959 TEST(EigenSpatialConvolutionsTest, BatchedCuboidRowMajor) {
    960   const int batches = 2;
    961   const int in_channels = 10;
    962   const int in_depth = 5;
    963   const int in_rows = 8;
    964   const int in_cols = 7;
    965 
    966   const int kern_filters = 7;
    967   const int kern_depth = 3;
    968   const int kern_width = 4;
    969   const int kern_height = 4;
    970 
    971   const int out_depth = in_depth;
    972   const int out_height = in_rows;
    973   const int out_width = in_cols;
    974 
    975   Tensor<float, 5, RowMajor> input(batches, in_cols, in_rows, in_depth,
    976                                    in_channels);
    977   Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth,
    978                                     in_channels, kern_filters);
    979   Tensor<float, 5, RowMajor> result(batches, out_width, out_height, out_depth,
    980                                     kern_filters);
    981   input = input.constant(11.0f) + input.random();
    982   kernel = kernel.constant(2.0f) + kernel.random();
    983   result.setRandom();
    984 
    985   result = CuboidConvolution(input, kernel);
    986 
    987   EXPECT_EQ(result.dimension(4), kern_filters);
    988   EXPECT_EQ(result.dimension(3), out_depth);
    989   EXPECT_EQ(result.dimension(2), out_height);
    990   EXPECT_EQ(result.dimension(1), out_width);
    991   EXPECT_EQ(result.dimension(0), batches);
    992 
    993   const int off_p = (kern_depth - 1) / 2;
    994   const int off_r = (kern_height - 1) / 2;
    995   const int off_c = (kern_width - 1) / 2;
    996 
    997   for (int b = 0; b < batches; b++) {
    998     for (int od = 0; od < kern_filters; ++od) {
    999       for (int i = 0; i < out_depth; ++i) {
   1000         for (int j = 0; j < out_height; ++j) {
   1001           for (int k = 0; k < out_width; ++k) {
   1002             float expected = 0.0f;
   1003             for (int c = 0; c < kern_width; ++c) {
   1004               for (int r = 0; r < kern_height; ++r) {
   1005                 for (int p = 0; p < kern_depth; ++p) {
   1006                   for (int id = 0; id < in_channels; ++id) {
   1007                     if (p - off_p + i >= 0 && r - off_r + j >= 0 &&
   1008                         c - off_c + k >= 0 && p - off_p + i < in_depth &&
   1009                         r - off_r + j < in_rows && c - off_c + k < in_cols) {
   1010                       expected += input(b, c - off_c + k, r - off_r + j,
   1011                                         p - off_p + i, id) *
   1012                                   kernel(c, r, p, id, od);
   1013                     }
   1014                   }
   1015                 }
   1016               }
   1017             }
   1018             EigenApprox(result(b, k, j, i, od), expected);
   1019           }
   1020         }
   1021       }
   1022     }
   1023   }
   1024 }
   1025 
   1026 TEST(EigenSpatialConvolutionsTest, StridedValidCuboid) {
   1027   const int in_channels = 10;
   1028   const int in_depth = 8;
   1029   const int in_rows = 7;
   1030   const int in_cols = 5;
   1031 
   1032   const int kern_filters = 7;
   1033   const int kern_depth = 3;
   1034   const int kern_width = 3;
   1035   const int kern_height = 3;
   1036 
   1037   const int out_depth = 3;
   1038   const int out_height = 3;
   1039   const int out_width = 2;
   1040 
   1041   Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols);
   1042   Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height,
   1043                           kern_width);
   1044   Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width);
   1045   input = input.constant(11.0f) + input.random();
   1046   kernel = kernel.constant(2.0f) + kernel.random();
   1047   result.setRandom();
   1048 
   1049   const int stride = 2;
   1050   result =
   1051       CuboidConvolution(input, kernel, stride, stride, stride, PADDING_VALID);
   1052 
   1053   EXPECT_EQ(result.dimension(0), kern_filters);
   1054   EXPECT_EQ(result.dimension(1), out_depth);
   1055   EXPECT_EQ(result.dimension(2), out_height);
   1056   EXPECT_EQ(result.dimension(3), out_width);
   1057 
   1058   for (int od = 0; od < kern_filters; ++od) {
   1059     for (int i = 0; i < out_depth; ++i) {
   1060       for (int j = 0; j < out_height; ++j) {
   1061         for (int k = 0; k < out_width; ++k) {
   1062           float expected = 0.0f;
   1063           for (int c = 0; c < kern_width; ++c) {
   1064             for (int r = 0; r < kern_height; ++r) {
   1065               for (int p = 0; p < kern_depth; ++p) {
   1066                 for (int id = 0; id < in_channels; ++id) {
   1067                   expected += input(id, p + stride * i, r + stride * j,
   1068                                     c + stride * k) *
   1069                               kernel(od, id, p, r, c);
   1070                 }
   1071               }
   1072             }
   1073           }
   1074           EigenApprox(result(od, i, j, k), expected);
   1075         }
   1076       }
   1077     }
   1078   }
   1079 }
   1080 
   1081 TEST(EigenSpatialConvolutionsTest, StridedValidCuboidRowMajor) {
   1082   const int in_channels = 10;
   1083   const int in_depth = 8;
   1084   const int in_rows = 7;
   1085   const int in_cols = 5;
   1086 
   1087   const int kern_filters = 7;
   1088   const int kern_depth = 3;
   1089   const int kern_width = 3;
   1090   const int kern_height = 3;
   1091 
   1092   const int out_depth = 3;
   1093   const int out_height = 3;
   1094   const int out_width = 2;
   1095 
   1096   Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels);
   1097   Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth,
   1098                                     in_channels, kern_filters);
   1099   Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth,
   1100                                     kern_filters);
   1101   input = input.constant(11.0f) + input.random();
   1102   kernel = kernel.constant(2.0f) + kernel.random();
   1103   result.setRandom();
   1104 
   1105   const int stride = 2;
   1106   result =
   1107       CuboidConvolution(input, kernel, stride, stride, stride, PADDING_VALID);
   1108 
   1109   EXPECT_EQ(result.dimension(3), kern_filters);
   1110   EXPECT_EQ(result.dimension(2), out_depth);
   1111   EXPECT_EQ(result.dimension(1), out_height);
   1112   EXPECT_EQ(result.dimension(0), out_width);
   1113 
   1114   for (int od = 0; od < kern_filters; ++od) {
   1115     for (int i = 0; i < out_depth; ++i) {
   1116       for (int j = 0; j < out_height; ++j) {
   1117         for (int k = 0; k < out_width; ++k) {
   1118           float expected = 0.0f;
   1119           for (int c = 0; c < kern_width; ++c) {
   1120             for (int r = 0; r < kern_height; ++r) {
   1121               for (int p = 0; p < kern_depth; ++p) {
   1122                 for (int id = 0; id < in_channels; ++id) {
   1123                   expected += input(c + stride * k, r + stride * j,
   1124                                     p + stride * i, id) *
   1125                               kernel(c, r, p, id, od);
   1126                 }
   1127               }
   1128             }
   1129           }
   1130           EigenApprox(result(k, j, i, od), expected);
   1131         }
   1132       }
   1133     }
   1134   }
   1135 }
   1136 
   1137 TEST(EigenSpatialConvolutionsTest, StridedSameCuboid) {
   1138   const int in_channels = 10;
   1139   const int in_depth = 8;
   1140   const int in_rows = 7;
   1141   const int in_cols = 5;
   1142 
   1143   const int kern_filters = 7;
   1144   const int kern_depth = 3;
   1145   const int kern_width = 3;
   1146   const int kern_height = 3;
   1147 
   1148   const int stride = 2;
   1149   const int out_depth = ceil_div(in_depth, stride);
   1150   const int out_height = ceil_div(in_rows, stride);
   1151   const int out_width = ceil_div(in_cols, stride);
   1152 
   1153   Tensor<float, 4> input(in_channels, in_depth, in_rows, in_cols);
   1154   Tensor<float, 5> kernel(kern_filters, in_channels, kern_depth, kern_height,
   1155                           kern_width);
   1156   Tensor<float, 4> result(kern_filters, out_depth, out_height, out_width);
   1157   input = input.constant(11.0f) + input.random();
   1158   kernel = kernel.constant(2.0f) + kernel.random();
   1159   result.setRandom();
   1160 
   1161   result =
   1162       CuboidConvolution(input, kernel, stride, stride, stride, PADDING_SAME);
   1163 
   1164   EXPECT_EQ(result.dimension(0), kern_filters);
   1165   EXPECT_EQ(result.dimension(1), out_depth);
   1166   EXPECT_EQ(result.dimension(2), out_height);
   1167   EXPECT_EQ(result.dimension(3), out_width);
   1168 
   1169   const int pad_p = (out_depth - 1) * stride - in_depth + kern_depth;
   1170   const int pad_r = (out_height - 1) * stride - in_rows + kern_height;
   1171   const int pad_c = (out_width - 1) * stride - in_cols + kern_width;
   1172 
   1173   // Number of pixels the input is extended with at the lower end in every
   1174   // dimension.
   1175   const int dp = pad_p / 2;
   1176   const int dr = pad_r / 2;
   1177   const int dc = pad_c / 2;
   1178 
   1179   for (int od = 0; od < kern_filters; ++od) {
   1180     for (int i = 0; i < out_depth; ++i) {
   1181       for (int j = 0; j < out_height; ++j) {
   1182         for (int k = 0; k < out_width; ++k) {
   1183           float expected = 0.0f;
   1184           for (int c = 0; c < kern_width; ++c) {
   1185             for (int r = 0; r < kern_height; ++r) {
   1186               for (int p = 0; p < kern_depth; ++p) {
   1187                 for (int id = 0; id < in_channels; ++id) {
   1188                   const int in_p = p - dp + i * stride;
   1189                   const int in_r = r - dr + j * stride;
   1190                   const int in_c = c - dc + k * stride;
   1191                   if (in_p >= 0 && in_r >= 0 && in_c >= 0 && in_p < in_depth &&
   1192                       in_r < in_rows && in_c < in_cols) {
   1193                     expected +=
   1194                         input(id, in_p, in_r, in_c) * kernel(od, id, p, r, c);
   1195                   }
   1196                 }
   1197               }
   1198             }
   1199           }
   1200           EigenApprox(result(od, i, j, k), expected);
   1201         }
   1202       }
   1203     }
   1204   }
   1205 }
   1206 
   1207 TEST(EigenSpatialConvolutionsTest, StridedSameCuboidRowMajor) {
   1208   const int in_channels = 10;
   1209   const int in_depth = 8;
   1210   const int in_rows = 7;
   1211   const int in_cols = 5;
   1212 
   1213   const int kern_filters = 7;
   1214   const int kern_depth = 3;
   1215   const int kern_width = 3;
   1216   const int kern_height = 3;
   1217 
   1218   const int stride = 2;
   1219   const int out_depth = ceil_div(in_depth, stride);
   1220   const int out_height = ceil_div(in_rows, stride);
   1221   const int out_width = ceil_div(in_cols, stride);
   1222 
   1223   Tensor<float, 4, RowMajor> input(in_cols, in_rows, in_depth, in_channels);
   1224   Tensor<float, 5, RowMajor> kernel(kern_width, kern_height, kern_depth,
   1225                                     in_channels, kern_filters);
   1226   Tensor<float, 4, RowMajor> result(out_width, out_height, out_depth,
   1227                                     kern_filters);
   1228   input = input.constant(11.0f) + input.random();
   1229   kernel = kernel.constant(2.0f) + kernel.random();
   1230   result.setRandom();
   1231 
   1232   result =
   1233       CuboidConvolution(input, kernel, stride, stride, stride, PADDING_SAME);
   1234 
   1235   EXPECT_EQ(result.dimension(3), kern_filters);
   1236   EXPECT_EQ(result.dimension(2), out_depth);
   1237   EXPECT_EQ(result.dimension(1), out_height);
   1238   EXPECT_EQ(result.dimension(0), out_width);
   1239 
   1240   const int pad_p = (out_depth - 1) * stride - in_depth + kern_depth;
   1241   const int pad_r = (out_height - 1) * stride - in_rows + kern_height;
   1242   const int pad_c = (out_width - 1) * stride - in_cols + kern_width;
   1243 
   1244   // Number of pixels the input is extended with at the lower end in every
   1245   // dimension.
   1246   const int dp = pad_p / 2;
   1247   const int dr = pad_r / 2;
   1248   const int dc = pad_c / 2;
   1249 
   1250   for (int od = 0; od < kern_filters; ++od) {
   1251     for (int i = 0; i < out_depth; ++i) {
   1252       for (int j = 0; j < out_height; ++j) {
   1253         for (int k = 0; k < out_width; ++k) {
   1254           float expected = 0.0f;
   1255           for (int c = 0; c < kern_width; ++c) {
   1256             for (int r = 0; r < kern_height; ++r) {
   1257               for (int p = 0; p < kern_depth; ++p) {
   1258                 for (int id = 0; id < in_channels; ++id) {
   1259                   const int in_p = p - dp + i * stride;
   1260                   const int in_r = r - dr + j * stride;
   1261                   const int in_c = c - dc + k * stride;
   1262                   if (in_p >= 0 && in_r >= 0 && in_c >= 0 && in_p < in_depth &&
   1263                       in_r < in_rows && in_c < in_cols) {
   1264                     expected +=
   1265                         input(in_c, in_r, in_p, id) * kernel(c, r, p, id, od);
   1266                   }
   1267                 }
   1268               }
   1269             }
   1270           }
   1271           EigenApprox(result(k, j, i, od), expected);
   1272         }
   1273       }
   1274     }
   1275   }
   1276 }
   1277 
   1278 // A test case discovered when testing backward spatial convolution where the
   1279 // special tensor contraction mapper for spatial convolution contains a bug.
   1280 TEST(EigenSpatialConvolutionsTest, SpatialConvContractionMapper) {
   1281   // We have a 3x4 input image with 2x2 patch and stride of 2.
   1282   // The output has size 1x2.
   1283   typedef Tensor<float, 1>::DimensionPair DimPair;
   1284   Tensor<float, 4> out(1, 1, 2, 1);
   1285   Tensor<float, 4> kern(1, 1, 2, 2);
   1286   for (int i = 0; i < kern.size(); ++i) {
   1287     kern.coeffRef(i) = static_cast<float>(i) + 1;
   1288   }
   1289   for (int i = 0; i < out.size(); ++i) {
   1290     out.coeffRef(i) = static_cast<float>(i) + 1;
   1291   }
   1292 
   1293   DSizes<ptrdiff_t, 4> strides;
   1294   strides[0] = 1;
   1295   strides[1] = 2;
   1296   strides[2] = 2;
   1297   strides[3] = 1;
   1298 
   1299   array<std::pair<ptrdiff_t, ptrdiff_t>, 4> paddings;
   1300   paddings[0] = std::make_pair(0, 0);
   1301   paddings[1] = std::make_pair(1, 2);
   1302   paddings[2] = std::make_pair(1, 1);
   1303   paddings[3] = std::make_pair(0, 0);
   1304 
   1305   DSizes<ptrdiff_t, 3> out_dim;
   1306   out_dim[0] = 1;
   1307   out_dim[1] = 4;
   1308   out_dim[2] = 12;
   1309 
   1310   array<bool, 4> kernel_reverse;
   1311   kernel_reverse[0] = false;
   1312   kernel_reverse[1] = false;
   1313   kernel_reverse[2] = true;
   1314   kernel_reverse[3] = true;
   1315 
   1316   DSizes<ptrdiff_t, 3> k_dims;
   1317   k_dims[0] = 1;
   1318   k_dims[1] = 1;
   1319   k_dims[2] = 4;
   1320 
   1321   array<DimPair, 2> contract_dims;
   1322   contract_dims[0] = DimPair(0, 0);
   1323   contract_dims[1] = DimPair(2, 1);
   1324 
   1325   DSizes<ptrdiff_t, 4> in_dim;
   1326   in_dim[0] = 1;
   1327   in_dim[1] = 3;
   1328   in_dim[2] = 4;
   1329   in_dim[3] = 1;
   1330 
   1331   DSizes<ptrdiff_t, 2> in_dbg_dim;
   1332   in_dbg_dim[0] = 3;
   1333   in_dbg_dim[1] = 4;
   1334 
   1335   DSizes<ptrdiff_t, 2> out_dbg_dim;
   1336   out_dbg_dim[0] = 4;
   1337   out_dbg_dim[1] = 12;
   1338 
   1339   // This is the formula for computing the backward prop for input with a
   1340   // spatial convolution.
   1341   Tensor<float, 4> direct =
   1342       kern.reverse(kernel_reverse)
   1343           .reshape(k_dims)
   1344           .contract(
   1345               out.extract_image_patches(2, 2, 1, 1, 1, 1, 2, 2, 1, 2, 1, 1, 0)
   1346                   .reshape(out_dim),
   1347               contract_dims)
   1348           .reshape(in_dim);
   1349 
   1350   Tensor<float, 4> indirect =
   1351       kern.reverse(kernel_reverse)
   1352           .reshape(k_dims)
   1353           .contract(
   1354               out.inflate(strides)
   1355                   .pad(paddings)
   1356                   .extract_image_patches(2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0)
   1357                   .reshape(out_dim),
   1358               contract_dims)
   1359           .reshape(in_dim);
   1360 
   1361   eigen_assert(dimensions_match(direct.dimensions(), indirect.dimensions()));
   1362   for (size_t i = 0; i < direct.dimensions().TotalSize(); ++i) {
   1363     EigenApprox(direct.data()[i], indirect.data()[i]);
   1364   }
   1365   EigenApprox(1.0f, direct(0, 0, 0, 0));
   1366   EigenApprox(3.0f, direct(0, 0, 1, 0));
   1367   EigenApprox(2.0f, direct(0, 0, 2, 0));
   1368   EigenApprox(6.0f, direct(0, 0, 3, 0));
   1369 
   1370   EigenApprox(2.0f, direct(0, 1, 0, 0));
   1371   EigenApprox(4.0f, direct(0, 1, 1, 0));
   1372   EigenApprox(4.0f, direct(0, 1, 2, 0));
   1373   EigenApprox(8.0f, direct(0, 1, 3, 0));
   1374 }
   1375 
   1376 }  // namespace Eigen
   1377