! Copyright 2024 - The Minton Group at Purdue University
! This file is part of Swiftest.
! Swiftest is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License 
! as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
! Swiftest is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty 
! of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
! You should have received a copy of the GNU General Public License along with Swiftest. 
! If not, see: https://www.gnu.org/licenses. 

module operators
   !! author: David A. Minton
   !!
   !! Custom operators, including
   !!   A .cross. B = Cross product of A(1:NDIM) and B(1:NDIM) 
   !!
   !! Each operator can also do element-wise computation on arrays of the form .mag. A(1:NDIM, 1:n)
   use globals
   implicit none
   public

   !********************************************************************************************************************************
   ! Interfaces for .cross. operator: Computes the cross product of two (NDIM) vectors or (NDIM,:) arrays 
   !********************************************************************************************************************************

   interface operator(.cross.)
      pure module function operator_cross_sp(A, B) result(C)
         !$omp declare simd(operator_cross_sp)
         implicit none
         real(SP), dimension(:), intent(in) :: A, B
         real(SP), dimension(NDIM) :: C
      end function operator_cross_sp

      pure module function operator_cross_dp(A, B) result(C)
         !$omp declare simd(operator_cross_dp)
         implicit none
         real(DP), dimension(:), intent(in) :: A, B
         real(DP), dimension(NDIM) :: C
      end function operator_cross_dp

#ifdef QUADPREC
      pure module function operator_cross_qp(A, B) result(C)
         !$omp declare simd(operator_cross_qp)
         implicit none
         real(QP), dimension(:), intent(in) :: A, B
         real(QP), dimension(NDIM) :: C
      end function operator_cross_qp
#endif

      pure module function operator_cross_i1b(A, B) result(C)
         !$omp declare simd(operator_cross_i1b)
         implicit none
         integer(I1B), dimension(:), intent(in) :: A, B
         integer(I1B), dimension(NDIM) :: C
      end function operator_cross_i1b

      pure module function operator_cross_i2b(A, B) result(C)
         !$omp declare simd(operator_cross_i2b)
         implicit none
         integer(I2B), dimension(:), intent(in) :: A, B
         integer(I2B), dimension(NDIM) :: C
      end function operator_cross_i2b

      pure module function operator_cross_i4b(A, B) result(C)
         !$omp declare simd(operator_cross_i4b)
         implicit none
         integer(I4B), dimension(:), intent(in) :: A, B
         integer(I4B), dimension(NDIM) :: C
      end function operator_cross_i4b

      pure module function operator_cross_i8b(A, B) result(C)
         !$omp declare simd(operator_cross_i8b)
         implicit none
         integer(I8B), dimension(:), intent(in) :: A, B
         integer(I8B), dimension(NDIM) :: C
      end function operator_cross_i8b

      pure module function operator_cross_el_sp(A, B) result(C)
         implicit none
         real(SP), dimension(:,:), intent(in) :: A, B
         real(SP), dimension(:,:), allocatable :: C
      end function operator_cross_el_sp

      pure module function operator_cross_el_dp(A, B) result(C)
         implicit none
         real(DP), dimension(:,:), intent(in) :: A, B
         real(DP), dimension(:,:), allocatable :: C
      end function operator_cross_el_dp

#ifdef QUADPREC
      pure module function operator_cross_el_qp(A, B) result(C)
         implicit none
         real(QP), dimension(:,:), intent(in) :: A, B
         real(QP), dimension(:,:), allocatable :: C
      end function operator_cross_el_qp
#endif

      pure module function operator_cross_el_i1b(A, B) result(C)
         implicit none
         integer(I1B), dimension(:,:), intent(in) :: A, B
         integer(I1B), dimension(:,:), allocatable :: C
      end function operator_cross_el_i1b

      pure module function operator_cross_el_i2b(A, B) result(C)
         implicit none
         integer(I2B), dimension(:,:), intent(in) :: A, B
         integer(I2B), dimension(:,:), allocatable :: C
      end function operator_cross_el_i2b

      pure module function operator_cross_el_i4b(A, B) result(C)
         implicit none
         integer(I4B), dimension(:,:), intent(in) :: A, B
         integer(I4B), dimension(:,:), allocatable :: C
      end function operator_cross_el_i4b

      pure module function operator_cross_el_i8b(A, B) result(C)
         implicit none
         integer(I8B), dimension(:,:), intent(in) :: A, B
         integer(I8B), dimension(:,:), allocatable :: C
      end function operator_cross_el_i8b
   end interface

   !********************************************************************************************************************************
   ! Interfaces for .mag. operator: Computes the magnitude of a vector or array of vectors using norm2
   !********************************************************************************************************************************

   interface operator(.mag.)
      pure module function operator_mag_sp(A) result(B)
         !$omp declare simd(operator_mag_sp)
         implicit none
         real(SP), dimension(:), intent(in) :: A
         real(SP)                           :: B
      end function operator_mag_sp

      pure module function operator_mag_dp(A) result(B)
         !$omp declare simd(operator_mag_dp)
         implicit none
         real(DP), dimension(:), intent(in) :: A
         real(DP)                           :: B
      end function operator_mag_dp

#ifdef QUADPREC
      pure module function operator_mag_qp(A) result(B)
         !$omp declare simd(operator_mag_qp)
         implicit none
         real(QP), dimension(:), intent(in) :: A
         real(QP)                           :: B
      end function operator_mag_qp
#endif
      pure module function operator_mag_el_sp(A) result(B)
         implicit none
         real(SP), dimension(:,:), intent(in) :: A
         real(SP), dimension(:), allocatable  :: B
      end function operator_mag_el_sp

      pure module function operator_mag_el_dp(A) result(B)
         implicit none
         real(DP), dimension(:,:), intent(in) :: A
         real(DP), dimension(:), allocatable  :: B
      end function operator_mag_el_dp

#ifdef QUADPREC
      pure module function operator_mag_el_qp(A) result(B)
         implicit none
         real(QP), dimension(:,:), intent(in) :: A
         real(QP), dimension(:), allocatable  :: B
      end function operator_mag_el_qp
#endif

   end interface


   !********************************************************************************************************************************
   ! Interfaces for .unit. operator: Returns a unit vector or array of unit vectors from an input vector or array of vectors
   !********************************************************************************************************************************

   interface operator(.unit.)
      pure module function operator_unit_sp(A) result(B)
         !$omp declare simd(operator_unit_sp)
         implicit none
         real(SP), dimension(:), intent(in)  :: A
         real(SP), dimension(NDIM)              :: B
      end function operator_unit_sp

      pure module function operator_unit_dp(A) result(B)
         !$omp declare simd(operator_unit_dp)
         implicit none
         real(DP), dimension(:), intent(in)  :: A
         real(DP), dimension(NDIM)              :: B
      end function operator_unit_dp

#ifdef QUADPREC
      pure module function operator_unit_qp(A) result(B)
         !$omp declare simd(operator_unit_qp)
         implicit none
         real(QP), dimension(:), intent(in)  :: A
         real(QP), dimension(NDIM)              :: B
      end function operator_unit_qp
#endif

      pure module function operator_unit_el_sp(A) result(B)
         implicit none
         real(SP), dimension(:,:), intent(in)  :: A
         real(SP), dimension(:,:), allocatable :: B
      end function operator_unit_el_sp

      pure module function operator_unit_el_dp(A) result(B)
         implicit none
         real(DP), dimension(:,:), intent(in) :: A
         real(DP), dimension(:,:), allocatable  :: B
      end function operator_unit_el_dp

#ifdef QUADPREC
      pure module function operator_unit_el_qp(A) result(B)
         implicit none
         real(QP), dimension(:,:), intent(in)  :: A
         real(QP), dimension(:,:), allocatable :: B
      end function operator_unit_el_qp
#endif
   end interface


end module operators
