#-------------------------------------------------------------
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.
#
#-------------------------------------------------------------

/*
 * Local Response Normalization (LRN) layer.
 */

forward = function(matrix[double] X, int C, int Hin, int Win,
                   int N, double alpha, double beta, double K)
    return (matrix[double] Y) {
  /*
   * Computes the forward pass for a Local Response Normalization
   * (LRN) layer.  The LRN layer performs a lateral normalization
   * over channels at each spatial location.
   *
   * This is the cross-channel LRN used in AlexNet:
   * `y_{x,y}^i = x_{x,y}^i / (K + alpha * sum_{j=max(0,i-n/2)}^{min(C-1,i+n/2)} (x_{x,y}^j)^2)^beta`
   *
   * Inputs:
   *  - X: Inputs, of shape (N, C*Hin*Win).
   *  - C: Number of input channels.
   *  - Hin: Input height.
   *  - Win: Input width.
   *  - N: Number of channels to sum over (i.e. size of local region).
   *  - alpha: Scaling parameter.
   *  - beta: Exponent parameter.
   *  - K: Additive constant to avoid divide-by-zero.
   *
   * Outputs:
   *  - Y: Outputs, of shape (N, C*Hin*Win).
   */
  N_batch = nrow(X)
  
  # Initialize output
  Y = matrix(0, rows=N_batch, cols=C*Hin*Win)
  
  # Reshape for easier manipulation
  X_reshaped = matrix(X, rows=N_batch, cols=C*Hin*Win)
  
  # Compute normalization
  half_N = as.integer(N / 2)
  
  for (i in 1:N_batch) {
    # Get current sample
    x = matrix(X_reshaped[i,], rows=C, cols=Hin*Win, byrow=TRUE)
    y = matrix(0, rows=C, cols=Hin*Win)
    
    # For each channel
    for (c in 1:C) {
      # Define the local region
      j_start = max(1, c - half_N)
      j_end = min(C, c + half_N)
      
      # Compute sum of squares in the local region
      scale = matrix(K, rows=1, cols=Hin*Win)
      for (j in j_start:j_end) {
        scale = scale + alpha * (x[j,])^2
      }
      
      # Apply normalization
      y[c,] = x[c,] / (scale^beta)
    }
    
    # Reshape back and store
    Y[i,] = matrix(y, rows=1, cols=C*Hin*Win, byrow=TRUE)
  }
}

backward = function(matrix[double] dY, matrix[double] X, int C, int Hin, int Win,
                    int N, double alpha, double beta, double K)
    return (matrix[double] dX) {
  /*
   * Computes the backward pass for a Local Response Normalization layer.
   *
   * Inputs:
   *  - dY: Gradient wrt Y, of shape (N, C*Hin*Win).
   *  - X: Inputs, of shape (N, C*Hin*Win).
   *  - C: Number of input channels.
   *  - Hin: Input height.
   *  - Win: Input width.
   *  - N: Number of channels to sum over.
   *  - alpha: Scaling parameter.
   *  - beta: Exponent parameter.
   *  - K: Additive constant.
   *
   * Outputs:
   *  - dX: Gradient wrt X, of shape (N, C*Hin*Win).
   */
  N_batch = nrow(X)
  
  # Initialize gradient
  dX = matrix(0, rows=N_batch, cols=C*Hin*Win)
  
  # Reshape for easier manipulation
  X_reshaped = matrix(X, rows=N_batch, cols=C*Hin*Win)
  dY_reshaped = matrix(dY, rows=N_batch, cols=C*Hin*Win)
  
  half_N = as.integer(N / 2)
  
  for (i in 1:N_batch) {
    # Get current sample
    x = matrix(X_reshaped[i,], rows=C, cols=Hin*Win, byrow=TRUE)
    dy = matrix(dY_reshaped[i,], rows=C, cols=Hin*Win, byrow=TRUE)
    dx = matrix(0, rows=C, cols=Hin*Win)
    
    # First, compute the scale values for all channels
    scale = matrix(K, rows=C, cols=Hin*Win)
    for (c in 1:C) {
      j_start = max(1, c - half_N)
      j_end = min(C, c + half_N)
      for (j in j_start:j_end) {
        scale[c,] = scale[c,] + alpha * (x[j,])^2
      }
    }
    
    # Compute gradients
    for (c in 1:C) {
      # Channels that this channel influences
      k_start = max(1, c - half_N)
      k_end = min(C, c + half_N)
      
      for (k in k_start:k_end) {
        if (k == c) {
          # Gradient from own normalization
          dx[c,] = dx[c,] + dy[k,] * scale[k,]^(-beta)
        }
        # Gradient from normalizing other channels
        dx[c,] = dx[c,] - 2 * alpha * beta * dy[k,] * x[k,] * x[c,] * scale[k,]^(-beta-1)
      }
    }
    
    # Reshape back and store
    dX[i,] = matrix(dx, rows=1, cols=C*Hin*Win, byrow=TRUE)
  }
} 