diff --git a/manuals/nbody.pdf b/manuals/nbody.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..dd0f5a60c39a7659e839c7233cf8f430108ff438
Binary files /dev/null and b/manuals/nbody.pdf differ
diff --git a/project_description.md b/project_description.md
index dfa2a6ca61b3fe9f8e80b280c7f90cb3740ee6fd..396d03fe2a048e02fa3a42b2d882298ade9d38a8 100644
--- a/project_description.md
+++ b/project_description.md
@@ -34,6 +34,7 @@ Start of july - End of August (exact dates YTBD)
 * http://adsabs.harvard.edu/abs/2013MNRAS.432.2779B
 * http://adsabs.harvard.edu/abs/2019arXiv190207718B
 * [Website containing some lectures on N-body integration](http://silkroad.bao.ac.cn/web/index.php/seminars/lectures)
+* https://arxiv.org/abs/1711.09100
 
 ## Some watching material:
 * https://www.cita.utoronto.ca/presentation-archive/?talk_id=745
diff --git a/python_scripts_for_nbody6/ascii_readout_nbody6pp_example.py b/python_scripts_for_nbody6/ascii_readout_nbody6pp_example.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8f8eba9124abc6e83fedce9efad6bca20ebb37a
--- /dev/null
+++ b/python_scripts_for_nbody6/ascii_readout_nbody6pp_example.py
@@ -0,0 +1,24 @@
+# Ascii readout script for nbody6++ files. Leading it into something that caries the dataframe
+# Will prove as an example to other likewise scripts
+
+import os
+import pandas as pd
+
+# TODO: put this into a function or a class.
+
+# Point to the source directory.
+source_dir = '/vol/ph/astro_code/dhendriks/work_dir/nbody6'
+
+# Create the header line labels and data line labels
+header_line_labels = ['NS', 'TIME[Myr]']
+N_LABEL = ['TIME[NB]', 'I', 'NAME', 'K*', 'RI[RC]', 'M[M*]', 'Log10(L[L*])', 'LOG10(RS[R*])' , 'LOG10(Teff[K]']
+
+# Go through file
+with open(os.path.join(source_dir, 'sev.83_0'), 'r') as f:
+    header_1_data = f.readline().split()
+
+    data = []
+    for line in f:
+        data.append(line.split())
+
+df = pd.DataFrame(data, columns=N_LABEL)
\ No newline at end of file
diff --git a/python_scripts_for_nbody6/out.hdf5 b/python_scripts_for_nbody6/out.hdf5
new file mode 100644
index 0000000000000000000000000000000000000000..82daee09f5d9bab4d4ef6cf1c24d6067e7457f8a
Binary files /dev/null and b/python_scripts_for_nbody6/out.hdf5 differ
diff --git a/python_scripts_for_nbody6/readout_script.py b/python_scripts_for_nbody6/readout_script.py
index 8ba12d534861b30958ac36d07d9b93d46adac6f2..f963163c7c3dc63abef609db9f88791b59e3264b 100644
--- a/python_scripts_for_nbody6/readout_script.py
+++ b/python_scripts_for_nbody6/readout_script.py
@@ -381,5 +381,5 @@ def main():
     f5.close()             
 
         
-if __name__ == '__main__':
-    main() 
\ No newline at end of file
+# if __name__ == '__main__':
+#     main() 
\ No newline at end of file
diff --git a/python_scripts_for_nbody6/readout_script_nbody6pp_binaries.py b/python_scripts_for_nbody6/readout_script_nbody6pp_binaries.py
new file mode 100644
index 0000000000000000000000000000000000000000..6c5361502988b5c6adc5003278f7b02b2e23950a
--- /dev/null
+++ b/python_scripts_for_nbody6/readout_script_nbody6pp_binaries.py
@@ -0,0 +1,396 @@
+# Initially written by Mark Gieles. 
+# Modified by David Hendriks
+
+# Code to convert NBODY6 OUT3 file to HDF5 file format
+import numpy as np
+import struct
+import h5py
+import sys
+import argparse
+
+# TODO: Fix cuda
+# import pycuda.autoinit
+# import pycuda.driver as drv
+# from pycuda.compiler import SourceModule
+
+import matplotlib.pyplot as plt
+import os
+import argparse
+
+
+# Script for nbody6++ binary output files to hdf5.
+def read_nbody6pp(f5, input_filename, sev):
+    """
+        Read in the nbody6 output file(s).
+
+        Keyword arguments:
+        f5 -- The hdf5 object where the data will be written to
+        input_filename -- The OUT3 file generated by nbody6
+        sev -- flag to include output of stellar evolution
+    """
+
+    # open NBODY6 file
+    with open(input_filename, "rb") as f: 
+       # start looping over time and reading the nbody6 data. 
+        # itime tracks the number of timesteps outputted.
+        itime = -1
+        
+        # runs on some computers seem to generate extra bytes after the record length.
+        # 'extra' and its associated reads below take care of that.
+        extra = 0 
+        
+        # Do stuff with byte.
+        byte = f.read(4) # begin header block
+        model = 0
+
+
+        # Read out first header:
+        blocksize1 = struct.unpack('i', byte)[0] # header block size
+        print(blocksize1)
+        if extra == 1:
+            f.read(4)
+        itime = itime + 1
+        # read the header for this time
+        ntot = struct.unpack('i',f.read(4))[0] #ntot
+        if ntot < 10 and itime == 0:
+            # check for a nonsensical ntot value, 
+            # which is symptomatic of bizarre extra record padding that occurs on some
+            # systems. if needed, include extra reads to deal with these.
+            extra = 1
+            ntot = struct.unpack('i',f.read(4))[0]
+            print('extra! '+str(extra))
+            
+        model = struct.unpack('i',f.read(4))[0] #model
+        nrun =  struct.unpack('i',f.read(4))[0] #nrun
+        nk = struct.unpack('i',f.read(4))[0]    #nk 
+        print(f'ntot:{ntot}, model:{model}, nrun:{nrun}, nk:{nk}')
+
+        #print(struct.unpack('i',f.read(4)))
+        # Readout second header:
+        #byte = f.read(4) # begin header block
+        #blocksize2 = struct.unpack('i', byte)[0] # header block size
+        #print(blocksize2)
+        time = struct.unpack('i', f.read(4))[0]
+        npairs = struct.unpack('i', f.read(4))[0]
+        ding2 = struct.unpack('i', f.read(4))[0]
+        ding3 = struct.unpack('i', f.read(4))[0]
+        print(f'time:{time} npairs {npairs} ding2{ding2} ding3{ding3}')
+
+
+
+
+
+
+        # print(blocksize1)
+        # if extra == 1:
+        #     f.read(4)
+        # itime = itime + 1
+        # # read the header for this time
+        # ntot = struct.unpack('i',f.read(4))[0] #ntot
+        # if ntot < 10 and itime == 0:
+        #     # check for a nonsensical ntot value, 
+        #     # which is symptomatic of bizarre extra record padding that occurs on some
+        #     # systems. if needed, include extra reads to deal with these.
+        #     extra = 1
+        #     ntot = struct.unpack('i',f.read(4))[0]
+        #     print('extra! '+str(extra))
+            
+        # model = struct.unpack('i',f.read(4))[0] #model
+        # nrun =  struct.unpack('i',f.read(4))[0] #nrun
+        # nk = struct.unpack('i',f.read(4))[0]    #nk
+
+
+
+
+
+
+
+
+        quit()
+        # # while byte:
+        #     blocksize1 = struct.unpack('i', byte)[0] # header block size
+        #     print(blocksize1)
+        #     if extra == 1:
+        #         f.read(4)
+        #     itime = itime + 1
+        #     # read the header for this time
+        #     ntot = struct.unpack('i',f.read(4))[0] #ntot
+        #     if ntot < 10 and itime == 0:
+        #         # check for a nonsensical ntot value, 
+        #         # which is symptomatic of bizarre extra record padding that occurs on some
+        #         # systems. if needed, include extra reads to deal with these.
+        #         extra = 1
+        #         ntot = struct.unpack('i',f.read(4))[0]
+        #         print('extra! '+str(extra))
+                
+        #     model = struct.unpack('i',f.read(4))[0] #model
+        #     nrun =  struct.unpack('i',f.read(4))[0] #nrun
+        #     nk = struct.unpack('i',f.read(4))[0]    #nk
+
+
+
+
+def read_nbody6(f5, file, sev): 
+    """Read in the nbody6 output file(s).
+    
+       Keyword arguments:
+       filename -- the OUT3 file generated by nbody6
+    
+    """
+    # open NBODY6 file
+    f = open(file,'r')
+
+    # start looping over time and reading the nbody6 data. 
+    # itime tracks the number of timesteps outputted.
+    itime = -1
+    
+    # runs on some computers seem to generate extra bytes after the record length.
+    # 'extra' and its associated reads below take care of that.
+    extra = 0 
+    
+    byte = f.read(4) # begin header block
+    model = 0
+
+    while byte != "":
+        blocksize1 = struct.unpack('i',byte)[0] # header block size
+        if extra == 1:
+            f.read(4)
+        itime = itime + 1
+        # read the header for this time
+        ntot = struct.unpack('i',f.read(4))[0] #ntot
+        if ntot < 10 and itime == 0:
+            # check for a nonsensical ntot value, 
+            # which is symptomatic of bizarre extra record padding that occurs on some
+            # systems. if needed, include extra reads to deal with these.
+            extra = 1
+            ntot = struct.unpack('i',f.read(4))[0]
+            print('extra! '+str(extra))
+            
+        model = struct.unpack('i',f.read(4))[0] #model
+        nrun =  struct.unpack('i',f.read(4))[0] #nrun
+        nk = struct.unpack('i',f.read(4))[0]    #nk
+
+        blocksize2 = struct.unpack('i',f.read(4))[0] #end header block size
+        if extra == 1:
+            f.read(4)
+            
+        # check for consistency
+        if blocksize1 != blocksize2:
+            print('header trouble! t = '+str(itime)+' '+str(blocksize1)+' '+str(blocksize2))
+            sys.exit(1)
+                
+        # now read the star data
+        blocksize1 = struct.unpack('i',f.read(4))[0] #begin data block size
+        if extra == 1:
+            f.read(4)
+                        
+        alist = []
+        for i in range(nk):
+            alist.append(struct.unpack('f',f.read(4))[0]) #Sverre's 'as'
+        alist.append(ntot) 
+
+        stardata = np.zeros((ntot, 11))
+        datalist=[] # masses
+        for i in range(ntot):
+            datalist.append(struct.unpack('f',f.read(4))[0])
+
+        stardata[:,0] = datalist
+         
+        datalistx=[] # positions
+        datalisty=[]
+        datalistz=[]
+        for i in range(ntot):           
+            datalistx.append(struct.unpack('f',f.read(4))[0])
+            datalisty.append(struct.unpack('f',f.read(4))[0])
+            datalistz.append(struct.unpack('f',f.read(4))[0]) 
+
+        stardata[:,1] = datalistx
+        stardata[:,2] = datalisty
+        stardata[:,3] = datalistz
+        
+        datalistx=[] # velocities
+        datalisty=[]
+        datalistz=[]
+
+        for i in range(ntot):           
+            datalistx.append(struct.unpack('f',f.read(4))[0])
+            datalisty.append(struct.unpack('f',f.read(4))[0])
+            datalistz.append(struct.unpack('f',f.read(4))[0])
+
+        stardata[:,4] = datalistx
+        stardata[:,5] = datalisty
+        stardata[:,6] = datalistz    
+            
+        datalist=[] # names
+        for i in range(ntot):
+            datalist.append(struct.unpack('i',f.read(4))[0])
+        stardata[:,7] = datalist
+
+        if (sev):
+            datalist=[] # kstar
+            for i in range(ntot):
+                datalist.append(struct.unpack('i',f.read(4))[0])
+            stardata[:,8] = datalist
+
+            datalist=[] # lum
+            for i in range(ntot):
+                datalist.append(struct.unpack('f',f.read(4))[0])
+            stardata[:,9] = datalist
+
+            datalist=[] # teff
+            for i in range(ntot):
+                datalist.append(struct.unpack('f',f.read(4))[0])
+            stardata[:,10] = datalist
+ 
+        blocksize2 = struct.unpack('i',f.read(4))[0] #end data block size
+        if extra == 1:
+            f.read(4)
+
+        # check for consistency
+        if blocksize1 != blocksize2:
+            print('star data trouble! '+str(itime))
+            sys.exit(3)
+                
+        splitarray = np.hsplit(stardata, [1,4,7,8,9,10])
+
+        # Output snapshot in hdf5 file
+        h5output(f5, alist, splitarray, model-1, sev)   
+        
+        # next header blocksize for loop control
+        byte = f.read(4) # begin header block
+                          
+    # close OUT3 and other files if present
+    f.close()
+
+
+def h5output(f5, alist, splitarray, model, sev):
+    """output the nbody6 snapshot data in hdf5 format. Plots the first snapshot
+       just to make sure it's sensible looking.
+    
+       Keyword arguments:
+       outputfilename -- the name of the hdf5 file to create
+       alist -- header data from the nbody6 data. this is sverre's AS list.
+       splitarray -- the data that has been parsed from the nbody6 files
+    """
+
+    # Create model group
+    modgrp = f5.create_group("%05d"%model)
+    f5.attrs["nmod"]=model+1        # Number of models, updated everytime a snapshot is written
+
+    # create groups for the "stars" and "cluster" data 
+    stargrp    = f5.create_group("%05d/stars"%model)
+    clustergrp = f5.create_group("%05d/cluster"%model)
+
+    # Compute bound particles
+    nbound,mbound,vcom,bound,kinbound,potbound = compute_energies(splitarray[0][:,0],splitarray[1],splitarray[2], np.array(alist[6:9]),alist[1])
+
+    
+    # Assume the following for AS list:
+    # AS(1) = TTOT
+    # AS(2) = NBIN
+    # AS(3) = RBAR 
+    # AS(4) = ZMBAR
+    # AS(5) = TSTAR
+    # AS(6) = VSTAR
+    # AS(7:9) = RDENS
+    # AS(10:12) = RG
+    # AS(13:15) = VG
+    # AS(16) = RC
+    # AS(17) = NC
+
+
+    # Populate the groups with data
+    massdset  = stargrp.create_dataset('mass',  data =  alist[3]*splitarray[0][:,0],dtype='float32')
+    posdset   = stargrp.create_dataset('pos',   data =  alist[2]*splitarray[1],dtype='float32')
+    veldset   = stargrp.create_dataset('vel',   data =  alist[5]*splitarray[2],dtype='float32')
+    namedset  = stargrp.create_dataset('id',    data = splitarray[3][:,0],dtype='i') 
+    if (sev):
+        kstardset = stargrp.create_dataset('kstar', data = splitarray[4][:,0],dtype='i') 
+        lumdset   = stargrp.create_dataset('lum',   data = splitarray[5][:,0],dtype='float32') 
+        teffdset  = stargrp.create_dataset('teff',  data = splitarray[6][:,0],dtype='float32') 
+    bounddset = stargrp.create_dataset('bound', data = bound,dtype='bool') 
+
+    timedset   = clustergrp.create_dataset('age',  data = alist[0]*alist[4])
+    nbindset   = clustergrp.create_dataset('nbin', data = alist[1],dtype='i')
+    rdensdset  = clustergrp.create_dataset('rdens',data = np.array(alist[6:9])*alist[2]) 
+    vcomdset   = clustergrp.create_dataset('vcom' ,data = np.array(vcom)*alist[5]) 
+    rgdset     = clustergrp.create_dataset('rg',   data = np.array(alist[9:12])) 
+    vgdset     = clustergrp.create_dataset('vg',   data = np.array(alist[12:15])) 
+    rcdset     = clustergrp.create_dataset('rcore',data = alist[2]*alist[15]) 
+
+    # Compute half-mass radii
+    rh = compute_rh(alist[3]*splitarray[0][:,0],alist[2]*splitarray[1], np.array(alist[6:9])*alist[2],splitarray[4][:,0],bound, sev)
+    rhndset    = clustergrp.create_dataset('rhn',   data = rh[0],dtype='float32') 
+    rhn300dset = clustergrp.create_dataset('rhn300',data = rh[1],dtype='float32') 
+    rhmdset    = clustergrp.create_dataset('rhm',   data = rh[2],dtype='float32') 
+
+    ncdset     = clustergrp.create_dataset('ncore', data = alist[16],dtype='i') 
+
+    nbdset     = clustergrp.create_dataset('nbound',data = nbound,dtype='i') 
+    mbdset     = clustergrp.create_dataset('mbound',data = mbound*alist[3],dtype='float32') 
+    kinbdset   = clustergrp.create_dataset('kinbound',data = kinbound,dtype='float32') 
+    potbdset   = clustergrp.create_dataset('potbound',data = potbound,dtype='float32') 
+
+    print(" T = %10.3f [NBODY] %10.3f [MYR]; M(bound) = %10.3f, rhn = %10.3f; rhm = %10.3f"%(alist[0],alist[0]*alist[4],mbound, rh[0], rh[2]))
+
+
+
+    # Units information as attributes 
+    massdset.attrs["unit"] = "[Msun]"
+    posdset.attrs["unit"]  = "[pc] in guide centre reference frame"               
+    veldset.attrs["unit"]  = "[km/s] in guide centre reference frame"  
+
+    if (sev):
+        lumdset.attrs["unit"]  = "[log10 Lsun]" 
+        teffdset.attrs["unit"] = "[log10 Teff]" 
+
+    timedset.attrs["unit"]  = "[Myr]"     
+    rdensdset.attrs["unit"] = "[pc] in guide centre reference frame"     
+    vcomdset.attrs["unit"]  = "[km/s] in guide centre reference frame"     
+
+    rgdset.attrs["unit"] = "[kpc] in Galactic reference frame"     
+    vgdset.attrs["unit"] = "[km/s] in Galactic reference frame"     
+
+    rcdset.attrs["unit"] = "[pc]"     
+    mbdset.attrs["unit"] = "[Msun]"     
+
+    return()
+        
+        
+def main():
+    parser = argparse.ArgumentParser(description='Converts NBODY6++ output file to HDF5 format')
+    parser.add_argument('-f', dest='files', action="store", default='OUT3', help='NBODY6 files to convert [default="OUT3"]. delimited list input.')
+    parser.add_argument('-s', dest='sev', action="store_true", default=False, help='activate reading stellar evolution data [default=False]')   
+
+    args = parser.parse_args()
+
+    # open up the hdf5 file
+    f5 = h5py.File('out.hdf5', 'w')
+
+    # Loop over all the given binary
+    for file in args.files.split():
+        print(f"Converting {file}")
+        read_nbody6pp(f5, file, args.sev)
+
+    f5.close()
+
+
+if __name__=='__main__':
+    main()
+
+# def main():
+#     parser = argparse.ArgumentParser(description='Converts OUT3 data from NBODY6 to HDF5 format')
+#     parser.add_argument('-f',dest='files',action="store",default='OUT3',help='NBODY6 files to convert [default="OUT3"]')
+#     parser.add_argument('-s', dest='sev', action="store_true", default=False, help='activate reading stellar evolution data [default=False]')
+
+#     args = parser.parse_args()
+
+#     # open up the hdf5 file!
+#     f5 = h5py.File('out.hdf5','w')
+
+#     # Loop over all NBODY6 OUT3 files
+#     for file in args.files.split():
+#         print " File = ",file
+#         read_nbody6(f5, file, args.sev)
+
+#     f5.close()
\ No newline at end of file
diff --git a/python_scripts_for_nbody6/test_input.py b/python_scripts_for_nbody6/test_input.py
new file mode 100644
index 0000000000000000000000000000000000000000..644854176795232fcc700a9f39f4fa6c36473dcc
--- /dev/null
+++ b/python_scripts_for_nbody6/test_input.py
@@ -0,0 +1,28 @@
+from scipy.io import FortranFile
+import struct
+
+fname = '/vol/ph/astro_code/dhendriks/work_dir/nbody6/conf.3_0'
+f = FortranFile('/vol/ph/astro_code/dhendriks/work_dir/nbody6/conf.3_0', 'r')
+
+f.read_ints()
+
+# a = f.read_reals(dtype=float)
+# print(a)
+
+# import numpy as np
+# with open(fname,'rb') as f:
+#     for k in range(4):
+#         data = np.fromfile(f, dtype=np.float32, count = 2*3)
+#         print(np.reshape(data,(2,3)))
+
+# with open(fname, 'rb') as f:
+#     while True:
+#         raw = f.read(4)
+#         if len(raw)!=4:
+#             break # ignore the incomplete "record" if any
+#         record = struct.unpack("I", raw )
+#         print(record)
+
+# with open(fname, 'rb') as f:
+#     for line in f:
+#         print(f.readline())
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..6973aea407e29071af5fbeccdc49e3eb73a23c3d
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,22 @@
+appdirs==1.4.3
+atomicwrites==1.3.0
+attrs==19.1.0
+cycler==0.10.0
+decorator==4.4.0
+h5py==2.9.0
+kiwisolver==1.1.0
+Mako==1.0.10
+MarkupSafe==1.1.1
+matplotlib==3.1.0
+more-itertools==7.0.0
+numpy==1.16.3
+pandas==0.24.2
+pluggy==0.11.0
+py==1.8.0
+pyparsing==2.4.0
+pytest==4.5.0
+python-dateutil==2.8.0
+pytools==2019.1.1
+pytz==2019.1
+six==1.12.0
+wcwidth==0.1.7