# RoadRunner transit model#

*Author: Hannu Parviainen* *Last edited: 23 April 2024*

The RoadRunner transit model Parviainen (2020) can reproduce an exoplanet transit signal for any radially symmetric stellar limb darkening with high precision, and generally does this faster than the analytic model for quadratic limb darkening by Mandel & Agol (2002). The model also provides partial derivatives with respect to the impact parameter and radius ratio with very little overhead.

This notebook explains how and what RoadRunner does to achieve the combined speed and flexibility.

**Summary for those who don’t bother to read all the following:** The RoadRunner model represents the transit as a product of an average limb darkening occluded by the planet and planet-star intersection area. The former can be calculated as a weighted sum of the discretised limb darkening profile, and the latter is fast to calculate analytically. The weights need to be computed for a given radius ratio, but this takes only some s of computing time after which the model evaluation
for a single point takes some hundreds of ns.

**Even shorter summary:** The RoadRunner model represents the transit as a product of the uniform limb darkening transit model and a one-variable function. Because of this, its partial derivatives with respect with radius ratio and impact parameter can also be calculated as slightly modified versions of those for the uniform limb darkening transit model, but for any radially symmetric limb darkening profile imagineable.

**Warning:** This notebook shows the low-level working of the RoadRunner model. For actual transit modelling, you will most likely just want to do

```
from pytransit import RoadRunnerModel
```

and let the higher-level code take care of all the dirty work.

## The basics#

### Transit signal#

An exoplanet transit model aims to reproduce the photometric signal caused by a planet crossing over the disk of its host star. The flux normalised to the out-of-transit level is

where is the total flux from the star,

is the flux occluded by the planet,

stands for the area of the star, stands for the area of the stellar surface occluded by the planet, and is the stellar surface brightness as a function of the distance from the stellar centre, .

### The RoadRunner model#

The main idea behind the RoadRunner transit model is simple: the integral of the stellar surface brightness occluded by the planet can be presented as a product of the mean occluded surface brightness and occluded area. When the stellar surface brightness is given by a radially symmetric limb darkening model, , where and is the distance from the centre of the stellar disk (normalised to stellar radius), *the flux blocked by the planet is simply
the mean limb darkening value covered by the planet multiplied by the covered area,*

Now, the mean limb darkening covered by a planet, , is a function of the limb darkening profile, planet-star radius ratio, , and planet-star centre distance (impact parameter), . Given and a limb darkening profile, can be presented as a (relatively) smooth function of .

Because of this, **a transit model for a given :math:`k` and any radially symmetric limb darkening profile can be presented as a product of a one-variable function, :math:`hat{I}(g)`, where :math:`g = b/(1+k)` and :math:`0 leq g leq 1`, and circle-circle intersection area**

The average surface brightness can be calculated for a given and as

where is a normalised weight function (). If we discretise the stellar disk, , to bins, we can present the integral above as a dot product of a weight and limb darkening profile vectors

So, the transit model can be presented as a product of and , where is a weighted average of a limb darkening profile.

### Connection with the transit model for uniform limb darkening#

The transit model for uniform stellar limb darkening is

so *the RoadRunner model can be seen as a simple extension of the uniform model.* Because of this, its partial derivatives with respect to impact parameter and radius ratio are extremely easy and fast to compute (as shown below).

## Initialisation#

```
[1]:
```

```
%pylab inline
```

```
Populating the interactive namespace from numpy and matplotlib
```

```
[2]:
```

```
rc('figure', figsize=(13,4))
```

```
[3]:
```

```
from pytransit.models.numba.rrmodel import (create_z_grid,
calculate_weights_2d, calculate_weights_3d,
circle_circle_intersection_area, circle_circle_intersection_area_v)
from pytransit.models.numba.ldmodels import ld_quadratic, ldi_quadratic
```

## The weight array#

We start by discretising the stellar disk into radial bins from to . This is done using the `create_z_grid`

function that returns the bin edges (`ze`

) and means (`zm`

). We also calculate the values normally used by the limb darkening models.

```
[4]:
```

```
ze, zm = create_z_grid(0.7, 60, 60)
mu = sqrt(1-zm**2)
```

Next, we discretise the grazing parameter into bins from to and calculate a 2D weight array. This is done using the `calculate_weights_2d`

that takes the radius ratio, `k`

, the edges `ze`

, and .

```
[5]:
```

```
k = 0.1
gs, dg, w = calculate_weights_2d(k, ze, 250)
bs = gs * (1+k)
```

The function returns an array of mean values, , and a 2D weight array with a shape (, )

```
[6]:
```

```
fig, ax = subplots()
ax.imshow(w, vmin=0, vmax=0.1, aspect='auto', extent=(0, 1, 0, 1), origin='lower')
setp(ax, xlabel='Normalised distance', ylabel='Grazing parameter', title=f'2D weight array for k={k:0.2f}')
fig.tight_layout()
```

## The average limb darkening occluded by the planet#

The calculation of the weight array for a given (shown above) is the most numerically demanding task in the RoadRunner transit model evaluation, and the rest is very fast.

We continue by evaluating the limb darkening model for the value corresponding to the discretised mean values.

```
[7]:
```

```
ldp = ld_quadratic(mu, array([0.53, 0.30]))
```

```
[8]:
```

```
fig, ax = subplots()
ax.plot(zm, ldp, c='C1')
setp(ax, xlabel='Normalised distance from the centre of the star', ylabel='Surface brightness',
title='Example LD profile', xlim=(0, 1+1.1*k))
fig.tight_layout()
```

And then calculate the average limb darkening as a function of as a dot product of the 2D weight array and the limb darkening profile

```
[9]:
```

```
lda = dot(w, ldp)
```

The averaged limb darkening, , is now a (discretised) function of the grazing parameter. It is smooth from to , and again from to , but its first derivative has a discontinuity at , that is, .

```
[10]:
```

```
fig, axs = subplots(2, 1, figsize=(13,6), sharex='all')
axs[0].plot(gs, lda, c='C0')
axs[1].plot(0.5*(gs[1:]+gs[:-1]), diff(lda)/dg, c='C2')
[[ax.axvline((1+s*k)/(1+k), ls=ls) for s,ls in zip([-1, 0, 1], ['--', ':', '--'])] for ax in axs]
setp(axs, xlim=(0, 1.02))
setp(axs[0], ylabel='Mean occluded LD', title=f'Averaged limb darkening for k={k:0.2f}')
setp(axs[1], ylabel='$\delta/\delta g$ mean occluded LD', ylim=(-3.0, 0.2), xlabel='Grazing parameter')
fig.tight_layout()
```

## The transit signal#

Now we are nearly ready to evaluate the transit model. However, since the transit signal is the integrated stellar surface brightness minus the surface brightness occluded by the planet, we need to first calculate the first term.

The integration of the limb darkening model over the stellar disk can usually be done analytically, and if not, is cheap to do numerically. PyTransit implements the integral of the quadratic limb darkening model in `pytransit.models.numba.ldmodel.ldi_quadratic`

(yes, it’s a bit hidden, but these functions are rarely needed outside the RoadRunner model).

```
[11]:
```

```
ist = ldi_quadratic(array([0.53, 0.30]))
```

And now, armed with the total stellar surface brightness and discretised into a grid, we’re finally ready to go! The most straight-forward approach to evaluate the transit model is simply to map the given values to , interpolate the discretised for the values (I’m showing the example using `interp`

, but do the interpolation manually in the PyTransit implementation), and multiply by the circle-circle intersection area:

```
[12]:
```

```
b = 0.4
g = b / (1+k)
ipl = interp(g, gs, lda) * circle_circle_intersection_area(1.0, k, b)
flux = (ist - ipl) / ist
flux
```

```
[12]:
```

```
0.987692639827011
```

where we normalise the out-of-transit level to unity.

Now, lets do the same for a set of star-planet centre distances

```
[13]:
```

```
b = linspace(-1.2, 1.2, 200)
g = b / (1+k)
ipl = interp(abs(g), gs, lda) * circle_circle_intersection_area_v(1.0, k, abs(b))
```

```
[14]:
```

```
fig, ax = subplots()
ax.plot(b, (ist - ipl) / ist)
setp(ax, xlabel='Planet-star centre distance [$R_\star$]', ylabel='Normalised flux', xlim=(-1.2, 1.2))
fig.tight_layout()
```

## Derivatives#

The full transit model is, again,

where is the average limb darkening covered by the planet and is the planet-star intersection area. So, the transit model is basically the transit model for uniform limb darkening multiplied by the average limb darkening. Because of this, we can get the partial derivatives of the transit model with respect to the grazing parameter (or impact parameter) and radius ratio easily by using the partial derivatives for the uniform model derived in Agol et al. (AJ, 159, 2020, ALFM from now on).

We get the partial derivative of the flux with respect to the radius ratio using the product rule,

where the first term is negligible compared to the second. Because of this, we can make a simplifying assumption that , after which the derivative is simply the partial derivative of the uniform model with respect to the radius ratio derived in ALFM (Eq. 35) multiplied by the normalised average limb darkening,

where is given in Eq. 33 of ALFM, and I’ve simplified the notation a bit for clarity. The angle is calculated by the accurate circle-circle intersection method also described in ALFM, so we get the partial derivative of the model as a by-product of normal model evaluation without any additional work.

The partial derivative of the flux with respect to the impact parameter is

were can again be found from ALFM (Eq. 35). Thus, we get

and using numerical differentials calculated from the array, we get

where is given in Eq. 31 of ALFM, and is again calculated by the circle-circle intersection method.

Both partial derivatives are implemented in PyTransit (`pytransit.models.numba.rrmodel.dfdk`

and `pytransit.models.numba.rrmodel.dfdb`

) and their calculation is extremely fast ( ns for a single point). PyTransit is not yet using the derivatives anywhere, but this may likely change in the future.

## Multicolour photometry and transmission spectroscopy#

Since the weight array needs to be calculated only once for a given , evaluating the model for multiple passbands with different limb darkening coefficients is fast.

```
[15]:
```

```
ldcs = [[0.53, 0.30], [0.12, 0.11]]
ldps = [ld_quadratic(mu, array(ldc)) for ldc in ldcs]
ldas = [dot(w, ldp) for ldp in ldps]
```

```
[16]:
```

```
fig, axs = subplots(2, 1, figsize=(13,6), sharex='all')
for lda in ldas:
axs[0].plot(gs, lda, c='C0')
axs[1].plot(0.5*(gs[1:]+gs[:-1]), diff(lda)/dg, c='C2')
[[ax.axvline((1+s*k)/(1+k), ls=ls) for s,ls in zip([-1, 0, 1], ['--', ':', '--'])] for ax in axs]
setp(axs, xlim=(0, 1.02))
setp(axs[0], ylabel='Mean occluded LD', title=f'Averaged limb darkening for k={k:0.2f}')
setp(axs[1], ylabel='$\delta/\delta g$ mean occluded LD', ylim=(-3.0, 0.2), xlabel='Grazing parameter')
fig.tight_layout()
```

If we can assume that the changes in are small enough that can be considered constant (basically always), we can calculate for the mean but still calculate the intersection areas using the true values.

```
[17]:
```

```
ks = [0.099, 0.101]
ipls = [interp(abs(g), gs, lda) * circle_circle_intersection_area_v(1.0, k, abs(b)) for lda,k in zip(ldas,ks)]
ists = [ldi_quadratic(array(ldc)) for ldc in ldcs]
fig, ax = subplots()
[ax.plot(b, (ist - ipl) / ist) for ist,ipl in zip(ists,ipls)]
setp(ax, xlabel='Planet-star centre distance [$R_\star$]', ylabel='Normalised flux', xlim=(-1.2, 1.2))
fig.tight_layout()
```

## Future optimisations#

The model as it currently stands (PyTransit v1.4) simply discretises into an uniform grid between 0 and 1. However, both the model accuracy and evaluation speed can probably be improved by making the discretisation smarter.

As the first thing, the grazing parameter space can be divided at . The averaged limb darkening is very smooth before this contact point, so it can either be calculated with a lower resolution, or possibly even represented as a low-order polynomial. The discretisation could be done with a higher resolution after the contact point, or maybe mapped so that the areas with larger absolute derivatives are sampled with a higher resolution.

## Acknowledgements#

I would like to thank E. Agol for advising me to look into the calculation of the model derivatives.

©2024 Hannu Parviainen