LCOV - code coverage report
Current view: top level - turb/private - thermohaline.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 0.0 % 144 0
Test Date: 2025-05-08 18:23:42 Functions: 0.0 % 6 0

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 2010-2021  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 thermohaline
      21              : 
      22              :    use const_def, only: dp, one_third, pi, qe, amu, boltzm, crad, clight
      23              :    use num_lib
      24              :    use utils_lib
      25              :    use auto_diff
      26              : 
      27              :    implicit none
      28              : 
      29              :    private
      30              :    public :: get_D_thermohaline
      31              : 
      32              : contains
      33              : 
      34              :    !> Computes the diffusivity of thermohaline mixing when the
      35              :    !! thermal gradient is stable and the composition gradient is unstable.
      36              :    !!
      37              :    !! @param thermohaline_option A string specifying which thermohaline prescription to use.
      38              :    !! @param grada Adiabatic gradient dlnT/dlnP
      39              :    !! @param gradr Radiative temperature gradient dlnT/dlnP, equals the actual gradient because there's no convection
      40              :    !! @param T Temperature
      41              :    !! @param opacity opacity
      42              :    !! @param rho Density
      43              :    !! @param Cp Heat capacity at constant pressure
      44              :    !! @param gradL_composition_term dlnMu/dlnP where Mu is the mean molecular weight.
      45              :    !! @param iso The index of the species that drives thermohaline mixing.
      46              :    !! @param XH1 Mass fraction of H1.
      47              :    !! @param thermohaline_coeff Free parameter multiplying the thermohaline diffusivity.
      48              :    !! @param D_thrm Output, diffusivity.
      49              :    !! @param ierr Output, error index.
      50            0 :    subroutine get_D_thermohaline(thermohaline_option, &
      51              :                                  grada, gradr, T, opacity, rho, Cp, gradL_composition_term, &
      52              :                                  iso, XH1, thermohaline_coeff, D_thrm, ierr)
      53              :       character(len=*), intent(in) :: thermohaline_option
      54              :       real(dp), intent(in) :: &
      55              :          grada, gradr, T, opacity, rho, Cp, gradL_composition_term, XH1, &
      56              :          thermohaline_coeff
      57              :       integer, intent(in) :: iso
      58              :       real(dp), intent(out) :: D_thrm
      59              :       integer, intent(out) :: ierr
      60            0 :       real(dp) :: dgrad, K_therm, K_T, K_mu, nu, R0, Pr, tau, r_th
      61              :       include 'formats'
      62            0 :       dgrad = max(1d-40, grada - gradr)  ! positive since Schwarzschild stable
      63            0 :       K_therm = 4d0*crad*clight*pow3(T)/(3d0*opacity*rho)  ! thermal conductivity
      64            0 :       if (thermohaline_option == 'Kippenhahn') then
      65              :          ! Kippenhahn, R., Ruschenplatt, G., & Thomas, H.-C. 1980, A&A, 91, 175
      66            0 :          D_thrm = -3d0*K_therm/(2*rho*cp)*gradL_composition_term/dgrad
      67            0 :       else if (thermohaline_option == 'Traxler_Garaud_Stellmach_11' .or. &
      68              :                thermohaline_option == 'Brown_Garaud_Stellmach_13') then
      69            0 :          call get_diff_coeffs(K_therm, Cp, rho, T, opacity, iso, XH1, K_T, K_mu, nu)
      70            0 :          R0 = (gradr - grada)/gradL_composition_term
      71            0 :          Pr = nu/K_T
      72            0 :          tau = K_mu/K_T
      73            0 :          r_th = (R0 - 1d0)/(1d0/tau - 1d0)
      74            0 :          if (r_th >= 1d0) then  ! stable if R0 >= 1/tau
      75            0 :             D_thrm = 0d0
      76            0 :          else if (Pr < 0d0) then
      77              :             ! Bad results from get_diff_coeffs will just result in NaNs from thermohaline options, so skip
      78            0 :             D_thrm = 0d0
      79            0 :          else if (thermohaline_option == 'Traxler_Garaud_Stellmach_11') then
      80              :             ! Traxler, Garaud, & Stellmach, ApJ Letters, 728:L29 (2011).
      81              :             ! also see Denissenkov. ApJ 723:563–579, 2010.
      82            0 :             D_thrm = 101d0*sqrt(K_mu*nu)*exp(-3.6d0*r_th)*pow(1d0 - r_th, 1.1d0)  ! eqn 24
      83              :          else  ! if (s% thermohaline_option == 'Brown_Garaud_Stellmach_13') then
      84            0 :             D_thrm = K_mu*(Numu(R0, r_th, pr, tau) - 1d0)
      85              :          end if
      86              :       else
      87            0 :          D_thrm = 0
      88            0 :          ierr = -1
      89            0 :          write (*, *) 'unknown for MLT thermohaline_option'//trim(thermohaline_option)
      90              :       end if
      91            0 :       D_thrm = thermohaline_coeff*D_thrm
      92            0 :    end subroutine get_D_thermohaline
      93              : 
      94            0 :    subroutine get_diff_coeffs(K_therm, Cp, rho, T, opacity, iso, XH1, kt, kmu, vis)
      95              :       use chem_def, only: chem_isos
      96              :       real(dp), intent(in) :: K_therm, Cp, rho, T, opacity, XH1
      97              :       integer, intent(in) :: iso
      98              :       real(dp), intent(out) :: kt, kmu, vis
      99            0 :       real(dp) :: loglambdah, loglambdacx, loglambdacy, ccx, ccy, qe4
     100            0 :       real(dp) :: Bcoeff, chemA, chemZ, acx, acy, nu_mol, nu_rad
     101              :       real(dp), parameter :: sqrt5 = sqrt(5d0)
     102            0 :       kt = K_therm/(Cp*rho)  ! thermal diffusivity (assumes radiatively dominated)
     103            0 :       qe4 = pow4(qe)
     104              : 
     105              :       ! Log Lambda for pure H (equation 10 from Proffitt Michaud 93)
     106            0 :       loglambdah = -19.26d0 - 0.5d0*log(rho) + 1.5d0*log(T) - 0.5d0*log(1d0 + 0.5d0*(1 + XH1))
     107            0 :       nu_rad = 4d0*crad*pow4(T)/(15d0*clight*opacity*pow2(rho))  ! radiative viscosity
     108            0 :       nu_mol = 0.406d0*sqrt(amu)*pow(boltzm*T, 2.5d0)/(qe4*loglambdah*rho)
     109              :       ! From Spitzer "Physics of Fully Ionized Gases equation 5-54
     110              :       ! Assumes pure H. Still trying to work out what it would be for a mixture.
     111            0 :       vis = nu_mol + nu_rad  ! total viscosity
     112              : 
     113              :       ! The following is from Proffitt & Michaud, 1993.
     114              :       ! Their constant B (equation 15)
     115            0 :       Bcoeff = (15.d0/16.d0)*sqrt(2.d0*amu/(5*pi))*pow(boltzm, 2.5d0)/qe4
     116              :       ! Extract what species drives the thermohaline concvection
     117            0 :       chemA = chem_isos%Z_plus_N(iso)
     118            0 :       chemZ = chem_isos%Z(iso)
     119              : 
     120            0 :       if (chemZ > 2) then
     121              :          ! This is if the driving chemical is NOT He.
     122              :          ! Log Lambda for H-dominant chem mixture (equation 10)
     123            0 :          loglambdacx = loglambdah - log(chemz)
     124              :          ! Log Lambda for He-dominant chem mixture (equation 10)
     125            0 :          loglambdacy = loglambdah - log(2.d0*chemz)
     126              :          ! Calculation of C_ij coeffs (equation 12)
     127            0 :          ccx = log(exp(1.2d0*loglambdacx) + 1.0d0)/1.2d0
     128            0 :          ccy = log(exp(1.2d0*loglambdacy) + 1.0d0)/1.2d0
     129              :          ! Reduced masses (I had to guess, from Bahcall & Loeb 1990), with H and He
     130            0 :          acx = (1.d0*chemA)/(1.d0 + chemA)
     131            0 :          acy = 4*chemA/(4.d0 + chemA)
     132              :          ! My formula (see notes) based on Proffitt and Michaud 1993
     133              :          kmu = 2*Bcoeff*pow(T, 2.5d0)/(sqrt5*rho*chemZ*chemZ)/ &
     134            0 :                (XH1*sqrt(acx)*ccx + (1 - XH1)*sqrt(acy)*ccy)
     135              : 
     136              :       else
     137              :          ! Log Lambda for H-He mixture (equation 10)
     138              :          loglambdah = -19.26d0 - log(2d0) - 0.5d0*log(rho) + &
     139            0 :                       1.5d0*log(T) - 0.5d0*log(1d0 + 0.5d0*(1 + XH1))
     140              :          ! Calculation of C_ij coeffs (equation 12)
     141            0 :          ccy = log(exp(1.2d0*loglambdah) + 1d0)/1.2d0
     142              :          ! My formula (see notes) based on Proffitt and Michaud 1993
     143            0 :          kmu = (Bcoeff*pow(T, 2.5d0)/(rho*ccy))*(3 + XH1)/((1 + XH1)*(3 + 5*XH1)*(0.7d0 + 0.3d0*XH1))
     144              : 
     145              :       end if
     146              :       ! write(57,*) kt,kmu,vis,chemZ
     147              : 
     148            0 :    end subroutine get_diff_coeffs
     149              : 
     150            0 :    real(dp) function numu(R0, r_th, prandtl, diffratio)
     151              :       !Function calculates Nu_mu from input parameters, following Brown et al. 2013.
     152              :       !Written by P. Garaud (2013). Please email pgaraud@ucsc.edu for troubleshooting.
     153              : 
     154              :       real(dp), intent(in) :: R0, r_th, prandtl, diffratio
     155            0 :       real(dp) :: maxl2, maxl, lambdamax
     156            0 :       real(dp) :: myvars(2)
     157              :       integer :: ierr, iter, max_iters
     158              : 
     159              :       ! Initialize guess using estimates from Brown et al. 2013
     160            0 :       call analytical_estimate_th(maxl, lambdamax, r_th, prandtl, diffratio)
     161              : 
     162            0 :       myvars(1) = maxl
     163            0 :       myvars(2) = lambdamax
     164              : 
     165              :       !Call Newton relaxation algorithm
     166            0 :       call NR(myvars, prandtl, diffratio, R0, ierr)
     167              : 
     168              :       !If the growth rate is negative, then try another set of parameters as first guess.
     169              :       !Repeat as many times as necessary until convergence is obtained.
     170            0 :       iter = 1
     171            0 :       max_iters = 200
     172            0 :       do while (iter <= max_iters .and. ((myvars(2) < 0) .or. (ierr /= 0)))
     173              :          !write(*,*) 'Alternative', r_th,prandtl,diffratio,iter
     174              :          !Reset guess values
     175            0 :          myvars(1) = maxl
     176            0 :          myvars(2) = lambdamax
     177              :          !Call relaxation for slightly different Pr, tau, R0.
     178            0 :          call NR(myvars, prandtl*(1d0 + iter*1.d-2), diffratio, R0/(1d0 + iter*1.d-2), ierr)
     179              :          !If it converged this time, call NR for the real parameters.
     180            0 :          if (ierr == 0) call NR(myvars, prandtl, diffratio, R0, ierr)
     181              :          !write(*,*) prandtl,diffratio,R0,myvars(1),myvars(2),ierr
     182              :          !Otherwise, increase counter and try again.
     183            0 :          iter = iter + 1
     184              :       end do
     185              : 
     186            0 :       if ((myvars(2) < 0) .or. (ierr /= 0)) then
     187            0 :          write (*, *) "WARNING: thermohaline Newton relaxation failed to converge, falling back to estimate"
     188            0 :          maxl2 = maxl*maxl
     189              :       else  ! NR succeeded, so use results in myvars
     190              :          !Plug solution into "l^2" and lambda.
     191            0 :          maxl2 = myvars(1)*myvars(1)
     192            0 :          lambdamax = myvars(2)
     193              :          !write(*,*) prandtl,diffratio,r_th,maxl2,lambdamax
     194              :       end if
     195              : 
     196              :       !Calculate Nu_mu using Formula (33) from Brown et al, with C = 7.
     197            0 :       numu = 1.d0 + 49.d0*lambdamax*lambdamax/(diffratio*maxl2*(lambdamax + diffratio*maxl2))
     198              : 
     199            0 :    end function numu
     200              : 
     201            0 :    subroutine thermohaline_rhs(myx, myf, myj, prandtl, diffratio, R0)
     202              :       ! This routine is needed for the NR solver.
     203              :       ! Inputs the two following equations for lambda and maxl2:
     204              :       ! lambda^3 + a_2 lambda^2 + a_1 lambda + a_0 = 0 (eq. 19 of Brown et al.)
     205              :       ! b_2 lambda^2 + b_1 lambda + b_0 = 0 (eq. 20 of Brown et al.)
     206              :       ! Inputs f, the equations, and j, their jacobian.
     207              :       ! Written by P. Garaud (2013). Please email pgaraud@ucsc.edu for troubleshooting.
     208              : 
     209              :       real(dp), intent(in) :: myx(2), prandtl, diffratio, R0
     210              :       real(dp), intent(out) :: myf(2), myj(2, 2)
     211            0 :       real(dp) :: a_2, a_1, a_0, b_2, b_1, b_0, myterm, myx1_2, myx1_3, myx1_4
     212              : 
     213              :       ! This inputs the coefficients.
     214            0 :       b_2 = 1d0 + prandtl + diffratio
     215            0 :       myx1_2 = myx(1)*myx(1)
     216            0 :       myx1_3 = myx1_2*myx(1)
     217            0 :       myx1_4 = myx1_3*myx(1)
     218            0 :       a_2 = myx1_2*b_2
     219            0 :       myterm = diffratio*prandtl + prandtl + diffratio
     220            0 :       b_1 = 2*myx1_2*myterm
     221            0 :       a_1 = myx1_4*myterm + prandtl*(1.0d0 - (1d0/R0))
     222            0 :       b_0 = 3.d0*myx1_4*diffratio*prandtl + prandtl*(diffratio - (1d0/R0))
     223            0 :       a_0 = myx1_4*myx1_2*diffratio*prandtl + myx1_2*prandtl*(diffratio - (1d0/R0))
     224              : 
     225              :       !         write(*,*) a_2,a_1,a_0,b_2,b_1,b_0
     226              : 
     227              :       ! These are equations 19 and 20
     228            0 :       myf(1) = ((myx(2) + a_2)*myx(2) + a_1)*myx(2) + a_0
     229            0 :       myf(2) = b_2*myx(2)*myx(2) + b_1*myx(2) + b_0
     230              : 
     231              :       ! These are their Jacobians for the NR relaxation.
     232              :       myj(1, 1) = 2*myx(1)*b_2*myx(2)*myx(2) + &
     233              :                   4*myx1_3*myterm*myx(2) + 6*myx1_4*myx(1)*diffratio*prandtl &
     234            0 :                   + 2*myx(1)*prandtl*(diffratio - (1d0/R0))
     235            0 :       myj(1, 2) = 3*myx(2)*myx(2) + 2*a_2*myx(2) + a_1
     236            0 :       myj(2, 1) = 4*myx(1)*myterm*myx(2) + 12.d0*myx1_3*diffratio*prandtl
     237            0 :       myj(2, 2) = 2*b_2*myx(2) + b_1
     238              : 
     239            0 :    end subroutine thermohaline_rhs
     240              : 
     241            0 :    subroutine analytical_estimate_th(maxl, lambdamax, r_th, prandtl, diffratio)
     242              :       ! Inputs analytical estimates for l and lambda from Brown et al. 2013.
     243              : 
     244              :       real(dp), intent(in) :: r_th, prandtl, diffratio
     245              :       real(dp), intent(out) :: maxl, lambdamax
     246            0 :       real(dp) :: phi, maxl4, maxl6
     247              : 
     248            0 :       phi = diffratio/prandtl
     249              : 
     250            0 :       if (r_th < 0.5d0) then
     251            0 :          if (r_th > prandtl) then
     252            0 :             maxl = pow((1.d0/(1.d0 + phi)) - 2.d0*dsqrt(r_th*phi)/pow(1d0 + phi, 2.5d0), 0.25d0)
     253              :             ! Equation (B14)
     254            0 :             maxl4 = maxl*maxl*maxl*maxl
     255            0 :             maxl6 = maxl4*maxl*maxl
     256            0 :             lambdamax = 2*prandtl*phi*maxl6/(1d0 - (1d0 + phi)*maxl4)  ! Equation (B11)
     257              :          else
     258            0 :             maxl = dsqrt(dsqrt(1d0/(1d0 + phi)) - dsqrt(prandtl)*(1d0 + phi/((1d0 + phi)*(1d0 + phi))))
     259              :             ! Equation (B5)
     260            0 :             lambdamax = dsqrt(prandtl) - prandtl*dsqrt(1d0 + phi)  ! Equation (B5)
     261              :          end if
     262              :       else
     263            0 :          maxl = pow((one_third)*(1d0 - r_th) + (1d0 - r_th)*(1d0 - r_th)*(5d0 - 4d0*phi)/27d0, 0.25d0)
     264              :          ! Equation (B19) carried to next order (doesn't work well otherwise)
     265            0 :          maxl4 = maxl*maxl*maxl*maxl
     266            0 :          maxl6 = maxl4*maxl*maxl
     267            0 :          lambdamax = 2d0*prandtl*phi*maxl6/(1d0 - (1d0 + phi)*maxl4)  ! Equation (B11)
     268              :       end if
     269            0 :       if (lambdamax < 0) then  ! shouldn't be needed, but just as precaution
     270            0 :          maxl = 0.5d0
     271            0 :          lambdamax = 0.5d0
     272              :       end if
     273              : 
     274            0 :    end subroutine analytical_estimate_th
     275              : 
     276            0 :    subroutine NR(xrk, prandtl, diffratio, R0, ierr)
     277              :       ! Newton Relaxation routine used to solve cubic & quadratic in thermohaline case.
     278              :       ! Written by P. Garaud (2013). Please email pgaraud@ucsc.edu for troubleshooting.
     279              : 
     280              :       real(dp), parameter :: acy = 1.d-13  ! accuracy of NR solution.
     281              :       integer, parameter :: niter = 20  ! max number of iterations allowed before giving up.
     282              :       integer, parameter :: &  ! array dimension input parameters for dgesvx
     283              :          n = 2, &
     284              :          nrhs = 1, &
     285              :          lda = n, &
     286              :          ldaf = n, &
     287              :          ldb = n, &
     288              :          ldx = n
     289              : 
     290              :       real(dp), intent(inout) :: xrk(2)
     291              :       real(dp), intent(in) :: prandtl, diffratio, R0
     292              :       integer, intent(out) :: ierr
     293              :       integer :: iter
     294            0 :       real(dp) :: f(2)  ! Functions f
     295            0 :       real(dp) :: j(2, 2)  ! Jacobian
     296            0 :       real(dp) :: err, errold  ! Error at each iteration
     297            0 :       real(dp) :: x1_sav, x2_sav
     298            0 :       real(dp) :: A(lda, n), AF(ldaf, n), R(n), C(n), B(ldb, nrhs), X(ldx, nrhs), &
     299            0 :                   rcond, ferr(nrhs), berr(nrhs), work(4*n)
     300              :       character :: fact, trans, equed
     301              :       integer :: ipiv(n), iwork(n)
     302              : 
     303              :       include 'formats'
     304              : 
     305              :       ! Initialize flags and other counters.
     306            0 :       ierr = 0
     307            0 :       iter = 0
     308            0 :       err = 1d99
     309            0 :       errold = 1d99
     310              :       ! Save input guess
     311            0 :       x1_sav = xrk(1)
     312            0 :       x2_sav = xrk(2)
     313              : 
     314              :       ! While error is too large .and. decreasing, iterate.
     315            0 :       do while ((err > acy) .and. (ierr == 0) .and. (iter < niter))
     316            0 :          call thermohaline_rhs(xrk, f, j, prandtl, diffratio, R0)
     317              : 
     318            0 :          fact = 'E'
     319            0 :          trans = 'N'
     320            0 :          equed = ''
     321              : 
     322            0 :          A = j
     323            0 :          B(1, 1) = f(1)
     324            0 :          B(2, 1) = f(2)
     325              : 
     326              :          call dgesvx(fact, trans, n, nrhs, A, lda, AF, ldaf, ipiv, &
     327              :                      equed, r, c, B, ldb, x, ldx, rcond, ferr, berr, &
     328            0 :                      work, iwork, ierr)
     329              : 
     330            0 :          if (ierr /= 0) then
     331              :             !write(*,*) 'dgesvx failed in thermohaline routine', iter
     332              :             !write(*,2) j(1,1),j(1,2)
     333              :             !write(*,2) j(2,1),j(2,2)
     334              :          else
     335            0 :             iter = iter + 1
     336            0 :             f(1) = X(1, 1)
     337            0 :             f(2) = X(2, 1)
     338            0 :             err = dsqrt(f(1)*f(1) + f(2)*f(2))  ! Calculate the new error
     339              :             ! If, after a while, the error is still not decreasing, give up and exit NR.
     340              :             ! Otherwise, continue.
     341            0 :             if ((iter > 5) .and. (err > errold)) then
     342              :                ! Write(*,2) 'Error not decreasing at iter', iter, err, errold
     343            0 :                ierr = 1
     344              :                ! Reset xs and exit loop.
     345            0 :                xrk(1) = x1_sav
     346            0 :                xrk(2) = x2_sav
     347              :             else
     348            0 :                xrk = xrk - f  ! The solution is now in f, so update x
     349              :                errold = err
     350              :             end if
     351              :          end if
     352              :       end do
     353              : 
     354            0 :       if (err <= acy) then
     355            0 :          ierr = 0
     356              :       else
     357              :          ! write(*,2) 'NR failed to converge', err, iter
     358            0 :          ierr = 1
     359              :       end if
     360              : 
     361            0 :    end subroutine NR
     362              : 
     363              : end module thermohaline
        

Generated by: LCOV version 2.0-1