Line data Source code
1 : ! ***********************************************************************
2 : !
3 : ! Copyright (C) 2010-2019 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 : ! Routine eval_fp_ft for computing rotation corrections to the stellar structure equations.
21 : ! Following the method of Kippenhahn & Thomas, 1970; Endal & Sofia 1976, as implemented in
22 : ! Paxton et al., 2019, using the updated fits from Fabry, Marchant & Sana, 2022
23 :
24 : module hydro_rotation
25 :
26 : use const_def, only: pi, pi4, ln10, two_thirds, one_third, one_sixth
27 : use star_utils, only: get_r_from_xh
28 : use star_private_def
29 :
30 : implicit none
31 :
32 : private
33 : public :: log_term_power
34 : public :: a
35 : public :: c
36 : public :: w_div_w_roche_jrot
37 : public :: w_div_w_roche_omega
38 : public :: set_rotation_info
39 : public :: compute_j_fluxes_and_extra_jdot
40 : public :: set_surf_avg_rotation_info
41 : public :: use_xh_to_update_i_rot_and_j_rot
42 : public :: set_i_rot_from_omega_and_j_rot
43 : public :: use_xh_to_update_i_rot
44 : public :: update1_i_rot_from_xh
45 : public :: get_rotation_sigmas
46 : public :: set_omega
47 : public :: set_uniform_omega
48 : public :: set_i_rot
49 : public :: eval_i_rot
50 :
51 : real(dp), parameter :: log_term_power = 5.626d0
52 :
53 : contains
54 :
55 : ! compute w_div_w_roche for a known angular frequency omega, rpsi, and Mpsi
56 0 : real(dp) function w_div_w_roche_omega(rpsi, Mphi, omega, cgrav, max_w, max_w2, w_div_wc_flag) result(w_roche)
57 : real(dp), intent(in) :: rpsi, Mphi, omega, cgrav, max_w, max_w2
58 : logical, intent(in) :: w_div_wc_flag
59 0 : real(dp) :: wr, wr_high, wr_low, dimless_rpsi, new_dimless_rpsi, rpsi_lim1, rpsi_lim2
60 :
61 0 : if (omega == 0d0) then
62 0 : w_roche = 0d0
63 : return
64 : end if
65 :
66 0 : dimless_rpsi = rpsi * pow(abs(omega), two_thirds) / pow(cgrav * Mphi, one_third)
67 0 : if (.not. w_div_wc_flag) then
68 : ! verify if w_div_w_roche is not above max, otherwise limit it to that
69 0 : wr = max_w
70 0 : new_dimless_rpsi = pow(wr, two_thirds) * rpsi_from_re_factor(pow2(wr), pow4(wr), pow6(wr))
71 0 : if (dimless_rpsi > new_dimless_rpsi) then
72 0 : w_roche = wr
73 : return
74 : end if
75 : else
76 : ! smoothly cap to max_w2 to get a continuous function
77 : ! nothing is done when we are below max_w, but between max_w and max_w2 we smoothly
78 : ! produce an asymptote that would result in w_div_wc=max_w2 for jrot->infinity
79 0 : wr = max_w
80 0 : rpsi_lim1 = pow(wr, two_thirds) * rpsi_from_re_factor(pow2(wr), pow4(wr), pow6(wr))
81 :
82 0 : wr = max_w2
83 0 : rpsi_lim2 = pow(wr, two_thirds) * rpsi_from_re_factor(pow2(wr), pow4(wr), pow6(wr))
84 :
85 0 : if (abs(dimless_rpsi) > rpsi_lim1) then
86 0 : dimless_rpsi = sigmoid(abs(dimless_rpsi), rpsi_lim1, rpsi_lim2)
87 : end if
88 : end if
89 :
90 : ! otherwise, bisect result
91 0 : wr_high = wr
92 0 : wr_low = 0
93 0 : do while (wr_high - wr_low > 1d-6)
94 0 : wr = 0.5d0 * (wr_high + wr_low)
95 0 : new_dimless_rpsi = pow(wr, two_thirds) * rpsi_from_re_factor(pow2(wr), pow4(wr), pow6(wr))
96 0 : if (dimless_rpsi > new_dimless_rpsi) then
97 : wr_low = wr
98 : else
99 0 : wr_high = wr
100 : end if
101 : end do
102 0 : w_roche = 0.5d0*(wr_high+wr_low)
103 :
104 0 : if (omega < 0d0) then
105 0 : w_roche = -w_roche
106 : end if
107 :
108 : end function w_div_w_roche_omega
109 :
110 : ! compute w_div_w_roche for a known specific angular momentum jrot, rpsi, and Mphi
111 0 : real(dp) function w_div_w_roche_jrot(rpsi, Mphi, jrot, cgrav, max_w, max_w2, w_div_wc_flag) result(w_roche)
112 : real(dp), intent(in) :: rpsi,Mphi,jrot,cgrav, max_w, max_w2
113 : logical, intent(in) :: w_div_wc_flag
114 0 : real(dp) :: wr, wr_high, wr_low, dimless_factor, new_dimless_factor
115 0 : real(dp) :: w2, w4, w6, w_log_term, jr_lim1, jr_lim2
116 :
117 0 : if (jrot == 0d0) then
118 0 : w_roche = 0d0
119 : return
120 : end if
121 :
122 0 : dimless_factor = abs(jrot) / sqrt(cgrav * Mphi * rpsi)
123 :
124 0 : if (.not. w_div_wc_flag) then
125 : ! verify if w_div_w_roche is not above max, otherwise limit it to that
126 0 : wr = max_w
127 0 : w2 = pow2(wr)
128 0 : w4 = pow4(wr)
129 0 : w6 = pow6(wr)
130 0 : w_log_term = log(1d0 - pow(wr, log_term_power))
131 0 : new_dimless_factor = two_thirds * wr * C(w2, w4, w6, w_log_term) / A(w4, w6, w_log_term)
132 0 : if (dimless_factor > new_dimless_factor) then
133 0 : w_roche = wr
134 : return
135 : end if
136 : else
137 : ! smoothly cap to max_w2 to get a continuous function
138 : ! nothing is done when we are below max_w, but between max_w and max_w2 we smoothly
139 : ! produce an asymptote that would result in w_div_wc=max_w2 for jrot->infinity
140 0 : wr = max_w
141 0 : w4 = pow4(wr)
142 0 : w6 = pow6(wr)
143 0 : w_log_term = log(1 - pow(wr, log_term_power))
144 0 : jr_lim1 = two_thirds * wr * C(pow2(wr), w4, w6, w_log_term) / A(w4, w6, w_log_term)
145 :
146 0 : wr = max_w2
147 0 : w4 = pow4(wr)
148 0 : w6 = pow6(wr)
149 0 : w_log_term = log(1 - pow(wr, log_term_power))
150 0 : jr_lim2 = two_thirds * wr * C(pow2(wr), w4, w6, w_log_term) / A(w4, w6, w_log_term)
151 :
152 0 : if (abs(dimless_factor) > jr_lim1) then
153 0 : dimless_factor = sigmoid(abs(dimless_factor), jr_lim1, jr_lim2)
154 : end if
155 : end if
156 :
157 : ! otherwise, bisect result
158 0 : wr_high = wr
159 0 : wr_low = 0
160 0 : do while (wr_high - wr_low > 1d-6)
161 0 : wr = 0.5d0 * (wr_high + wr_low)
162 0 : w2 = pow2(wr)
163 0 : w4 = pow4(wr)
164 0 : w6 = pow6(wr)
165 0 : w_log_term = log(1d0 - pow(wr, log_term_power))
166 0 : new_dimless_factor = two_thirds * wr * C(w2, w4, w6, w_log_term) / A(w4, w6, w_log_term)
167 0 : if (dimless_factor > new_dimless_factor) then
168 : wr_low = wr
169 : else
170 0 : wr_high = wr
171 : end if
172 : end do
173 0 : w_roche = 0.5d0 * (wr_high + wr_low)
174 :
175 0 : if (jrot < 0d0) then
176 0 : w_roche = -w_roche
177 : end if
178 :
179 : end function w_div_w_roche_jrot
180 :
181 0 : subroutine set_i_rot(s, skip_w_div_w_crit_roche)
182 : type (star_info), pointer :: s
183 : logical, intent(in) :: skip_w_div_w_crit_roche
184 : integer :: k
185 : include 'formats'
186 :
187 0 : !$OMP PARALLEL DO PRIVATE(k) SCHEDULE(dynamic,2)
188 : do k=1,s% nz
189 : if (.not. skip_w_div_w_crit_roche) then
190 : s% w_div_w_crit_roche(k) = &
191 : w_div_w_roche_jrot(s% r(k),s% m(k),s% j_rot(k),s% cgrav(k), &
192 : s% w_div_wcrit_max, s% w_div_wcrit_max2, s% w_div_wc_flag)
193 : end if
194 : call eval_i_rot(s, k, s% r(k), s% w_div_w_crit_roche(k), s% i_rot(k))
195 : end do
196 : !$OMP END PARALLEL DO
197 :
198 0 : end subroutine set_i_rot
199 :
200 0 : subroutine set_i_rot_from_omega_and_j_rot(s)
201 : use auto_diff_support
202 : type (star_info), pointer :: s
203 : integer :: k
204 : include 'formats'
205 0 : do k=1,s% nz
206 0 : if (s% omega(k) /= 0d0) then
207 : ! we can directly compute i_rot using j_rot and omega
208 0 : s% i_rot(k) = 0d0
209 0 : s% i_rot(k)% val = s% j_rot(k)/s% omega(k)
210 : else
211 0 : call update1_i_rot_from_xh(s, k)
212 : end if
213 : end do
214 0 : end subroutine set_i_rot_from_omega_and_j_rot
215 :
216 0 : subroutine set_j_rot(s)
217 : type (star_info), pointer :: s
218 : integer :: k
219 : include 'formats'
220 0 : do k=1,s% nz
221 0 : s% j_rot(k) = s% i_rot(k)% val*s% omega(k)
222 : end do
223 0 : end subroutine set_j_rot
224 :
225 0 : subroutine set_omega(s, str)
226 : type (star_info), pointer :: s
227 : character (len=*) :: str
228 : integer :: k
229 : include 'formats'
230 0 : do k=1,s% nz
231 0 : s% omega(k) = s% j_rot(k)/s% i_rot(k)% val
232 : end do
233 0 : end subroutine set_omega
234 :
235 : subroutine check_omega(s, str)
236 : type (star_info), pointer :: s
237 : character (len=*) :: str
238 : integer :: k
239 : logical :: okay
240 : include 'formats'
241 : okay = .true.
242 : do k=1,s% nz
243 : if (abs(s% omega(k) - s% j_rot(k)/s% i_rot(k)% val) > 1d-14) then
244 : write(*,2) 'omega error', k, s% omega(k) - s% j_rot(k)/s% i_rot(k)% val
245 : okay = .false.
246 : exit
247 : end if
248 : end do
249 : if (okay) return
250 : write(*,*) trim(str)
251 : call mesa_error(__FILE__,__LINE__,'check_omega')
252 : end subroutine check_omega
253 :
254 0 : subroutine update1_i_rot_from_xh(s, k)
255 : type (star_info), pointer :: s
256 : integer, intent(in) :: k
257 : real(dp) :: r00
258 : include 'formats'
259 :
260 0 : r00 = get_r_from_xh(s,k)
261 :
262 0 : call eval_i_rot(s, k, r00, s% w_div_w_crit_roche(k), s% i_rot(k))
263 0 : end subroutine update1_i_rot_from_xh
264 :
265 0 : subroutine use_xh_to_update_i_rot(s)
266 : type (star_info), pointer :: s
267 : integer :: k
268 0 : do k=1,s% nz
269 0 : if (s% j_rot(k) /= 0d0) then
270 : s% w_div_w_crit_roche(k) = &
271 : w_div_w_roche_jrot(get_r_from_xh(s,k),s% m(k),s% j_rot(k),s% cgrav(k), &
272 0 : s% w_div_wcrit_max, s% w_div_wcrit_max2, s% w_div_wc_flag)
273 : else
274 0 : s% w_div_w_crit_roche(k) = 0d0
275 : end if
276 : end do
277 0 : do k=1,s% nz
278 0 : call update1_i_rot_from_xh(s,k)
279 : end do
280 0 : end subroutine use_xh_to_update_i_rot
281 :
282 0 : subroutine use_xh_to_update_i_rot_and_j_rot(s)
283 : type (star_info), pointer :: s
284 : integer :: k
285 0 : do k=1,s% nz
286 : s% w_div_w_crit_roche(k) = &
287 : w_div_w_roche_omega(get_r_from_xh(s,k),s% m(k),s% omega(k),s% cgrav(k), &
288 0 : s% w_div_wcrit_max, s% w_div_wcrit_max2, s% w_div_wc_flag)
289 : end do
290 0 : do k=1,s% nz
291 0 : call update1_i_rot_from_xh(s,k)
292 : end do
293 0 : call set_j_rot(s)
294 0 : end subroutine use_xh_to_update_i_rot_and_j_rot
295 :
296 0 : subroutine get_rotation_sigmas(s, nzlo, nzhi, dt, ierr)
297 : type (star_info), pointer :: s
298 : integer, intent(in) :: nzlo, nzhi
299 : real(dp), intent(in) :: dt
300 : integer, intent(out) :: ierr
301 :
302 : integer :: k, nz
303 : real(dp), allocatable :: am_nu(:), am_sig(:)
304 :
305 : include 'formats'
306 :
307 0 : ierr = 0
308 0 : nz = s% nz
309 :
310 0 : allocate(am_nu(nz), am_sig(nz))
311 :
312 0 : call get1_am_sig(s, nzlo, nzhi, s% am_nu_j, s% am_sig_j, dt, ierr)
313 0 : if (ierr /= 0) then
314 0 : if (s% report_ierr) write(*,1) 'failed in get_rotation_sigmas'
315 0 : return
316 : end if
317 :
318 0 : do k=1,nz
319 0 : am_nu(k) = s% am_nu_j(k) + s% am_nu_omega(k)
320 : end do
321 : ! do it this way so apply limit to sum; sum is used as diffusion coeff for omega
322 0 : call get1_am_sig(s, nzlo, nzhi, am_nu, am_sig, dt, ierr)
323 0 : if (ierr /= 0) then
324 0 : if (s% report_ierr) write(*,1) 'failed in get_rotation_sigmas'
325 0 : return
326 : end if
327 :
328 0 : do k=1,nz
329 0 : s% am_sig_omega(k) = max(0d0, am_sig(k) - s% am_sig_j(k))
330 : end do
331 :
332 0 : end subroutine get_rotation_sigmas
333 :
334 0 : subroutine get1_am_sig(s, nzlo, nzhi, am_nu, am_sig, dt, ierr)
335 : type (star_info), pointer :: s
336 : integer, intent(in) :: nzlo, nzhi
337 : real(dp), intent(in) :: dt
338 : real(dp), dimension(:) :: am_nu, am_sig
339 : integer, intent(out) :: ierr
340 :
341 : integer :: k, nz, nz2
342 0 : real(dp) :: r, D, am_nu_E00, am_nu_Ep1, dmbar, s1, &
343 0 : sig_term_limit, xmstar, siglim
344 :
345 : include 'formats'
346 :
347 0 : ierr = 0
348 0 : xmstar = s% xmstar
349 0 : sig_term_limit = s% am_sig_term_limit
350 0 : nz = s% nz
351 : ! note: am_sig is cell centered, so combine adjacent am_nu face values.
352 0 : am_nu_E00 = 0; am_nu_Ep1 = 0
353 0 : nz2 = nzhi
354 0 : if (nzhi == nz) then
355 0 : k = nz
356 0 : D = am_nu_E00
357 0 : r = 0.5d0*s% r(k)
358 0 : s1 = pi4*r*r*s% rho(k)
359 0 : am_sig(k) = s1*s1*D/s% dm(k)
360 0 : nz2 = nz-1
361 : end if
362 0 : do k = nzlo, nz2
363 0 : am_nu_E00 = max(0d0, am_nu(k))
364 0 : am_nu_Ep1 = max(0d0, am_nu(k+1))
365 : ! Meynet, Maeder, & Mowlavi, A&A 416, 1023-1036, 2004, eqn 51 with f = 1/2.
366 0 : D = 2*(am_nu_E00*am_nu_Ep1)/max(1d-99, am_nu_E00 + am_nu_Ep1)
367 0 : r = 0.5d0*(s% r(k) + s% r(k+1)) ! consistent with f = 1/2
368 0 : s1 = pi4*r*r*s% rho(k)
369 0 : am_sig(k) = s1*s1*D/s% dm(k)
370 : end do
371 :
372 : ! can get numerical problems unless limit am_sig
373 : ! adjust am_sig to make sure am_sig*dt/dmbar is < allowed limit
374 0 : do k = nzlo, nzhi
375 0 : if (k < nz) then
376 0 : dmbar = xmstar*min(s% dq(k),s% dq(k+1))
377 0 : siglim = sig_term_limit*dmbar/dt
378 : else
379 0 : dmbar = xmstar*s% dq(k)
380 0 : siglim = sig_term_limit*dmbar/dt
381 : end if
382 0 : if (am_sig(k) > siglim) then
383 0 : am_sig(k) = siglim
384 : end if
385 : end do
386 :
387 0 : end subroutine get1_am_sig
388 :
389 0 : subroutine set_uniform_omega(id, omega, ierr)
390 : use auto_diff_support
391 : integer, intent(in) :: id
392 : real(dp), intent(in) :: omega
393 : integer, intent(out) :: ierr
394 : type (star_info), pointer :: s
395 : integer :: k, nz
396 : include 'formats'
397 0 : call get_star_ptr(id, s, ierr)
398 0 : if (ierr /= 0) return
399 0 : nz = s% nz
400 0 : do k=1, nz
401 0 : s% omega(k) = omega
402 0 : s% fp_rot(k) = 0d0
403 0 : s% ft_rot(k) = 0d0
404 : end do
405 0 : call use_xh_to_update_i_rot_and_j_rot(s)
406 0 : s% w_div_w_crit_roche(1:nz) = 0d0
407 0 : s% r_polar(1:nz) = 0d0
408 0 : s% r_equatorial(1:nz) = 0d0
409 0 : s% am_nu_rot(1:nz) = 0d0
410 0 : s% am_nu_non_rot(1:nz) = 0d0
411 0 : s% am_nu_omega(1:nz) = 0d0
412 0 : s% am_nu_j(1:nz) = 0d0
413 0 : s% am_sig_omega(1:nz) = 0d0
414 0 : s% am_sig_j(1:nz) = 0d0
415 0 : s% domega_dlnR(1:nz) = 0d0
416 0 : s% richardson_number(1:nz) = 0d0
417 0 : s% D_mix_non_rotation(1:nz) = 0d0
418 0 : s% D_visc(1:nz) = 0d0
419 0 : s% D_DSI(1:nz) = 0d0
420 0 : s% D_SH(1:nz) = 0d0
421 0 : s% D_SSI(1:nz) = 0d0
422 0 : s% D_ES(1:nz) = 0d0
423 0 : s% D_GSF(1:nz) = 0d0
424 0 : s% D_ST(1:nz) = 0d0
425 0 : s% nu_ST(1:nz) = 0d0
426 0 : s% omega_shear(1:nz) = 0d0
427 0 : s% dynamo_B_r(1:nz) = 0d0
428 0 : s% dynamo_B_phi(1:nz) = 0d0
429 0 : call set_rotation_info(s, .false., ierr)
430 0 : if (ierr /= 0) return
431 0 : s% need_to_setvars = .true.
432 0 : end subroutine set_uniform_omega
433 :
434 0 : subroutine set_rotation_info(s, skip_w_div_w_crit_roche, ierr)
435 : type (star_info), pointer :: s
436 : logical, intent(in) :: skip_w_div_w_crit_roche
437 : integer, intent(out) :: ierr
438 : include 'formats'
439 0 : ierr = 0
440 :
441 0 : if (.not. s% rotation_flag) return
442 :
443 0 : call set_i_rot(s, skip_w_div_w_crit_roche)
444 0 : call set_omega(s, 'set_rotation_info')
445 :
446 0 : if (.not. s% use_other_eval_fp_ft) then
447 : call eval_fp_ft( &
448 : s% id, s% nz, s% m, s% r, s% rho, s% omega, s% ft_rot, s% fp_rot, &
449 0 : s% r_polar, s% r_equatorial, s% report_ierr, ierr)
450 : else
451 : call s% other_eval_fp_ft( &
452 : s% id, s% nz, s% m, s% r, s% rho, s% omega, s% ft_rot, s% fp_rot, &
453 0 : s% r_polar, s% r_equatorial, s% report_ierr, ierr)
454 : end if
455 0 : if (ierr /= 0) then
456 0 : write(*,*) 'failed in eval_fp_ft'
457 : end if
458 0 : end subroutine set_rotation_info
459 :
460 33 : subroutine set_surf_avg_rotation_info(s)
461 : use star_utils, only: get_Lrad_div_Ledd
462 : type (star_info), pointer :: s
463 : real(dp) :: &
464 33 : dm, dmsum, omega_sum, omega_crit_sum, omega_div_omega_crit_sum, &
465 33 : v_rot_sum, v_crit_sum, v_div_v_crit_sum, Lrad_div_Ledd_sum, &
466 33 : gamma_factor, omega_crit, omega, kap_sum, &
467 33 : j_rot_sum, j_rot, v_rot, v_crit, Lrad_div_Ledd, dtau, tau, &
468 33 : cgrav, kap, mmid, Lmid, rmid, logT_sum, logRho_sum
469 : integer :: k, ierr
470 : logical, parameter :: dbg = .false.
471 : include 'formats'
472 :
473 33 : if (.not. s% rotation_flag) then
474 33 : s% omega_avg_surf = 0
475 33 : s% omega_crit_avg_surf = 0
476 33 : s% w_div_w_crit_avg_surf = 0
477 33 : s% j_rot_avg_surf = 0
478 33 : s% v_rot_avg_surf = 0
479 33 : s% v_crit_avg_surf = 0
480 33 : s% v_div_v_crit_avg_surf = 0
481 33 : s% Lrad_div_Ledd_avg_surf = 0
482 33 : s% opacity_avg_surf = 0
483 33 : s% logT_avg_surf = 0
484 33 : s% logRho_avg_surf = 0
485 : return
486 : end if
487 :
488 : ierr = 0
489 0 : call set_rotation_info(s,.true.,ierr)
490 0 : if (ierr /= 0) then
491 0 : write(*,*) 'got ierr from call set_rotation_info in set_surf_avg_rotation_info'
492 0 : write(*,*) 'just ignore it'
493 : end if
494 :
495 0 : tau = s% tau_factor*s% tau_base
496 0 : dmsum = 0d0
497 0 : Lrad_div_Ledd_sum = 0d0
498 0 : rmid = 0d0
499 :
500 0 : do k = 1, s% nz - 1
501 0 : kap = s% opacity(k)
502 0 : rmid = s% rmid(k)
503 0 : mmid = 0.5d0*(s% m_grav(k) + s% m_grav(k+1))
504 0 : Lmid = 0.5d0*(s% L(k) + s% L(k+1))
505 0 : cgrav = 0.5d0*(s% cgrav(k) + s% cgrav(k+1))
506 0 : dm = s% dm(k)
507 0 : dtau = dm*kap/(pi4*rmid*rmid)
508 :
509 0 : if (tau + dtau <= s% surf_avg_tau_min) then
510 : tau = tau + dtau
511 : cycle
512 : end if
513 :
514 : ! check for partial contribution from cell
515 : ! the tau < s% surf_avg_tau is meant for the case in which the surface tau is set
516 : ! equal or larger to surf_avg_tau. In that case we just use the values of the surface cell.
517 0 : if (tau < s% surf_avg_tau) then
518 0 : if (tau < s% surf_avg_tau_min) then ! only use part of this cell
519 0 : dm = dm*(tau + dtau - s% surf_avg_tau_min)/dtau
520 0 : else if (tau + dtau > s% surf_avg_tau) then ! only use part of this cell
521 0 : dm = dm*(s% surf_avg_tau - tau)/dtau
522 : !write(*,2) 'tau limit', k, (s% surf_avg_tau - tau)/dtau
523 : end if
524 : end if
525 0 : dmsum = dmsum + dm
526 0 : Lrad_div_Ledd = get_Lrad_div_Ledd(s,k)
527 0 : Lrad_div_Ledd_sum = Lrad_div_Ledd_sum + dm*Lrad_div_Ledd
528 0 : tau = tau + dtau
529 0 : if (tau >= s% surf_avg_tau) exit
530 : end do
531 :
532 0 : s% Lrad_div_Ledd_avg_surf = Lrad_div_Ledd_sum/dmsum
533 0 : gamma_factor = 1d0 - min(s% Lrad_div_Ledd_avg_surf, 0.9999d0)
534 :
535 0 : tau = s% tau_factor*s% tau_base
536 0 : dmsum = 0
537 0 : j_rot_sum = 0
538 0 : omega_sum = 0
539 0 : omega_crit_sum = 0
540 0 : omega_div_omega_crit_sum = 0
541 0 : v_rot_sum = 0
542 0 : v_crit_sum = 0
543 0 : v_div_v_crit_sum = 0
544 0 : kap_sum = 0
545 0 : logT_sum = 0
546 0 : logRho_sum = 0
547 :
548 0 : do k = 1, s% nz - 1
549 :
550 0 : kap = s% opacity(k)
551 : ! TODO: better explain
552 : ! Use equatorial radius
553 0 : rmid = 0.5d0*(s% r_equatorial(k) + s% r_equatorial(k+1))
554 0 : dm = s% dm(k)
555 0 : dtau = dm*kap/(pi4*rmid*rmid)
556 :
557 0 : if (tau + dtau <= s% surf_avg_tau_min) then
558 : tau = tau + dtau
559 : cycle
560 : end if
561 :
562 : ! check for partial contribution from cell
563 : ! the tau < s% surf_avg_tau is meant for the case in which the surface tau is set
564 : ! equal or larger to surf_avg_tau. In this case we just use the values of the surface cell.
565 0 : if (tau < s% surf_avg_tau) then
566 0 : if (tau < s% surf_avg_tau_min) then ! only use part of this cell
567 0 : dm = dm*(tau + dtau - s% surf_avg_tau_min)/dtau
568 0 : else if (tau + dtau > s% surf_avg_tau) then ! only use part of this cell
569 0 : dm = dm*(s% surf_avg_tau - tau)/dtau
570 : end if
571 : end if
572 :
573 0 : dmsum = dmsum + dm
574 0 : cgrav = 0.5d0*(s% cgrav(k) + s% cgrav(k+1))
575 0 : mmid = 0.5d0*(s% m_grav(k) + s% m_grav(k+1))
576 0 : omega = 0.5d0*(s% omega(k) + s% omega(k+1))
577 0 : j_rot = 0.5d0*(s% j_rot(k) + s% j_rot(k+1))
578 :
579 0 : kap_sum = kap_sum + dm*kap
580 0 : j_rot_sum = j_rot_sum + dm*j_rot
581 :
582 0 : omega_crit = sqrt(gamma_factor*cgrav*mmid/pow3(rmid))
583 0 : omega_div_omega_crit_sum = omega_div_omega_crit_sum + dm*abs(omega/omega_crit)
584 :
585 0 : v_rot = omega*rmid
586 0 : v_crit = omega_crit*rmid
587 0 : omega_sum = omega_sum + dm*omega
588 0 : omega_crit_sum = omega_crit_sum + dm*omega_crit
589 0 : v_rot_sum = v_rot_sum + dm*v_rot
590 0 : v_crit_sum = v_crit_sum + dm*v_crit
591 0 : v_div_v_crit_sum = v_div_v_crit_sum + dm*abs(v_rot/v_crit)
592 0 : logT_sum = logT_sum + dm*s% lnT(k)/ln10
593 0 : logRho_sum = logRho_sum + dm*s% lnd(k)/ln10
594 0 : kap_sum = kap_sum + dm*kap
595 0 : tau = tau + dtau
596 0 : if (tau >= s% surf_avg_tau) exit
597 :
598 : end do
599 :
600 0 : s% logT_avg_surf = logT_sum/dmsum
601 0 : s% logRho_avg_surf = logRho_sum/dmsum
602 0 : s% opacity_avg_surf = kap_sum/dmsum
603 0 : s% j_rot_avg_surf = j_rot_sum/dmsum
604 0 : s% omega_avg_surf = omega_sum/dmsum
605 0 : s% omega_crit_avg_surf = omega_crit_sum/dmsum
606 0 : s% w_div_w_crit_avg_surf = omega_div_omega_crit_sum/dmsum
607 0 : s% v_rot_avg_surf = v_rot_sum/dmsum
608 0 : s% v_crit_avg_surf = v_crit_sum/dmsum
609 0 : s% v_div_v_crit_avg_surf = v_div_v_crit_sum/dmsum
610 :
611 33 : end subroutine set_surf_avg_rotation_info
612 :
613 : ! Input variables:
614 : ! N Number of meshpoints used by the model (arrays are this size)
615 : ! XM Mass coordinate [gram]
616 : ! R Radius coordinate [cm]
617 : ! RHO Density [gram/cm^3]
618 : ! AW Angular velocity [rad/sec]
619 : ! Output variables:
620 : ! Correction factor FT at each meshpoint
621 : ! Correction factor FP at each meshpoint
622 : ! r_polar, r_equatorial at each meshpoint
623 0 : subroutine eval_fp_ft(id, nz, xm, r, rho, aw, ft, fp, r_polar, r_equatorial, report_ierr, ierr)
624 33 : use num_lib
625 : use auto_diff_support
626 : integer, intent(in) :: id
627 : integer, intent(in) :: nz
628 : real(dp), intent(in) :: aw(:), r(:), rho(:), xm(:) ! (nz)
629 : type(auto_diff_real_star_order1), intent(out) :: ft(:), fp(:) ! (nz)
630 : real(dp), intent(inout) :: r_polar(:), r_equatorial(:) ! (nz)
631 : logical, intent(in) :: report_ierr
632 : integer, intent(out) :: ierr
633 :
634 : type (star_info), pointer :: s
635 : integer :: j
636 :
637 : logical :: dbg
638 :
639 : type (auto_diff_real_1var_order1) :: A_omega, w, w2, w4, w6, w_log_term, fp_temp, ft_temp
640 :
641 : include 'formats'
642 :
643 : ierr = 0
644 0 : call star_ptr(id, s, ierr)
645 0 : if (ierr /= 0) return
646 :
647 0 : dbg = .false. ! (s% model_number >= 5)
648 :
649 0 : !$OMP PARALLEL DO PRIVATE(j, A_omega, fp_temp, ft_temp, w, w2, w4, w6, w_log_term) SCHEDULE(dynamic,2)
650 : do j=1, s% nz
651 : ! Compute fp, ft, re and rp using fits to the Roche geometry of a single star.
652 : ! by this point in the code, w_div_w_crit_roche is set
653 : w = abs(s% w_div_w_crit_roche(j))
654 : w% d1val1 = 1d0
655 :
656 : w2 = pow2(w)
657 : w4 = pow4(w)
658 : w6 = pow6(w)
659 : w_log_term = log(1d0 - pow(w, log_term_power))
660 : ! cannot use real function below because need derivatives
661 : A_omega = 1d0 + 0.3293d0 * w4 - 0.4926d0 * w6 - 0.5560d0 * w_log_term
662 :
663 : ! fits for fp, ft; Fabry+2022, Eqs. A.10, A.11
664 : fp_temp = (1d0 - two_thirds * w2 - 0.2133d0 * w4 - 0.1068d0 * w6) / A_omega
665 : ft_temp = (1d0 - 0.07955d0 * w4 - 0.2322d0 * w6) / A_omega
666 :
667 : ! re and rp can be derived analytically from w_div_wcrit
668 : r_equatorial(j) = r(j) * re_from_rpsi_factor(w2% val, w4% val, w6% val)
669 : r_polar(j) = r_equatorial(j) / (1d0 + 0.5d0 * w2% val)
670 :
671 : ! Be sure they are consistent with r_Psi
672 : r_equatorial(j) = max(r_equatorial(j), r(j))
673 : r_polar(j) = min(r_polar(j), r(j))
674 :
675 : fp(j) = 0d0
676 : ft(j) = 0d0
677 : fp(j)% val = fp_temp% val
678 : ft(j)% val = ft_temp% val
679 : if (s% w_div_wc_flag) then
680 : fp(j)% d1Array(i_w_div_wc_00) = fp_temp% d1val1
681 : ft(j)% d1Array(i_w_div_wc_00) = ft_temp% d1val1
682 : end if
683 : !if (j == s% solver_test_partials_k) then
684 : ! s% solver_test_partials_val = fp(j)
685 : ! s% solver_test_partials_var = s% i_w_div_wc
686 : ! s% solver_test_partials_dval_dx = s% dfp_rot_dw_div_wc(j)
687 : !end if
688 : end do
689 : !$OMP END PARALLEL DO
690 :
691 0 : end subroutine eval_fp_ft
692 :
693 0 : subroutine eval_i_rot(s, k, r00, w_div_w_crit_roche, i_rot)
694 0 : use auto_diff_support
695 : type (star_info), pointer :: s
696 : integer, intent(in) :: k ! just for debugging
697 : real(dp), intent(in) :: r00, w_div_w_crit_roche
698 : type(auto_diff_real_star_order1), intent(out) :: i_rot
699 :
700 : type(auto_diff_real_2var_order1) :: ir, r, re, w, w2, w4, w6, w_log_term
701 :
702 : include 'formats'
703 :
704 0 : i_rot = 0d0
705 0 : if (s% use_other_eval_i_rot) then
706 0 : call s% other_eval_i_rot(s% id, k, r00, w_div_w_crit_roche, i_rot)
707 0 : else if (s% simple_i_rot_flag) then
708 0 : i_rot = two_thirds * r00 * r00
709 0 : i_rot% d1Array(i_lnR_00) = 2d0 * i_rot% val
710 0 : i_rot% d1Array(i_w_div_wc_00) = 0d0
711 : else
712 0 : w = abs(w_div_w_crit_roche)
713 0 : w% d1val1 = 1d0
714 :
715 0 : r = r00
716 0 : r% d1val2 = r00 ! Makes the independent variable lnR
717 :
718 0 : w2 = pow2(w)
719 0 : w4 = pow4(w)
720 0 : w6 = pow6(w)
721 0 : w_log_term = log(1d0 - pow(w, log_term_power))
722 : ! cannot use real functions below, since need derivatives
723 0 : re = r * (1d0 + one_sixth * w2 - 0.005124d0 * w4 + 0.06562d0 * w6)
724 : ! Compute i_rot following Fabry+2022, Eq. A.9
725 : ir = two_thirds * pow2(re) * (1d0 + w2 / 5d0 + 0.4140d0 * w4 - 0.8650d0 * w6 - 0.8370d0 * w_log_term) &
726 0 : / (1d0 + 0.3293d0 * w4 - 0.4926d0 * w6 - 0.5560d0 * w_log_term)
727 :
728 0 : i_rot = 0d0
729 0 : i_rot = ir% val
730 0 : i_rot% d1Array(i_w_div_wc_00) = ir% d1val1
731 0 : i_rot% d1Array(i_lnR_00) = ir% d1val2
732 : end if
733 :
734 0 : end subroutine eval_i_rot
735 :
736 0 : subroutine compute_j_fluxes_and_extra_jdot(id, ierr)
737 0 : use auto_diff_support
738 : integer, intent(in) :: id
739 : integer, intent(out) :: ierr
740 : type (star_info), pointer :: s
741 : type(auto_diff_real_star_order1) :: omega00, omegap1, r00, rp1, i_rot00, i_rotp1, rho00, part1
742 :
743 : integer :: k
744 : logical :: dbg
745 :
746 0 : real(dp) :: pi2_div4
747 : ierr = 0
748 0 : dbg = .false.
749 :
750 0 : call star_ptr(id, s, ierr)
751 0 : if (ierr /= 0) return
752 0 : if (s% am_D_mix_factor==0d0) then
753 0 : s% am_nu_omega(:) = 0d0
754 : end if
755 :
756 0 : s% extra_jdot(:) = 0d0
757 0 : if (s% use_other_torque) then
758 0 : call s% other_torque(s% id, ierr)
759 0 : if (ierr /= 0) then
760 0 : if (s% report_ierr .or. dbg) &
761 0 : write(*, *) 'solve_omega_mix: other_torque returned ierr', ierr
762 0 : return
763 : end if
764 : end if
765 0 : if (associated(s% binary_other_torque)) then
766 0 : call s% binary_other_torque(s% id, ierr)
767 0 : if (ierr /= 0) then
768 0 : if (s% report_ierr .or. dbg) &
769 0 : write(*, *) 'solve_omega_mix: binary_other_torque returned ierr', ierr
770 0 : return
771 : end if
772 : end if
773 :
774 0 : pi2_div4 = pow2(pi)/4d0
775 0 : do k=1, s% nz-1
776 :
777 0 : r00 = wrap_r_00(s, k)
778 0 : rp1 = wrap_r_p1(s, k)
779 0 : rho00 = wrap_d_00(s, k)
780 :
781 0 : omega00 = wrap_omega_00(s, k)
782 0 : omegap1 = wrap_omega_p1(s, k)
783 :
784 0 : i_rot00 = s% i_rot(k)
785 0 : i_rotp1 = shift_p1(s% i_rot(k+1))
786 :
787 0 : part1 = -pi2_div4*pow4(r00+rp1)*pow2(rho00)*(i_rot00+i_rotp1)*(s% am_nu_omega(k)+s% am_nu_omega(k+1))
788 0 : s% j_flux(k) = part1*(omega00-omegap1)/s% dm(k)
789 :
790 : !! this is to test partials
791 : !if (k==188) then
792 : ! part1 = (omega00-omegap1)/s% dm(k)
793 : ! s% solver_test_partials_val = part1% val
794 : ! s% solver_test_partials_var = s% i_lnR !s% i_w_div_wc
795 : ! s% solver_test_partials_dval_dx = part1% d1Array(i_lnR_00) !part1% d1Array(i_w_div_wc_00)
796 : !end if
797 :
798 : end do
799 0 : s% j_flux(s% nz) = 0d0
800 0 : end subroutine compute_j_fluxes_and_extra_jdot
801 :
802 0 : real(dp) function rpsi_from_re_factor(o2, o4, o6)
803 : real(dp), intent(in) :: o2
804 : real(dp), intent(in) :: o4
805 : real(dp), intent(in) :: o6
806 : ! Fabry+2022, Eq. A.2
807 0 : rpsi_from_re_factor = 1d0 - one_sixth * o2 + 0.02025d0 * o4 - 0.03870d0 * o6
808 0 : end function rpsi_from_re_factor
809 :
810 : real(dp) function re_from_rpsi_factor(o2, o4, o6)
811 : real(dp), intent(in) :: o2
812 : real(dp), intent(in) :: o4
813 : real(dp), intent(in) :: o6
814 : ! Fabry+2022, Eq. A.3
815 : re_from_rpsi_factor = 1d0 + one_sixth * o2 - 0.005124d0 * o4 + 0.06562d0 * o6
816 : end function re_from_rpsi_factor
817 :
818 0 : real(dp) function A(o4, o6, o_log_term)
819 : real(dp), intent(in) :: o4
820 : real(dp), intent(in) :: o6
821 : real(dp), intent(in) :: o_log_term
822 : ! Fabry+2022, Eq. A.6
823 0 : A = 1d0 + 0.3293d0 * o4 - 0.4926d0 * o6 - 0.5560d0 * o_log_term
824 0 : end function A
825 :
826 : real(dp) function B(o2, o4, o6, o_log_term)
827 : real(dp), intent(in) :: o2
828 : real(dp), intent(in) :: o4
829 : real(dp), intent(in) :: o6
830 : real(dp), intent(in) :: o_log_term
831 : ! Fabry+2022, Eq. A.7
832 : B = 1d0 + o2 / 5d0 + 0.4140d0 * o4 - 0.8650d0 * o6 - 0.8370d0 * o_log_term
833 : end function B
834 :
835 0 : real(dp) function C(o2, o4, o6, o_log_term)
836 : real(dp), intent(in) :: o2
837 : real(dp), intent(in) :: o4
838 : real(dp), intent(in) :: o6
839 : real(dp), intent(in) :: o_log_term
840 : ! Fabry+2022, Eq. A.12
841 0 : C = 1d0 + 17d0 / 60d0 * o2 + 0.4010d0 * o4 - 0.8606d0 * o6 - 0.9305d0 * o_log_term
842 0 : end function C
843 :
844 0 : real(dp) function sigmoid(x, xmax1, xmax2)
845 : real(dp), intent(in) :: x, xmax1, xmax2
846 : ! smoothly maps abs(x) = [xmax1, infty] to sigmoid = [xmax1, xmax2]
847 0 : sigmoid = 2d0 * (xmax2 - xmax1) / (1d0 + exp(-2d0 * (abs(x) - xmax1) / (xmax2 - xmax1))) - xmax2 + 2d0 * xmax1
848 0 : end function sigmoid
849 :
850 : end module hydro_rotation
|