LCOV - code coverage report
Current view: top level - eos/private - skye_coulomb_solid.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 89.1 % 92 82
Test Date: 2025-05-08 18:23:42 Functions: 87.5 % 8 7

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 2022  The MESA Team
       4              : !
       5              : !   This program is free software: you can redistribute it and/or modify
       6              : !   it under the terms of the GNU Lesser General Public License
       7              : !   as published by the Free Software Foundation,
       8              : !   either version 3 of the License, or (at your option) any later version.
       9              : !
      10              : !   This program is distributed in the hope that it will be useful,
      11              : !   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12              : !   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
      13              : !   See the GNU Lesser General Public License for more details.
      14              : !
      15              : !   You should have received a copy of the GNU Lesser General Public License
      16              : !   along with this program. If not, see <https://www.gnu.org/licenses/>.
      17              : !
      18              : ! ***********************************************************************
      19              : 
      20              : module skye_coulomb_solid
      21              :    use math_lib
      22              :    use math_def
      23              :    use auto_diff
      24              :    use const_def, only: dp, pi, fine, eulernum
      25              : 
      26              :    implicit none
      27              : 
      28              :    contains
      29              : 
      30              :    !> Computes the classical and quantum anharmonic non-ideal
      31              :    !! free energy of a one-component plasma in the solid phase
      32              :    !! using classical fits due to
      33              :    !! Farouki & Hamaguchi'93.
      34              :    !!
      35              :    !! Quantum corrections to this are due to
      36              :    !! Potekhin & Chabrier 2010 (DOI 10.1002/ctpp.201010017)
      37              :    !!
      38              :    !! For a more recent reference and more mathematical detail see Appendix B.2 of
      39              :    !! A. Y. Potekhin and G. Chabrier: Equation of state for magnetized Coulomb plasmas
      40              :    !!
      41              :    !! Based on the routine ANHARM8 due to Potekhin and Chabrier.
      42              :    !!
      43              :    !! @param GAMI Ion coupling parameter (Gamma_i)
      44              :    !! @param TPT effective T_p/T - ion quantum parameter
      45              :    !! @param F free energy per kT per ion
      46       310914 :    function ocp_solid_anharmonic_free_energy(GAMI,TPT) result(F)
      47              :       type(auto_diff_real_2var_order3), intent(in) :: GAMI,TPT
      48              :       type(auto_diff_real_2var_order3) :: F
      49              : 
      50              :       real(dp), parameter :: A1 = 10.9d0
      51              :       real(dp), parameter :: A2 = 247d0
      52              :       real(dp), parameter :: A3 = 1.765d5
      53              :       real(dp), parameter :: B1 = 0.12d0  ! coefficient of \eta^2/\Gamma at T=0
      54              : 
      55              :       type(auto_diff_real_2var_order3) :: TPT2
      56              : 
      57       310914 :       TPT2=TPT*TPT
      58              : 
      59       310914 :       F = -(A1 / GAMI + A2 / (2d0 * pow2(GAMI)) + A3 / (3d0 * pow3(GAMI)))
      60       310914 :       F = F * exp(-(B1 / A1) * TPT2)  ! suppress.factor of classical anharmonicity
      61       310914 :       F = F - B1 * TPT2 / GAMI  ! Quantum correction
      62       310914 :    end function ocp_solid_anharmonic_free_energy
      63              : 
      64              :    !> Computes the harmonic non-ideal free energy of a
      65              :    !! one-component plasma in the solid phase using fits due to
      66              :    !! Baiko, Potekhin, & Yakovlev (2001). Includes both classical
      67              :    !! and quantum corrections.
      68              :    !!
      69              :    !! For a more recent reference and more mathematical detail see Appendix B.2 of
      70              :    !! A. Y. Potekhin and G. Chabrier: Equation of state for magnetized Coulomb plasmas
      71              :    !!
      72              :    !! Based on the routines HLfit12 and FHARM12 due to Potekhin and Chabrier.
      73              :    !!
      74              :    !! @param GAMI Ion coupling parameter (Gamma_i)
      75              :    !! @param TPT effective T_p/T - ion quantum parameter
      76              :    !! @param F free energy per kT per ion
      77       310914 :    function ocp_solid_harmonic_free_energy(GAMI,TPT_in) result(F)
      78              :       ! Inputs
      79              :       type(auto_diff_real_2var_order3), intent(in) :: GAMI,TPT_in
      80              : 
      81              :       ! Intermediates
      82              :       type(auto_diff_real_2var_order3) :: TPT, UP, DN, EA, EB, EG, E0
      83              :       type(auto_diff_real_2var_order3) :: Fth, U0
      84              : 
      85              :       ! Output
      86              :       type(auto_diff_real_2var_order3) :: F
      87              : 
      88              :       real(dp), parameter :: CM = .895929256d0  ! Madelung
      89              :       real(dp), parameter :: EPS=1d-5
      90              : 
      91              :       ! BCC lattice.
      92              :       ! Empirically the Potekhin & Chabrier fit to the BCC lattice produces
      93              :       ! a lower free energy than their fit to the FCC lattice in all cases of
      94              :       ! interest (and no cases of which I am aware), so we can specialize
      95              :       ! to the BCC case.
      96              :       real(dp), parameter :: CLM=-2.49389d0  ! 3*ln<\omega/\omega_p>
      97              :       real(dp), parameter :: U1=0.5113875d0
      98              :       real(dp), parameter :: ALPHA=0.265764d0
      99              :       real(dp), parameter :: BETA=0.334547d0
     100              :       real(dp), parameter :: GAMMA=0.932446d0
     101              :       real(dp), parameter :: A1=0.1839d0
     102              :       real(dp), parameter :: A2=0.593586d0
     103              :       real(dp), parameter :: A3=0.0054814d0
     104              :       real(dp), parameter :: A4=5.01813d-4
     105              :       real(dp), parameter :: A6=3.9247d-7
     106              :       real(dp), parameter :: A8=5.8356d-11
     107              :       real(dp), parameter :: B0=261.66d0
     108              :       real(dp), parameter :: B2=7.07997d0
     109              :       real(dp), parameter :: B4=0.0409484d0
     110              :       real(dp), parameter :: B5=0.000397355d0
     111              :       real(dp), parameter :: B6=5.11148d-5
     112              :       real(dp), parameter :: B7=2.19749d-6
     113              :       real(dp), parameter :: C9=0.004757014d0
     114              :       real(dp), parameter :: C11=0.0047770935d0
     115              :       real(dp), parameter :: B9=A6*C9
     116              :       real(dp), parameter :: B11=A8*C11
     117              : 
     118              : 
     119       310914 :       TPT = TPT_in
     120              : 
     121       310914 :       if (TPT > 1d0/EPS) then  ! asymptote of Eq.(13) of BPY'01
     122            0 :          F=-1d0 / (C11*TPT*TPT*TPT)
     123       310914 :       else if (TPT < EPS) then  ! Eq.(17) of BPY'01
     124            0 :          F=3d0*log(TPT)+CLM-1.5d0*U1*TPT+TPT*TPT/24.d0
     125              :       else
     126       310914 :          UP=1d0+TPT*(A1+TPT*(A2+TPT*(A3+TPT*(A4+TPT*TPT*(A6+TPT*TPT*A8)))))
     127       310914 :          DN=B0+TPT*TPT*(B2+TPT*TPT*(B4+TPT*(B5+TPT*(B6+TPT*(B7+TPT*TPT*(B9+TPT*TPT*B11))))))
     128              : 
     129       310914 :          EA=exp(-ALPHA*TPT)
     130       310914 :          EB=exp(-BETA*TPT)
     131       310914 :          EG=exp(-GAMMA*TPT)
     132              : 
     133       310914 :          F=log(1.d0-EA)+log(1.d0-EB)+log(1.d0-EG)-UP/DN  ! F_{thermal}/NT
     134              :       end if
     135              : 
     136       310914 :       U0=-CM*GAMI       ! perfect lattice
     137       310914 :       E0=1.5d0*U1*TPT   ! zero-point energy
     138       310914 :       Fth=F+E0          ! Thermal component
     139       310914 :       F=U0+Fth          ! Total
     140              : 
     141       310914 :       F = F -3d0 * log(TPT) + 1.5d0*log(GAMI) + 1.323515d0     ! Subtract out ideal free energy
     142              :                                                                ! We use this form because it's what PC fit against
     143              :                                                                ! in producing FHARM
     144              : 
     145       310914 :    end function ocp_solid_harmonic_free_energy
     146              : 
     147              :    !> Calculates an exponential with cutoffs to prevent over/underflow.
     148              :    !!
     149              :    !! @param x Input to take the exponential of.
     150              :    !! @param ex Output (exp(x) clipped to avoid over/underflow).
     151       310914 :    type(auto_diff_real_2var_order3) function safe_exp(x) result(ex)
     152              :       type(auto_diff_real_2var_order3), intent(in) :: x
     153       310914 :       ex = exp(max(-1d2,min(1d2,x)))
     154       310914 :    end function safe_exp
     155              : 
     156              :    !> Calculates a tanh with cutoffs to prevent over/underflow.
     157              :    !!
     158              :    !! @param x Input to take the exponential of.
     159              :    !! @param ex Output (exp(x) clipped to avoid over/underflow).
     160       310914 :    type(auto_diff_real_2var_order3) function safe_tanh(x) result(th)
     161              :       type(auto_diff_real_2var_order3), intent(in) :: x
     162       310914 :       th = tanh(max(-1d2,min(1d2,x)))
     163       310914 :    end function safe_tanh
     164              : 
     165              :    !> Calculates the electron-ion screening corrections to the free energy
     166              :    !! of a one-component plasma in the solid phase using the fits of Potekhin & Chabrier 2013.
     167              :    !! In the non-degenerate limit we smoothly transition to the liquid-phase screening
     168              :    !! free energy, which is just an easy way to ensure we reproduce the Debye-Huckel limit.
     169              :    !!
     170              :    !! @param Z ion charge
     171              :    !! @param mi ion mass in amu
     172              :    !! @param ge electron interaction parameter
     173              :    !! @param rs non-dimensionalized electron radius
     174              :    !! @param F non-ideal free energy
     175       310914 :    function ocp_solid_screening_free_energy_correction(Z, mi, ge, rs) result(F)
     176              :          use skye_coulomb_liquid, only: me_in_amu, ocp_liquid_screening_free_energy_correction
     177              :          real(dp), intent(in) :: Z, mi
     178              :          type(auto_diff_real_2var_order3), intent(in) :: ge, rs
     179              : 
     180       310914 :          real(dp) :: s, b1, b2, b3, b4, COTPT
     181              :          real(dp), parameter :: aTF = 0.00352d0
     182              : 
     183              :          type(auto_diff_real_2var_order3) :: TPT, f_inf, A, Q, xr, supp, g, alpha, Fliq, gr, switch
     184              :          type(auto_diff_real_2var_order3) :: F
     185              : 
     186       310914 :          s = 1d0 / (1d0 + 1d-2 * pre_z(int(Z))% logz_3_2 + 0.097d0 / pre_z(int(Z))% z2)
     187       310914 :          b1 = 1d0 - 1.1866d0 * pre_z(int(Z))% zm0p267 + 0.27d0 / Z
     188              :          b2 = 1d0 + (2.25d0 * pre_z(int(Z))% zm1_3) * (1d0 + 0.684d0 * pre_z(int(Z))% z5 + 0.222d0 * pre_z(int(Z))% z6) &
     189       310914 :                   / (1d0 + 0.222d0 * pre_z(int(Z))% z6)
     190       310914 :          b3 = 41.5d0 / (1d0 + pre_z(int(Z))% logz)
     191       310914 :          b4 = 0.395d0 * pre_z(int(Z))% logz + 0.347d0 * pre_z(int(Z))% zm3_2
     192              : 
     193       310914 :          g = ge * pre_z(int(Z))% z5_3
     194              : 
     195       310914 :          COTPT = sqrt(3d0 * me_in_amu / mi) / pre_z(int(Z))% z7_6
     196       310914 :          TPT = g * COTPT / sqrt(RS)
     197       310914 :          supp = safe_exp(-pow2(0.205d0 * TPT))
     198       310914 :          Q = sqrt((pow2(0.205d0 * TPT) + log(1d0 + supp)) / log(eulernum - (eulernum - 2d0) * supp))
     199              : 
     200       310914 :          xr = pow(9d0 * pi / 4d0, 1d0/3d0) * fine / rs
     201       310914 :          A = (b3 + 17.9d0 * pow2(xr)) / (1d0 + b4 * pow2(xr))
     202       310914 :          f_inf = aTF * pre_z(int(Z))% z2_3 * b1 * sqrt(1d0 + b2 / pow2(xr))
     203              : 
     204       310914 :          F = -f_inf * g * (1d0 + A * pow(Q / g, s))
     205              : 
     206       310914 :          gr = sqrt(1d0 + pow2(xr))
     207       310914 :          alpha = 3d0 * pow(4d0 / (9d0 * pi), 2d0/3d0) * (rs / ge) * gr
     208              : 
     209       310914 :          Fliq = ocp_liquid_screening_free_energy_correction(Z, mi, ge, rs)
     210              : 
     211       310914 :          switch = pow3(safe_tanh(2d0*alpha))
     212       310914 :          F = switch * Fliq + (1d0 - switch) * F
     213              : 
     214       310914 :    end function ocp_solid_screening_free_energy_correction
     215              : 
     216              :    !> Computes the correction deltaG to the linear mixing rule for a two-component Coulomb solid mixture.
     217              :    !! From Shuji Ogata, Hiroshi Iyetomi, and Setsuo Ichimaru 1993
     218              :    !!
     219              :    !! Based on simulations done for charge ratio 4/3 <= Rz <= 4 and 163 <= Gamma <= 383, where
     220              :    !! Gamma here is the ionic interaction strength measured for the lower-charge species.
     221              :    !! deltaG is defined as deltaF / (x1*x2*Gamma), where deltaF is a free energy per kT per ion
     222              :    !! and x1 is the abundance of species 1.
     223              :    !!
     224              :    !! @param x2 Abundance of the higher-charge species
     225              :    !! @param Rz Charge ratio of species (> 1 by definition).
     226            0 :    real(dp) function deltaG_Ogata93(x2, Rz) result(dG)
     227              :       ! Inputs
     228              :       real(dp), intent(in) :: x2
     229              :       real(dp), intent(in) :: Rz
     230              : 
     231              :       ! Intermediates
     232            0 :       real(dp) :: CR
     233              : 
     234            0 :       CR = 0.05d0 * pow2(Rz - 1d0) / ((1d0 + 0.64d0 * (Rz - 1d0)) * (1d0 + 0.5d0 * pow2(Rz - 1d0)))
     235              :       dG = CR / (1 + &
     236              :            (sqrt(x2) * (sqrt(x2) - 0.3d0) * (sqrt(x2) - 0.7d0) * (sqrt(x2) - 1d0)) &
     237            0 :            * 27d0 * (Rz - 1d0) / (1d0 + 0.1d0 * (Rz - 1d0)))
     238              : 
     239       310914 :    end function deltaG_Ogata93
     240              : 
     241              :    !> Computes the correction deltaG to the linear mixing rule for a two-component Coulomb solid mixture.
     242              :    !! Originally from PhysRevE.79.016411 (Equation of state of classical Coulomb plasma mixtures)
     243              :    !! by Potekhin, Alexander Y. and Chabrier, Gilles and Rogers, Forrest J.
     244              :    !! https://link.aps.org/doi/10.1103/PhysRevE.79.016411
     245              :    !!
     246              :    !! @param x Abundance of the higher-charge species
     247              :    !! @param Rz Charge ratio of species (> 1 by definition).
     248      1300910 :    real(dp) function deltaG_PC13(x2, Rz) result(dG)
     249              :       ! Inputs
     250              :       real(dp), intent(in) :: x2
     251              :       real(dp), intent(in) :: Rz
     252              : 
     253              :       ! Intermediates
     254      1300910 :       real(dp) :: x
     255              : 
     256      1300910 :       x = x2 / Rz + (1d0 - 1d0 / Rz) * pow(x2, Rz)
     257      1300910 :       dG = 0.012d0 * ((x*(1d0-x)) / (x2*(1d0-x2))) * (1d0 - 1d0/pow2(Rz)) * (1d0 - x2 + x2 * pow(Rz,5d0/3d0))
     258              : 
     259      1300910 :    end function deltaG_PC13
     260              : 
     261              :    !> Calculates the correction to the linear mixing rule for a Coulomb solid mixture
     262              :    !! by extending a two-component deltaG prescription to the multi-component case, using the
     263              :    !! prescription of Medin & Cumming 2010.
     264              :    !!
     265              :    !! @param n Number of species
     266              :    !! @param AY Array of length NMIX holding the masses of species
     267              :    !! @param AZion Array of length NMIX holding the charges of species
     268              :    !! @param GAME election interaction parameter
     269              :    !! @param F mixing free energy correction per ion per kT.
     270        45520 :    function solid_mixing_rule_correction(Skye_solid_mixing_rule, n, AY, AZion, GAME) result(F)
     271              :       ! Inputs
     272              :       character(len=128), intent(in) :: Skye_solid_mixing_rule
     273              :       integer, intent(in) :: n
     274              :       real(dp), intent(in) :: AZion(:), AY(:)
     275              :       type(auto_diff_real_2var_order3), intent(in) :: GAME
     276              : 
     277              :       ! Intermediates
     278              :       integer :: i,j, num_unique_charges
     279       712868 :       real(dp) :: unique_charges(n), charge_abundances(n)
     280              :       logical :: found
     281              :       integer :: found_index
     282        45520 :       real(dp) :: RZ, aj, dG
     283              :       type(auto_diff_real_2var_order3) :: GAMI
     284              : 
     285              :       ! Output
     286              :       type(auto_diff_real_2var_order3) :: F
     287              : 
     288              :       ! Parameters
     289              :       real(dp), parameter :: C = 0.012d0
     290              :       real(dp), parameter :: eps = 1d-40
     291              : 
     292              :       ! Identify and group unique charges
     293        45520 :       num_unique_charges = 0
     294       356434 :       do i=1,n
     295      1300910 :          found = .false.
     296              : 
     297      1300910 :          do j=1,num_unique_charges
     298      1300910 :             if (unique_charges(j) == AZion(i)) then
     299              :                found = .true.
     300              :                found_index = j
     301              :                exit
     302              :             end if
     303              :          end do
     304              : 
     305       356434 :          if (.not. found) then
     306       310914 :             num_unique_charges = num_unique_charges + 1
     307       310914 :             unique_charges(num_unique_charges) = AZion(i)
     308       310914 :             charge_abundances(num_unique_charges) = AY(i)
     309              :          else
     310            0 :             charge_abundances(found_index) = charge_abundances(found_index) + AY(i)
     311              :          end if
     312              :       end do
     313              : 
     314        45520 :       F = 0d0
     315       356434 :       do i=1,num_unique_charges
     316       310914 :          if (unique_charges(i) == 0d0) cycle
     317      2647340 :          do j=1,num_unique_charges
     318      2290906 :             if (unique_charges(j) == 0d0) cycle
     319              : 
     320              :             ! Expression needs R > 1.
     321              :             ! From PC2013: 'dF_sol = Sum_i Sum_{j>i} ... where the indices are arranged so that Z_j < Z_{j+1}'
     322              :             ! So if j > i then Z_j > Z_i, which means R = Z_j / Z_i > 1.
     323              :             ! We extend to the case of equality by grouping equal-charge species together, as above.
     324      2290906 :             if (unique_charges(j) < unique_charges(i)) cycle
     325              : 
     326      1300910 :             RZ = unique_charges(j)/unique_charges(i)  ! Charge ratio
     327              : 
     328              :             ! max avoids divergence.
     329              :             ! The contribution to F scales as abundance_sum^2, so in cases where the max returns eps
     330              :             ! we don't care much about the error this incurs.
     331      1300910 :             aj = charge_abundances(j) / max(eps, charge_abundances(i) + charge_abundances(j))  ! = x2 / (x1 + x2) in MC10's language
     332      1300910 :             if (Skye_solid_mixing_rule == 'Ogata') then
     333            0 :                dG = deltaG_Ogata93(aj, RZ)
     334      1300910 :             else if (Skye_solid_mixing_rule == 'PC') then
     335      1300910 :                dG = deltaG_PC13(aj, RZ)
     336              :             else
     337            0 :                write(*,*) 'Error: Invalid choice for Skye_solid_mixing_rule.'
     338            0 :                stop
     339              :             end if
     340              : 
     341      1300910 :             GAMI=pow(unique_charges(i),5d0/3d0)*GAME
     342      2601820 :             F = F +  GAMI * (charge_abundances(i) * charge_abundances(j) * dG)
     343              :          end do
     344              :       end do
     345        45520 :    end function solid_mixing_rule_correction
     346              : 
     347              : 
     348              : end module skye_coulomb_solid
        

Generated by: LCOV version 2.0-1