code: def calc_ntxent_loss_manual(
      self, out0, out1, temperature: float
  ) -> float:
      # using the pseudocode directly from https://arxiv.org/pdf/2002.05709.pdf , Algorithm 1
  
      out0 = np.array(out0)
      out1 = np.array(out1)
  
      N = len(out0)
      z = np.concatenate([out0, out1], axis=0)
      # different to the notation in the paper, in our case z[k] and z[k+N]
      # are different augmentations of the same image
  
      s_i_j = np.zeros((2 * len(out0), 2 * len(out1)))
      for i in range(2 * N):
          for j in range(2 * N):
              sim = np.inner(z[i], z[j]) / (
                  np.linalg.norm(z[i]) * np.linalg.norm(z[j])
              )
              s_i_j[i, j] = sim
  
      exponential_i_j = np.exp(s_i_j / temperature)
  
      l_i_j = np.zeros_like(exponential_i_j)
      for i in range(2 * N):
          for j in range(2 * N):
              nominator = exponential_i_j[i, j]
              denominator = 0
              for k in range(2 * N):
                  if k != i:
                      denominator += exponential_i_j[i, k]
              l_i_j[i, j] = -1 * np.log(nominator / denominator)
  
      loss = 0
      for k in range(N):
          loss += l_i_j[k, k + N] + l_i_j[k + N, k]
      loss /= 2 * N
      return loss
  
imports: import unittest
  import numpy
  import pytest
  import torch
  from pytest_mock import MockerFixture
  from torch import distributed
  from lightly.loss import NTXentLoss
label_desc: Use image node_id 2 for calling the TestNTXentLossUnitTest obj's underlying member method code with example usage: obj.calc_ntxent_loss_manual(out0, out1, temperature) and returns: loss