Skip to content

Commit 16b6401

Browse files
authored
Merge pull request #646 from MESAHub/kap_derivative_testing
Automatic differentiation for cubic interpolation across type-1 and type-2 kap tables
2 parents cbb5136 + 7d3d000 commit 16b6401

File tree

17 files changed

+1461
-155
lines changed

17 files changed

+1461
-155
lines changed

docs/source/changelog.rst

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,9 @@ New Features
2121
``max_allowed_nz`` is now ignored if the value is less than or equal to zero.
2222

2323
Kap
24-
~~~~~
24+
~~~
2525

26-
`High Temperature Opacity Tables`
27-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26+
**High Temperature Opacity Tables**
2827

2928
Type 1 Rosseland-mean opacity tables from The Los Alamos
3029
OPLIB database (`Colgan et al. 2016 <https://ui.adsabs.harvard.edu/abs/2016ApJ...817..116C/abstract>`_) are now available (Farag et al. 2024).
@@ -52,8 +51,8 @@ implementation of these tables. For further details on these new OPLIB opacity t
5251
the Type 1 OPAL/OP tables as well as their effect on solar models can be found in
5352
in Farag et al. 2024.
5453

55-
`Low Temperature Opacity Tables`
56-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
54+
55+
**Low Temperature Opacity Tables**
5756

5857
Low temperature Rosseland-mean opacity tables for both (`AAG21, Asplund et al. 2021 <https://ui.adsabs.harvard.edu/abs/2021A%26A...653A.141A/abstract>`_),
5958
and (`MB22, Magg et al. 2022 <https://doi.org/10.1051/0004-6361/202142971>`_)
@@ -65,6 +64,65 @@ options for :ref:`kap/defaults:kap_lowT_prefix`:
6564
+ ``'lowT_fa05_mb22'``
6665
+ ``'lowT_fa05_aag21'``
6766

67+
**Opacity interpolation**
68+
69+
We have updated the opacity interpolation scheme to provide much higher quality derivatives when doing cubic interpolation
70+
in composition.
71+
72+
MESA interpolates across opacity tables in the :math:`X–Z` plane through the use of two consequtive 1D splines.
73+
MESA offers users the ability to choose linear or cubic interpolation for these splines,
74+
while leaving the default as linear interpolation::
75+
76+
cubic_interpolation_in_X = .false.
77+
cubic_interpolation_in_Z = .false.
78+
79+
This choice of default was primarily due to the fact that
80+
the previous cubic composition interpolation scheme in MESA suffered from poor quality interpolated opacity derivatives with respect to
81+
density and temperature, which often disagreed with the numerical derivatives produced via nearest neighbor
82+
Richardson extrapolation. The figure below shows this comparison on a logarithmic scale, where in general red indicates poor quality
83+
derivatives and blue indicates high quality derivatives.
84+
85+
.. figure:: changelog_plots/cubic_dfridr_dkapdT.png
86+
:alt: old cubic relative kap derivative error
87+
88+
This figure shows the logarithmic relative error in the derivative :math:`\partial \kappa / \partial T` (:math:`X` = 0.625, :math:`Z` = 0.015),
89+
for an OPAL opacity table grid using Grevesse & Sauval (1998) abundances, generated from MESA’s kap module, using the previous cubic interpolation scheme.
90+
The OPLIB log(:math:`R`) = −8, 1.5 table boundaries are marked with a solid black line and the OPAL/OP log(:math:`R`) = 1.0 boundary is shown with a dashed line.
91+
The approximate location of the Z-dependent transition to an electron conduction dominated opacity is marked with dot-dash blue curve.
92+
Regions for Atomic, molecular, and compton scattering opacity are labeled and presented with their associated blending regions.
93+
94+
95+
While the opacity derivatives do not directly appear in the canonical equations of stellar structure, they do appear in the Jacobian matrix for MESA's implicit solver.
96+
Numerically unstable opacity derivatives can halt the progress of the solver and ultimately crash a calculation.
97+
98+
To improve the numerical stability of MESA's cubic opacity interpolation routines, we have implemented
99+
automatic differentiation into the opacity interpolating functions. Now, when using cubic interpolation, the opacity derivatives for an arbitrary mixture
100+
in the :math:`X–Z` plane are computed by taking the derivative of the interpolating function as opposed to the interpolant of the derivatives. This improvement
101+
has led to a significant reduction in the relative derivative error and an increase in the numerical accuracy of opacity derivatives computed with cubic interpolation.
102+
103+
.. figure:: changelog_plots/cubic_dfridr_dkapdT_ad.png
104+
:alt: new cubic relative kap derivative error
105+
106+
Same as previous figure, but for new cubic interpolation scheme taking advantage of automatic differentiation.
107+
108+
109+
This new implementation of cubic interpolation in composition for opacity tables comes close to achieving the derivative quality of the linear interpolation
110+
option (shown below), while also providing more accurate opacity physics between opacity table grid points.
111+
112+
.. figure:: changelog_plots/linear_dfridr_dkapdT.png
113+
:alt: linear relative kap derivative error
114+
115+
Same as previous figure, but for linear interpolation instead of cubic.
116+
117+
118+
For this MESA release, linear interpolation remains the default method for interpolating in composition between opacity tables
119+
while we continue to investigate the residual areas where cubic interpolation appears to occasionally produce lower quality derivatives.
120+
However, adopting cubic interpolation has been shown to consistently increase the overall
121+
opacity of a model, and can directly effect the structure of solar models, see Appendix B & C in Farag et al. 2024.
122+
We anticipate making cubic interpolation the default in a future MESA release version.
123+
We encourage users to experiment with these different opacity interpolation routines and be mindful of the effect they can have on their stellar models.
124+
125+
68126
Chem
69127
~~~~~
70128
New initial metal mass fractions ``initial_zfracs`` taken from photospheric estimates of the solar heavy element abundances in (AAG21, Asplund et al. 2021) and (MB22, Magg et al. 2022)
884 KB
Loading
875 KB
Loading
875 KB
Loading

interp_1d/make/makefile_base

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ include $(MESA_DIR)/utils/makefile_header
1515
SRCS = \
1616
interp_1d_misc.f90 \
1717
interp_1d_pm.f90 \
18+
interp_1d_pm_autodiff.f90 \
1819
interp_1d_mp.f90 \
20+
interp_1d_mp_autodiff.f90\
1921
interp_1d_misc_sg.f90 \
2022
interp_1d_pm_sg.f90 \
2123
interp_1d_mp_sg.f90 \
@@ -28,9 +30,9 @@ SRCS = \
2830
# LIBRARIES
2931

3032
ifeq ($(USE_SHARED), YES)
31-
LIBS_OTHER = $(LIBS_MATRIX)
33+
LIBS_OTHER = auto_diff $(LIBS_MATRIX)
3234
DEPS_OTHER = $(patsubst %,$(MESA_LIB_DIR)/lib%.$(LIB_SUFFIX),$(LIBS_OTHER))
33-
LOAD_OTHER = -L$(MESA_LIB_DIR) $(LOAD_MATRIX)
35+
LOAD_OTHER = -L$(MESA_LIB_DIR) -lauto_diff $(LOAD_MATRIX)
3436
endif
3537

3638
#################################################################

interp_1d/private/interp_1d_misc.f90

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
module interp_1d_misc
2727

2828
use const_lib, only: dp
29-
29+
use auto_diff
30+
3031
implicit none
3132

3233
contains
@@ -181,7 +182,75 @@ subroutine do_interp_values(init_x, nx, f1, nv, x, vals, ierr)
181182

182183

183184
end subroutine do_interp_values
185+
186+
subroutine do_interp_values_autodiff(init_x, nx, f1, nv, x, vals, ierr)
187+
type(auto_diff_real_2var_order1), intent(in) :: init_x(:) ! (nx) ! junction points, strictly monotonic
188+
integer, intent(in) :: nx ! length of init_x vector
189+
type(auto_diff_real_2var_order1), intent(in), pointer :: f1(:) ! =(4, nx) ! data & interpolation coefficients
190+
integer, intent(in) :: nv ! length of new x vector and vals vector
191+
type(auto_diff_real_2var_order1), intent(in) :: x(:) ! (nv) ! locations where want interpolated values
192+
! strictly monotonic in same way as init_x
193+
! values out of range of init_x's are clipped to boundaries of init_x's
194+
type(auto_diff_real_2var_order1), intent(inout) :: vals(:) ! (nv)
195+
integer, intent(out) :: ierr ! 0 means aok
196+
197+
integer :: k_old, k_new
198+
type(auto_diff_real_2var_order1) :: xk_old, xkp1_old, xk_new, delta
199+
logical :: increasing
200+
type(auto_diff_real_2var_order1), pointer :: f(:,:) ! (4, nx) ! data & interpolation coefficients
201+
202+
ierr = 0
203+
204+
if (nx == 1) then
205+
vals(1:nv) = f1(1)
206+
return
207+
end if
208+
209+
f(1:4,1:nx) => f1(1:4*nx)
210+
211+
if(init_x(1) < init_x(nx)) then
212+
increasing = .true.
213+
end if
214+
215+
k_old = 1; xk_old = init_x(k_old); xkp1_old = init_x(k_old+1)
216+
217+
do k_new = 1, nv
184218

219+
xk_new = x(k_new)
220+
if (increasing) then
221+
if (xk_new > init_x(nx)) then
222+
xk_new = init_x(nx)
223+
else if (xk_new < init_x(1)) then
224+
xk_new = init_x(1)
225+
end if
226+
else ! decreasing
227+
if (xk_new < init_x(nx)) then
228+
xk_new = init_x(nx)
229+
else if (xk_new > init_x(1)) then
230+
xk_new = init_x(1)
231+
end if
232+
end if
233+
do while ((increasing .and. xk_new > xkp1_old) .or. ((.not. increasing) .and. xk_new < xkp1_old))
234+
k_old = k_old + 1
235+
if (k_old >= nx) then
236+
k_old = k_old - 1
237+
xk_new = xkp1_old
238+
exit
239+
end if
240+
xk_old = xkp1_old
241+
xkp1_old = init_x(k_old+1)
242+
end do
243+
244+
delta = xk_new - xk_old
245+
246+
vals(k_new) = &
247+
f(1, k_old) + delta*(f(2, k_old) &
248+
+ delta*(f(3, k_old) + delta*f(4, k_old)))
249+
250+
end do
251+
252+
253+
end subroutine do_interp_values_autodiff
185254

186255
subroutine do_interp_values_and_slopes(init_x, nx, f1, nv, x, vals, slopes, ierr)
187256
real(dp), intent(in) :: init_x(:) ! (nx) ! junction points, strictly monotonic
@@ -576,10 +645,10 @@ subroutine minmod4(z, n, f1, f2, f3, f4)
576645
call minmod(z, n, z, f3)
577646
call minmod(z, n, z, f4)
578647
end subroutine minmod4
579-
580-
648+
649+
581650
subroutine median(z, n, f1, f2, f3)
582-
real(dp), intent(inout) :: z(:)
651+
real(dp), intent(inout) :: z(:)
583652
integer, intent(in) :: n ! length of vectors
584653
real(dp), intent(in) :: f1(:), f2(:), f3(:)
585654
real(dp), target :: tmp1_ary(n), tmp2_ary(n)
@@ -593,4 +662,61 @@ subroutine median(z, n, f1, f2, f3)
593662
end subroutine median
594663

595664

665+
type(auto_diff_real_2var_order1) function minmod1_autodiff(f1, f2)
666+
use auto_diff
667+
type(auto_diff_real_2var_order1), intent(in) :: f1, f2
668+
minmod1_autodiff = 0.5d0 * (sign(f1) + sign(f2)) * min(abs(f1), abs(f2))
669+
end function minmod1_autodiff
670+
671+
672+
type(auto_diff_real_2var_order1) function median1_autodiff(f1, f2, f3)
673+
use auto_diff
674+
type(auto_diff_real_2var_order1), intent(in) :: f1, f2, f3
675+
median1_autodiff = f1 + minmod1_autodiff(f2 - f1, f3 - f1)
676+
end function median1_autodiff
677+
678+
679+
subroutine minmod_autodiff(z, n, f1, f2)
680+
use auto_diff
681+
type(auto_diff_real_2var_order1), intent(inout) :: z(:)
682+
integer, intent(in) :: n ! length of vectors
683+
integer :: k
684+
type(auto_diff_real_2var_order1), intent(in) :: f1(:), f2(:)
685+
do k =1, n
686+
z(k) = 0.5d0 * (sign(f1(k)) + sign(f2(k))) * min(abs(f1(k)), abs(f2(k)))
687+
end do
688+
end subroutine minmod_autodiff
689+
690+
subroutine minmod4_autodiff(z, n, f1, f2, f3, f4)
691+
use auto_diff
692+
type(auto_diff_real_2var_order1), intent(inout) :: z(:)
693+
integer, intent(in) :: n ! length of vectors
694+
type(auto_diff_real_2var_order1), intent(in) :: f1(:), f2(:), f3(:), f4(:)
695+
call minmod_autodiff(z, n, f1, f2)
696+
call minmod_autodiff(z, n, z, f3)
697+
call minmod_autodiff(z, n, z, f4)
698+
end subroutine minmod4_autodiff
699+
700+
701+
subroutine median_autodiff(z, n, f1, f2, f3)
702+
use auto_diff
703+
type(auto_diff_real_2var_order1), intent(inout) :: z(:)
704+
integer, intent(in) :: n ! length of vectors
705+
integer :: k
706+
type(auto_diff_real_2var_order1), intent(in) :: f1(:), f2(:), f3(:)
707+
type(auto_diff_real_2var_order1), target :: tmp1_ary(n), tmp2_ary(n)
708+
type(auto_diff_real_2var_order1), pointer :: tmp1(:), tmp2(:)
709+
tmp1 => tmp1_ary
710+
tmp2 => tmp2_ary
711+
do k =1,n
712+
tmp1(k) = f2(k) - f1(k)
713+
tmp2(k) = f3(k) - f1(k)
714+
end do
715+
call minmod_autodiff(z, n, tmp1, tmp2)
716+
do k =1,n
717+
z(k) = z(k) + f1(k)
718+
end do
719+
end subroutine median_autodiff
720+
721+
596722
end module interp_1d_misc

0 commit comments

Comments
 (0)