Skip to content

Commit 2ffa279

Browse files
authored
Merge pull request #41 from hersle/ksampling
Improve sampling of perturbation modes for angular spectra
2 parents a4b4d07 + 39801a2 commit 2ffa279

File tree

6 files changed

+168
-16
lines changed

6 files changed

+168
-16
lines changed

docs/src/comparison.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -409,10 +409,10 @@ l = 20:20:2000 # CLASS default is lmax = 2500
409409
Dl1 = Dl_class([:TT, :TE, :EE], l, pars)
410410
jl = SphericalBesselCache(l)
411411
Dl2 = Dl_symboltz([:TT, :TE, :EE], jl, pars)
412-
plot_compare(l, l, Dl1[:, 1], Dl2[:, 1], "l", "Dₗ(TT)"; tol = 2e-12)
412+
plot_compare(l, l, Dl1[:, 1], Dl2[:, 1], "l", "Dₗ(TT)"; tol = 1e-12)
413413
```
414414
```@example class
415-
plot_compare(l, l, Dl1[:, 2], Dl2[:, 2], "l", "Dₗ(TE)"; tol = 3e-14)
415+
plot_compare(l, l, Dl1[:, 2], Dl2[:, 2], "l", "Dₗ(TE)"; tol = 2e-14)
416416
```
417417
```@example class
418418
plot_compare(l, l, Dl1[:, 3], Dl2[:, 3], "l", "Dₗ(EE)"; tol = 6e-15)

docs/src/observables.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,36 @@ rs = sound_horizon(sol)
157157
plot(τs, rs; xlabel = "τ / H₀⁻¹", ylabel = "rₛ / (c/H₀)")
158158
```
159159

160+
## Source functions
161+
162+
```@docs
163+
source_grid
164+
source_grid_adaptive
165+
```
166+
167+
```@example
168+
using SymBoltz, Plots, DataInterpolations
169+
M = ΛCDM(h = nothing, ν = nothing)
170+
pars = parameters_Planck18(M)
171+
prob = CosmologyProblem(M, pars)
172+
sol = solve(prob)
173+
174+
τs = sol[M.τ] # conformal times in background solution
175+
ks = [1.0, 2000.0] # initial coarse grid
176+
ks, Ss = source_grid_adaptive(prob, [M.ST0], τs, ks; atol = 5.0)
177+
iτ = argmax(sol[M.b.v]) # index of decoupling time
178+
iτs = iτ-75:iτ+75 # indices around decoupling
179+
p1 = surface(ks, τs[iτs], Ss[1, iτs, :]; camera = (45, 25), xlabel = "k", ylabel = "τ", zlabel = "S", colorbar = false)
180+
181+
lgas = -6.0:0.2:0.0
182+
τs = LinearInterpolation(sol[M.τ], sol[log10(M.g.a)])(lgas) # τ at given lg(a)
183+
ks = 5.0:5.0:100.0
184+
Ss = source_grid(prob, [M.g.Ψ], τs, ks)
185+
p2 = wireframe(ks, lgas, Ss[1, :, :]; camera = (75, 20), xlabel = "k", ylabel = "lg(a)", zlabel = "Φ")
186+
187+
plot(p1, p2)
188+
```
189+
160190
## Line-of-sight integration
161191

162192
```@docs

src/SymBoltz.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export RMΛ, ΛCDM, w0waCDM, QCDM, GRΛCDM, BDΛCDM
4545
export CosmologyProblem, CosmologySolution
4646
export solve, solvebg, solvept, remake, issuccess, parameter_updater
4747
export parameters_Planck18
48-
export spectrum_primordial, spectrum_matter, spectrum_matter_nonlinear, spectrum_cmb, correlation_function, variance_matter, stddev_matter, los_integrate, source_grid, sound_horizon, SphericalBesselCache
48+
export spectrum_primordial, spectrum_matter, spectrum_matter_nonlinear, spectrum_cmb, correlation_function, variance_matter, stddev_matter, los_integrate, source_grid, source_grid_adaptive, sound_horizon, SphericalBesselCache
4949
export express_derivatives
5050

5151
end

src/observables/angular.jl

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,16 @@ function spectrum_cmb(ΘlAs::AbstractMatrix, ΘlBs::AbstractMatrix, P0s::Abstrac
181181
end
182182

183183
"""
184-
spectrum_cmb(modes::AbstractVector, prob::CosmologyProblem, jl::SphericalBesselCache; normalization = :Cl, unit = nothing, Δkτ0 = 2π/2, Δkτ0_S = 8.0, kτ0min = 0.1*ls[begin], kτ0max = 3*ls[end], u = (τ->tanh(τ)), u⁻¹ = (u->atanh(u)), Nlos = 768, integrator = TrapezoidalRule(), bgopts = (alg = Rodas4P(), reltol = 1e-9, abstol = 1e-9), ptopts = (alg = KenCarp4(), reltol = 1e-8, abstol = 1e-8), thread = true, verbose = false, kwargs...)
184+
spectrum_cmb(modes::AbstractVector, prob::CosmologyProblem, jl::SphericalBesselCache; normalization = :Cl, unit = nothing, kτ0s = 0.1*jl.l[begin]:2π/2:2*jl.l[end], u = (τ->tanh(τ)), u⁻¹ = (u->atanh(u)), Nlos = 768, integrator = TrapezoidalRule(), bgopts = (alg = Rodas4P(), reltol = 1e-9, abstol = 1e-9), ptopts = (alg = KenCarp4(), reltol = 1e-8, abstol = 1e-8), sourceopts = (atol = 1e0,), verbose = false, kwargs...)
185185
186186
Compute the CMB power spectra `modes` (`:TT`, `:EE`, `:TE` or an array thereof) ``C_l^{AB}``'s at angular wavenumbers `ls` from the cosmological solution `sol`.
187187
If `unit` is `nothing` the spectra are of dimensionless temperature fluctuations relative to the present photon temperature; while if `unit` is a temperature unit the spectra are of dimensionful temperature fluctuations.
188188
"""
189-
function spectrum_cmb(modes::AbstractVector, prob::CosmologyProblem, jl::SphericalBesselCache; normalization = :Cl, unit = nothing, Δkτ0 = 2π/2, Δkτ0_S = 8.0, kτ0min = 0.1*jl.l[begin], kτ0max = 3*jl.l[end], u =->tanh(τ)), u⁻¹ = (u->atanh(u)), Nlos = 768, integrator = TrapezoidalRule(), bgopts = (alg = Rodas4P(), reltol = 1e-9, abstol = 1e-9), ptopts = (alg = KenCarp4(), reltol = 1e-8, abstol = 1e-8), thread = true, verbose = false, kwargs...)
189+
function spectrum_cmb(modes::AbstractVector, prob::CosmologyProblem, jl::SphericalBesselCache; normalization = :Cl, unit = nothing, kτ0s = 0.1*jl.l[begin]:2π/2:2*jl.l[end], u =->tanh(τ)), u⁻¹ = (u->atanh(u)), Nlos = 768, integrator = TrapezoidalRule(), bgopts = (alg = Rodas4P(), reltol = 1e-9, abstol = 1e-9), ptopts = (alg = KenCarp4(), reltol = 1e-8, abstol = 1e-8), sourceopts = (atol = 1e0,), verbose = false, kwargs...)
190190
ls = jl.l
191-
kτ0s_coarse, kτ0s_fine = cmb_kτ0s(ls[begin], ls[end]; Δkτ0, Δkτ0_S, kτ0min, kτ0max)
192191
sol = solve(prob; bgopts, verbose)
193192
τ0 = getsym(sol, prob.M.τ0)(sol)
194-
ks_coarse = kτ0s_coarse ./ τ0
193+
ks_fine = collect(kτ0s ./ τ0)
195194
τs = sol.bg.t # by default, use background (thermodynamics) time points for line of sight integration
196195
τi = τs[begin]
197196
if Nlos != 0 # instead choose Nlos time points τ = τ(u) corresponding to uniformly spaced u
@@ -209,11 +208,10 @@ function spectrum_cmb(modes::AbstractVector, prob::CosmologyProblem, jl::Spheric
209208
Ss = Num[]
210209
iT > 0 && push!(Ss, prob.M.ST0)
211210
iE > 0 && push!(Ss, prob.M.ST2_polarization)
212-
Ss_coarse = source_grid(prob, Ss, τs, ks_coarse; bgopts, ptopts, thread, verbose) # TODO: pass kτ0 and x # TODO: pass bgsol
211+
ks_coarse = range(ks_fine[begin], ks_fine[end]; length = 2)
212+
ks_coarse, Ss_coarse = source_grid_adaptive(prob, Ss, τs, ks_coarse; bgopts, ptopts, verbose, sourceopts...) # TODO: thread flag # TODO: pass kτ0 and x # TODO: pass bgsol
213213

214214
# Interpolate source function to finer k-grid
215-
ks_fine = collect(kτ0s_fine ./ τ0)
216-
ks_fine = clamp.(ks_fine, ks_coarse[begin], ks_coarse[end]) # TODO: ideally avoid
217215
Ss_fine = source_grid(Ss_coarse, ks_coarse, ks_fine)
218216
Ss_fine[:, end, :] .= 0.0
219217

@@ -267,7 +265,7 @@ function spectrum_cmb(mode::Symbol, args...; kwargs...)
267265
return spectrum_cmb([mode], args...; kwargs...)[:, begin]
268266
end
269267

270-
function cmb_kτ0s(lmin, lmax; Δkτ0 = 2π/2, Δkτ0_S = 8.0, kτ0min = 0.1*lmin, kτ0max = 3*lmax)
268+
function cmb_kτ0s(lmin, lmax; Δkτ0 = 2π/2, Δkτ0_S = 8.0, kτ0min = 0.1*lmin, kτ0max = 2*lmax)
271269
kτ0s_fine = range(kτ0min, kτ0max, step=Δkτ0) # use integer multiple so endpoints are the same
272270
kτ0s_coarse = range(kτ0s_fine[begin], kτ0s_fine[end], length = Int(floor((kτ0max-kτ0min)/Δkτ0_S+1)))
273271
kτ0s_coarse[begin] == kτ0s_fine[begin] && kτ0s_coarse[end] == kτ0s_fine[end] || error("different wavenumber endpoints")

src/observables/fourier.jl

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,12 @@ end
152152
# TODO: take in ks and N_skip_interp = 1, 2, 3, ... for more efficient? same for τ?
153153
# TODO: source_grid(prob::CosmologyProblem seemed type-stable?
154154
# TODO: return getter for (ks, τs)
155+
"""
156+
source_grid(sol::CosmologySolution, Ss::AbstractVector, τs)
157+
158+
Evaluate and return source functions ``S(τ,k)`` from the solution `sol`.
159+
The source functions are given by symbolic expressions `Ss`, and evaluated on a grid with conformal times `τs` and the same wavenumbers as `sol` is solved for.
160+
"""
155161
function source_grid(sol::CosmologySolution, Ss::AbstractVector, τs)
156162
# Evaluate integrated perturbations on coarse grid
157163
ks = sol.ks
@@ -165,6 +171,13 @@ function source_grid(sol::CosmologySolution, Ss::AbstractVector, τs)
165171
return Ss
166172
end
167173

174+
"""
175+
source_grid(Ss_coarse::AbstractArray, ks_coarse, ks_fine; ktransform = identity)
176+
177+
Interpolate values `Ss_coarse` of source functions ``S(τ,k)`` from a coarse wavenumber grid `ks_coarse` to a fine grid `ks_fine`.
178+
The interpolation is linear in `ktransform(k)` (e.g. `identity` for interpolation in ``k`` or `log` for interpolation in ``\\ln k``.
179+
Conformal times are unchanged.
180+
"""
168181
function source_grid(Ss_coarse::AbstractArray, ks_coarse, ks_fine; ktransform = identity)
169182
size_coarse = size(Ss_coarse)
170183
size_fine = (size_coarse[1], size_coarse[2], length(ks_fine))
@@ -182,12 +195,18 @@ function source_grid(Ss_coarse::AbstractArray, ks_coarse, ks_fine; ktransform =
182195
return Ss_fine
183196
end
184197

185-
# TODO: take in kτ0s and xs
186-
function source_grid(prob::CosmologyProblem, S::AbstractArray, τs, ks; bgopts = (), ptopts = (), thread = true, verbose = false)
198+
"""
199+
source_grid(prob::CosmologyProblem, Ss::AbstractArray, τs, ks; bgopts = (), ptopts = (), thread = true, verbose = false)
200+
201+
Compute and evaluate source functions ``S(τ,k)`` with symbolic expressions `Ss` on a grid with conformal times `τs` and wavenumbers `ks` from the problem `prob`.
202+
203+
The options `bgopts` and `ptopts` are passed to the background and perturbation solves.
204+
"""
205+
function source_grid(prob::CosmologyProblem, Ss::AbstractArray, τs, ks; bgopts = (), ptopts = (), thread = true, verbose = false)
187206
bgsol = solvebg(prob.bg; bgopts..., verbose)
188-
getSs = map(s -> getsym(prob.pt, s), S)
189-
Ss = similar(bgsol, length(S), length(τs), length(ks))
190-
extrema(τs) == extrema(bgsol.t) || error("input τs and computed background solution have different timespans") # TODO: don't rely on
207+
getSs = map(S -> getsym(prob.pt, S), Ss)
208+
Ss = similar(bgsol, length(Ss), length(τs), length(ks))
209+
minimum(τs) bgsol.t[begin] && maximum(τs) bgsol.t[end] || error("input τs and computed background solution have different timespans")
191210
function output_func(sol, ik)
192211
for iS in eachindex(getSs)
193212
Ss[iS, :, ik] .= getSs[iS](sol)
@@ -197,3 +216,87 @@ function source_grid(prob::CosmologyProblem, S::AbstractArray, τs, ks; bgopts =
197216
solvept(prob.pt, bgsol, ks, prob.bgspline; output_func, saveat = τs, ptopts..., thread, verbose)
198217
return Ss
199218
end
219+
220+
# Called internally by source_grid_adaptive
221+
function source_grid_refine(i1, i2, ks, Ss, Sk, savelock; verbose = false, kwargs...)
222+
k1 = ks[i1]
223+
k2 = ks[i2]
224+
S1 = Ss[:, :, i1]
225+
S2 = Ss[:, :, i2]
226+
227+
k2 k1 || error("shit")
228+
229+
k = (k1 + k2) / 2
230+
S = Sk(k)
231+
232+
i = 0 # define in outer scope, then set inside lock block
233+
@lock savelock begin
234+
push!(ks, k)
235+
i = length(ks) # copy, since first refine below can modify i before call to second refine
236+
Ss[:, :, i] .= S
237+
verbose && println("Refined k-grid between [$k1, $k2] on thread $(threadid()) to $i total points")
238+
end
239+
240+
Sint = (S1 .+ S2) ./ 2 # linear interpolation
241+
242+
# check if interpolation is close enough for all sources
243+
# (equivalent to finding the source grid of each source separately)
244+
@sync if !all(isapprox(S[iS, :], Sint[iS, :]; kwargs...) for iS in 1:size(Ss, 1))
245+
@spawn source_grid_refine(i1, i, ks, Ss, Sk, savelock; verbose, kwargs...) # refine left subinterval
246+
@spawn source_grid_refine(i, i2, ks, Ss, Sk, savelock; verbose, kwargs...) # refine right subinterval
247+
end
248+
end
249+
250+
# TODO: Hermite interpolation
251+
# TODO: create SourceFunction type that does k and τ interpolation?
252+
"""
253+
source_grid_adaptive(prob::CosmologyProblem, Ss::AbstractVector, τs, ks; bgopts = (), ptopts = (), sort = true, kwargs...)
254+
255+
Adaptively compute and evaluate source functions ``S(τ,k)`` with symbolic expressions `Ss` on a grid with fixed conformal times `τs`, but adaptively refined grid of wavenumbers from the problem `prob`.
256+
The source functions are first evaluated on the (coarse) initial grid `ks`.
257+
Each subinterval ``(k₁, k₂)`` of `ks` is then adaptively refined until the linear interpolation ``Sᵢ = (S(k₁)+S(k₂))/2`` to the midpoint ``k=(k₁+k₂)/2`` approximates the actual value ``S(k)`` there within some tolerance.
258+
The comparison ``Sᵢ ≈ S`` is done with `isapprox(Sᵢ, S; kwargs...)`, where `S` and `Sᵢ` are vectors with the (conformal) timeseries of the source function for that wavenumber.
259+
It receives the keyword arguments `kwargs` passed to this function, so `atol`, `rtol` and/or `norm` can be specified to tune the tolerance.
260+
261+
Returns the refined wavenumbers sorted in ascending order and a grid with the corresponding source function values.
262+
If not `sort`, the wavenumbers and source function values are instead left in the order in which they were inserted in the refinement process.
263+
264+
The options `bgopts` and `ptopts` are passed to the background and perturbation solves.
265+
"""
266+
function source_grid_adaptive(prob::CosmologyProblem, Ss::AbstractVector, τs, ks; bgopts = (), ptopts = (), sort = true, kwargs...)
267+
bgsol = solvebg(prob.bg; bgopts...)
268+
ptprob0, ptprobgen = setuppt(prob.pt, bgsol, prob.bgspline)
269+
270+
getSs = map(S -> getsym(prob.pt, S), Ss)
271+
function Sk(k)
272+
ptprob = ptprobgen(ptprob0, k)
273+
ptsol = solvept(ptprob; saveat = τs, ptopts...)
274+
S = transpose(stack(map(getS -> getS(ptsol), getSs)))
275+
S[:, end] .= 0.0 # avoid NaN/Infs from 1/χ today; fine in LOS integration, since always weighted by 0-valued Bessel function # TODO: avoid
276+
return S
277+
end
278+
279+
length(ks) 2 || error("Initial k-grid must have at least 2 values")
280+
ks = Vector(ks) # common to input a range
281+
Ss = similar(bgsol, length(Ss), length(τs), 1024)
282+
283+
savelock = ReentrantLock()
284+
285+
@threads for i in 1:length(ks)
286+
Ss[:, :, i] .= Sk(ks[i])
287+
end
288+
@threads for i in 1:length(ks)-1
289+
source_grid_refine(i, i+1, ks, Ss, Sk, savelock; kwargs...)
290+
end
291+
292+
# sort according to k
293+
if sort
294+
is = sortperm(ks)
295+
ks = ks[is]
296+
Ss = Ss[:, :, is]
297+
else
298+
Ss = Ss[:, :, 1:length(ks)]
299+
end
300+
301+
return ks, Ss
302+
end

src/solve.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,27 @@ function solvept(ptprob::ODEProblem, bgsol::ODESolution, ks::AbstractArray, bgsp
427427
verbose && println()
428428
return ptsols
429429
end
430+
"""
431+
solvept(ptprob::ODEProblem; alg = DEFAULT_PTALG, reltol = 1e-8, abstol = 1e-8, kwargs...)
432+
433+
Solve the perturbation problem `ptprob` and return the solution.
434+
Its wavenumber and background spline must already be initialized, for example with `setuppt`.
435+
436+
# Examples
437+
438+
```julia
439+
# ...
440+
prob = CosmologyProblem(M, pars)
441+
bgsol = solvebg(prob.bg)
442+
ptprob0, ptprobgen = SymBoltz.setuppt(prob.pt, bgsol, prob.bgspline)
443+
k = 1.0
444+
ptprob = ptprobgen(ptprob0, k)
445+
ptsol = solvept(ptprob)
446+
```
447+
"""
448+
function solvept(ptprob::ODEProblem; alg = DEFAULT_PTALG, reltol = 1e-8, abstol = 1e-8, kwargs...)
449+
return solve(ptprob, alg; reltol, abstol, kwargs...)
450+
end
430451

431452
function time_today(prob::CosmologyProblem)
432453
getτ0 = SymBoltz.getsym(prob.bg, :τ0)

0 commit comments

Comments
 (0)