@@ -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