text: import torch
  import torch.nn as nn
  import numpy as np
  
  # Expression pinBallLoss(const Expression& out_ex, const Expression& actuals_ex) {//used by Dynet, learning loss function
  #   vector<Expression> losses;
  #   for (unsigned int indx = 0; indx<OUTPUT_SIZE; indx++) {
  #     auto forec = pick(out_ex, indx);
  #     auto actual = pick(actuals_ex, indx);
  #     if (as_scalar(actual.value()) > as_scalar(forec.value()))
  #       losses.push_back((actual - forec)*TRAINING_TAU);
  #     else
  #       losses.push_back((actual - forec)*(TRAINING_TAU - 1));
  #   }
  #   return sum(losses) / OUTPUT_SIZE * 2;
  # }
  
  #     as defined in the blog post --- https://eng.uber.com/m4-forecasting-competition/
  
  
  class PinballLoss(nn.Module):
  
      def __init__(self, training_tau, output_size, device):
          super(PinballLoss, self).__init__()
          self.training_tau = training_tau
          self.output_size = output_size
          self.device = device
  
      def forward(self, predictions, actuals):
          cond = torch.zeros_like(predictions).to(self.device)
          loss = torch.sub(actuals, predictions).to(self.device)
  
          less_than = torch.mul(loss, torch.mul(torch.gt(loss, cond).type(torch.FloatTensor).to(self.device),
                                                self.training_tau))
  
          greater_than = torch.mul(loss, torch.mul(torch.lt(loss, cond).type(torch.FloatTensor).to(self.device),
                                                   (self.training_tau - 1)))
  
          final_loss = torch.add(less_than, greater_than)
          # losses = []
          # for i in range(self.output_size):
          #     prediction = predictions[i]
          #     actual = actuals[i]
          #     if actual > prediction:
          #         losses.append((actual - prediction) * self.training_tau)
          #     else:
          #         losses.append((actual - prediction) * (self.training_tau - 1))
          # loss = torch.Tensor(losses)
          return torch.sum(final_loss) / self.output_size * 2
  
  
  # test1 = torch.rand(100)
  # test2 = torch.rand(100)
  # pb = PinballLoss(0.5, 100)
  # pb(test1, test2)
  
  
  ### sMAPE
  
  # float sMAPE(vector<float>& out_vect, vector<float>& actuals_vect) {
  #   float sumf = 0;
  #   for (unsigned int indx = 0; indx<OUTPUT_SIZE; indx++) {
  #     auto forec = out_vect[indx];
  #     auto actual = actuals_vect[indx];
  #     sumf+=abs(forec-actual)/(abs(forec)+abs(actual));
  #   }
  #   return sumf / OUTPUT_SIZE * 200;
  # }
  
  
  def non_sMAPE(predictions, actuals, output_size):
      sumf = 0
      for i in range(output_size):
          prediction = predictions[i]
          actual = actuals[i]
          sumf += abs(prediction - actual) / (abs(prediction) + abs(actual))
      return sumf / output_size * 200
  
  
  def sMAPE(predictions, actuals, N):
      predictions = predictions.float()
      actuals = actuals.float()
      sumf = torch.sum(torch.abs(predictions - actuals) / (torch.abs(predictions) + torch.abs(actuals)))
      return ((2 * sumf) / N) * 100
  
  
  def np_sMAPE(predictions, actuals, N):
      predictions = torch.from_numpy(np.array(predictions))
      actuals = torch.from_numpy(np.array(actuals))
      return float(sMAPE(predictions, actuals, N))
  
  
  ### wQuantLoss
  
  # float wQuantLoss(vector<float>& out_vect, vector<float>& actuals_vect) {
  #   float sumf = 0; float suma=0;
  #   for (unsigned int indx = 0; indx<OUTPUT_SIZE; indx++) {
  #     auto forec = out_vect[indx];
  #     auto actual = actuals_vect[indx];
  #     suma+= abs(actual);
  #     if (actual > forec)
  #       sumf = sumf + (actual - forec)*TAU;
  #     else
  #       sumf = sumf + (actual - forec)*(TAU - 1);
  #   }
  #   return sumf / suma * 200;
  # }
  
  def wQuantLoss(predictions, actuals, output_size, training_tau):
      sumf = 0
      suma = 0
      for i in range(output_size):
          prediction = predictions[i]
          actual = actuals[i]
  
          suma += abs(actual)
          if (actual > prediction):
              sumf = sumf + (actual - prediction) * training_tau
          else:
              sumf = sumf + (actual - prediction) * (training_tau - 1)
  
      return sumf / suma * 200
  
  
  # test1 = torch.rand(100)
  # test2 = torch.rand(100)
  # wQuantLoss(test1, test2, 100, 0.5)
  
  
  ### ErrorFunc
  
  # float errorFunc(vector<float>& out_vect, vector<float>& actuals_vect) {
  #   if (PERCENTILE==50)
  #     return sMAPE(out_vect, actuals_vect);
  #   else
  #     return wQuantLoss(out_vect, actuals_vect);
  # }
  
  def errorFunc(predictions, actuals, output_size, percentile):
      if (percentile == 50):
          return sMAPE(predictions, actuals, output_size)
      else:
          return wQuantLoss(predictions, actuals, output_size, percentile / 100)
  
  
  # test1 = torch.rand(100)
  # test2 = torch.rand(100)
  # print(errorFunc(test1, test2, 100, 48))
  # print(wQuantLoss(test1, test2, 100, 0.48))
  # print(errorFunc(test1, test2, 100, 50))
  # print(sMAPE(test1, test2, 100))
  
  def main():
      # Test vectorized calculation
      test1 = torch.rand(100)
      test2 = torch.rand(100)
      cpu_loss = non_sMAPE(test1, test2, 100)
      vec_loss = sMAPE(test1, test2, 100)
  
  if __name__ == '__main__':
      main()
  