# OFTI Introduction

by Isabel Angelo and Sarah Blunt (2018)

OFTI (Orbits For The Impatient) is an orbit-generating algorithm designed specifically to handle data covering short fractions of long-period exoplanets (Blunt et al. 2017). Here we go through steps of using OFTI within `orbitize`

!

```
[1]:
```

```
import orbitize
```

## Basic Orbit Generating

Orbits are generated in OFTI through a `Driver`

class within orbitize. Once we have imported this class:

```
[2]:
```

```
import orbitize.driver
```

we can initialize a `Driver`

object specific to our data:

```
[3]:
```

```
myDriver = orbitize.driver.Driver('{}/GJ504.csv'.format(orbitize.DATADIR), # path to data file
'OFTI', # name of algorithm for orbit-fitting
1, # number of secondary bodies in system
1.22, # total mass [M_sun]
56.95, # total parallax of system [mas]
mass_err=0.08, # mass error [M_sun]
plx_err=0.26) # parallax error [mas]
```

Because OFTI is an object class within orbitize, we can assign all of the OFTI attributes onto a variable (`s`

). We can then generate orbits for `s`

using a function called `run_sampler`

, a method of the `OFTI`

class. The `run_sampler`

method takes in the desired number of accepted orbits as an input.

Here we use run OFTI to randomly generate orbits until 1000 are accepted:

```
[4]:
```

```
s = myDriver.sampler
orbits = s.run_sampler(1000)
```

```
```

We have now generated 1000 possible orbits for our system. Here, `orbits`

is a (1000 x 8) array, where each of the 1000 elements corresponds to a single orbit. An orbit is represented by 8 orbital elements.

Here is an example of what an accepted orbit looks like from orbitize:

```
[5]:
```

```
orbits[0]
```

```
[5]:
```

```
array([4.93916907e+01, 8.90197501e-03, 2.63925411e+00, 2.44962990e+00,
9.31508665e-01, 1.20302112e-01, 5.74242058e+01, 1.22728974e+00])
```

To further inspect what each of the 8 elements in your orbit represents, you can view the system.param_idx variable. This is a dictionary that tells you the indices of your orbit that correspond to semi-major axis (a), eccentricity (e), inclination (i), argument of periastron (aop), position angle of nodes (pan), and epoch of periastron passage (epp). The last two indices are the parallax and system mass, and the number following the parameter name indicates the number of the body in the system.

```
[6]:
```

```
s.system.param_idx
```

```
[6]:
```

```
{'sma1': 0,
'ecc1': 1,
'inc1': 2,
'aop1': 3,
'pan1': 4,
'tau1': 5,
'plx': 6,
'mtot': 7}
```

## Plotting

Now that we can generate possible orbits for our system, we want to plot the data to interpret our results. Here we will go through a brief overview on ways to visualize your data within orbitize. For a more detailed guide on data visualization capabilities within orbitize, see the Orbitize plotting tutorial.

### Histogram

One way to visualize our results is through histograms of our computed orbital parameters. Our orbits are outputted from `run_sampler`

as an array of orbits, where each orbit is represented by a set of orbital elements:

```
[7]:
```

```
print(orbits.shape)
orbits[:5]
```

```
(1000, 8)
```

```
[7]:
```

```
array([[4.93916907e+01, 8.90197501e-03, 2.63925411e+00, 2.44962990e+00,
9.31508665e-01, 1.20302112e-01, 5.74242058e+01, 1.22728974e+00],
[4.69543031e+01, 1.31571508e-01, 2.52917998e+00, 1.34963602e+00,
4.18692436e+00, 4.17659289e-01, 5.73207900e+01, 1.23162413e+00],
[5.15848551e+01, 1.18074455e-01, 2.26110475e+00, 2.98346893e+00,
2.31713931e+00, 3.94202277e-03, 5.69191065e+01, 1.15389146e+00],
[3.89558225e+01, 3.71357464e-01, 2.94801131e+00, 3.08542398e+00,
4.77645562e+00, 7.15043369e-01, 5.72827098e+01, 1.05721164e+00],
[8.19463988e+01, 1.45955646e-02, 2.11512811e+00, 4.52064036e+00,
4.44802306e+00, 9.32004660e-01, 5.72429430e+01, 1.35323242e+00]])
```

We can effectively view outputs from `run_sampler`

by creating a histogram of a given orbit element to see its distribution of possible values. Our system.param_idx dictionary is useful here. We can use it to determine the index of a given orbit that corresponds to the orbital element we are interested in:

```
[8]:
```

```
s.system.param_idx
```

```
[8]:
```

```
{'sma1': 0,
'ecc1': 1,
'inc1': 2,
'aop1': 3,
'pan1': 4,
'tau1': 5,
'plx': 6,
'mtot': 7}
```

If we want to plot the distribution of orbital semi-major axes (a) in our generated orbits, we would use the index dictionary `s.system.param_idx`

to index the semi-major axis element from each orbit:

```
[9]:
```

```
sma = [x[s.system.param_idx['sma1']] for x in orbits]
%matplotlib inline
import matplotlib.pyplot as plt
plt.hist(sma, bins=30)
plt.xlabel('orbital semi-major axis [AU]')
plt.ylabel('occurrence')
plt.show()
```

You can use this method to create histograms of any orbital element you are interested in:

```
[10]:
```

```
ecc = [x[s.system.param_idx['ecc1']] for x in orbits]
i = [x[s.system.param_idx['inc1']] for x in orbits]
plt.figure(figsize=(10,3))
plt.subplot(131)
plt.hist(sma, bins=30)
plt.xlabel('orbital semi-major axis [AU]')
plt.ylabel('occurrence')
plt.subplot(132)
plt.hist(ecc, bins=30)
plt.xlabel('eccentricity [0,1]')
plt.ylabel('occurrence')
plt.subplot(133)
plt.hist(i, bins=30)
plt.xlabel('inclination angle [rad]')
plt.ylabel('occurrence')
plt.show()
```

In addition to our `orbits`

array, Orbitize also creates a `Results`

class that contains built-in plotting capabilities for two types of plots: corner plots and orbit plots.

### Corner Plot

After generating the samples, the `run_sampler`

method also creates a `Results`

object that can be accessed with `s.results`

:

```
[11]:
```

```
myResults = s.results
```

We can now create a corner plot using the function `plot_corner`

within the `Results`

class. This function requires an input list of the parameters, in string format, that you wish to include in your corner plot. We can even plot all of the orbital parameters at once! As shown below:

```
[12]:
```

```
corner_figure = myResults.plot_corner(param_list=['sma1', 'ecc1', 'inc1', 'aop1', 'pan1','tau1'])
```

### A Note about Convergence

Those of you with experience looking at corner plots will note that the result here does not look converged (i.e. we need more samples for our results to be statistically significant). Because this is a tutorial, we didn’t want you to have to wait around for a while for the OFTI results to converge.

**It’s safe to say that OFTI should accept a minimum of 10,000 orbit for convergence. For pretty plots to go in publications, we recommend at least 1,000,000 accepted orbits.**

### Orbit Plot

What about if we want to see how the orbits look in the sky? Don’t worry, the `Results`

class has a command for that too! It’s called `plot_orbits`

. We can create a simple orbit plot by running the command as follows:

```
[13]:
```

```
epochs = myDriver.system.data_table['epoch']
orbit_figure = myResults.plot_orbits(
start_mjd=epochs[0] # Minimum MJD for colorbar (here we choose first data epoch)
)
```

```
WARNING: ErfaWarning: ERFA function "d2dtf" yielded 1 of "dubious year (Note 5)" [astropy._erfa.core]
WARNING: ErfaWarning: ERFA function "dtf2d" yielded 1 of "dubious year (Note 6)" [astropy._erfa.core]
WARNING: ErfaWarning: ERFA function "utctai" yielded 1 of "dubious year (Note 3)" [astropy._erfa.core]
WARNING: ErfaWarning: ERFA function "taiutc" yielded 1 of "dubious year (Note 4)" [astropy._erfa.core]
WARNING: ErfaWarning: ERFA function "dtf2d" yielded 8 of "dubious year (Note 6)" [astropy._erfa.core]
```

```
<Figure size 1008x432 with 0 Axes>
```

## Advanced OFTI and API Interaction

We’ve seen how the `run_sampler`

command is the fastest way to generate orbits within OFTI. For users interested in what’s going on under-the-hood, this part of the tutorial takes us each step of `run_sampler`

. Understanding the intermediate stages of orbit-fitting can allow for more customization that goes beyond Orbitize’s default parameters.

We begin again by intializing a `sampler`

object on which we can run OFTI:

```
[14]:
```

```
myDriver = orbitize.driver.Driver('{}/GJ504.csv'.format(orbitize.DATADIR), # path to data file
'OFTI', # name of algorith for orbit-fitting
1, # number of secondary bodies in system
1.22, # total mass [M_sun]
56.95, # total parallax of system [mas]
mass_err=0.08, # mass error [M_sun]
plx_err=0.26) # parallax error [mas]
```

```
[15]:
```

```
s = myDriver.sampler
```

In orbitize, the first thing that OFTI does is prepare an initial set of possible orbits for our object through a function called `prepare_samples`

, which takes in the number of orbits to generate as an input. For example, we can generate 100,000 orbits as follows:

```
[16]:
```

```
samples = s.prepare_samples(100000)
```

Here, `samples`

is an array of randomly generated orbits that have been scaled-and-rotated to fit our astrometric observations. The first and second dimension of this array are the number of orbital elements and total orbits generated, respectively. In other words, each element in `samples`

represents the value of a particular orbital element for each generated orbit:

```
[17]:
```

```
print('samples: ', samples.shape)
print('first element of samples: ', samples[0].shape)
```

```
samples: (8, 100000)
first element of samples: (100000,)
```

Once our initial set of orbits is generated, the orbits are vetted for likelihood in a function called `reject`

. This function computes the probability of an orbit based on its associated chi squared. It then rejects orbits with lower likelihoods and accepts the orbits that are more probable. The output of this function is an array of possible orbits for our input system.

```
[18]:
```

```
orbits, lnlikes = s.reject(samples)
```

Our `orbits`

array represents the final orbits that are output by OFTI. Each element in this array contains the 8 orbital elements that are computed by orbitize:

```
[19]:
```

```
orbits.shape
```

```
[19]:
```

```
(1, 8)
```

We can synthesize this sequence with the `run_sampler()`

command, which runs through the steps above until the input number of orbits has been accepted. Additionally, we can specify the number of orbits generated by `prepare_samples`

each time the sequence is initiated with an argument called `num_samples`

. Higher values for `num_samples`

will output more accepted orbits, but may take longer to run since all initially prepared orbits will be run through the rejection step.

```
[20]:
```

```
orbits = s.run_sampler(100, num_samples=1000)
```

```
```

## Saving and Loading Results

Finally, we can save our generated orbits in a file that can be easily read for future use and analysis. Here we will walk through the steps of saving a set of orbits to a file in hdf5 format. The easiest way to do this is using `orbitize.Results.save_results()`

:

```
[21]:
```

```
s.results.save_results('orbits.hdf5')
```

Now when you are ready to use your orbits data, it is easily accessible through the file we’ve created. One way to do this is to load the data into a new `results`

object; in this way you can make use of the functions that we learn before, like `plot_corner`

and `plot_orbits`

. To do this, use the `results`

module:

```
[22]:
```

```
import orbitize.results
loaded_results = orbitize.results.Results() # create a blank results object to load the data
loaded_results.load_results('orbits.hdf5')
```

Alternatively, you can directly access the saved data using the `h5py`

module:

```
[23]:
```

```
import h5py
f = h5py.File('orbits.hdf5', 'r')
orbits = f['post']
print('orbits array dimensions: ', orbits.shape)
print('orbital elements for first orbit: ', orbits[0])
f.close()
```

```
orbits array dimensions: (100, 8)
orbital elements for first orbit: [42.56737306 0.18520168 2.50497349 1.46225095 3.29419715 0.60649612
57.26589357 1.1478072 ]
```

And now we can easily work with the saved orbits that were generated by `orbitize!`

Find out more about generating orbits in `orbitize!`

with tutorials here.