Skip to content

Commit f1588dd

Browse files
committed
Faster, more accurate and lower memory neutrino background implementation
1 parent 0eea66d commit f1588dd

2 files changed

Lines changed: 84 additions & 33 deletions

File tree

‎fortran/camb.f90‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,6 @@ subroutine CAMB_FreeGlobalMemory()
845845
if (allocated(ThermalNuBackground%p1)) deallocate(ThermalNuBackground%p1)
846846
if (allocated(ThermalNuBackground%dr1)) deallocate(ThermalNuBackground%dr1)
847847
if (allocated(ThermalNuBackground%dp1)) deallocate(ThermalNuBackground%dp1)
848-
if (allocated(ThermalNuBackground%ddr1)) deallocate(ThermalNuBackground%ddr1)
849848

850849
end subroutine CAMB_FreeGlobalMemory
851850

‎fortran/massive_neutrinos.f90‎

Lines changed: 84 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ module MassiveNu
88
real(dl), parameter :: const2 = 5._dl/7._dl/const_pi**2 !0.072372274_dl
99

1010
!Steps for spline interpolation (use series outside this range)
11-
integer, parameter :: nrhopn=1400
11+
integer, parameter :: nrhopn=400
1212
real(dl), parameter :: am_min = 0.3_dl
1313
!smallest a*m_nu to integrate distribution function rather than using series
1414
real(dl), parameter :: am_max = 70._dl
@@ -17,6 +17,15 @@ module MassiveNu
1717
!Actual range for using series (to avoid inaccurate ends of spline)
1818
real(dl), parameter :: am_minp=am_min + am_max/(nrhopn-1)*1.01_dl
1919
real(dl), parameter :: am_maxp=am_max*0.9_dl
20+
!Optimized 8-point background quadrature (derived via minimax/least-squares fit in scripts/nu_background_quadrature.py)
21+
! set legacy toggle true to use (less accurate and slower) original 100-step grid.
22+
logical, parameter :: use_legacy_nu_background_grid = .false.
23+
real(dl), parameter :: nu_background_q(8) = (/0.2937822_dl, 0.73583979_dl, 1.49222507_dl, 2.68795368_dl, &
24+
4.30678084_dl, 4.63078102_dl, 7.37122449_dl, 11.91683009_dl/)
25+
real(dl), parameter :: nu_background_rho_weights(8) = (/0.000640376236953801_dl, 0.01312614_dl, 0.10233804_dl, &
26+
0.31935253_dl, 0.12193422_dl, 0.27616318_dl, 0.15455734_dl, 0.01189232_dl/)
27+
real(dl), parameter :: nu_background_pressure_weights(8) = (/0.0002028435952467642_dl, 0.00440170_dl, 0.03405480_dl, &
28+
0.10658874_dl, 0.03987692_dl, 0.09276772_dl, 0.05146992_dl, 0.00396969_dl/)
2029

2130
Type TNuPerturbations
2231
!Sample for massive neutrino momentum
@@ -30,7 +39,7 @@ module MassiveNu
3039
Type TThermalNuBackground
3140
!Quantities for the neutrino background momentum distribution assuming thermal
3241
real(dl) dam !step in a*m
33-
real(dl), dimension(:), allocatable :: r1,p1,dr1,dp1,ddr1
42+
real(dl), dimension(:), allocatable :: r1,p1,dr1,dp1
3443
real(dl), private :: target_rho
3544
contains
3645
procedure :: init => ThermalNuBackground_init
@@ -86,15 +95,15 @@ subroutine TNuPerturbations_init(this,Accuracy)
8695
this%nu_q(1:3) = (/0.913201, 3.37517, 7.79184/)
8796
this%nu_int_kernel(1:3) = (/0.0687359, 3.31435, 2.29911/)
8897
else if (this%nqmax==4) then
89-
!Best 4-point rule from compiled CAMB tests: free-node least-squares fit for n=-4,-2..2 and v(am/q), 1/v(am/q)
98+
!Free-node least-squares fit for n=-4,-2..2 and v(am/q), 1/v(am/q)
9099
!Original rule kept here for reference:
91100
!this%nu_q(1:4) = (/0.7, 2.62814, 5.90428, 12.0/)
92101
!this%nu_int_kernel(1:4) = (/0.0200251, 1.84539, 3.52736, 0.289427/)
93102
this%nu_q(1:4) = (/0.5802007037903776_dl, 2.2150938570691223_dl, 4.948032138986023_dl, 9.65253759848097_dl/)
94103
this%nu_int_kernel(1:4) = (/0.0082119845039711_dl, 1.1143258498419168_dl, &
95104
3.6819104154615907_dl, 0.8777790167504481_dl/)
96105
else if (this%nqmax==5) then
97-
!Best 5-point rule from compiled CAMB tests: exact for n=-4,-2..2 with remaining freedom fit to v(am/q), 1/v(am/q)
106+
!Exact for n=-4,-2..2 with remaining freedom fit to v(am/q), 1/v(am/q)
98107
!Original rule kept here for reference:
99108
!this%nu_q(1:5) = (/0.583165, 2.0, 4.0, 7.26582, 13.0/)
100109
!this%nu_int_kernel(1:5) = (/0.0081201, 0.689407, 2.8063, 2.05156, 0.126817/)
@@ -118,32 +127,45 @@ end subroutine TNuPerturbations_init
118127
subroutine ThermalNuBackground_init(this)
119128
use splines
120129
class(TThermalNuBackground) :: this
121-
! Initialize interpolation tables for massive neutrinos.
122-
! Use cubic splines interpolation of log rhonu and pnu vs. log a*m.
130+
! Initialize interpolation tables for massive neutrino background.
123131
integer i
124-
real(dl) am, rhonu,pnu
132+
real(dl) am, rhonu,pnu, drhonu_dam, dpnu_dam
125133
real(dl) spline_data(nrhopn)
126134

127135
if (allocated(this%r1)) return
128136
ThermalNuBack => ThermalNuBackground !ifort bug workaround
129137

130-
allocate(this%r1(nrhopn),this%p1(nrhopn),this%dr1(nrhopn),this%dp1(nrhopn),this%ddr1(nrhopn))
138+
allocate(this%r1(nrhopn),this%p1(nrhopn),this%dr1(nrhopn),this%dp1(nrhopn))
131139
this%dam=(am_max-am_min)/(nrhopn-1)
132140

133-
!$OMP PARALLEL DO DEFAULT(SHARED), SCHEDULE(STATIC), &
134-
!$OMP& PRIVATE(am,rhonu,pnu)
135-
do i=1,nrhopn
136-
am=am_min + (i-1)*this%dam
137-
call nuRhoPres(am,rhonu,pnu)
138-
this%r1(i)=rhonu
139-
this%p1(i)=pnu
140-
end do
141-
!$OMP END PARALLEL DO
141+
if (use_legacy_nu_background_grid) then
142+
!$OMP PARALLEL DO DEFAULT(SHARED), SCHEDULE(STATIC), &
143+
!$OMP& PRIVATE(am,rhonu,pnu)
144+
do i=1,nrhopn
145+
am=am_min + (i-1)*this%dam
146+
call nuRhoPres(am,rhonu,pnu)
147+
this%r1(i)=rhonu
148+
this%p1(i)=pnu
149+
end do
150+
!$OMP END PARALLEL DO
142151

143-
call splini(spline_data,nrhopn)
144-
call splder(this%r1,this%dr1,nrhopn,spline_data)
145-
call splder(this%p1,this%dp1,nrhopn,spline_data)
146-
call splder(this%dr1,this%ddr1,nrhopn,spline_data)
152+
call splini(spline_data,nrhopn)
153+
call splder(this%r1,this%dr1,nrhopn,spline_data)
154+
call splder(this%p1,this%dp1,nrhopn,spline_data)
155+
else
156+
!$OMP PARALLEL DO DEFAULT(SHARED), SCHEDULE(STATIC), &
157+
!$OMP& PRIVATE(am,rhonu,pnu,drhonu_dam,dpnu_dam)
158+
do i=1,nrhopn
159+
am=am_min + (i-1)*this%dam
160+
call nuRhoPres_8point(am,rhonu,pnu)
161+
call nuRhoPres_8point_derivs(am,drhonu_dam,dpnu_dam)
162+
this%r1(i)=rhonu
163+
this%p1(i)=pnu
164+
this%dr1(i)=drhonu_dam*this%dam
165+
this%dp1(i)=dpnu_dam*this%dam
166+
end do
167+
!$OMP END PARALLEL DO
168+
end if
147169

148170
end subroutine ThermalNuBackground_init
149171

@@ -181,6 +203,43 @@ subroutine nuRhoPres(am,rhonu,pnu)
181203

182204
end subroutine nuRhoPres
183205

206+
subroutine nuRhoPres_8point(am,rhonu,pnu)
207+
! Optimized 8-point shared-node quadrature for ThermalNuBackground table generation.
208+
real(dl), intent(in) :: am
209+
real(dl), intent(out) :: rhonu, pnu
210+
real(dl) inv_v, v
211+
integer i
212+
213+
rhonu = 0._dl
214+
pnu = 0._dl
215+
do i=1,size(nu_background_q)
216+
inv_v = sqrt(1._dl + (am/nu_background_q(i))**2)
217+
v = 1._dl/inv_v
218+
rhonu = rhonu + nu_background_rho_weights(i)*inv_v
219+
pnu = pnu + nu_background_pressure_weights(i)*v
220+
end do
221+
222+
end subroutine nuRhoPres_8point
223+
224+
subroutine nuRhoPres_8point_derivs(am,drhonu_dam,dpnu_dam)
225+
! Exact a*m derivatives of the optimized 8-point background quadrature.
226+
real(dl), intent(in) :: am
227+
real(dl), intent(out) :: drhonu_dam, dpnu_dam
228+
real(dl) :: inv_v, v, q2
229+
integer i
230+
231+
drhonu_dam = 0._dl
232+
dpnu_dam = 0._dl
233+
do i=1,size(nu_background_q)
234+
q2 = nu_background_q(i)**2
235+
inv_v = sqrt(1._dl + (am/nu_background_q(i))**2)
236+
v = 1._dl/inv_v
237+
drhonu_dam = drhonu_dam + nu_background_rho_weights(i)*am*v/q2
238+
dpnu_dam = dpnu_dam - nu_background_pressure_weights(i)*am*v**3/q2
239+
end do
240+
241+
end subroutine nuRhoPres_8point_derivs
242+
184243
subroutine ThermalNuBackground_rho_P(this,am,rhonu,pnu)
185244
class(TThermalNuBackground) :: this
186245
real(dl), intent(in) :: am
@@ -312,9 +371,8 @@ function ThermalNuBackground_drho(this,am,adotoa) result (rhonudot)
312371
! Compute the time derivative of the mean density in massive neutrinos
313372
class(TThermalNuBackground) :: this
314373
real(dl) adotoa,rhonudot
315-
real(dl) d, am2
374+
real(dl) am2, rhonu, pnu
316375
real(dl), intent(IN) :: am
317-
integer i
318376

319377
if (am< am_minp) then
320378
!rhonudot = 2*const2*am**2*adotoa
@@ -324,15 +382,9 @@ function ThermalNuBackground_drho(this,am,adotoa) result (rhonudot)
324382
else if (am>am_maxp) then
325383
rhonudot = 3/(2*fermi_dirac_const)*(zeta3*am +( -(15*zeta5)/2 + 2835._dl/16*zeta7/am**2)/am)*adotoa
326384
else
327-
d=(am-am_min)/this%dam+1._dl
328-
i=int(d)
329-
d=d-i
330-
! Cubic spline interpolation for rhonudot.
331-
rhonudot=this%dr1(i)+d*(this%ddr1(i)+d*(3._dl*(this%dr1(i+1)-this%dr1(i)) &
332-
-2._dl*this%ddr1(i)-this%ddr1(i+1)+d*(this%ddr1(i)+this%ddr1(i+1) &
333-
+2._dl*(this%dr1(i)-this%dr1(i+1)))))
334-
335-
rhonudot=adotoa*rhonudot/this%dam*am
385+
call this%rho_P(am,rhonu,pnu)
386+
! am * (d rho_nu / d am) analytically simplifies exactly to (rho_nu - 3 P_nu)
387+
rhonudot = (rhonu - 3._dl*pnu)*adotoa
336388
end if
337389

338390
end function ThermalNuBackground_drho

0 commit comments

Comments
 (0)