! Copyright 2026 - 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. 

submodule(collision) s_collision_regime
   use swiftest
contains

   module subroutine collision_regime_collider(self, nbody_system, param)
      !! Author: Jennifer L.L. Pouplin, Carlisle A. Wishard, and David A. Minton 
      !!
      !! Determine which fragmentation regime the set of impactors will be. This subroutine is a wrapper for the non-polymorphic 
      !! fraggle_regime_collresolve subroutine.
      !! It converts to SI units prior to calling
      implicit none 
      ! Arguments
      class(collision_basic),   intent(inout) :: self         
         !! Collision system impactors object
      class(base_nbody_system), intent(in)    :: nbody_system 
         !! Swiftest nbody system object
      class(base_parameters),   intent(in)    :: param        
         !! Current Swiftest run configuration parameters
      ! Internals
      real (DP) :: mtot
      integer(I4B) :: i,jtarg, jproj
      
      associate(impactors => self%impactors)
      select type (nbody_system)
      class is (swiftest_nbody_system)
      select type(param)
      class is (swiftest_parameters)

         select case(param%collision_model)
         case("MERGE")
            impactors%regime = COLLRESOLVE_REGIME_MERGE
            if (allocated(impactors%mass_dist)) deallocate(impactors%mass_dist)
            allocate(impactors%mass_dist(1))
            impactors%mass_dist(1) = mtot
         case default
            if (impactors%mass(1) > impactors%mass(2)) then
               jtarg = 1
               jproj = 2
            else
               jtarg = 2
               jproj = 1
            end if

            if (impactors%mass(jtarg) / impactors%mass(jproj) > 500.0_DP) then
               call collision_regime_HG20(self, nbody_system, param) ! Cratering collisions. Mass ratio (proj:target) <~ 1:500 
            else 
               call collision_regime_LS12(self, nbody_system, param) ! Large planetary collisions. Mass ratio (proj:target) >= 1:500
            end if
            call collision_io_log_regime(self%impactors)
            
         end select
      end select
      end select
      end associate

      return
   end subroutine collision_regime_collider

   subroutine collision_regime_HG20(collider, nbody_system, param)
      !! Author: Kaustub P. Anand, David A. Minton
      !!
      !! Determine the collisional regime of two colliding bodies based on the model by Hyodo and Genda (2020)
      !!
      !! This is a wrapper subroutine that converts quantities to SI units and calls the main HG20 subroutine
      !! NOTE: Impact angle is converted to degrees
      !! structured similar to collision_regime_LS12

      implicit none
      ! Arguments
      class(collision_basic),       intent(inout) :: collider     
         !! The impactors to determine the regime for
      class(swiftest_nbody_system), intent(in)    :: nbody_system
         !! Swiftest n-body system object
      class(swiftest_parameters),   intent(in)    :: param        
         !! The current parameters

      ! Internals
      integer(I4B) :: i,jtarg, jproj
      real(DP), dimension(2) :: radius_si, mass_si
      real(DP) :: min_mfrag_si
      real(DP), dimension(NDIM)  :: x_tar_si, v_tar_si, x_imp_si, v_imp_si
      real(DP) :: mlr, mslr, mtot, Qloss, Qmerge
      integer(I4B), parameter :: NMASS_DIST = 3   ! Number of mass bins returned by the regime calculation (largest fragment, second
                                                ! largest, and remainder)  
      real(DP), dimension(NDIM) :: Ip, rot, L_rot
      real(DP) :: radius, volume


      associate(impactors => collider%impactors)

         ! Convert all quantities to SI units and determine which of the pair is the projectile vs. target before sending them to 
         ! the regime determination subroutine
         if (impactors%mass(1) > impactors%mass(2)) then
            jtarg = 1
            jproj = 2
         else
            jtarg = 2
            jproj = 1
         end if
         ! The two-body equivalent masses of the collider system
         mass_si(:)    = impactors%mass([jtarg, jproj]) * param%MU2KG 
         
         ! The two-body equivalent radii of the collider system
         radius_si(:)  = impactors%radius([jtarg, jproj]) * param%DU2M 

         ! The first body of the two-body equivalent position vector the collider system
         x_tar_si(:)      = impactors%rb(:,jtarg) * param%DU2M                   

         ! The first body of the two-body equivalent velocity vector the collider system
         v_tar_si(:)      = impactors%vb(:,jtarg) * param%DU2M / param%TU2S      

         ! The second body of the two-body equivalent position vector the collider system
         x_imp_si(:)      = impactors%rb(:,jproj) * param%DU2M                   

         ! The second body of the two-body equivalent velocity vector the collider system
         v_imp_si(:)      = impactors%vb(:,jproj) * param%DU2M / param%TU2S           
         
         ! The minimum fragment mass to generate. Collider systems that would otherwise generate less massive fragments than this 
         ! value will be forced to merge instead
         min_mfrag_si  = (param%min_GMfrag / param%GU) * param%MU2KG 
      
         mtot = sum(mass_si(:)) 

         ! Use the positions and velocities of the parents from indside the step (at collision) to calculate the collisional regime
         call collision_regime_HG20_SI(mass_si(jtarg), mass_si(jproj), radius_si(jtarg), &
                                       x_tar_si(:), x_imp_si(:), v_tar_si(:), v_imp_si(:), &
                                       min_mfrag_si, impactors%regime, mlr, mslr, Qloss, Qmerge)

         ! Convert back from SI to system units
         mlr = mlr / param%MU2KG
         mslr = mslr / param%MU2kg
         Qloss = Qloss * (param%TU2S / param%DU2M)**2 / param%MU2KG
         Qmerge = Qmerge * (param%TU2S / param%DU2M)**2 / param%MU2KG
         mtot = mtot / param%MU2kg

         ! If this is came back as a merger, check to make sure that the rotation of the merged body doesn't exceed the rotation 
         ! barrier
         if (impactors%regime == COLLRESOLVE_REGIME_MERGE) then
            volume = 4._DP / 3._DP * PI * sum(impactors%radius(:)**3)
            radius = (3._DP * volume / (4._DP * PI))**(THIRD)
#ifdef DOCONLOC
            do concurrent(i = 1:NDIM) shared(impactors,Ip,L_rot)
#else
            do concurrent(i = 1:NDIM)
#endif
               Ip(i) = sum(impactors%mass(:) * impactors%Ip(i,:)) 
               L_rot(i) = sum(impactors%L_orbit(i,:) + impactors%L_rot(i,:))
            end do
            Ip(:) = Ip(:) / mtot
            rot(:) = L_rot(:) * RAD2DEG / (Ip(3) * mtot * radius**2)
            if (.mag.rot(:) > collider%max_rot) then ! The merged body would rotation too fast, so reclasify this as a hit and run
               mlr = impactors%mass(jtarg)
               mslr = impactors%mass(jproj)
               impactors%regime = COLLRESOLVE_REGIME_HIT_AND_RUN
               impactors%Qloss = 0.0_DP
            else
               mlr =  mtot
               mslr = 0.0_DP
               impactors%Qloss = Qmerge
            end if
         else
            impactors%Qloss = Qloss
         end if

         if (allocated(impactors%mass_dist)) deallocate(impactors%mass_dist)
         allocate(impactors%mass_dist(NMASS_DIST))
         impactors%mass_dist(1) = min(max(mlr, 0.0_DP), mtot)
         impactors%mass_dist(2) = min(max(mslr, 0.0_DP), mtot)
         impactors%mass_dist(3) = min(max(mtot - mlr - mslr, 0.0_DP), mtot)

      end associate

      return

   end subroutine collision_regime_HG20

   subroutine collision_regime_HG20_SI(M_tar, M_imp, rad_tar, & 
                                          rh_tar, rh_imp, vb_tar, vb_imp, &
                                          min_mfrag, regime, Mlr, Mslr, Qloss, Qmerge)
      !! Author: Kaustub P. Anand, David A. Minton
      !!
      !! Determine the collisional regime of two colliding bodies when projectile mass is very small compared to target mass
      !! Current version requires all values to be converted to SI units prior to calling the function
      !!       References:
      !!       Hyodo, R., Genda, H., 2020. Escape and Accretion by Cratering Impacts: Formulation of Scaling Relations for 
      !!          High-speed Ejecta. ApJ 898 30. https://doi.org/10.3847/1538-4357/ab9897
      !!       Housen, K., Holsapple, K., 2011. Ejecta from Impact Craters. Icarus, Volume 211, Issue 1, 2011.
      !!          https://doi.org/10.1016/j.icarus.2010.09.017
      !!       Leinhardt, Z.M., Stewart, S.T., 2012. Collisions between Gravity-dominated Bodies. I. Outcome Regimes and 
      !!          Scaling Laws 745, 79. https://doi.org/10.1088/0004-637X/745/1/79
      !!

      implicit none
      ! Arguments
      real(DP), intent(in)           :: M_tar, M_imp, rad_tar, min_mfrag 
      real(DP), dimension(:), intent(in)  :: rh_tar, rh_imp, vb_tar, vb_imp
      real(DP), intent(out) :: Mlr, Mslr
         !! Largest and second-largest remnant defined for various regimes
      real(DP), intent(out)          :: Qloss
         !! The energy lost in the collision if it was a fragmentation event
      real(DP), intent(out)          :: Qmerge 
         !! The energy lost in the collision if it was a perfect merger
      integer(I4B), intent(out) :: regime
         !! The collisional regime

      ! Constants
      real(DP), parameter :: a_tar = 4.12e-5_DP
         !! Best-fit parameter for ejection velocity mu_HG20(theta) for the target body (Table 1 in HG20) 
      real(DP), parameter :: b_tar = 2.71e-3_DP
         !! Best-fit parameter for ejection velocity mu_HG20(theta) for the target body (Table 1 in HG20)
      real(DP), parameter :: c_tar = 5.13e-1_DP
         !! Best-fit parameter for ejection velocity mu_HG20(theta) for the target body (Table 1 in HG20)
      real(DP), parameter :: d_tar = 1.52e-5_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the target body (Table 1 in HG20)
      real(DP), parameter :: e_tar = -3.21e-3_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the target body (Table 1 in HG20)
      real(DP), parameter :: f_tar = 1.41e-1_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the target body (Table 1 in HG20)
      real(DP), parameter :: g_tar = -4.02_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the target body (Table 1 in HG20)

      real(DP), parameter :: a_imp = -7.06e-5_DP
         !! Best-fit parameter for ejection velocity mu_HG20(theta) for the impactor body (Table 1 in HG20)
      real(DP), parameter :: b_imp = 1.54e-2_DP
         !! Best-fit parameter for ejection velocity mu_HG20(theta) for the impactor body (Table 1 in HG20)
      real(DP), parameter :: c_imp = -1.93e-1_DP
         !! Best-fit parameter for ejection velocity mu_HG20(theta) for the impactor body (Table 1 in HG20)
      real(DP), parameter :: d_imp = 3.34e-5_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the impactor body (Table 1 in HG20)
      real(DP), parameter :: e_imp = -5.13e-3_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the impactor body (Table 1 in HG20)
      real(DP), parameter :: f_imp = 1.28e-1_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the impactor body (Table 1 in HG20)
      real(DP), parameter :: g_imp = -8.71e-1_DP
         !! Best-fit parameter for ejecta C_HG20(theta) for the impactor body (Table 1 in HG20)

      real(DP), parameter :: k = 0.3_DP
         !! material property constant from Housen and Holsapple (2011) (HH11)
      real(DP), parameter :: C_0 = 1.5_DP
         !! material property constant from Housen and Holsapple (2011) (HH11)
      real(DP), parameter :: mu_HH11 = 0.55_DP
         !! material property constant from Housen and Holsapple (2011) (HH11)
         !! 0.55 for nonporous rocky and icy materials

      real(DP), parameter   :: BETA = 2.85_DP 
         !! slope of sfd for remnants from LS12 2.85
      integer(I4B), parameter :: N1 = 1 
         !! number of objects with mass equal to the largest remnant from LS12
      integer(I4B), parameter :: N2 = 2  
         !! number of objects with mass larger than second largest remnant from LS12
      real(DP), parameter   :: DENSITY1 = 1000.0_DP 
         !! standard density parameter from LS12 [kg/m3]

      ! Internals
      real(DP) :: M_tot, M_esc_tar_HG20, M_esc_tar_HH11, M_esc_tar, M_esc_imp, M_acc_imp, M_esc_total, M_tmp
      real(DP) :: Rc1, c_star, ke, pe, U_binding
      real(DP) :: V_imp, V_esc
      real(DP) :: theta_rad, theta
      real(DP) :: C_HH11, C_HG20_tar, C_HG20_imp, mu_HG20_tar, mu_HG20_imp
      
      M_tot = M_tar + M_imp
      Rc1 = (3 * M_tot / (4 * PI * DENSITY1))**THIRD ! Stewart and Leinhardt (2009) 
      c_star = calc_c_star(Rc1)

      V_imp = norm2(vb_imp(:) - vb_tar(:)) ! Impact velocity
      theta_rad = calc_theta(rh_tar, vb_tar, rh_imp, vb_imp) ! Impact angle in radians
      theta = theta_rad * RAD2DEG ! Impact angle in degrees
      V_esc = sqrt(2 * GC * M_tar / rad_tar) ! Escape velocity of the target body

      ! Specific binding energy
      U_binding = (3 * GC * M_tot) / (5 * Rc1) ! LS12 eq. 27
      ke = 0.5_DP * V_imp**2
      pe = - GC * M_tar * M_imp / (M_tot * norm2(rh_imp - rh_tar))

      ! Calculate target body ejecta mass that escapes from the target body 

      mu_HG20_tar = a_tar * theta**2 + b_tar * theta + c_tar
      C_HG20_tar = exp(d_tar * theta**3 + e_tar * theta**2 + f_tar * theta + g_tar)
      M_esc_tar_HG20 = C_HG20_tar * (V_esc / (V_imp * sin(theta_rad)))**(-3.0_DP * mu_HG20_tar) ! impactor mass units

      C_HH11 = 3 * k / (4 * PI) * C_0**(3.0_DP * mu_HH11)
      M_esc_tar_HH11 = C_HH11 * (V_esc / (V_imp * sin(theta_rad)))**(-3.0_DP * mu_HH11) ! impactor mass units

      M_esc_tar = min(M_esc_tar_HG20, M_esc_tar_HH11) ! impactor mass units
      M_esc_tar = max(M_esc_tar, 0.0_DP) ! Min value of M_esc_tar is 0.0 (impactor mass units)

      ! Calculate impactor body ejecta mass that escapes from the target

      if (theta < 15.0_DP) then ! Below table 1
         mu_HG20_imp = 0.0_DP
         C_HG20_imp = 1.0_DP
      else
         mu_HG20_imp = a_imp * theta**2 + b_imp * theta + c_imp
         C_HG20_imp = exp(d_imp * theta**3 + e_imp * theta**2 + f_imp * theta + g_imp) 
      end if

      M_esc_imp = C_HG20_imp * (V_esc / (V_imp * sin(theta_rad)))**(-3.0_DP * mu_HG20_imp) ! impactor mass units
      M_esc_imp = min(M_esc_imp, 1.0_DP) ! Max value of M_esc_imp is 1.0 (impactor mass units)
      M_esc_imp = max(M_esc_imp, 0.0_DP) ! Min value of M_esc_imp is 0.0 (impactor mass units)

      M_esc_total = M_esc_tar + M_esc_imp ! impactor mass units

      ! Find collisional regime (MERGE or EROSION/DISRUPTION)

      if ((M_tar < min_mfrag).or.(M_imp < min_mfrag)) then 
         regime = COLLRESOLVE_REGIME_MERGE !perfect merging regime
         call swiftest_io_log_one_message(COLLISION_LOG_OUT, &
                                 "Fragments would have mass below the minimum. Converting this collision into a merger.")
      else 
         if (M_esc_total < 1.0_DP) then 
            regime = COLLRESOLVE_REGIME_MERGE ! Net accretion regime       ! does partial accretion need a separate check?
         else if (M_esc_total >= 1.0_DP) then
            regime = COLLRESOLVE_REGIME_DISRUPTION ! Net erosion regime
         else 
            call swiftest_io_log_one_message(COLLISION_LOG_OUT,"Error no regime found in symba_regime")
         end if
      end if
      
      ! Convert from impactor mass units to system mass units
      M_esc_total = M_esc_total * M_imp
      M_esc_tar = M_esc_tar * M_imp
      M_esc_imp = M_esc_imp * M_imp

      M_acc_imp = M_imp - M_esc_imp ! impactor material accreted by the target body

      ! Calculate largest and second-largest remnant masses and energy loss
      Mlr = M_tar - M_esc_tar + M_acc_imp

      Mslr = max(M_tot * (3.0_DP - BETA) * (1.0_DP - N1 * Mlr / M_tot) / (N2 * BETA), min_mfrag)  !LS12 eq (37)

      if (Mslr > Mlr) then ! The second-largest fragment is actually larger than the largest, so we will swap them
         M_tmp = Mlr
         Mlr = Mslr
         Mslr = M_tmp
      end if
      
      Qloss = (c_star - 1.0_DP) * U_binding * M_tot ! Convert specific energy loss to total energy loss in the system
      Qmerge = (ke + pe + U_binding) * M_tot ! The  energy lost if this were a perfect merger

      return
      
      contains

         function calc_theta(r_tar, v_tar, r_imp, v_imp) result(theta)
            !! author: Kaustub P. Anand, Jennifer L. L. Pouplin, David A. Minton
            !!
            !! Calculate the impact angle between two colliding bodies
            !! For HG20, theta = 90 degrees - asin(b) where b is the impact parameter 
            !! (if impactor radius << target radius)
            !! 90 degrees is a head-on collision, angle calculated from horizon
            !!
            implicit none
            ! Arguments
            real(DP), dimension(NDIM), intent(in) :: r_tar, v_tar, r_imp, v_imp
            ! Result
            real(DP) :: theta ! radians
            ! Internals
            real(DP), dimension(NDIM)  :: imp_vel, distance

            imp_vel(:) = v_imp(:) - v_tar(:)
            distance(:) = r_imp(:) - r_tar(:)

            theta = dot_product(distance(:), imp_vel(:)) / (.mag.distance(:) * .mag.imp_vel(:))

            if (abs(theta) >= 1.0_DP) then
               theta = sign(1.0_DP, theta) 
            end if

            theta = acos(theta) - PIBY2 

            return
         end function calc_theta

         function calc_c_star(Rc1) result(c_star)
            !! author: David A. Minton
            !!
            !! Calculates c_star as a function of impact equivalent radius. It interpolates between 5 for ~1 km sized bodies to
            !! 1.8 for ~10000 km sized bodies. See LS12 Fig. 4 for details.
            !! 
            implicit none
            ! Arguments
            real(DP), intent(in) :: Rc1
            ! Result
            real(DP)             :: c_star
            ! Internals
            real(DP), parameter  :: loR   = 1.0e3_DP ! Lower bound of interpolation size (m)
            real(DP), parameter  :: hiR   = 1.0e7_DP ! Upper bound of interpolation size (m)
            real(DP), parameter  :: loval = 5.0_DP   ! Value of C* at lower bound
            real(DP), parameter  :: hival = 1.9_DP   ! Value of C* at upper bound

            if (Rc1 < loR) then
               c_star = loval
            else if (Rc1 < hiR) then
               c_star = loval + (hival - loval) * log(Rc1 / loR) / log(hiR /loR)
            else
               c_star = hival
            end if
            return
         end function calc_c_star 

   end subroutine collision_regime_HG20_SI


   subroutine collision_regime_LS12(collider, nbody_system, param) 
      !! Author: Jennifer L.L. Pouplin, Carlisle A. Wishard, and David A. Minton 
      !!
      !! Determine the collisional regime of two colliding bodies based on the model by Leinhard and Stewart (2012)
      !!
      !! This is a wrapper subroutine that converts quantities to SI units and calls the main LS12 subroutine
      implicit none
      ! Arguments
      class(collision_basic),       intent(inout) :: collider     
         !! The impactors to determine the regime for
      class(swiftest_nbody_system), intent(in)    :: nbody_system 
         !! Swiftest n-body system object
      class(swiftest_parameters),   intent(in)    :: param        
         !! The current parameters
      ! Internals
      integer(I4B) :: i,jtarg, jproj
      real(DP), dimension(2) :: radius_si, mass_si, density_si
      real(DP) :: min_mfrag_si, Mcb_si
      real(DP), dimension(NDIM)  :: x1_si, v1_si, x2_si, v2_si
      real(DP) :: mlr, mslr, mslr_hr, mtot, dentot, Qloss, Qmerge
      integer(I4B), parameter :: NMASS_DIST = 3 ! Number of mass bins returned by the regime calculation (largest fragment, second
                                                ! largest, and remainder)  
      real(DP), dimension(NDIM) :: Ip, rot, L_rot
      real(DP) :: radius, volume
      
      associate(impactors => collider%impactors)

         ! Convert all quantities to SI units and determine which of the pair is the projectile vs. target before sending them to 
         ! the regime determination subroutine
         if (impactors%mass(1) > impactors%mass(2)) then
            jtarg = 1
            jproj = 2
         else
            jtarg = 2
            jproj = 1
         end if
         ! The two-body equivalent masses of the collider system
         mass_si(:)    = impactors%mass([jtarg, jproj]) * param%MU2KG 
         
         ! The two-body equivalent radii of the collider system
         radius_si(:)  = impactors%radius([jtarg, jproj]) * param%DU2M 

         ! The two-body equivalent density of the collider system
         density_si(:) = mass_si(:) / (4.0_DP / 3._DP * PI * radius_si(:)**3) 

         ! The first body of the two-body equivalent position vector the collider system
         x1_si(:)      = impactors%rb(:,jtarg) * param%DU2M                   

         ! The first body of the two-body equivalent velocity vector the collider system
         v1_si(:)      = impactors%vb(:,jtarg) * param%DU2M / param%TU2S      

         ! The second body of the two-body equivalent position vector the collider system
         x2_si(:)      = impactors%rb(:,jproj) * param%DU2M                   

         ! The second body of the two-body equivalent velocity vector the collider system
         v2_si(:)      = impactors%vb(:,jproj) * param%DU2M / param%TU2S      

         ! The central body mass of the system
         Mcb_si        = nbody_system%cb%mass * param%MU2KG           
         
         ! The minimum fragment mass to generate. Collider systems that would otherwise generate less massive fragments than this 
         ! value will be forced to merge instead
         min_mfrag_si  = (param%min_GMfrag / param%GU) * param%MU2KG 
      
         mtot = sum(mass_si(:)) 
         dentot = sum(mass_si(:) * density_si(:)) / mtot 

         ! Use the positions and velocities of the parents from indside the step (at collision) to calculate the collisional regime
         call collision_regime_LS12_SI(Mcb_si, mass_si(jtarg), mass_si(jproj), radius_si(jtarg), radius_si(jproj), &
                                       x1_si(:), x2_si(:), v1_si(:), v2_si(:), density_si(jtarg), density_si(jproj), &
                                       min_mfrag_si, impactors%regime, mlr, mslr, mslr_hr, Qloss, Qmerge)

         ! Convert back from SI to system units
         mlr = mlr / param%MU2KG
         mslr = mslr / param%MU2kg
         mslr_hr = mslr_hr / param%MU2kg
         Qloss = Qloss * (param%TU2S / param%DU2M)**2 / param%MU2KG
         Qmerge = Qmerge * (param%TU2S / param%DU2M)**2 / param%MU2KG
         mtot = mtot / param%MU2kg

         ! If this is came back as a merger, check to make sure that the rotation of the merged body doesn't exceed the rotation 
         ! barrier
         if (impactors%regime == COLLRESOLVE_REGIME_MERGE) then
            volume = 4._DP / 3._DP * PI * sum(impactors%radius(:)**3)
            radius = (3._DP * volume / (4._DP * PI))**(THIRD)
#ifdef DOCONLOC
            do concurrent(i = 1:NDIM) shared(impactors,Ip,L_rot)
#else
            do concurrent(i = 1:NDIM)
#endif
               Ip(i) = sum(impactors%mass(:) * impactors%Ip(i,:)) 
               L_rot(i) = sum(impactors%L_orbit(i,:) + impactors%L_rot(i,:))
            end do
            Ip(:) = Ip(:) / mtot
            rot(:) = L_rot(:) * RAD2DEG / (Ip(3) * mtot * radius**2)
            if (.mag.rot(:) > collider%max_rot) then ! The merged body would rotation too fast, so reclasify this as a hit and run
               mlr = impactors%mass(jtarg)
               mslr = impactors%mass(jproj)
               impactors%regime = COLLRESOLVE_REGIME_HIT_AND_RUN
               impactors%Qloss = 0.0_DP
            else
               mlr =  mtot
               mslr = 0.0_DP
               impactors%Qloss = Qmerge
            end if
         else
            impactors%Qloss = Qloss
         end if

         if (allocated(impactors%mass_dist)) deallocate(impactors%mass_dist)
         allocate(impactors%mass_dist(NMASS_DIST))
         impactors%mass_dist(1) = min(max(mlr, 0.0_DP), mtot)
         impactors%mass_dist(2) = min(max(mslr, 0.0_DP), mtot)
         impactors%mass_dist(3) = min(max(mtot - mlr - mslr, 0.0_DP), mtot)

      end associate

      return
   end subroutine collision_regime_LS12


   subroutine collision_regime_LS12_SI(Mcb, m1, m2, rad1, rad2, rh1, rh2, vb1, vb2, den1, den2, min_mfrag, &
                                                regime, Mlr, Mslr, Mslr_hitandrun, Qloss, Qmerge)
      !! Author: Jennifer L.L. Pouplin, Carlisle A. Wishard, and David A. Minton
      !!
      !! Determine the collisional regime of two colliding bodies. 
      !! Current version requires all values to be converted to SI units prior to calling the function
      !!       References:
      !!       Kokubo, E., Genda, H., 2010. Formation of Terrestrial Planets from Protoplanets Under a Realistic Accretion 
      !!          Condition. ApJL 714, L21. https://doi.org/10.1088/2041-8205/714/1/L21
      !!       Leinhardt, Z.M., Stewart, S.T., 2012. Collisions between Gravity-dominated Bodies. I. Outcome Regimes and Scaling 
      !!          Laws 745, 79. https://doi.org/10.1088/0004-637X/745/1/79
      !!       Mustill, A.J., Davies, M.B., Johansen, A., 2018. The dynamical evolution of transiting planetary systems including 
      !!          a realistic collision prescription. Mon Not R Astron Soc 478, 2896–2908. https://doi.org/10.1093/mnras/sty1273
      !!       Rufu, R., Aharonson, O., 2019. Impact Dynamics of Moons Within a Planetary Potential. J. Geophys. Res. Planets 124, 
      !!          1008–1019. https://doi.org/10.1029/2018JE005798
      !!       Stewart, S.T., Leinhardt, Z.M., 2012. Collisions between Gravity-dominated Bodies. II. The Diversity of Impact 
      !!          Outcomes during the End Stage of Planet Formation. ApJ 751, 32. https://doi.org/10.1088/0004-637X/751/1/32
      !!
      implicit none
      ! Arguments
      real(DP), intent(in)           :: Mcb, m1, m2, rad1, rad2, den1, den2, min_mfrag 
      real(DP), dimension(:), intent(in)  :: rh1, rh2, vb1, vb2
      integer(I4B), intent(out)         :: regime
      real(DP), intent(out)          :: Mlr, Mslr, Mslr_hitandrun 
         !! Largest and second-largest remnant defined for various regimes
      real(DP), intent(out)          :: Qloss  
         !! The energy lost in the collision if it was a fragmentation event
      real(DP), intent(out)          :: Qmerge 
         !! The energy lost in the collision if it was a perfect merger
      ! Constants
      integer(I4B), parameter :: N1 = 1 
         !! number of objects with mass equal to the largest remnant from LS12
      integer(I4B), parameter :: N2 = 2  
         !! number of objects with mass larger than second largest remnant from LS12
      real(DP), parameter   :: DENSITY1 = 1000.0_DP 
         !! standard density parameter from LS12 [kg/m3]
      real(DP), parameter   :: MU_BAR = 0.37_DP 
         !!0.385#0.37#0.3333# 3.978 # 1/3 material parameter for hydrodynamic planet-size bodies (LS12)
      real(DP), parameter   :: BETA = 2.85_DP 
         !! slope of sfd for remnants from LS12 2.85
      real(DP), parameter   :: ETA = -1.50_DP 
         !! LS12 eq. (44)
      real(DP), parameter   :: C1 = 2.43_DP 
         !! Kokubo & Genda (2010) eq. (3)
      real(DP), parameter   :: C2 = -0.0408_DP 
         !! Kokubo & Genda (2010) eq. (3)
      real(DP), parameter   :: C3 = 1.86_DP 
         !! Kokubo & Genda (2010) eq. (3)
      real(DP), parameter   :: C4 = 1.08_DP 
         !! Kokubo & Genda (2010) eq. (3)
      real(DP), parameter   :: CRUFU = 2.0_DP - 3 * MU_BAR 
         !! central potential variable from Rufu and Aharonson (2019)
      real(DP), parameter   :: SUPERCAT_QRATIO = 1.8_DP 
         !! See Section 4.1 of LS12
      ! Internals
      real(DP)           :: a1, alpha, aint, b, bcrit, c_star, egy, zeta, l, lint, mu, phi, theta, ke, pe
      real(DP)           :: Qr, Qrd_pstar, Qr_erosion, Qr_supercat
      real(DP)           :: Vhr, Verosion, Vescp, Vhill, Vimp, Vsupercat
      real(DP)           :: Mint, Mtot, Mtmp, Mbig, Msmall
      real(DP)           :: Rc1, rhill 
      real(DP)           :: Mresidual
      real(DP)           :: U_binding

      Vimp = norm2(vb2(:) - vb1(:))
      b = calc_b(rh2, vb2, rh1, vb1)
      l = (rad1 + rad2) * (1 - b)
      egy = 0.5_DP * dot_product(vb1, vb1) - GC * Mcb / norm2(rh1)
      a1 = - GC * Mcb / 2.0_DP / egy
      Mtot = m1 + m2 
      mu = (m1 * m2) / Mtot
      if (l < 2 * rad2) then
         !calculate Mint
         phi = 2 * acos((l - rad2) / rad2)
         aint = rad2**2 * (PI - (phi - sin(phi)) / 2.0_DP)
         lint = 2 * sqrt(rad2**2 - (rad2 - l / 2.0_DP) ** 2) 
         Mint = aint * lint  ![kg]
         alpha = (l**2) * (3 * rad2 - l) / (4 * (rad2**3))
      else
         alpha = 1.0_DP
         Mint = m2
      end if 
      Rc1 = (3 * Mtot / (4 * PI * DENSITY1))**THIRD ! Stewart and Leinhardt (2009) 
      c_star = calc_c_star(Rc1)

      !calculate Vescp
      Vescp = sqrt(2 * GC * Mtot / Rc1) ! Steward and Leinhardt (2012) e.g. Fig. 7 caption.

      !calculate rhill
      rhill = a1 * (m1 / (3 *(Mcb + m1)))**(1.0_DP/3.0_DP)

      !calculate Vhill
      if ((rad2 + rad1) < rhill) then 
         Vhill = sqrt(2 * GC * m1 * ((rhill**2 - rhill * (rad1 + rad2)) / &
         (rhill**2 - 0.5_DP * (rad1 + rad2)**2)) / (rad1 + rad2))
      else
         Vhill = Vescp
      end if 

      !calculate Qr_pstar
      Qrd_pstar = calc_Qrd_pstar(m1, m2, alpha, c_star) * (Vhill / Vescp)**CRUFU !Rufu and Aharaonson eq (3)

      !calculate Verosion
      Qr_erosion = 2 * (1.0_DP - m1 / Mtot) * Qrd_pstar
      Verosion = (2 * Qr_erosion * Mtot / mu)** (1.0_DP / 2.0_DP)
      Qr = mu*(Vimp**2) / Mtot / 2.0_DP

      !calculate mass largest remnant Mlr 
      !calculate Vsupercat
      Qr_supercat = SUPERCAT_QRATIO * Qrd_pstar ! See LS12 Section 4.1 
      Vsupercat = sqrt(2 * Qr_supercat * Mtot / mu)

      !calculate Vhr
      zeta = (m1 - m2) / Mtot
      theta = 1.0_DP - b
      Vhr = Vescp * (C1 * zeta**2 * theta**(2.5_DP) + C2 * zeta**2 + C3 * theta**(2.5_DP) + C4) ! Kokubo & Genda (2010) eq. (3)
      bcrit = rad1 / (rad1 + rad2)
      ! Specific binding energy
      U_binding = (3 * GC * Mtot) / (5 * Rc1) ! LS12 eq. 27
      ke = 0.5_DP * Vimp**2
      pe = - GC * m1 * m2 / (Mtot * norm2(rh2 - rh1))

      if ((m1 < min_mfrag).or.(m2 < min_mfrag)) then 
         regime = COLLRESOLVE_REGIME_MERGE !perfect merging regime
         call swiftest_io_log_one_message(COLLISION_LOG_OUT, &
                                 "Fragments would have mass below the minimum. Converting this collision into a merger.")
      else 
         if( Vimp < Vescp) then
            regime = COLLRESOLVE_REGIME_MERGE !perfect merging regime
         else if (Vimp < Verosion) then 
            if (b < bcrit) then
               regime = COLLRESOLVE_REGIME_MERGE !partial accretion regime"
            else if ((b > bcrit) .and. (Vimp < Vhr)) then
               regime = COLLRESOLVE_REGIME_MERGE ! graze and merge
            else
               regime = COLLRESOLVE_REGIME_HIT_AND_RUN !hit and run
            end if 
         else if (Vimp > Verosion .and. Vimp < Vsupercat) then
            regime = COLLRESOLVE_REGIME_DISRUPTION !disruption
         else if (Vimp > Vsupercat) then 
            regime = COLLRESOLVE_REGIME_SUPERCATASTROPHIC ! supercatastrophic
         else 
            call swiftest_io_log_one_message(COLLISION_LOG_OUT,"Error no regime found in symba_regime")
         end if 
      end if 

      if (regime == COLLRESOLVE_REGIME_SUPERCATASTROPHIC) then
         Mlr  = max(Mtot * 0.1_DP * (Qr / (Qrd_pstar * SUPERCAT_QRATIO))**(ETA), min_mfrag)   !LS12 eq (44)
      else if (regime == COLLRESOLVE_REGIME_HIT_AND_RUN) then
         Mlr = m1
      else 
         Mlr = max((1.0_DP - Qr / Qrd_pstar / 2.0_DP) * Mtot, min_mfrag) ! [kg] # LS12 eq (5)
      end if
      Mbig = max(m1,Mlr)
      Msmall = mtot - Mbig
      Mslr_hitandrun = max(calc_Qrd_rev(Msmall, Mbig, Mint, den1, den2, Vimp, c_star), min_mfrag)
      if (regime == COLLRESOLVE_REGIME_HIT_AND_RUN ) then
         Mslr = Mslr_hitandrun
      else
         Mslr = max(Mtot * (3.0_DP - BETA) * (1.0_DP - N1 * Mlr / Mtot) / (N2 * BETA), min_mfrag)  !LS12 eq (37)
      end if

      Mresidual = Mtot - Mlr - Mslr
      if (Mresidual < 0.0_DP) then ! prevents final masses from going negative
         Mlr = Mlr + Mresidual
      end if

      if (Mslr > Mlr) then ! The second-largest fragment is actually larger than the largest, so we will swap them
         Mtmp = Mlr
         Mlr = Mslr
         Mslr = Mtmp
      end if

      Qloss = (c_star - 1.0_DP) * U_binding * Mtot ! Convert specific energy loss to total energy loss in the system
      Qmerge = (ke + pe + U_binding) * Mtot ! The  energy lost if this were a perfect merger
         
      return 

      contains

         function calc_Qrd_pstar(Mtarg, Mp, alpha, c_star) result(Qrd_pstar)
            !! author: Jennifer L.L. Pouplin and Carlisle A. Wishard
            !!
            !! Calculates the corrected Q* for oblique impacts. See Eq. (15) of LS12.
            !!       Reference:
            !!       Leinhardt, Z.M., Stewart, S.T., 2012. Collisions between Gravity-dominated Bodies. I. Outcome Regimes and 
            !!          Scaling Laws 745, 79. https://doi.org/10.1088/0004-637X/745/1/79
            !! 
            implicit none
            ! Arguments
            real(DP),intent(in) :: Mtarg, Mp, alpha, c_star
            ! Result
            real(DP)      :: Qrd_pstar
            ! Internals
            real(DP)      :: Qrd_star1, mu_alpha, mu, Qrd_star, gamma

            ! calc mu, mu_alpha
            mu = (Mtarg * Mp) / (Mtarg + Mp)  ! [kg]
            mu_alpha = (Mtarg * alpha * Mp) / (Mtarg + alpha * Mp)  ! [kg]
            gamma = Mp / Mtarg
            ! calc Qrd_star1
            Qrd_star1 = (c_star * 4 * PI * DENSITY1 * GC * Rc1**2) / 5.0_DP ! LS12 eq. 28
            ! calc Qrd_star
            Qrd_star = Qrd_star1 * (((gamma + 1.0_DP)**2) / (4 * gamma))**(2.0_DP / (3 * MU_BAR) - 1.0_DP)  !(eq 23)
            ! calc Qrd_pstar, v_pstar
            Qrd_pstar = ((mu / mu_alpha)**(2.0_DP - 3 * MU_BAR / 2.0_DP)) * Qrd_star  ! (eq 15)

            return
         end function calc_Qrd_pstar

         function calc_Qrd_rev(Mp, Mtarg, Mint, den1, den2, Vimp, c_star) result(Mslr)
            !! author: Jennifer L.L. Pouplin and Carlisle A. Wishard
            !!
            !! Calculates mass of second largest fragment.
            !! 
            implicit none
            ! Arguments
            real(DP),intent(in) :: Mp, Mtarg, Mint, den1, den2, Vimp, c_star
            ! Result
            real(DP) :: Mslr
            ! Internals
            real(DP) :: mtot_rev, mu_rev, gamma_rev, Qrd_star1, Qrd_star, mu_alpha_rev
            real(DP) :: Qrd_pstar, Rc1, Qr_rev, Qrd_pstar_rev, Qr_supercat_rev

            ! calc Mslr, Rc1, mu, gammalr
            mtot_rev =  Mint + Mp
            Rc1 = (3 * (Mint / den1 + Mp / den2) / (4 * PI))**(1.0_DP/3.0_DP) ! [m] Mustill et al 2018
            mu_rev = (Mint * Mp) / mtot_rev ! [kg] eq 49 LS12
            mu_alpha_rev = (Mtarg * alpha * Mp) / (Mtarg + alpha * Mp)
            gamma_rev = Mint / Mp ! eq 50 LS12
            !calc Qr_rev
            Qr_rev = mu_rev * (Vimp**2) / (2 * mtot_rev)
            ! calc Qrd_star1, v_star1
            Qrd_star1 = (c_star * 4 * PI * mtot_rev * GC ) / Rc1 / 5.0_DP
            ! calc Qrd_pstar_rev
            Qrd_star = Qrd_star1 * (((gamma_rev + 1.0_DP)**2) / (4 * gamma_rev)) ** (2.0_DP / (3 * MU_BAR) - 1.0_DP) !(eq 52)
            Qrd_pstar = Qrd_star * ((mu_rev / mu_alpha_rev)**(2.0_DP - 3 * MU_BAR / 2.0_DP))
            Qrd_pstar_rev = Qrd_pstar * (Vhill / Vescp)**CRUFU !Rufu and Aharaonson eq (3)
            !calc Qr_supercat_rev
            Qr_supercat_rev = 1.8_DP * Qrd_pstar_rev 
            if (Qr_rev > Qr_supercat_rev ) then 
               Mslr = mtot_rev * (0.1_DP * ((Qr_rev / (Qrd_pstar_rev * 1.8_DP))**(-1.5_DP)))   !eq (44)
            else if ( Qr_rev < Qrd_pstar_rev ) then 
               Mslr = Mp 
            else 
               Mslr = (1.0_DP - Qr_rev / Qrd_pstar_rev / 2.0_DP) * (mtot_rev)  ! [kg] #(eq 5)
            end if 

            if ( Mslr > Mp ) Mslr = Mp !check conservation of mass

            return
         end function calc_Qrd_rev

         function calc_b(proj_pos, proj_vel, targ_pos, targ_vel) result(sintheta)
            !! author: Jennifer L.L. Pouplin, Carlisle A. Wishard, and David A. Minton
            !!
            !! Calculates the impact factor b = sin(theta), where theta is the angle between the relative velocity
            !! and distance vectors of the target and projectile bodies. See Fig. 2 of Leinhardt and Stewart (2012)
            !! 
            implicit none
            ! Arguments
            real(DP), dimension(:), intent(in) :: proj_pos, proj_vel, targ_pos, targ_vel
            ! Result
            real(DP)             :: sintheta
            ! Internals
            real(DP), dimension(NDIM)     :: imp_vel, distance, x_cross_v      

            imp_vel(:) = proj_vel(:) - targ_vel(:)
            distance(:) = proj_pos(:) - targ_pos(:)
            x_cross_v(:) = distance(:) .cross. imp_vel(:) 
            sintheta = norm2(x_cross_v(:)) / norm2(distance(:)) / norm2(imp_vel(:))
            return 
         end function calc_b


         function calc_c_star(Rc1) result(c_star)
            !! author: David A. Minton
            !!
            !! Calculates c_star as a function of impact equivalent radius. It interpolates between 5 for ~1 km sized bodies to
            !! 1.8 for ~10000 km sized bodies. See LS12 Fig. 4 for details.
            !! 
            implicit none
            ! Arguments
            real(DP), intent(in) :: Rc1
            ! Result
            real(DP)             :: c_star
            ! Internals
            real(DP), parameter  :: loR   = 1.0e3_DP ! Lower bound of interpolation size (m)
            real(DP), parameter  :: hiR   = 1.0e7_DP ! Upper bound of interpolation size (m)
            real(DP), parameter  :: loval = 5.0_DP   ! Value of C* at lower bound
            real(DP), parameter  :: hival = 1.9_DP   ! Value of C* at upper bound

            if (Rc1 < loR) then
               c_star = loval
            else if (Rc1 < hiR) then
               c_star = loval + (hival - loval) * log(Rc1 / loR) / log(hiR /loR)
            else
               c_star = hival
            end if
            return
         end function calc_c_star 

   end subroutine collision_regime_LS12_SI

end submodule s_collision_regime