Skip to content

Commit 3b64539

Browse files
authored
Merge pull request #218 from astro-group-bristol/fergus/adaptive-docs
Documentation updates
2 parents cc798d2 + 8d27037 commit 3b64539

17 files changed

Lines changed: 219 additions & 13 deletions

docs/make.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ makedocs(
2121
"Point functions" => "point-functions.md",
2222
"Disc emissivity" => "emissivity.md",
2323
"Line profiles" => "lineprofiles.md",
24+
"Adaptive tracing" => "adaptive-tracing.md",
2425
],
2526
"Geodesics and integration" => [
2627
"Implementing new metrics" => "custom-metrics.md",

docs/src/accretion-geometry.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ cross_section
2929
### Available accretion geometry
3030

3131
```@docs
32-
GeometricThinDisc
32+
ThinDisc
3333
ThickDisc
3434
ShakuraSunyaev
3535
```

docs/src/adaptive-tracing.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Adaptive tracing
2+
3+
Gradus.jl has a variety of skies and image planes that it supports, which act as a method for representing the view of a particular observer in the system. The difference in definition that Gradus uses is that a _sky_ represents either a full sphere (or hemisphere) of possible trajectories, whereas a _image plane_ is a subset of the sphere that projects onto a two-dimensional plane. Image planes are only really used in the context of calculating observational signatures from the perspective of telescopes, whereas skies are used as intermediary representations to calculate e.g. illumination patterns or visibilities.
4+
5+
To integrate over a sky, each angle pair ``(\theta, \phi)`` is used to calculate a direction vector, which is transformed by the local basis into an initial velocity vector that is sensitive to the motion of the emitter. These trajectories can be heavily distorted due to relativistic beaming if the emitter is moving at relativistic speeds.
6+
7+
One of the main problem with integrating over a sky in any kind of numerically meaningful way is that the angles, if sampled uniformly over ``\theta \sim \mathcal{U}(0, \pi)`` and ``\phi \sim \mathcal{U}(0, 2\pi)``, then the majority of trajectories will be scrunched near the poles. To avoid this issue, one can instead sample ``\cos \theta \sim \mathcal{U}(-1, 1)``, which acts to impose the ``\sin \theta`` probability density term.
8+
9+
This however still has the problem that often very few regions of the sky actually represent something interesting. For example, say we were calculating the illumination pattern of the disc for an emitter close the black hole -- we do not care about those trajectories that fall into the black hole or escape to infinity, only those that hit the disc. However, a hand-wavey 50% of the sky will be either the black hole or infinity, so it is wasting precious CPU cycles calculating those trajectories when we know they are not relevant to our result.
10+
11+
This is where _adaptive tracing_ is useful. It traces an initial coarse grid of _pilot geodesics_ that can be used to work out where information of interest is in the sky.
12+
13+
To illustrate this, we'll use a pre-defined adaptive sky implementation, but at the end of these instructions will be a section on [Custom refinement criteria](@ref).
14+
15+
```julia
16+
using Gradus, Makie, CairoMakie
17+
18+
m = KerrMetric(1.0, 0.998)
19+
# the ring-corona can also be used to represent any off-axis point
20+
corona = RingCorona(; r = 10.0, h = 4.0)
21+
d = ThinDisc(0.0, Inf)
22+
23+
sky = AdaptiveSky(m, corona, d)
24+
```
25+
26+
The [`AdaptiveSky`](@ref) uses a grid of 3x3 cells, where each cell can be
27+
continuously refined to smaller 3x3 cells. It refers to each refinement as a `level`, where `level == 1` are the top 9 cells.
28+
29+
We need to prime the refined grid, so we trace three levels, corresponding to ``9^3 = 729`` trajectories.
30+
31+
```@docs
32+
Gradus.trace_initial!
33+
```
34+
35+
```julia
36+
# trace an initial grid of points
37+
trace_initial!(sky)
38+
```
39+
40+
The printout may record a greater number of values than you expect. This is
41+
because it is recording the number of values it has stored, and some cells
42+
double up in the implementation (though they are not traced, their memory is
43+
duplicated currently).
44+
45+
We can visualise the sky using one of the unpacking functions
46+
47+
```@docs
48+
Gradus.unpack_sky
49+
```
50+
51+
The `V` type of the sky we have constructed is [`CoronaGridValues`](@ref).
52+
53+
```julia
54+
function plot_sky(sky; kwargs...)
55+
phi, cos_theta, values = unpack_sky(sky)
56+
theta = acos.(cos_theta)
57+
# get the radius that each geodesic hit
58+
Rs = [v.r for v in values]
59+
60+
fig, ax, _ = scatter(phi, theta; color = log10.(Rs), kwargs...)
61+
ax.xlabel = "ϕ"
62+
ax.ylabel = "θ"
63+
fig
64+
end
65+
66+
plot_sky(sky)
67+
```
68+
![](./figs/adaptive.initial.png)
69+
70+
Not all of the points have been drawn, since those that either fell into the black hole or went to inifinity had their radius set to `NaN`. This is a feature of the particular [`AdaptiveSky`](@ref) we have used, but you can handle this however you like in your own implementation.
71+
72+
Lets say we are now interested in the edges of the disc, and want to refine those points. We can define a _refine_ condition that is given pairs of neighbouring points and asks whether the cells they are in need to be refined.
73+
74+
In our case, if one or the other is `NaN` (but not both), we want to refine those cells. We'll implement the full function, but there are utilities to help as well like [`Gradus.refine_function`](@ref).
75+
```julia
76+
function refine_edges(sky::AdaptiveSky, i1::Int, i2::Int)
77+
v1 = sky.values[i1]
78+
v2 = sky.values[i2]
79+
if isnan(v1.r) && isnan(v2.r)
80+
false # do not refine
81+
else
82+
isnan(v1.r) || isnan(v2.r)
83+
end
84+
end
85+
86+
trace_step!(sky; check_refine = refine_edges, verbose = true)
87+
```
88+
89+
Plotting the sky again
90+
91+
```julia
92+
plot_sky(sky; markersize = 6)
93+
```
94+
95+
!!! note
96+
97+
There is currently a bug where the bottom row of cells are always refined
98+
when checking `NaN`. This will be fixed in future versions.
99+
100+
![](./figs/adaptive.step-1.png)
101+
102+
We can run this step a couple more times:
103+
104+
```julia
105+
for i in 1:2
106+
trace_step!(sky; check_refine = refine_edges, verbose = true)
107+
end
108+
109+
plot_sky(sky; markersize = 2)
110+
```
111+
112+
![](./figs/adaptive.step-3.png)
113+
114+
Cool, now let's inpaint the rest using an interpolation scheme:
115+
116+
```@docs
117+
Gradus.fill_sky_values
118+
```
119+
120+
!!! warning
121+
122+
The interpolation scheme used is very poor at the moment, and effectively does column-wise interpolation, without considering other surrounding neighbours. This will also be fixed in future versions.
123+
124+
```julia
125+
phi, theta, grid = fill_sky_values(sky, 1080)
126+
Rs = [v.r for v in grid]
127+
128+
fig, ax, _ = heatmap(phi, theta, log10.(Rs)')
129+
ax.xlabel = "ϕ"
130+
ax.ylabel = "θ"
131+
fig
132+
```
133+
134+
![](./figs/adaptive.approx-inpaint.png)
135+
136+
What if we had a refinemenet criteria based on the relative values of neighbours?
137+
138+
```julia
139+
function refine_distant(sky::AdaptiveSky, i1::Int, i2::Int)
140+
v1 = sky.values[i1]
141+
v2 = sky.values[i2]
142+
if isnan(v1.r) && isnan(v2.r)
143+
false # do not refine
144+
else
145+
# refine if they are more than 10% different
146+
!isapprox(v1.r, v2.r, rtol = 0.1)
147+
end
148+
end
149+
150+
# create a fresh sky
151+
sky = AdaptiveSky(m, corona, d)
152+
trace_initial!(sky)
153+
for i in 1:3
154+
trace_step!(sky; check_refine = refine_distant, verbose = true)
155+
end
156+
157+
plot_sky(sky; markersize = 2)
158+
```
159+
160+
![](./figs/adaptive.refine-distance.png)
161+
162+
Now our inpainting should look more convincing
163+
164+
```julia
165+
phi, theta, grid = fill_sky_values(sky, 1080)
166+
Rs = [v.r for v in grid]
167+
168+
fig, ax, _ = heatmap(phi, theta, log10.(Rs)')
169+
ax.xlabel = "ϕ"
170+
ax.ylabel = "θ"
171+
fig
172+
```
173+
174+
![](./figs/adaptive.better-inpaint.png)
175+
176+
## Custom adaptive refinement
177+
178+
```@docs
179+
Gradus.AdaptiveSky
180+
Gradus.trace_step!
181+
Gradus.refine_function
182+
Gradus.fine_refine_function
183+
Gradus.Grids.AdaptiveCell
184+
Gradus.Grids.AdaptiveGrid
185+
```
34.7 KB
Loading
60 KB
Loading

docs/src/figs/adaptive.initial.png

137 KB
Loading
310 KB
Loading

docs/src/figs/adaptive.step-1.png

175 KB
Loading

docs/src/figs/adaptive.step-3.png

132 KB
Loading

docs/src/getting-started.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ plot_horizon!(m)
127127
Choosing the initial velocity in this manner lacks interpretation. We can instead use so-called _impact parameters_ $(\alpha, \beta)$. These may be thought of as follows:
128128

129129
```@docs
130-
impact_parameters_to_three_velocity
130+
map_impact_parameters
131131
```
132132

133133
Finally, if you imagine a two dimensional image plane, where $x$ is the horizontal and $y$ the vertical coordinate, the $\alpha$ impact parameter corresponds to that closest approach along the $x$ axis, and $\beta$ along the $y$ axis.
@@ -487,7 +487,7 @@ plot!(bins, f_s_thin_disc, label = "Schwarzschild[thin]")
487487

488488
## Where to go from here?
489489

490-
The documentation is a rich resource for information related to using Gradus, and tailoring the toolkit for your needs. Take a look at [Examples](@ref) for a number of quick recipes, or try [Implementing a new metric](@ref) and study a different spacetime.
490+
The documentation is a rich resource for information related to using Gradus, and tailoring the toolkit for your needs. Take a look at [Examples](@ref) for a number of quick recipes, or try [Implementing a new metric](@ref) and study a different spacetime. If you just want to have fun making images, check out [Adaptive tracing](@ref).
491491

492492

493493
[^1]: Johannsen, Tim. ‘Regular Black Hole Metric with Three Constants of Motion’. Physical Review D 88, no. 4 (1 August 2013): 044002. https://doi.org/10.1103/PhysRevD.88.044002.

0 commit comments

Comments
 (0)