local Gram, parent = torch.class('nn.GramMatrix', 'nn.Module')


--[[
A layer to compute the Gram Matrix of inputs.

Input:
- features: A tensor of shape (N, C, H, W) or (C, H, W) giving features for
  either a single image or a minibatch of images.

Output:
- gram: A tensor of shape (N, C, C) or (C, C) giving Gram matrix for input.
--]]


function Gram:__init(normalize)
  parent.__init(self)
  if normalize ~= nil then
    assert(type(normalize) == 'boolean', 'normalize has to be true/false')
    self.normalize = normalize
  else
    self.normalize = true
  end
  self.buffer = torch.Tensor()
end


function Gram:updateOutput(input)
  local C, H, W
  if input:dim() == 3 then
    C, H, W = input:size(1), input:size(2), input:size(3)
    local x_flat = input:view(C, H * W)
    self.output:resize(C, C)
    self.output:mm(x_flat, x_flat:t())
  elseif input:dim() == 4 then
    local N = input:size(1)
    C, H, W = input:size(2), input:size(3), input:size(4)
    self.output:resize(N, C, C)
    local x_flat = input:view(N, C, H * W)
    self.output:resize(N, C, C)
    self.output:bmm(x_flat, x_flat:transpose(2, 3))
  end
  if self.normalize then
    -- print('in gram forward; dividing by ', C * H * W)
    self.output:div(C * H * W)
  end
  return self.output
end


function Gram:updateGradInput(input, gradOutput)
  self.gradInput:resizeAs(input):zero()
  local C, H, W
  if input:dim() == 3 then
    C, H, W = input:size(1), input:size(2), input:size(3)
    local x_flat = input:view(C, H * W)
    self.buffer:resize(C, H * W)
    self.buffer:mm(gradOutput, x_flat)
    self.buffer:addmm(gradOutput:t(), x_flat)
    self.gradInput = self.buffer:view(C, H, W)
  elseif input:dim() == 4 then
    local N = input:size(1)
    C, H, W = input:size(2), input:size(3), input:size(4)
    local x_flat = input:view(N, C, H * W)
    self.buffer:resize(N, C, H * W)
    self.buffer:bmm(gradOutput, x_flat)
    self.buffer:baddbmm(gradOutput:transpose(2, 3), x_flat)
    self.gradInput = self.buffer:view(N, C, H, W)
  end
  if self.normalize then
    self.buffer:div(C * H * W)
  end
  assert(self.gradInput:isContiguous())
  return self.gradInput
end

