From 15b0a5cdd0ac941aaaab054cd9079b648ae22224 Mon Sep 17 00:00:00 2001 From: Fergus Baker Date: Sun, 2 Oct 2022 19:39:03 +0200 Subject: [PATCH] Generic source-to-disc flux (#53) * julia formatting * bugfixes in tetrad + new basis function * formatting and clean * LNRF test Also removed Dilaton-Axion from tests (see issue #52). * lnrf tests and correct domain * use correct analytic expressions * divide flux by time * energy bin size * allow type to be inferred * switch default solver + inference * revert to Tsit5 since other default too noisy * slightly altered api * update exports * fix tetrad tests * voronoi disc profile stores geodesic points as well * julia formatting * source-to-disc flux model * formatting * bump version * typo fixes, includes, and exports --- Project.toml | 2 +- src/Gradus.jl | 19 +++- src/GradusBase/GradusBase.jl | 19 +++- src/GradusBase/geometry.jl | 52 ++++++--- src/corona-to-disc/corona-models.jl | 11 ++ src/corona-to-disc/disc-profiles.jl | 14 +-- src/corona-to-disc/flux-calculations.jl | 79 ++++++++++++++ src/corona-to-disc/transfer-functions.jl | 2 +- src/metrics/dilaton-axion-ad.jl | 3 +- src/orbits/circular-orbits.jl | 79 +++++++------- src/orbits/orbit-interpolations.jl | 6 +- src/redshift.jl | 2 +- .../method-implementations/auto-diff.jl | 8 +- src/tracing/tracing.jl | 20 ++-- test/runtests.jl | 6 +- test/unit/gradusbase.geometry.jl | 102 ++++++++++++++++-- 16 files changed, 330 insertions(+), 94 deletions(-) create mode 100644 src/corona-to-disc/flux-calculations.jl diff --git a/Project.toml b/Project.toml index 25ab8f29..6c75f130 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "Gradus" uuid = "c5b7b928-ea15-451c-ad6f-a331a0f3e4b7" authors = ["fjebaker "] -version = "0.1.11" +version = "0.1.12" [deps] Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" diff --git a/src/Gradus.jl b/src/Gradus.jl index 119622f5..bacf3373 100644 --- a/src/Gradus.jl +++ b/src/Gradus.jl @@ -46,7 +46,14 @@ import .GradusBase: metric_type, metric_components, inverse_metric_components, - unpack_solution + unpack_solution, + dotproduct, + propernorm, + tetradframe, + lnrbasis, + lnrframe, + lowerindices, + raiseindices export AbstractMetricParams, getgeodesicpoint, @@ -57,7 +64,14 @@ export AbstractMetricParams, constrain, inner_radius, metric_components, - inverse_metric_components + inverse_metric_components, + dotproduct, + propernorm, + tetradframe, + lnrbasis, + lnrframe, + lowerindices, + raiseindices """ abstract type AbstractPointFunction @@ -137,6 +151,7 @@ include("corona-to-disc/sky-geometry.jl") include("corona-to-disc/corona-models.jl") include("corona-to-disc/disc-profiles.jl") include("corona-to-disc/transfer-functions.jl") +include("corona-to-disc/flux-calculations.jl") include("metrics/boyer-lindquist-ad.jl") include("metrics/boyer-lindquist-fo.jl") diff --git a/src/GradusBase/GradusBase.jl b/src/GradusBase/GradusBase.jl index edc3429e..4ea6a1bf 100644 --- a/src/GradusBase/GradusBase.jl +++ b/src/GradusBase/GradusBase.jl @@ -1,10 +1,12 @@ module GradusBase -import Parameters: @with_kw -import SciMLBase import Base -import StaticArrays: SVector, MMatrix, SMatrix, @SVector -import Tullio: @tullio +import SciMLBase + +using Parameters: @with_kw +using StaticArrays: SVector, MMatrix, SMatrix, @SVector +using Tullio: @tullio +using LinearAlgebra: norm, inv # for doc bindings import ..Gradus @@ -27,6 +29,13 @@ constrain, inner_radius, metric_type, metric_components, -inverse_metric_components +inverse_metric_components, +dotproduct, +propernorm, +tetradframe, +lnrbasis, +lnrframe, +lowerindices, +raiseindices end # module diff --git a/src/GradusBase/geometry.jl b/src/GradusBase/geometry.jl index 0bc7b9c6..00d220f7 100644 --- a/src/GradusBase/geometry.jl +++ b/src/GradusBase/geometry.jl @@ -1,9 +1,9 @@ -function vector_to_local_sky(m::AbstractMetricParams{T}, u, θ, ϕ) where {T} +function vector_to_local_sky(m::AbstractMetricParams, u, θ, ϕ) error("Not implemented for $(typeof(m))") end -mdot(g, v1, v2) = @tullio r := g[i, j] * v1[i] * v2[j] -mnorm(g, v) = mdot(g, v, v) +dotproduct(g::AbstractMatrix, v1, v2) = @tullio r := g[i, j] * v1[i] * v2[j] +propernorm(g::AbstractMatrix, v) = dotproduct(g, v, v) """ mproject(g, v, u) @@ -11,7 +11,7 @@ mnorm(g, v) = mdot(g, v, v) Project vector `v` onto `u` with metric `g`. Optional first argument may be [`AbstractMetricParams`](@ref) for more optimized methods, which fallback to an einsum. """ -mproject(g, v, u) = mdot(g, v, u) / mnorm(g, u) +mproject(g, v, u) = dotproduct(g, v, u) / propernorm(g, u) function projectbasis(g, basis, v) s = zero(SVector{4,Float64}) @@ -21,7 +21,7 @@ function projectbasis(g, basis, v) s end -function grammschmidt(v, basis, g; tol = 4eps(Float64)) +function gramschmidt(v, basis, g; tol = 4eps(Float64)) p = projectbasis(g, basis, v) while sum(p) > tol @@ -30,19 +30,45 @@ function grammschmidt(v, basis, g; tol = 4eps(Float64)) end v = v .- p - vnorm = √mnorm(g, v) + vnorm = √abs(propernorm(g, v)) v / vnorm end -function tetradframe(m::AbstractMetricParams{T}, u, v) where {T} - g = metric(m, u) - +# TODO: this presupposes static and axis symmetric +@inline function tetradframe(g::AbstractMatrix, v) + vt = v ./ √abs(propernorm(g, v)) # start procedure with ϕ, which has zero for r and θ - vϕ = grammschmidt(@SVector[1.0, 0.0, 0.0, 1.0], (v,), g) + vϕ = gramschmidt(@SVector[1.0, 0.0, 0.0, 1.0], (vt,), g) # then do r, which has zero for θ - vr = grammschmidt(@SVector[1.0, 1.0, 0.0, 1.0], (v, vϕ), g) + vr = gramschmidt(@SVector[0.0, 1.0, 0.0, 0.0], (vt, vϕ), g) # then finally θ - vθ = grammschmidt(@SVector[1.0, 1.0, 1.0, 1.0], (v, vϕ, vr), g) + vθ = gramschmidt(@SVector[0.0, 0.0, 1.0, 0.0], (vt, vϕ, vr), g) + (vt, vr, vθ, vϕ) +end - (v, vr, vθ, vϕ) +tetradframe(m::AbstractMetricParams, u, v) = tetradframe(metric(m, u), v) + +# TODO: this presupposes static and axis symmetric +# tetrad with indices down: frame +function lnrframe(g::AbstractMatrix) + ω = -g[1, 4] / g[4, 4] + v = @SVector [1.0, 0.0, 0.0, ω] + tetradframe(g, v) end +lnrframe(m::AbstractMetricParams, u) = lnrframe(metric(m, u)) + +# tetrad with indices up: basis +function lnrbasis(g::AbstractMatrix) + ω = -g[1, 4] / g[4, 4] + v = @SVector [-ω, 0.0, 0.0, 1.0] + (vϕ, vr, vθ, vt) = tetradframe(inv(g), v) + # rearrange + (vt, vr, vθ, vϕ) +end +lnrbasis(m::AbstractMetricParams, u) = lnrbasis(metric(m, u)) + +lowerindices(g::AbstractMatrix, v) = g * v +lowerindices(m::AbstractMetricParams, u, v) = lowerindices(metric(m, u), v) + +raiseindices(ginv::AbstractMatrix, v) = ginv * v +raiseindices(m::AbstractMetricParams, u, v) = raiseindices(inv(metric(m, u)), v) diff --git a/src/corona-to-disc/corona-models.jl b/src/corona-to-disc/corona-models.jl index 5c3bbb06..3a11a0e7 100644 --- a/src/corona-to-disc/corona-models.jl +++ b/src/corona-to-disc/corona-models.jl @@ -29,4 +29,15 @@ function sample_velocity( end end +function source_velocity(m::AbstractMetricParams, model::AbstractCoronaModel) + error("Not implemented for $(typeof(model)).") +end + +function source_velocity(m::AbstractMetricParams, model::LampPostModel) + # stationary source + rθ = @SVector[model.h, model.θ] + gcomp = metric_components(m, rθ) + inv(√(-gcomp[1])) * @SVector[1.0, 0.0, 0.0, 0.0] +end + export LampPostModel diff --git a/src/corona-to-disc/disc-profiles.jl b/src/corona-to-disc/disc-profiles.jl index 2ea0bf1f..19f17c58 100644 --- a/src/corona-to-disc/disc-profiles.jl +++ b/src/corona-to-disc/disc-profiles.jl @@ -41,22 +41,24 @@ end # evaluate(aap::AbstractAccretionProfile, p) = # error("Not implemented for `$(typeof(aap))` yet.") -struct VoronoiDiscProfile{D,V} <: AbstractDiscProfile +struct VoronoiDiscProfile{D,V,G} <: AbstractDiscProfile disc::D polys::Vector{Vector{V}} generators::Vector{V} + geodesic_points::Vector{G} function VoronoiDiscProfile( d::D, polys::Vector{Vector{V}}, gen::Vector{V}, - ) where {V<:AbstractArray{T}} where {D,T} + gps::Vector{G}, + ) where {D<:AbstractAccretionDisc,V<:AbstractArray,G} if !isapprox(d.inclination, π / 2) return error( "Currently only supported for discs in the equitorial plane (θ=π/2).", ) end - new{D,V}(d, polys, gen) + new{D,V,G}(d, polys, gen, gps) end end @@ -67,9 +69,9 @@ end function VoronoiDiscProfile( m::AbstractMetricParams{T}, d::AbstractAccretionDisc{T}, - endpoints::AbstractVector{GeodesicPoint{T,V}}; + endpoints::AbstractVector{<:GeodesicPoint{T}}; padding = 1, -) where {T,V} +) where {T} dim = d.outer_radius + padding rect = VoronoiCells.Rectangle( GeometryBasics.Point2{T}(-dim, -dim), @@ -88,7 +90,7 @@ function VoronoiDiscProfile( polys_vecs = unpack_polys(polys) - VoronoiDiscProfile(d, polys_vecs, generators) + VoronoiDiscProfile(d, polys_vecs, generators, endpoints) end function VoronoiDiscProfile( diff --git a/src/corona-to-disc/flux-calculations.jl b/src/corona-to-disc/flux-calculations.jl new file mode 100644 index 00000000..83da6787 --- /dev/null +++ b/src/corona-to-disc/flux-calculations.jl @@ -0,0 +1,79 @@ +""" + lorentz_factor(g::AbstractMatrix, isco, u, v) + +Calculate Lorentz factor in LNRF of `u`. +""" +function lorentz_factor(g::AbstractMatrix, isco_r, u, v) + frame = Gradus.GradusBase.lnrbasis(g) + B = reduce(hcat, frame) + denom = B[:, 1] ⋅ v + + 𝒱ϕ = (B[:, 4] ⋅ v) / denom + + if u[2] < isco_r + 𝒱r = (B[:, 2] ⋅ v) / denom + inv(√(1 - 𝒱r^2 - 𝒱ϕ^2)) + else + inv(√(1 - 𝒱ϕ^2)) + end +end + +function flux_source_to_disc( + m::AbstractMetricParams, + model::AbstractCoronaModel, + vdp::AbstractDiscProfile, +) + error( + "Not implemented for metric $(typeof(m)) with model $(typeof(model)) and disc profile $(typeof(vdp)).", + ) +end + +function flux_source_to_disc( + m::AbstractMetricParams, + model::LampPostModel, + vdp::VoronoiDiscProfile; + α = 1.0, +) + v_source = source_velocity(m, model) + + intensity = inv.(getareas(vdp)) + total_intensity = sum(intensity) + + isco_r = isco(m) + intp = interpolate_plunging_velocities(m) + + disc_velocity(r) = + if r < isco_r + vtemp = intp(r) + @SVector [vtemp[1], -vtemp[2], vtemp[3], vtemp[4]] + else + CircularOrbits.fourvelocity(m, r) + end + + flux = args -> begin + (i, gp) = args + g_1 = metric(m, gp.u1) + g_2 = metric(m, gp.u2) + + # energy at source + @tullio E_s := -g_1[i, j] * gp.v1[i] * v_source[j] + + # energy at disc + v_disc = disc_velocity(gp.u2[2]) + @tullio E_d := -g_2[i, j] * gp.v2[i] * v_disc[j] + + # relative redshift source to disc + g_sd = E_d / E_s + + # area element + dA = inv(√(g_2[2, 2] * g_2[4, 4])) + + γ = lorentz_factor(g_2, isco_r, gp.u2, v_disc) + f_sd = intensity[i] / total_intensity + # total reflected flux + g_sd^(1 + α) * E_d^(-α) * dA * f_sd / γ + end + map(flux, enumerate(vdp.geodesic_points)) +end + +export flux_source_to_disc \ No newline at end of file diff --git a/src/corona-to-disc/transfer-functions.jl b/src/corona-to-disc/transfer-functions.jl index 9ac867fa..c98a7af8 100644 --- a/src/corona-to-disc/transfer-functions.jl +++ b/src/corona-to-disc/transfer-functions.jl @@ -18,7 +18,7 @@ function bin_transfer_function( e_mask = @. (e ≤ energy) & (energy < (e + de)) sub_flux = @views(flux[t_mask.&e_mask]) if length(sub_flux) > 0 - sum(sub_flux) + sum(sub_flux) / (dt * de) else NaN end diff --git a/src/metrics/dilaton-axion-ad.jl b/src/metrics/dilaton-axion-ad.jl index 90e63e7d..60f89d15 100644 --- a/src/metrics/dilaton-axion-ad.jl +++ b/src/metrics/dilaton-axion-ad.jl @@ -10,7 +10,8 @@ using ..StaticArrays @fastmath A(r, a, Δ, θ) = (r^2 + a^2)^2 - a^2 * Δ * sin(θ)^2 @fastmath W(βab, θ, βa) = 1 + (βab * (2 * cos(θ) - βab) + βa^2) * csc(θ)^2 -@fastmath Σ̂(Σ, β, b, r, βb, a, θ, rg) = Σ - (β^2 + 2b * r) + rg^2 * βb * (βb - 2 * (a / rg) * cos(θ)) +@fastmath Σ̂(Σ, β, b, r, βb, a, θ, rg) = + Σ - (β^2 + 2b * r) + rg^2 * βb * (βb - 2 * (a / rg) * cos(θ)) @fastmath Δ̂(Δ, β, b, r, βb, rg) = Δ - (β^2 + 2b * r) - rg * (rg + 2b) * βb^2 @fastmath Â(δ, a, Δ̂, W, θ) = δ^2 - a^2 * Δ̂ * W^2 * sin(θ)^2 @fastmath δ(r, b, a) = r^2 - 2b * r + a^2 diff --git a/src/orbits/circular-orbits.jl b/src/orbits/circular-orbits.jl index 1ba0fc96..070dca9b 100644 --- a/src/orbits/circular-orbits.jl +++ b/src/orbits/circular-orbits.jl @@ -6,12 +6,12 @@ import ..Gradus: metric_jacobian, inverse_metric_components -function Ω(m::AbstractAutoDiffStaticAxisSymmetricParams{T}, rθ, pos) where {T} +function Ω(m::AbstractAutoDiffStaticAxisSymmetricParams, rθ; contra_rotating = false) _, jacs = metric_jacobian(m, rθ) ∂rg = jacs[:, 1] Δ = √(∂rg[5]^2 - ∂rg[1] * ∂rg[4]) - if pos + if contra_rotating -(∂rg[5] + Δ) / ∂rg[4] else -(∂rg[5] - Δ) / ∂rg[4] @@ -22,11 +22,12 @@ function __energy(g, Ωϕ) @inbounds -(g[1] + g[5] * Ωϕ) / √(-g[1] - 2g[5] * Ωϕ - g[4] * Ωϕ^2) end -energy(m, r; contra_rotating = false) = - let rθ = @SVector([r, π / 2]) - Ωϕ = Ω(m, rθ, contra_rotating) - __energy(metric_components(m, rθ), Ωϕ) - end +function energy(m, rθ; contra_rotating = false) + Ωϕ = Ω(m, rθ; contra_rotating = contra_rotating) + __energy(metric_components(m, rθ), Ωϕ) +end +energy(m::AbstractAutoDiffStaticAxisSymmetricParams, r::Number; kwargs...) = + energy(m, @SVector([r, π / 2]); kwargs...) function __angmom(g, Ωϕ, prograde) @inbounds res = (g[5] + g[4] * Ωϕ) / √(-g[1] - 2g[5] * Ωϕ - g[4] * Ωϕ^2) @@ -37,39 +38,39 @@ function __angmom(g, Ωϕ, prograde) end end -angmom(m, r; contra_rotating = false, prograde = true) = - let rθ = @SVector([r, π / 2]) - Ωϕ = Ω(m, rθ, contra_rotating) - __angmom(metric_components(m, rθ), Ωϕ, prograde) - end +function angmom(m, rθ; contra_rotating = false, prograde = true) + Ωϕ = Ω(m, rθ; contra_rotating = contra_rotating) + __angmom(metric_components(m, rθ), Ωϕ, prograde) +end +angmom(m::AbstractAutoDiffStaticAxisSymmetricParams, r::Number; kwargs...) = + angmom(m, @SVector([r, π / 2]); kwargs...) function g_ginv_energy_angmom( - m::AbstractAutoDiffStaticAxisSymmetricParams{T}, - r; + m::AbstractAutoDiffStaticAxisSymmetricParams, + rθ; contra_rotating = false, prograde = true, -) where {T} - let rθ = @SVector([r, π / 2]) - - g = metric_components(m, rθ) - ginv = inverse_metric_components(g) +) + g = metric_components(m, rθ) + ginv = inverse_metric_components(g) - Ωϕ = Ω(m, rθ, contra_rotating) - E = __energy(g, Ωϕ) - L = __angmom(g, Ωϕ, prograde) + Ωϕ = Ω(m, rθ; contra_rotating = contra_rotating) + E = __energy(g, Ωϕ) + L = __angmom(g, Ωϕ, prograde) - (g, ginv, E, L) - end + (g, ginv, E, L) end function __vϕ(ginv, E, L) -E * ginv[5] + L * ginv[4] end -function vϕ(m::AbstractAutoDiffStaticAxisSymmetricParams{T}, r; kwargs...) where {T} - _, ginv, E, L = g_ginv_energy_angmom(m, r; kwargs...) +function vϕ(m::AbstractAutoDiffStaticAxisSymmetricParams, rθ; kwargs...) + _, ginv, E, L = g_ginv_energy_angmom(m, rθ; kwargs...) __vϕ(ginv, E, L) end +vϕ(m::AbstractAutoDiffStaticAxisSymmetricParams, r::Number; kwargs...) = + vϕ(m, @SVector([r, π / 2]); kwargs...) # this component doesn't actually seem to correctly constrain the geodesic # to being light- / null-like, or timelike. maybe revist? else call constrain before returning @@ -77,30 +78,26 @@ function __vt(ginv, E, L) -E * ginv[1] + L * ginv[5] end -function vt(m::AbstractAutoDiffStaticAxisSymmetricParams{T}, r; kwargs...) where {T} - _, ginv, E, L = g_ginv_energy_angmom(m, r; kwargs...) +function vt(m::AbstractAutoDiffStaticAxisSymmetricParams, rθ; kwargs...) + _, ginv, E, L = g_ginv_energy_angmom(m, rθ; kwargs...) __vt(ginv, E, L) end +vt(m::AbstractAutoDiffStaticAxisSymmetricParams, r::Number; kwargs...) = + vt(m, @SVector([r, π / 2]); kwargs...) -function fourvelocity( - m::AbstractAutoDiffStaticAxisSymmetricParams{T}, - r; - kwargs..., -) where {T} - _, ginv, E, L = g_ginv_energy_angmom(m, r; kwargs...) +function fourvelocity(m::AbstractAutoDiffStaticAxisSymmetricParams, rθ; kwargs...) + _, ginv, E, L = g_ginv_energy_angmom(m, rθ; kwargs...) vt = __vt(ginv, E, L) vϕ = __vϕ(ginv, E, L) @SVector [vt, 0.0, 0.0, vϕ] end +fourvelocity(m::AbstractAutoDiffStaticAxisSymmetricParams, r::Number; kwargs...) = + fourvelocity(m, @SVector([r, π / 2]); kwargs...) -function plunging_fourvelocity( - m::AbstractAutoDiffStaticAxisSymmetricParams{T}, - r; - kwargs..., -) where {T} - g, ginv, E, L = g_ginv_energy_angmom(m, r; kwargs...) +function plunging_fourvelocity(m::AbstractAutoDiffStaticAxisSymmetricParams, rθ; kwargs...) + g, ginv, E, L = g_ginv_energy_angmom(m, rθ; kwargs...) vt = __vt(ginv, E, L) vϕ = __vϕ(ginv, E, L) @@ -109,6 +106,8 @@ function plunging_fourvelocity( @SVector[vt, -sqrt(abs(nom / denom)), 0.0, vϕ] end +plunging_fourvelocity(m::AbstractAutoDiffStaticAxisSymmetricParams, r::Number; kwargs...) = + plunging_fourvelocity(m, @SVector([r, π / 2]); kwargs...) end # module diff --git a/src/orbits/orbit-interpolations.jl b/src/orbits/orbit-interpolations.jl index 67f5c0fb..a3197d6a 100644 --- a/src/orbits/orbit-interpolations.jl +++ b/src/orbits/orbit-interpolations.jl @@ -14,13 +14,13 @@ struct PlungingInterpolation{M,_interp_type} vr = sol[6, :][I] vϕ = sol[8, :][I] - rinterp = LinearInterpolation(r, vt) + rinterp = LinearInterpolation(r, vt; extrapolation_bc = Line()) new{M,typeof(rinterp)}( m, rinterp, - LinearInterpolation(r, vr), - LinearInterpolation(r, vϕ), + LinearInterpolation(r, vr; extrapolation_bc = Line()), + LinearInterpolation(r, vϕ; extrapolation_bc = Line()), ) end end diff --git a/src/redshift.jl b/src/redshift.jl index 0f5d58e8..95e0769e 100644 --- a/src/redshift.jl +++ b/src/redshift.jl @@ -162,7 +162,7 @@ end u_disc = @SVector [1 / disc_norm, 0, 0, Ωₑ(m.M, u[2], m.a) / disc_norm] # use Tullio to do the einsum - @tullio g := metric_matrix[i, j] * u_disc[i] * (-v[j]) + @tullio g := -metric_matrix[i, j] * u_disc[i] * v[j] 1 / g end diff --git a/src/tracing/method-implementations/auto-diff.jl b/src/tracing/method-implementations/auto-diff.jl index e0f021c7..f9c6d69a 100644 --- a/src/tracing/method-implementations/auto-diff.jl +++ b/src/tracing/method-implementations/auto-diff.jl @@ -266,12 +266,12 @@ function metric_jacobian(m::AbstractAutoDiffStaticAxisSymmetricParams, rθ) end @inbounds function geodesic_eq( - m::AbstractAutoDiffStaticAxisSymmetricParams{T}, - u, - v, + m::AbstractAutoDiffStaticAxisSymmetricParams, + u::AbstractArray{T}, + v::AbstractArray{T}, ) where {T} # get the only position components we need for this metric type - rθ = SVector{2,Float64}(u[2], u[3]) + rθ = SVector{2,T}(u[2], u[3]) # calculate all non-zero components, and use AD to get derivatives g_comps, jacs = metric_jacobian(m, rθ) # calculate all non-zero inverse matric components diff --git a/src/tracing/tracing.jl b/src/tracing/tracing.jl index 4583fb92..8286b7b1 100644 --- a/src/tracing/tracing.jl +++ b/src/tracing/tracing.jl @@ -68,13 +68,16 @@ function integrator_problem( time_domain, ) where {S,T} u_init = vcat(pos, vel) - ODEProblem{false}(u_init, time_domain) do u, p, λ - @inbounds let x = SVector{4}(@view(u[1:4])), v = SVector{4}(@view(u[5:8])) - dv = SVector{4}(geodesic_eq(m, x, v)) + + function f(u::SVector{8,T}, p, λ) where {T} + @inbounds let x = SVector{4,T}(@view(u[1:4])), v = SVector{4,T}(@view(u[5:8])) + dv = SVector{4,T}(geodesic_eq(m, x, v)) # SVector{8}(v[1], v[2], v[3], v[4], dv[1], dv[2], dv[3], dv[4]) vcat(v, dv) end end + + ODEProblem{false}(f, u_init, time_domain) end function integrator_problem( @@ -84,14 +87,17 @@ function integrator_problem( time_domain, ) where {T} u_init = vcat(pos, vel) - ODEProblem{true}(u_init, time_domain) do du, u, p, λ + + function f!(du, u::AbstractVector{T}, p, λ) where {T} @inbounds let x = @view(u[1:4]), v = @view(u[5:8]) - dv = SVector{4}(geodesic_eq(m, x, v)) + dv = SVector{4,T}(geodesic_eq(m, x, v)) - du[1:4] = v - du[5:8] = dv + du[1:4] .= v + du[5:8] .= dv end end + + ODEProblem{true}(f!, u_init, time_domain) end # single position and single velocity diff --git a/test/runtests.jl b/test/runtests.jl index 5ffd39f6..1b5a8aff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,6 +1,8 @@ -using Gradus, StaticArrays using Test +using Gradus +using StaticArrays using Aqua +using Tullio include("smoke-tests/rendergeodesics.jl") include("smoke-tests/tracegeodesics.jl") @@ -10,6 +12,8 @@ include("smoke-tests/disc-profiles.jl") include("smoke-tests/special-radii.jl") include("smoke-tests/cunningham-transfer-functions.jl") +include("unit/gradusbase.geometry.jl") + # little bit of aqua Aqua.test_undefined_exports(Gradus) Aqua.test_unbound_args(Gradus) diff --git a/test/unit/gradusbase.geometry.jl b/test/unit/gradusbase.geometry.jl index 1296def0..98a86227 100644 --- a/test/unit/gradusbase.geometry.jl +++ b/test/unit/gradusbase.geometry.jl @@ -1,4 +1,3 @@ - @testset "tetradframe" begin all_metrics = ( # can't do first order yet since no four velocity @@ -8,26 +7,111 @@ BoyerLindquistAD(1.0, -0.998), JohannsenAD(M = 1.0, a = 0.998, α13 = 1.0), JohannsenAD(M = 1.0, a = 0.998, α22 = 1.0), - DilatonAxionAD(M = 1.0, a = 0.998, β = 0.2, b = 1.0), + # DilatonAxionAD(M = 1.0, a = 0.998, β = 0.2, b = 1.0), ) radii = 5.0:0.8:10.0 - angles = 0.1:0.5:2π + angles = 0.1:0.5:π minkowski = @SMatrix [ - -1.0 0.0 0.0 0.0 ; - 0.0 1.0 0.0 0.0 ; - 0.0 0.0 1.0 0.0 ; - 0.0 0.0 0.0 1.0 + -1.0 0.0 0.0 0.0 + 0.0 1.0 0.0 0.0 + 0.0 0.0 1.0 0.0 + 0.0 0.0 0.0 1.0 ] for m in all_metrics, r in radii, θ in angles u = @SVector([0.0, r, θ, 0.0]) v = Gradus.constrain_all(m, u, CircularOrbits.fourvelocity(m, r), 1.0) # function that we are testing - M = GradusBase.tetradframe(m, u, v) + M = Gradus.GradusBase.tetradframe(m, u, v) m_mat = Gradus.metric(m, u) @tullio res[a, b] := m_mat[i, j] * M[a][i] * M[b][j] # ensure it gives minkowski - @test isapprox(res, minkowski, atol=1e-13) + @test isapprox(res, minkowski, atol = 1e-13) + end + + @testset "lnrf" begin + for m in all_metrics, r in radii, θ in angles + u = @SVector([0.0, r, θ, 0.0]) + + # function that we are testing + M = Gradus.GradusBase.lnrframe(m, u) + + m_mat = Gradus.metric(m, u) + @tullio res[a, b] := m_mat[i, j] * M[a][i] * M[b][j] + # ensure it gives minkowski + @test isapprox(res, minkowski, atol = 1e-13) + end + end + + @testset "kerr-lnrf" begin + # these test explicity check the LNRF calculations for the known + # theory of the Kerr metric + + function kerr_lnrframe(m, u) + A = Gradus.__BoyerLindquistFO.A(m.M, u[2], m.a, u[3]) + Σ = Gradus.__BoyerLindquistFO.Σ(u[2], m.a, u[3]) + Δ = Gradus.__BoyerLindquistFO.Δ(m.M, u[2], m.a) + ω = 2 * m.M * m.a * u[2] / A + + et = √(A / (Σ * Δ)) * @SVector [1.0, 0.0, 0.0, ω] + er = √(Δ / Σ) * @SVector [0.0, 1.0, 0.0, 0.0] + eθ = √(1 / Σ) * @SVector [0.0, 0.0, 1.0, 0.0] + eϕ = √(Σ / A) * (1 / sin(u[3])) * @SVector [0.0, 0.0, 0.0, 1.0] + + vecs = (et, er, eθ, eϕ) + reduce(hcat, vecs) + end + + function numerical_lnrframe(m, u) + vecs = Gradus.GradusBase.lnrframe(m, u) + reduce(hcat, vecs) + end + + for M = 0.2:0.8:2.0, a = -M:0.5:M + m = BoyerLindquistAD(M, a) + r = inner_radius(m) + 4.2 + for θ in angles + u = @SVector [0.0, r, θ, 0.0] + expected = kerr_lnrframe(m, u) + calculated = numerical_lnrframe(m, u) + + @test isapprox(calculated, expected, atol = 1e-13) + end + end + end + + @testset "kerr-lnrf-basis" begin + function kerr_lnrbasis(m, u) + A = Gradus.__BoyerLindquistFO.A(m.M, u[2], m.a, u[3]) + Σ = Gradus.__BoyerLindquistFO.Σ(u[2], m.a, u[3]) + Δ = Gradus.__BoyerLindquistFO.Δ(m.M, u[2], m.a) + ω = 2 * m.M * m.a * u[2] / A + + et = √(Σ * Δ / A) * @SVector [1.0, 0.0, 0.0, 0.0] + er = √(Σ / Δ) * @SVector [0.0, 1.0, 0.0, 0.0] + eθ = √Σ * @SVector [0.0, 0.0, 1.0, 0.0] + eϕ = √(A / Σ) * sin(u[3]) * @SVector [-ω, 0.0, 0.0, 1.0] + + vecs = (et, er, eθ, eϕ) + reduce(hcat, vecs) + end + + function numerical_lnrbasis(m, u) + vecs = Gradus.GradusBase.lnrbasis(m, u) + reduce(hcat, vecs) + end + + for M = 0.2:0.8:2.0, a = -M:0.5:M + m = BoyerLindquistAD(M, a) + r = inner_radius(m) + 0.3 + for θ in angles + u = @SVector [0.0, r, θ, 0.1] + expected = kerr_lnrbasis(m, u) + calculated = numerical_lnrbasis(m, u) + + @test isapprox(calculated, expected, atol = 1e-10) + end + end end end