Getting started
1. Set up your environment
After you have installed the package following the instructions in the homepage, start a Julia REPL with multithreading optionally enabled as
julia -t NT
where NT
is the number of threads you want to use. Then, load the package as
using Skylight
2. Create a spacetime
Here, we create a Kerr spacetime in Boyer-Lindquist coordinates:
spacetime = KerrSpacetimeBoyerLindquistCoordinates(M=1.0,a=0.5)
Find other available spacetimes at Spacetimes.
3. Create a radiative model
Here we create a Novikov-Thorne disk in prograde rotation around the black hole, specifying the inner and outer radii of the disk. The inner radius is set to the innermost stable circular orbit (ISCO) of the black hole, which depends on the spacetime parameters and rotation direction.
disk = NovikovThorneDisk(inner_radius=isco_radius(spacetime, ProgradeRotation()), outer_radius = 15.0)
Other available radiative models can be found at Radiative models.
4. Set up a camera
Then, we create a pinhole camera, where the position is given by the spacetime coordinates of the observation point, the aperture is determined by the horizontal and vertical aperture in degrees, and the number of pixels in each direction are given. Here the apertures are set to capture a wide view of the accretion disk. For more details on the camera setup, see Camera.
camera = PinholeCamera(position = [0.0, 500, π/2-π/20, 0.0],
horizontal_aperture_in_degrees = rad2deg(80/500),
vertical_aperture_in_degrees = rad2deg(80/500),
horizontal_number_of_pixels = 600,
vertical_number_of_pixels = 600)
5. Set up the configurations object
We gather the spacetime, radiative model, and camera into a configurations object. This setup is for a vacuum transport problem (in the observer-to-emitter method).
configurations = VacuumOTEConfigurations(spacetime=spacetime,
radiative_model=disk,
camera = camera,
unit_mass_in_solar_masses = 1e7)
where unit_mass_in_solar_masses
is the unit mass in solar masses which, together with $c=G=1$, fully determines your units system. This latter choice will affect the interpretation of the varius quantities set before as, e.g. the mass of the Kerr spacetime, which now is understood to correspond to $10^7$ solar masses.
For more general non-vacuum transfer problems, use
configurations = NonVacuumOTEConfigurations(spacetime = spacetime,
camera = camera,
radiative_model = model,
unit_mass_in_solar_masses = 1e7,
observation_energies = exp10.(range(-10, stop = -5.5, length = 20)))
where observation_energies
is a vector of the observation energies in CGS units.
6. Generate the initial data
Create the initial data for the transport problem with
initial_data = initialize(configurations)
The initial data will be a matrix having the initial conditions for each observation direction as columns, with the first four components being the spacetime coordinates (the same as the camera position), and the last four components are the components of the initial momentum in the corresponding coordinate frame.
7. Define callbacks
We define a callback to be called at each step of the equations integration. This is generally used to stop the integration under certain conditions as getting far away from the source, or intersecting the emitting surface. The default callbacks can be set up with
cb, cbp = callback_setup(configurations; rhorizon_bound=0.1)
where cb
is the Callback object, and cbp
contains the callback parameters. In this particular setup, an extra parameter has to be passed as an argument (rhorizon_bound
), which determines the minimum radial distance to which rays can approximate the event horizon before terminating the integration (this is because no future-directed rays can exit the event horizon, so, conversely, no past-directed rays can enter it).
Additionally, custom callbacks can be defined. For more details, see Callbacks and Event Handling.
8. Integrate the equations
Then, we integrate the radiative transfer and geodesic equations, choosing a solver method and setting the relative and absolute tolerances.
sim = integrate(initial_data,
configurations,
cb,
cbp;
method = VCABM(),
reltol = 1e-8,
abstol = 1e-8)
In certain setups, you may require lower errors for the integration to remain stable, especially when dealing with interpolated spacetimes. The integrate
function is a wrapper for the DifferentialEquations.jl package's solve
function. As such, you can pass any additional keyword arguments that are accepted by the solve
. In particular, any of the available solver methods can be used. For more information, see the DifferentialEquations.jl documentation.
The output data can be extracted as
output_data = sim.output_data
This matrix contains the final coordinates and momenta of each ray, with the same structure as the initial data.
9. Visualize the results
Finally, we compute, for instance, the observed bolometric intensity of the radiation field, and produce an image using CairoMakie as
using CairoMakie
Iobs = observed_bolometric_intensities(initial_data, output_data, configurations)
xs, ys = axes_ranges(camera)
zs = grid_view(Iobs, configurations)
fig = Figure(font = "CMU Serif")
ax = Axis(fig[1, 1],
xlabel = L"\alpha \, [\mathrm{deg}]",
ylabel = L"\beta \, [\mathrm{deg}]",
ylabelsize = 26,
xlabelsize = 26)
hmap = heatmap!(rad2deg.(xs), rad2deg.(ys), zs; colormap = :gist_heat, interpolate = true)
Colorbar(fig[:, end + 1],
hmap,
label = L"I \, [\mathrm{erg}/\mathrm{s}/\mathrm{cm^2}/\mathrm{sr}]",
labelsize = 26,
width = 15,
ticksize = 18,
tickalign = 1)
colsize!(fig.layout, 1, Aspect(1, 1.0))
colgap!(fig.layout, 7)
display(fig)