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

            Line data    Source code
       1              : ! ***********************************************************************
       2              : !
       3              : !   Copyright (C) 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 phase_separation
      21              : 
      22              :       use star_private_def
      23              :       use const_def, only: dp
      24              : 
      25              :       implicit none
      26              : 
      27              :       private
      28              :       public :: do_phase_separation
      29              : 
      30              :       logical, parameter :: dbg = .false.
      31              : 
      32              :       ! offset to higher phase than 0.5 to avoid interference
      33              :       ! between phase separation mixing and latent heat for Skye.
      34              :       real(dp), parameter :: eos_phase_boundary = 0.9d0
      35              : 
      36              :       contains
      37              : 
      38            0 :       subroutine do_phase_separation(s, dt, ierr)
      39              :          type (star_info), pointer :: s
      40              :          real(dp), intent(in) :: dt
      41              :          integer, intent(out) :: ierr
      42              : 
      43            0 :          if(s% phase_separation_option == 'CO') then
      44            0 :             call do_2component_phase_separation(s, dt, 'CO', ierr)
      45            0 :          else if(s% phase_separation_option == 'ONe') then
      46            0 :             call do_2component_phase_separation(s, dt, 'ONe', ierr)
      47              :          else
      48            0 :             write(*,*) 'invalid phase_separation_option'
      49            0 :             stop
      50              :          end if
      51            0 :       end subroutine do_phase_separation
      52              : 
      53            0 :       subroutine do_2component_phase_separation(s, dt, components, ierr)
      54              :          use chem_def, only: ic12, io16, ine20
      55              :          use chem_lib, only: chem_get_iso_id
      56              :          type (star_info), pointer :: s
      57              :          real(dp), intent(in) :: dt
      58              :          character (len=*), intent(in) :: components
      59              :          integer, intent(out) :: ierr
      60              : 
      61            0 :          real(dp) :: XNe, XO, XC, pad
      62              :          integer :: k, k_bound, kstart, net_ic12, net_io16, net_ine20
      63              :          logical :: save_Skye_use_ion_offsets
      64              : 
      65              :          ! Set phase separation mixing mass negative at beginning of phase separation
      66            0 :          s% phase_sep_mixing_mass = -1d0
      67            0 :          s% eps_phase_separation(1:s%nz) = 0d0
      68              : 
      69            0 :          if(s% phase(s% nz) < eos_phase_boundary) then
      70            0 :             s% crystal_core_boundary_mass = 0d0
      71            0 :             return
      72              :          end if
      73              : 
      74            0 :          net_ic12 = s% net_iso(ic12)
      75            0 :          net_io16 = s% net_iso(io16)
      76            0 :          net_ine20 = s% net_iso(ine20)
      77              : 
      78              :          ! Find zone of phase transition from liquid to solid
      79            0 :          k_bound = -1
      80            0 :          do k = s%nz,1,-1
      81            0 :             if(s% phase(k-1) <= eos_phase_boundary .and. s% phase(k) > eos_phase_boundary) then
      82              :                k_bound = k
      83              :                exit
      84              :             end if
      85              :          end do
      86              : 
      87            0 :          XC = s% xa(net_ic12,k_bound)
      88            0 :          XO = s% xa(net_io16,k_bound)
      89            0 :          XNe = s% xa(net_ine20,k_bound)
      90              :          ! Check that we're still in C/O or O/Ne dominated material as appropriate,
      91              :          ! otherwise skip phase separation
      92            0 :          if(components == 'CO'.and. XO + XC < 0.9d0) return
      93            0 :          if(components == 'ONe'.and. XNe + XO < 0.8d0) return  ! O/Ne mixtures tend to have more byproducts of burning mixed in
      94              : 
      95              :          ! If there is a phase transition, reset the composition at the boundary
      96            0 :          if(k_bound > 0) then
      97              : 
      98              :             ! core boundary needs to be padded by a minimal amount (less than a zone worth of mass)
      99              :             ! to account for loss of precision during remeshing.
     100            0 :             pad = s% min_dq * s% m(1) * 0.5d0
     101            0 :             do k = s%nz,1,-1
     102            0 :                if(s% m(k) > s% crystal_core_boundary_mass + pad) then
     103              :                   kstart = k
     104              :                   exit
     105              :                end if
     106              :             end do
     107              : 
     108              :             ! calculate energy associated with phase separation, ignoring the ionization
     109              :             ! energy term that Skye sometimes calculates
     110            0 :             save_Skye_use_ion_offsets = s% eos_rq% Skye_use_ion_offsets
     111            0 :             s% eos_rq% Skye_use_ion_offsets = .false.
     112            0 :             call update_model_(s,1,s%nz,.false.)
     113            0 :             do k=1,s% nz
     114            0 :                s% eps_phase_separation(k) = s% energy(k)
     115              :             end do
     116              : 
     117              :             ! loop runs outward starting at previous crystallization boundary
     118            0 :             do k = kstart,1,-1
     119              :                ! Start by checking if this material should be crystallizing
     120            0 :                if(s% phase(k) <= eos_phase_boundary) then
     121            0 :                   s% crystal_core_boundary_mass = s% m(k+1)
     122            0 :                   exit
     123              :                end if
     124              : 
     125            0 :                call move_one_zone(s,k,components)
     126              :                ! crystallized out to k now, liquid starts at k-1.
     127              :                ! now mix the liquid material outward until stably stratified
     128            0 :                call mix_outward(s, k-1, 0)
     129              : 
     130              :             end do
     131              : 
     132            0 :             call update_model_(s,1,s%nz,.false.)
     133              : 
     134              :             ! phase separation heating term for use by energy equation
     135            0 :             do k=1,s% nz
     136            0 :                s% eps_phase_separation(k) = (s% eps_phase_separation(k) - s% energy(k)) / dt
     137              :             end do
     138            0 :             s% eos_rq% Skye_use_ion_offsets = save_Skye_use_ion_offsets
     139            0 :             s% need_to_setvars = .true.
     140              :          end if
     141              : 
     142            0 :          ierr = 0
     143            0 :       end subroutine do_2component_phase_separation
     144              : 
     145            0 :       subroutine move_one_zone(s,k,components)
     146            0 :         use chem_def, only: ic12, io16, ine20
     147              :         use chem_lib, only: chem_get_iso_id
     148              :         type(star_info), pointer :: s
     149              :         integer, intent(in) :: k
     150              :         character (len=*), intent(in) :: components
     151              : 
     152            0 :         real(dp) :: XC, XO, XNe, XC1, XO1, XNe1, dXO, dXNe, Xfac
     153              :         integer :: net_ic12, net_io16, net_ine20
     154              : 
     155            0 :         net_ic12 = s% net_iso(ic12)
     156            0 :         net_io16 = s% net_iso(io16)
     157            0 :         net_ine20 = s% net_iso(ine20)
     158              : 
     159            0 :         if(components == 'CO') then
     160            0 :            XO = s% xa(net_io16,k)
     161            0 :            XC = s% xa(net_ic12,k)
     162              : 
     163              :            ! Call Blouin phase diagram.
     164              :            ! Need to rescale temporarily because phase diagram assumes XO + XC = 1
     165            0 :            Xfac = XO + XC
     166            0 :            XO = XO/Xfac
     167            0 :            XC = XC/Xfac
     168              : 
     169            0 :            dXO = blouin_delta_xo(XO)
     170              : 
     171            0 :            s% xa(net_io16,k) = Xfac*(XO + dXO)
     172            0 :            s% xa(net_ic12,k) = Xfac*(XC - dXO)
     173              : 
     174              :            ! Redistribute change in C,O into zone k-1,
     175              :            ! conserving total mass of C,O
     176            0 :            XC1 = s% xa(net_ic12,k-1)
     177            0 :            XO1 = s% xa(net_io16,k-1)
     178            0 :            s% xa(net_ic12,k-1) = XC1 + Xfac*dXO * s% dq(k) / s% dq(k-1)
     179            0 :            s% xa(net_io16,k-1) = XO1 - Xfac*dXO * s% dq(k) / s% dq(k-1)
     180            0 :         else if(components == 'ONe') then
     181            0 :            XNe = s% xa(net_ine20,k)
     182            0 :            XO = s% xa(net_io16,k)
     183              : 
     184              :            ! Call Blouin phase diagram.
     185              :            ! Need to rescale temporarily because phase diagram assumes XO + XNe = 1
     186            0 :            Xfac = XO + XNe
     187            0 :            XO = XO/Xfac
     188            0 :            XNe = XNe/Xfac
     189              : 
     190            0 :            dXNe = blouin_delta_xne(XNe)
     191              : 
     192            0 :            s% xa(net_ine20,k) = Xfac*(XNe + dXNe)
     193            0 :            s% xa(net_io16,k) = Xfac*(XO - dXNe)
     194              : 
     195              :            ! Redistribute change in Ne,O into zone k-1,
     196              :            ! conserving total mass of Ne,O
     197            0 :            XO1 = s% xa(net_io16,k-1)
     198            0 :            XNe1 = s% xa(net_ine20,k-1)
     199            0 :            s% xa(net_io16,k-1) = XO1 + Xfac*dXNe * s% dq(k) / s% dq(k-1)
     200            0 :            s% xa(net_ine20,k-1) = XNe1 - Xfac*dXNe * s% dq(k) / s% dq(k-1)
     201              :         else
     202            0 :            write(*,*) 'invalid components option in phase separation'
     203            0 :            stop
     204              :         end if
     205              : 
     206            0 :         call update_model_(s,k-1,s%nz,.true.)
     207              : 
     208            0 :       end subroutine move_one_zone
     209              : 
     210              :       ! mix composition outward until reaching stable composition profile
     211            0 :       subroutine mix_outward(s,kbot,min_mix_zones)
     212              :         type(star_info), pointer :: s
     213              :         integer, intent(in)      :: kbot, min_mix_zones
     214              : 
     215            0 :         real(dp) :: avg_xa(s%species)
     216            0 :         real(dp) :: mass, B_term, grada, gradr
     217              :         integer :: k, l, ktop
     218              :         logical :: use_brunt
     219              : 
     220            0 :         use_brunt = s% phase_separation_mixing_use_brunt
     221              : 
     222            0 :         do k=kbot-min_mix_zones,1,-1
     223            0 :            ktop = k
     224              : 
     225            0 :            if (s% m(ktop) > s% phase_sep_mixing_mass) then
     226            0 :               s% phase_sep_mixing_mass = s% m(ktop)
     227              :            end if
     228              : 
     229            0 :            mass = SUM(s%dm(ktop:kbot))
     230            0 :            do l = 1, s%species
     231            0 :               avg_xa(l) = SUM(s%dm(ktop:kbot)*s%xa(l,ktop:kbot))/mass
     232              :            end do
     233              : 
     234              :            ! some potential safeguards from conv_premix
     235              :            ! avg_xa = MAX(MIN(avg_xa, 1._dp), 0._dp)
     236              :            ! avg_xa = avg_xa/SUM(avg_xa)
     237              : 
     238            0 :            do l = 1, s%species
     239            0 :               s%xa(l,ktop:kbot) = avg_xa(l)
     240              :            end do
     241              : 
     242              :            ! updates, eos, opacities, mu, etc now that abundances have changed,
     243              :            ! but only in the cells near the boundary where we need to check here.
     244              :            ! Will call full update over mixed region after exiting loop.
     245            0 :            call update_model_(s, ktop-1, ktop+1, use_brunt)
     246              : 
     247            0 :            if(use_brunt) then
     248            0 :               B_term = s% unsmoothed_brunt_B(ktop)
     249            0 :               grada = s% grada_face(ktop)
     250            0 :               gradr = s% gradr(ktop)
     251            0 :               if(B_term + grada - gradr > 0d0) then
     252              :                  ! stable against further mixing, so exit loop
     253              :                  exit
     254              :               end if
     255              :            else  ! simpler calculation based on mu gradient
     256            0 :               if(s% mu(ktop) >= s% mu(ktop-1)) then
     257              :                  ! stable against further mixing, so exit loop
     258              :                  exit
     259              :               end if
     260              :            end if
     261              : 
     262              :         end do
     263              : 
     264              :         ! Call a final update over all mixed cells now.
     265            0 :         call update_model_(s, ktop, kbot+1, .true.)
     266              : 
     267            0 :       end subroutine mix_outward
     268              : 
     269            0 :       real(dp) function blouin_delta_xo(Xin)
     270              :         real(dp), intent(in) :: Xin  ! mass fraction
     271            0 :         real(dp) :: Xnew  ! mass fraction
     272            0 :         real(dp) :: xo, dxo  ! number fractions
     273            0 :         real(dp) :: a0, a1, a2, a3, a4, a5
     274              : 
     275              :         ! Convert input mass fraction to number fraction, assuming C/O mixture
     276            0 :         xo = (Xin/16d0)/(Xin/16d0 + (1d0 - Xin)/12d0)
     277              : 
     278            0 :         a0 = 0d0
     279            0 :         a1 = -0.311540d0
     280            0 :         a2 = 2.114743d0
     281            0 :         a3 = -1.661095d0
     282            0 :         a4 = -1.406005d0
     283            0 :         a5 = 1.263897d0
     284              : 
     285              :         dxo = &
     286              :              a0 + &
     287              :              a1*xo + &
     288              :              a2*xo*xo + &
     289              :              a3*xo*xo*xo + &
     290              :              a4*xo*xo*xo*xo + &
     291            0 :              a5*xo*xo*xo*xo*xo
     292              : 
     293            0 :         xo = xo + dxo
     294              : 
     295              :         ! Convert back to mass fraction
     296            0 :         Xnew = 16d0*xo/(16d0*xo + 12d0*(1d0-xo))
     297              : 
     298            0 :         blouin_delta_xo = Xnew - Xin
     299            0 :       end function blouin_delta_xo
     300              : 
     301            0 :       real(dp) function blouin_delta_xne(Xin)
     302              :         real(dp), intent(in) :: Xin  ! mass fraction
     303            0 :         real(dp) :: Xnew  ! mass fraction
     304            0 :         real(dp) :: xne, dxne  ! number fractions
     305            0 :         real(dp) :: a0, a1, a2, a3, a4, a5
     306              : 
     307              :         ! Convert input mass fraction to number fraction, assuming O/Ne mixture
     308            0 :         xne = (Xin/20d0)/(Xin/20d0 + (1d0 - Xin)/16d0)
     309              : 
     310            0 :         a0 = 0d0
     311            0 :         a1 = -0.120299d0
     312            0 :         a2 = 1.304399d0
     313            0 :         a3 = -1.722625d0
     314            0 :         a4 = 0.393996d0
     315            0 :         a5 = 0.144529d0
     316              : 
     317              :         dxne = &
     318              :              a0 + &
     319              :              a1*xne + &
     320              :              a2*xne*xne + &
     321              :              a3*xne*xne*xne + &
     322              :              a4*xne*xne*xne*xne + &
     323            0 :              a5*xne*xne*xne*xne*xne
     324              : 
     325            0 :         xne = xne + dxne
     326              : 
     327              :         ! Convert back to mass fraction
     328            0 :         Xnew = 20d0*xne/(20d0*xne + 16d0*(1d0-xne))
     329              : 
     330            0 :         blouin_delta_xne = Xnew - Xin
     331            0 :       end function blouin_delta_xne
     332              : 
     333            0 :       subroutine update_model_ (s, kc_t, kc_b, do_brunt)
     334              : 
     335              :         use turb_info, only: set_mlt_vars
     336              :         use brunt, only: do_brunt_B
     337              :         use micro
     338              : 
     339              :         type(star_info), pointer :: s
     340              :         integer, intent(in)      :: kc_t
     341              :         integer, intent(in)      :: kc_b
     342              :         logical, intent(in)      :: do_brunt
     343              : 
     344              :         integer  :: ierr
     345              :         integer  :: kf_t
     346              :         integer  :: kf_b
     347              : 
     348            0 :         logical :: mask(s%nz)
     349              : 
     350            0 :         mask(:) = .true.
     351              : 
     352              :         ! Update the model to reflect changes in the abundances across
     353              :         ! cells kc_t:kc_b (the mask part of this call is unused, mask=true for all zones).
     354              :         ! Do updates at constant (P,T) rather than constant (rho,T).
     355            0 :         s%fix_Pgas = .true.
     356            0 :         call set_eos_with_mask(s, kc_t, kc_b, mask, ierr)
     357            0 :         if (ierr /= 0) then
     358            0 :            write(*,*) 'phase_separation: error from call to set_eos_with_mask'
     359            0 :            stop
     360              :         end if
     361            0 :         s%fix_Pgas = .false.
     362              : 
     363              :         ! Update opacities across cells kc_t:kc_b (this also sets rho_face
     364              :         ! and related quantities on faces kc_t:kc_b)
     365              :         call set_micro_vars(s, kc_t, kc_b, &
     366            0 :              skip_eos=.TRUE., skip_net=.TRUE., skip_neu=.TRUE., skip_kap=.FALSE., ierr=ierr)
     367            0 :         if (ierr /= 0) then
     368            0 :            write(*,*) 'phase_separation: error from call to set_micro_vars'
     369            0 :            stop
     370              :         end if
     371              : 
     372              :         ! This is expensive, so only do it if we really need to.
     373            0 :         if(do_brunt) then
     374              :            ! Need to make sure we can set brunt for mix_outward calculation.
     375            0 :            if(.not. s% calculate_Brunt_B) then
     376            0 :               stop "phase separation requires s% calculate_Brunt_B = .true."
     377              :            end if
     378            0 :            call do_brunt_B(s, kc_t, kc_b, ierr)  ! for unsmoothed_brunt_B
     379            0 :            if (ierr /= 0) then
     380            0 :               write(*,*) 'phase_separation: error from call to do_brunt_B'
     381            0 :               stop
     382              :            end if
     383              :         end if
     384              : 
     385              :         ! Finally update MLT for interior faces
     386              : 
     387            0 :         kf_t = kc_t
     388            0 :         kf_b = kc_b + 1
     389              : 
     390            0 :         call set_mlt_vars(s, kf_t+1, kf_b-1, ierr)
     391            0 :         if (ierr /= 0) then
     392            0 :            write(*,*) 'phase_separation: failed in call to set_mlt_vars during update_model_'
     393            0 :            stop
     394              :         end if
     395              : 
     396            0 :         return
     397              : 
     398            0 :       end subroutine update_model_
     399              : 
     400              :       end module phase_separation
        

Generated by: LCOV version 2.0-1