LCOV - code coverage report
Current view: top level - turb/private - tdc_support.f90 (source / functions) Coverage Total Hit
Test: coverage.info Lines: 72.4 % 203 147
Test Date: 2025-05-08 18:23:42 Functions: 90.9 % 11 10

            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 tdc_support
      21              : 
      22              : use const_def, only: dp, pi, sqrt_2_div_3, boltz_sigma
      23              : use num_lib
      24              : use utils_lib
      25              : use auto_diff
      26              : 
      27              : implicit none
      28              : 
      29              : private
      30              : public :: set_Y
      31              : public :: Q_bisection_search
      32              : public :: dQdZ_bisection_search
      33              : public :: Af_bisection_search
      34              : public :: convert
      35              : public :: unconvert
      36              : public :: safe_tanh
      37              : public :: tdc_info
      38              : public :: eval_Af
      39              : public :: eval_xis
      40              : public :: compute_Q
      41              : 
      42              :    !> Stores the information which is required to evaluate TDC-related quantities and which
      43              :    !! do not depend on Y.
      44              :    !!
      45              :    !! @param report Write debug output if true, not if false.
      46              :    !! @param mixing_length_alpha Mixing length parameter
      47              :    !! @param alpha_TDC_DAMP TDC turbulent damping parameter
      48              :    !! @param alpha_TDC_DAMPR TDC radiative damping parameter
      49              :    !! @param alpha_TDC_PtdVdt TDC coefficient on P_turb*dV/dt. Physically should probably be 1.
      50              :    !! @param dt Time-step
      51              :    !! @param c0 A proportionality factor for the convective luminosity
      52              :    !! @param L luminosity
      53              :    !! @param L0 L0 = (Lrad / grad_rad) is the luminosity radiation would carry if dlnT/dlnP = 1.
      54              :    !! @param A0 Initial convection speed
      55              :    !! @param T Temperature
      56              :    !! @param rho Density (g/cm^3)
      57              :    !! @param dV 1/rho_face - 1/rho_start_face (change in specific volume at the face)
      58              :    !! @param Cp Heat capacity
      59              :    !! @param kap Opacity
      60              :    !! @param Hp Pressure scale height
      61              :    !! @param gradL gradL is the neutrally buoyant dlnT/dlnP (= grad_ad + grad_mu),
      62              :    !! @param grada grada is the adiabatic dlnT/dlnP,
      63              :    !! @param Gamma Gamma is the MLT Gamma efficiency parameter, which we evaluate in steady state from MLT.
      64              :    type tdc_info
      65              :       logical :: report
      66              :       real(dp) :: mixing_length_alpha, alpha_TDC_DAMP, alpha_TDC_DAMPR, alpha_TDC_PtdVdt, dt
      67              :       type(auto_diff_real_tdc) :: A0, c0, L, L0, gradL, grada
      68              :       type(auto_diff_real_star_order1) :: T, rho, dV, Cp, kap, Hp, Gamma
      69              :    end type tdc_info
      70              : 
      71              : contains
      72              : 
      73              :    !> Y = +- exp(Z)
      74              :    !! If Y > 0, Y = exp(Z)
      75              :    !! If Y < 0, Y = -exp(Z)
      76              :    !!
      77              :    !! @param Y_is_positive True if Y > 0, False otherwise.
      78              :    !! @param Z log|Y|
      79              :    !! @param Y (output)
      80       197007 :    type(auto_diff_real_tdc) function set_Y(Y_is_positive, Z) result(Y)
      81              :       logical, intent(in) :: Y_is_positive
      82              :       type(auto_diff_real_tdc), intent(in) :: Z
      83       197007 :       if (Y_is_positive) then
      84       195031 :          Y = exp(Z)
      85              :       else
      86         1976 :          Y = -exp(Z)
      87              :       end if
      88       197007 :    end function set_Y
      89              : 
      90              :    !> This routine performs a bisection search for Q=0 over a domain in Z for which Q is monotone.
      91              :    !! Monotonicity is assumed, not verified! This means failures can occur if monotonicity fails.
      92              :    !! The search continues until the domain is narrowed to less than a width of bracket_tolerance,
      93              :    !! or until more than max_iter iterations have been taken. Because this is just used to get us in
      94              :    !! the right ballpark, bracket_tolerance is set quite wide, to 1.
      95              :    !!
      96              :    !! There is a check at the start to verify that Q takes on opposite signs on either end of the
      97              :    !! domain. This is allows us to bail early if there is no root in the domain.
      98              :    !!
      99              :    !! @param info tdc_info type storing various quantities that are independent of Y.
     100              :    !! @param Y_is_positive True if Y > 0, False otherwise.
     101              :    !! @param lower_bound_Z Lower bound on Z. Note that this is an input *and* an output.
     102              :    !! @param upper_bound_Z Upper bound on Z. Note that this is an input *and* an output.
     103              :    !! @param Z Output: the midpoint of the final bounds.
     104              :    !! @param ierr 0 if everything worked, 1 if the domain does not contain a root.
     105        26991 :    subroutine Q_bisection_search(info, Y_is_positive, lower_bound_Z, upper_bound_Z, Z, ierr)
     106              :       ! Inputs
     107              :       type(tdc_info), intent(in) :: info
     108              :       logical, intent(in) :: Y_is_positive
     109              : 
     110              :       ! Outputs
     111              :       type(auto_diff_real_tdc), intent(inout) :: lower_bound_Z, upper_bound_Z
     112              :       type(auto_diff_real_tdc), intent(out) :: Z
     113              :       integer, intent(out) :: ierr
     114              : 
     115              :       ! Parameters
     116              :       real(dp), parameter :: bracket_tolerance = 1d0
     117              :       integer, parameter :: max_iter = 30
     118              : 
     119              :       ! Intermediates
     120              :       type(auto_diff_real_tdc) :: Y, Af, Q, Q_ub, Q_lb
     121              :       integer :: iter
     122              : 
     123         8997 :       ierr = 0
     124              : 
     125         8997 :       Y = set_Y(Y_is_positive, lower_bound_Z)
     126         8997 :       call compute_Q(info, Y, Q_lb, Af)
     127              : 
     128         8997 :       Y = set_Y(Y_is_positive, upper_bound_Z)
     129         8997 :       call compute_Q(info, Y, Q_ub, Af)
     130              : 
     131              :       ! Check to make sure that the lower and upper bounds on Z actually bracket
     132              :       ! a solution to Q(Y(Z)) = 0.
     133         8997 :       if (Q_lb * Q_ub > 0d0) then
     134            0 :          if (info%report) then
     135            0 :             write(*,*) 'Q bisection error. Initial Z window does not bracket a solution.'
     136            0 :             write(*,*) 'Q(Lower Z)',Q_lb%val
     137            0 :             write(*,*) 'Q(Upper Z)',Q_ub%val
     138            0 :             write(*,*) 'tolerance', bracket_tolerance
     139            0 :             write(*,*) 'Y', Y%val
     140            0 :             write(*,*) 'dYdZ', Y%d1val1
     141            0 :             write(*,*) 'exp(Z)', exp(Z%val)
     142            0 :             write(*,*) 'Z', Z%val
     143            0 :             write(*,*) 'A0', info%A0%val
     144            0 :             write(*,*) 'c0', info%c0%val
     145            0 :             write(*,*) 'L', info%L%val
     146            0 :             write(*,*) 'L0', info%L0%val
     147            0 :             write(*,*) 'grada', info%grada%val
     148            0 :             write(*,*) 'gradL', info%gradL%val
     149            0 :             write(*,'(A)')
     150              :          end if
     151            0 :          ierr = 1
     152            0 :          return
     153              :       end if
     154              : 
     155        71960 :       do iter=1,max_iter
     156        71960 :          Z = (upper_bound_Z + lower_bound_Z) / 2d0
     157        71960 :          Y = set_Y(Y_is_positive, Z)
     158              : 
     159        71960 :          call compute_Q(info, Y, Q, Af)
     160              : 
     161        71960 :          if (Q > 0d0 .and. Q_ub > 0d0) then
     162           10 :             upper_bound_Z = Z
     163           10 :             Q_ub = Q
     164        71950 :          else if (Q > 0d0 .and. Q_lb > 0d0) then
     165        38058 :             lower_bound_Z = Z
     166        38058 :             Q_lb = Q
     167        33892 :          else if (Q < 0d0 .and. Q_ub < 0d0) then
     168        33790 :             upper_bound_Z = Z
     169        33790 :             Q_ub = Q
     170          102 :          else if (Q < 0d0 .and. Q_lb < 0d0) then
     171          102 :             lower_bound_Z = Z
     172          102 :             Q_lb = Q
     173              :          end if
     174              : 
     175       143920 :          if (upper_bound_Z - lower_bound_Z < bracket_tolerance) then
     176         8997 :             Z = (upper_bound_Z + lower_bound_Z) / 2d0
     177         8997 :             call compute_Q(info, Y, Q, Af)
     178         8997 :             return
     179              :          end if
     180              :       end do
     181              : 
     182              :    end subroutine Q_bisection_search
     183              : 
     184              :    !> This routine performs a bisection search for dQ/dZ=0 with Y < 0.
     185              :    !! The domain is assumed to be restricted to have Af > 0, so that dQ/dZ is
     186              :    !! continuous and monotone. This is checked, and if it fails a MESA ERROR is called
     187              :    !! rather than throwing an ierr because if the rest of the TDC logic is correct this should
     188              :    !! not happen.
     189              :    !!
     190              :    !! The search continues until the domain is narrowed to less than a width of bracket_tolerance (1d-4),
     191              :    !! or until more than max_iter iterations have been taken.
     192              :    !!
     193              :    !! There is a check at the start to verify that dQ/dZ takes on opposite signs on either end of the
     194              :    !! domain. This is allows us to bail early if there is no root in the domain.
     195              :    !!
     196              :    !! @param info tdc_info type storing various quantities that are independent of Y.
     197              :    !! @param lower_bound_Z Lower bound on Z. Note that this is an input *and* an output.
     198              :    !! @param upper_bound_Z Upper bound on Z. Note that this is an input *and* an output.
     199              :    !! @param Z Output: the midpoint of the final bounds.
     200              :    !! @param has_root True if a root was found, False otherwise.
     201          136 :    subroutine dQdZ_bisection_search(info, lower_bound_Z_in, upper_bound_Z_in, Z, has_root)
     202              :       ! Inputs
     203              :       type(tdc_info), intent(in) :: info
     204              :       type(auto_diff_real_tdc), intent(in) :: lower_bound_Z_in, upper_bound_Z_in
     205              : 
     206              :       ! Outputs
     207              :       type(auto_diff_real_tdc), intent(out) :: Z
     208              :       logical, intent(out) :: has_root
     209              : 
     210              :       ! Parameters
     211              :       real(dp), parameter :: bracket_tolerance = 1d-4
     212              :       integer, parameter :: max_iter = 50
     213              : 
     214              :       ! Intermediates
     215              :       type(auto_diff_real_tdc) :: lower_bound_Z, upper_bound_Z
     216              :       type(auto_diff_real_tdc) :: Y, Af, Q, Q_lb, Q_ub, dQdZ, dQdZ_lb, dQdZ_ub
     217              :       integer :: iter
     218              : 
     219              :       ! Set up
     220           34 :       lower_bound_Z = lower_bound_Z_in  !lower_bound_Z_in
     221           34 :       lower_bound_Z%d1val1 = 1d0
     222           34 :       upper_bound_Z = upper_bound_Z_in
     223           34 :       upper_bound_Z%d1val1 = 1d0
     224              : 
     225              :       ! Check bounds
     226           34 :       Y = set_Y(.false., lower_bound_Z)
     227           34 :       call compute_Q(info, Y, Q_lb, Af)
     228           34 :       if (Af == 0) then
     229            0 :          write(*,*) 'Z_lb, A0, Af', lower_bound_Z%val, info%A0%val, Af%val
     230            0 :          call mesa_error(__FILE__,__LINE__,'bad call to tdc_support dQdZ_bisection_search: Af == 0')
     231              :       end if
     232           34 :       dQdZ_lb = differentiate_1(Q_lb)
     233              : 
     234           34 :       Y = set_Y(.false., upper_bound_Z)
     235           34 :       call compute_Q(info, Y, Q_ub, Af)
     236           34 :       if (Af == 0) then
     237            0 :          write(*,*) 'Z_ub, A0, Af', lower_bound_Z%val, info%A0%val, Af%val
     238            0 :          call mesa_error(__FILE__,__LINE__,'bad call to tdc_support dQdZ_bisection_search: Af == 0')
     239              :       end if
     240           34 :       dQdZ_ub = differentiate_1(Q_ub)
     241              : 
     242              :       ! Check to make sure that the lower and upper bounds on Z actually bracket
     243              :       ! a solution to dQ/dZ = 0.
     244           34 :       has_root = .true.
     245           34 :       if (dQdZ_lb * dQdZ_ub > 0d0) then
     246            0 :          if (info%report) then
     247            0 :             write(*,*) 'dQdZ bisection error. Initial Z window does not bracket a solution.'
     248            0 :             write(*,*) 'Q(Lower Z)',Q_lb%val
     249            0 :             write(*,*) 'Q(Upper Z)',Q_ub%val
     250            0 :             write(*,*) 'dQdZ(Lower Z)',dQdZ_lb%val
     251            0 :             write(*,*) 'dQdZ(Upper Z)',dQdZ_ub%val
     252            0 :             write(*,*) 'tolerance', bracket_tolerance
     253            0 :             write(*,*) 'Y', Y%val
     254            0 :             write(*,*) 'dYdZ', Y%d1val1
     255            0 :             write(*,*) 'exp(Z)', exp(Z%val)
     256            0 :             write(*,*) 'Z', Z%val
     257            0 :             write(*,*) 'A0', info%A0%val
     258            0 :             write(*,*) 'c0', info%c0%val
     259            0 :             write(*,*) 'L', info%L%val
     260            0 :             write(*,*) 'L0', info%L0%val
     261            0 :             write(*,*) 'grada', info%grada%val
     262            0 :             write(*,*) 'gradL', info%gradL%val
     263            0 :             write(*,'(A)')
     264              :          end if
     265            0 :          has_root = .false.
     266            0 :          return
     267              :       end if
     268              : 
     269              :       ! Bisection search
     270          680 :       do iter=1,max_iter
     271          680 :          Z = (upper_bound_Z + lower_bound_Z) / 2d0
     272          680 :          Z%d1val1 = 1d0
     273          680 :          Y = set_Y(.false., Z)
     274              : 
     275          680 :          call compute_Q(info, Y, Q, Af)
     276          680 :          dQdZ = differentiate_1(Q)
     277              : 
     278              :          ! We only ever call this when Y < 0.
     279              :          ! In this regime, dQ/dZ can take on either sign, and has at most one stationary point.
     280              : 
     281          680 :          if (info%report) write(*,*) 'Bisecting dQdZ. Z, dQdZ, Z_lb, dQdZ_lb, Z_ub, dQdZ_ub', &
     282            0 :                                       Z%val, dQdZ%val, lower_bound_Z%val, dQdZ_lb%val, upper_bound_Z%val, dQdZ_ub%val
     283              : 
     284          680 :          if (dQdZ > 0d0 .and. dQdZ_ub > 0d0) then
     285            0 :             upper_bound_Z = Z
     286            0 :             dQdZ_ub = dQdZ
     287          680 :          else if (dQdZ > 0d0 .and. dQdZ_lb > 0d0) then
     288          417 :             lower_bound_Z = Z
     289          417 :             dQdZ_lb = dQdZ
     290          263 :          else if (dQdZ < 0d0 .and. dQdZ_ub < 0d0) then
     291          263 :             upper_bound_Z = Z
     292          263 :             dQdZ_ub = dQdZ
     293            0 :          else if (dQdZ < 0d0 .and. dQdZ_lb < 0d0) then
     294            0 :             lower_bound_Z = Z
     295            0 :             dQdZ_lb = dQdZ
     296              :          end if
     297              : 
     298         1360 :          if (upper_bound_Z - lower_bound_Z < bracket_tolerance) then
     299           34 :             Z = (upper_bound_Z + lower_bound_Z) / 2d0
     300           34 :             call compute_Q(info, Y, Q, Af)
     301           34 :             return
     302              :          end if
     303              :       end do
     304              : 
     305              :    end subroutine dQdZ_bisection_search
     306              : 
     307              :    !> This routine performs a bisection search for the least-negative Y such that Af(Y) = 0.
     308              :    !! Once we find that, we return a Y that is just slightly less negative so that Af > 0.
     309              :    !! This is important for our later bisection of dQ/dZ, because we want the upper-Z end of
     310              :    !! the domain we bisect to have dQ/dZ < 0, which means it has to capture the fact that d(Af)/dZ < 0,
     311              :    !! and so the Z we return has to be on the positive-Af side of the discontinuity in dQdZ.
     312              :    !!
     313              :    !! Note that monotonicity is assumed, not verified!
     314              :    !! Af(Y) is monotonic in Y for negative Y by construction, so this shouldn't be a problem.
     315              :    !!
     316              :    !! The search continues until the domain is narrowed to less than a width of bracket_tolerance (1d-4),
     317              :    !! or until more than max_iter iterations have been taken.
     318              :    !!
     319              :    !! There is a check at the start to verify that Af == 0 at the most-negative end of the domain.
     320              :    !! This is allows us to bail early if there is no root in the domain.
     321              :    !!
     322              :    !! @param info tdc_info type storing various quantities that are independent of Y.
     323              :    !! @param lower_bound_Z Lower bound on Z. Note that this is an input *and* an output.
     324              :    !! @param upper_bound_Z Upper bound on Z. Note that this is an input *and* an output.
     325              :    !! @param Z Output: The bound on the positive-Af side of the root (which always is the lower bound).
     326              :    !! @param Af Output: Af(Z).
     327              :    !! @param ierr 0 if everything worked, 1 if the domain does not contain a root.
     328          136 :    subroutine Af_bisection_search(info, lower_bound_Z_in, upper_bound_Z_in, Z, Af, ierr)
     329              :       ! Inputs
     330              :       type(tdc_info), intent(in) :: info
     331              :       type(auto_diff_real_tdc), intent(in) :: lower_bound_Z_in, upper_bound_Z_in
     332              : 
     333              :       ! Outputs
     334              :       integer, intent(out) :: ierr
     335              :       type(auto_diff_real_tdc), intent(out) :: Z, Af
     336              : 
     337              :       ! Parameters
     338              :       real(dp), parameter :: bracket_tolerance = 1d-4
     339              :       integer, parameter :: max_iter = 50
     340              : 
     341              :       ! Intermediates
     342              :       type(auto_diff_real_tdc) :: lower_bound_Z, upper_bound_Z
     343              :       type(auto_diff_real_tdc) :: Y, Q
     344              :       integer :: iter
     345              : 
     346              :       ! Set up
     347           34 :       ierr = 0
     348           34 :       lower_bound_Z = lower_bound_Z_in
     349           34 :       upper_bound_Z = upper_bound_Z_in
     350              : 
     351           34 :       Y = set_Y(.false., upper_bound_Z)
     352           34 :       call compute_Q(info, Y, Q, Af)
     353           34 :       if (Af > 0) then  ! d(Af)/dZ < 0, so if Af(upper_bound_Z) > 0 there's no solution in this interval.
     354            0 :          ierr = 1
     355           34 :          return
     356              :       end if
     357              : 
     358           34 :       Y = set_Y(.false., lower_bound_Z)
     359           34 :       call compute_Q(info, Y, Q, Af)
     360           34 :       if (Af == 0) then
     361              :          ! We want to find Z such that Af(Z) is just barely above zero.
     362              :          ! We do this by finding Z such that Af(Z) == 0, then backing off to slightly smaller Z.
     363              :          ! Because d(Af)/dZ < 0, this gives a Z such that Af(Z) > 0.
     364              :          ! Hence, if Af(lower_bound_Z) == 0 then Af = 0 uniformly in this interval and we cannot
     365              :          ! return Z such that Af(Z) is just barely non-zero.
     366            0 :          ierr = 2
     367            0 :          return
     368              :       end if
     369              : 
     370          714 :       do iter=1,max_iter
     371          714 :          Z = (upper_bound_Z + lower_bound_Z) / 2d0
     372          714 :          Y = set_Y(.false., Z)
     373              : 
     374          714 :          call compute_Q(info, Y, Q, Af)
     375              : 
     376              :          ! Y < 0 so increasing Y means decreasing Z.
     377              :          ! d(Af)/dY > 0 so d(Af)/dZ < 0.
     378              : 
     379          714 :          if (Af > 0d0) then  ! Means we are at too-low Z.
     380          348 :             lower_bound_Z = Z
     381              :          else
     382          366 :             upper_bound_Z = Z
     383              :          end if
     384              : 
     385         1428 :          if (upper_bound_Z - lower_bound_Z < bracket_tolerance) then
     386              :             ! We return the lower bound because this is guaranteed to have Af > 0 (just barely).
     387              :             ! This is important for our later bisection of dQ/dZ, because we want the upper-Z end of
     388              :             ! the domain we bisect to have dQ/dZ < 0, which means it has to capture the fact that d(Af)/dZ < 0.
     389           34 :             Z = lower_bound_Z
     390           34 :             call compute_Q(info, Y, Q, Af)
     391           34 :             return
     392              :          end if
     393              :       end do
     394              : 
     395              :    end subroutine Af_bisection_search
     396              : 
     397              :    !> Computes the hyperbolic tangent of x in a way that is numerically safe.
     398              :    !!
     399              :    !! @param x Input
     400              :    !! @param z Output
     401       361826 :    type(auto_diff_real_tdc) function safe_tanh(x) result(z)
     402              :       type(auto_diff_real_tdc), intent(in) :: x
     403              : 
     404       361826 :       if (x > 50d0) then
     405       324742 :          z = 1d0
     406        37084 :       else if (x < -50d0) then
     407            0 :          z = -1d0
     408              :       else
     409        37084 :          z = tanh(x)
     410              :       end if
     411       361826 :    end function safe_tanh
     412              : 
     413              :    !> The TDC newton solver needs higher-order partial derivatives than
     414              :    !! the star newton solver, because the TDC one needs to pass back a result
     415              :    !! which itself contains the derivatives that the star solver needs.
     416              :    !! These additional derivatives are provided by the auto_diff_real_tdc type.
     417              :    !!
     418              :    !! This method converts a auto_diff_real_star_order1 variable into a auto_diff_real_tdc,
     419              :    !! setting the additional partial derivatives to zero. This 'upgrades' variables storing
     420              :    !! stellar structure to a form the TDC solver can use.
     421              :    !!
     422              :    !! @param K_in, input, an auto_diff_real_star_order1 variable
     423              :    !! @param K, output, an auto_diff_real_tdc variable.
     424      1733824 :    type(auto_diff_real_tdc) function convert(K_in) result(K)
     425              :       type(auto_diff_real_star_order1), intent(in) :: K_in
     426      1733824 :       K%val = K_in%val
     427     58950016 :       K%d1Array(1:SIZE(K_in%d1Array)) = K_in%d1Array
     428              :       K%d1val1 = 0d0
     429     58950016 :       K%d1val1_d1Array(1:SIZE(K_in%d1Array)) = 0d0
     430      1733824 :    end function convert
     431              : 
     432              :    !> The TDC newton solver needs higher-order partial derivatives than
     433              :    !! the star newton solver, because the TDC one needs to pass back a result
     434              :    !! which itself contains the derivatives that the star solver needs.
     435              :    !! These additional derivatives are provided by the auto_diff_real_tdc type.
     436              :    !!
     437              :    !! This method converts a auto_diff_real_tdc variable into a auto_diff_real_star_order1,
     438              :    !! dropping the additional partial derivatives which (after the TDC solver is done) are
     439              :    !! no longer needed. This allows the output of the TDC solver to be passed back to the star solver.
     440              :    !!
     441              :    !! @param K_in, input, an auto_diff_real_tdc variable
     442              :    !! @param K, output, an auto_diff_real_star_order1 variable.
     443       139481 :    type(auto_diff_real_star_order1) function unconvert(K_in) result(K)
     444              :       type(auto_diff_real_tdc), intent(in) :: K_in
     445       139481 :       K%val = K_in%val
     446      4742354 :       K%d1Array = K_in%d1Array(1:SIZE(K%d1Array))
     447       139481 :    end function unconvert
     448              : 
     449              :    !> Q is the residual in the TDC equation, namely:
     450              :    !!
     451              :    !! Q = (L - L0 * gradL) - (L0 + c0 * Af) * Y
     452              :    !!
     453              :    !! @param info tdc_info type storing various quantities that are independent of Y.
     454              :    !! @param Y superadiabaticity
     455              :    !! @param Q The residual of the above equation (an output).
     456              :    !! @param Af The final convection speed (an output).
     457       608852 :    subroutine compute_Q(info, Y, Q, Af)
     458              :       type(tdc_info), intent(in) :: info
     459              :       type(auto_diff_real_tdc), intent(in) :: Y
     460              :       type(auto_diff_real_tdc), intent(out) :: Q, Af
     461              :       type(auto_diff_real_tdc) :: xi0, xi1, xi2, Y_env
     462              : 
     463              :       ! Y = grad-gradL
     464              :       ! Gamma=(grad-gradE)/(gradE-gradL)
     465              :       ! So
     466              :       ! Y_env = grad-gradE = (grad-gradL)*Gamma/(1+Gamma) = Y*Gamma/(1+Gamma)
     467              :       ! So overall we just multiply the Y by Gamma/(1+Gamma) to get Y_env.
     468              :       !
     469              :       ! We only use Y_env /= Y when Y > 0 (i.e. the system is convectively unstable)
     470              :       ! because we only have a Gamma from MLT in that case.
     471              :       ! so when Y < 0 we just use Y_env = Y.
     472       304426 :       if (Y > 0) then
     473       180913 :          Y_env = Y * convert(info%Gamma/(1+info%Gamma))
     474              :       else
     475       123513 :          Y_env = Y
     476              :       end if
     477              : 
     478              :       ! Y_env sets the acceleration of blobs.
     479       304426 :       call eval_xis(info, Y_env, xi0, xi1, xi2)
     480       304426 :       Af = eval_Af(info%dt, info%A0, xi0, xi1, xi2)
     481              : 
     482              :       ! Y_env sets the convective flux but not the radiative flux.
     483       304426 :       Q = (info%L - info%L0*info%gradL) - info%L0 * Y - info%c0*Af*Y_env
     484              : 
     485       304426 :    end subroutine compute_Q
     486              : 
     487              :    !! Calculates the coefficients of the TDC velocity equation.
     488              :    !! The velocity equation is
     489              :    !!
     490              :    !! 2 dw/dt = xi0 + w * xi1 + w**2 * xi2 - Lambda
     491              :    !!
     492              :    !! where Lambda (currently set to zero) captures coupling between cells.
     493              :    !!
     494              :    !! The coefficients xi0/1/2 are given by
     495              :    !!
     496              :    !! xi0 = (epsilon_q + S * Y) / w
     497              :    !! xi1 = (-D_r + p_turb dV/dt) / w^2    [here V = 1/rho is the volume]
     498              :    !! xi2 = -D / w^3
     499              :    !!
     500              :    !! Note that these terms are evaluated on faces, because the convection speed
     501              :    !! is evaluated on faces. As a result all the inputs must either natively
     502              :    !! live on faces or be interpolated to faces.
     503              :    !!
     504              :    !! This function also has some side effects in terms of storing some of the terms
     505              :    !! it calculates for plotting purposes.
     506              :    !!
     507              :    !! @param info tdc_info type storing various quantities that are independent of Y.
     508              :    !! @param Y superadiabaticity
     509              :    !! @param xi0 Output, the constant term in the convective velocity equation.
     510              :    !! @param xi1 Output, the prefactor of the linear term in the convective velocity equation.
     511              :    !! @param xi2 Output, the prefactor of the quadratic term in the convective velocity equation.
     512       608852 :    subroutine eval_xis(info, Y, xi0, xi1, xi2)
     513              :       ! eval_xis sets up Y with partial wrt Z
     514              :       ! so results come back with partials wrt Z
     515              :       type(tdc_info), intent(in) :: info
     516              :       type(auto_diff_real_tdc), intent(in) :: Y
     517              :       type(auto_diff_real_tdc), intent(out) :: xi0, xi1, xi2
     518              :       type(auto_diff_real_tdc) :: S0, D0, DR0
     519              :       type(auto_diff_real_star_order1) :: gammar_div_alfa, Pt0, dVdt
     520              :       real(dp), parameter :: x_ALFAS = (1.d0/2.d0)*sqrt_2_div_3
     521              :       real(dp), parameter :: x_CEDE  = (8.d0/3.d0)*sqrt_2_div_3
     522              :       real(dp), parameter :: x_ALFAP = 2.d0/3.d0
     523              :       real(dp), parameter :: x_GAMMAR = 2.d0*sqrt(3.d0)
     524              : 
     525       304426 :       S0 = convert(x_ALFAS*info%mixing_length_alpha*info%Cp*info%T/info%Hp)*info%grada
     526       304426 :       S0 = S0*Y
     527       304426 :       D0 = convert(info%alpha_TDC_DAMP*x_CEDE/(info%mixing_length_alpha*info%Hp))
     528       304426 :       gammar_div_alfa = info%alpha_TDC_DAMPR*x_GAMMAR/(info%mixing_length_alpha*info%Hp)
     529       304426 :       DR0 = convert(4d0*boltz_sigma*pow2(gammar_div_alfa)*pow3(info%T)/(pow2(info%rho)*info%Cp*info%kap))
     530       304426 :       Pt0 = info%alpha_TDC_PtdVdt*x_ALFAP*info%rho
     531       304426 :       dVdt = info%dV/info%dt
     532              : 
     533       304426 :       xi0 = S0
     534       304426 :       xi1 = -(DR0 + convert(Pt0*dVdt))
     535       304426 :       xi2 = -D0
     536       304426 :    end subroutine eval_xis
     537              : 
     538              :    !! Calculates the solution to the TDC velocity equation.
     539              :    !! The velocity equation is
     540              :    !!
     541              :    !! 2 dw/dt = xi0 + w * xi1 + w**2 * xi2 - Lambda
     542              :    !!
     543              :    !! where Lambda (currently set to zero) captures coupling between cells.
     544              :    !! The xi0/1/2 variables are constants for purposes of solving this equation.
     545              :    !!
     546              :    !! An important related parameter is J:
     547              :    !!
     548              :    !! J^2 = xi1^2 - 4 * xi0 * xi2
     549              :    !!
     550              :    !! When J^2 > 0 the solution for w is hyperbolic in time.
     551              :    !! When J^2 < 0 the solution is trigonometric, behaving like tan(J * t / 4 + c).
     552              :    !!
     553              :    !! In the trigonometric branch note that once the solution passes through its first
     554              :    !! root the solution for all later times is w(t) = 0. This is not a solution to the
     555              :    !! convective velocity equation as written above, but *is* a solution to the convective
     556              :    !! energy equation (multiply the velocity equation by w), which is the one we actually
     557              :    !! want to solve (per Radek Smolec's thesis). We just solve the velocity form
     558              :    !! because it's more convenient.
     559              :    !!
     560              :    !! @param dt Time-step
     561              :    !! @param A0 convection speed from the start of the step (cm/s)
     562              :    !! @param xi0 The constant term in the convective velocity equation.
     563              :    !! @param xi1 The prefactor of the linear term in the convective velocity equation.
     564              :    !! @param xi2 The prefactor of the quadratic term in the convective velocity equation.
     565              :    !! @param Af Output, the convection speed at the end of the step (cm/s)
     566       304426 :    function eval_Af(dt, A0, xi0, xi1, xi2) result(Af)
     567              :       real(dp), intent(in) :: dt
     568              :       type(auto_diff_real_tdc), intent(in) :: A0, xi0, xi1, xi2
     569              :       type(auto_diff_real_tdc) :: Af  ! output
     570              :       type(auto_diff_real_tdc) :: J2, J, Jt4, num, den, y_for_atan, root
     571              : 
     572       304426 :       J2 = pow2(xi1) - 4d0 * xi0 * xi2
     573              : 
     574       304426 :       if (J2 > 0d0) then  ! Hyperbolic branch
     575       180913 :          J = sqrt(abs(J2))  ! Only compute once we know J2 is not 0
     576       180913 :          Jt4 = 0.25d0 * dt * J
     577       180913 :          num = safe_tanh(Jt4) * (2d0 * xi0 + A0 * xi1) + A0 * J
     578       180913 :          den = safe_tanh(Jt4) * (xi1 + 2d0 * A0 * xi2) - J
     579       180913 :          Af = num / den
     580       180913 :          if (Af < 0d0) then
     581       180913 :             Af = -Af
     582              :          end if
     583       123513 :       else if (J2 < 0d0) then  ! Trigonometric branch
     584        58271 :          J = sqrt(abs(J2))  ! Only compute once we know J2 is not 0
     585        58271 :          Jt4 = 0.25d0 * dt * J
     586              : 
     587              :          ! This branch contains decaying solutions that reach A = 0, at which point
     588              :          ! they switch onto the 'zero' branch. So we have to calculate the position of
     589              :          ! the first root to check it against dt.
     590        58271 :          y_for_atan = xi1 + 2d0 * A0 * xi2
     591        58271 :          root = atan(xi1 / J) - atan(y_for_atan / J)
     592              : 
     593              :          ! The root enters into a tangent, so we can freely shift it by pi and
     594              :          ! get another root. We care about the first positive root, and the above prescription
     595              :          ! is guaranteed to give an answer between (-2*pi,2*pi) because atan produces an answer in [-pi,pi],
     596              :          ! so we add/subtract a multiple of pi to get the root into [0,pi).
     597        58271 :          if (root > pi) then
     598            0 :             root = root - pi
     599        58271 :          else if (root < -pi) then
     600            0 :             root = root + 2d0*pi
     601        58271 :          else if (root < 0d0) then
     602            0 :             root = root + pi
     603              :          end if
     604              : 
     605        58271 :          if (Jt4 < root) then
     606         1576 :             num = -xi1 + J * tan(Jt4 + atan(y_for_atan / J))
     607         1576 :             den = 2d0 * xi2
     608         1576 :             Af = num / den
     609              :          else
     610        56695 :             Af = 0d0
     611              :          end if
     612              :       else  ! if (J2 == 0d0) then
     613        65242 :          Af = A0
     614              :       end if
     615              : 
     616       304426 :    end function eval_Af
     617              : 
     618            0 : end module tdc_support
        

Generated by: LCOV version 2.0-1