<jupyter_start><jupyter_text>DarkCapPy TemplateAuthor: <NAME>Email : <EMAIL>Date: 8/7/18  Introduction This package is set up to perform "out-of-the-box" calculations. Some calculations, in particular the reach plot for IceCube, may take hours of run time. If the time intensive calculations are interrupted, they must be restarted from the beginning. This notebook is a guide to using the package to perform time-intensive parameter scans. This package is optimized to perform parameter scans over mediator mass $m_{A'}$ and kinetic mixing $\varepsilon$ for a fixed dark matter mass $m_X$.This notebook utilizes [pandas](https://pandas.pydata.org/) to read and write intermediate results to external comma-separated-value (csv) files. These files allow longer calculations to be interrupted and resumed, even if the Jupyter kernel is restarted.This notebook generates 2 csv files, which we refer to as $\texttt{Sommerfeld.csv}$ and $\texttt{Signal.csv}$. It requires input from a third csv file which we refer to as $\texttt{Branch.csv}$.This notebook is divided into four main sections titled Package Test, Sommerfeld, Equilibrium Plots, and IceCube Signal. We provide a brief overview of each section below.  Package Test This section tests the package installation by calculating the number of signal events expected at IceCube for a fixxed set of parameters - dark matter mass, mediator mass, mediator coupling, and IceCube observation time. It outputs all intermediate results. This section fully demonstrates how to use DarkCapPy for calculations of the "dark Earthshine" scenario of a single set of parameters._Estimated Run Time_: 4 Minutes  Sommerfeld This section creates and populates $\texttt{Sommerfeld.csv}$ with 1. A single value of the dark matter mass $m_X$ in GeV, 2. A list of mediator masses $m_{A'}$ in GeV3. A list of corresponding thermally averaged Sommerfeld enhancements $\langle S_S(m_{A'}) \rangle$ 4. The part of the capture rate which only depends on $m_X$ and $\alpha_X$ $\kappa_0$ in units of GeV$^5$, $\kappa_0$.The user is prompted to input a value of $m_X$ when $\texttt{Sommerfeld.csv}$ is created. A list of mediator masses, $m_{A'}$ , are used to generate corresponding values of the thermally averaged Sommerfeld effect, $\langle S_S \rangle$. The quantity $\kappa_0$ depends only on $m_X$ and $\alpha_X$. When $m_X$ is fixed, $\kappa_0$ is uniquely determined by fixing $\alpha_X$ to give the correct dark matter abundance from thermal freeze out. The $\texttt{Sommerfeld.csv}$ file generated from this section is used as input for all later parts of this notebook._Estimated Run Time_: 6 Minutes  Equilibrium Plots This section reads in a completed $\texttt{Sommerfeld.csv}$ and generates plots of the equilibrium time in $(\varepsilon, m_{A'})$ space. The equilibrium time $\tau$ is the time it takes the captured dark matter population to reach a stable value. By default, we plot contour values of $\tau/\tau_\oplus = \{ 10^{-4} ,10^{-2}, 10^0, 10^2, 10^4 \}$. Where $\tau_\oplus = 4.5$ GYr is the age of the Earth._Estimated Run Time_:  IceCube Signal This section creates and populates $\texttt{Signal.csv}$ file that stores values pertaining to the number of signal events at IceCube. This section is the most time consuming and computation intensive section. This is because the typial resolution of these plots requires $\mathcal{O}(10^4)$ calculations.This section reads in (1) $\texttt{Sommerfeld.csv}$ and (2) $\texttt{Branch.csv}$ and outputs $\texttt{Signal.csv}$ which stores the range of mediator masses mass. When the calculations are complete, this section also reads in a completed $\texttt{Signal.csv}$ file and plots order-of-magnitude contours of the number of signal events at IceCube $N_\text{sig}$ against mediator mass $m_{A'}$ on the horizontal axis and kinetic mixing parameter $\varepsilon$ on the vertical axis. By default, the contour values are $N_\text{sig} = \{ 1, 10, 100, 1000 \}$.As a benchmark, the calculation rate on a modern laptop is about 140 points / minute.  Imports and Initialization In the following, we import the required libraries and define the paths to each data file.<jupyter_code>import numpy as np
import pandas as pd
from scipy.interpolate import interpolate
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.style
mpl.rcParams.update(mpl.rcParamsDefault)
%matplotlib inline
from datetime import datetime

from DarkCapPy import *
import DarkCapPy.DarkPhoton as DP

###################
# Define File Paths
###################
# These paths are specific to the preset folders in Temp;ate_Caluclation
def sommerfeldPath(file):
    path = 'Sommerfeld/' + file
    return path

def branchPath(file):
    path = 'Branching_Ratio/' + file
    return path

def signalPath(file):
    path = 'Signal/' + file
    return path
    
def signalBackupPath(file):
    path = 'Signal/Signal_Backups/' + file
    return path

print ('Complete')<jupyter_output><empty_output><jupyter_text>Package Test  (Single Parameter Point) This section is a self-contained test of `DarkCapPy`. It outputs the inermediate results and total number of singla events for a set of model parameters.We initliaize our parameters to match the values chosen for Table 1 of [arXiv:1509.07525](https://arxiv.org/abs/1509.07525) and generate the corresponding $N_\text{sig}$ point in $(m_{A'}, \varepsilon)$ space, taking the effective area of IceCube to be $A_\text{eff} = 1$ km$^2$ and the observation time to be $T = 10$ yr. All masses and energies are assumed to be in units of GeV. This cell takes about 6 minutes to run.Input parameters:    mx       = 1000    ma       = 1    epsilon  = 1e-8    alpha    = 1/137    alphax   = 0.035    tauCross = DP.tauCross = 1.41912e+17The anticipate output is:    Capture_1            : 110079473.7575132    Kappa_0              : 3.1452272427969017e+25    Capture_2            : 110082953.49789158    Therm Avg Sommerfeld : 238.71863691039448    Sigma V              : 3.8484490764205524e-09    Annihilation         : 1.8311290915841913e-46    EQ Time              : 7.04348098392085e+18    Gamma_ann            : 22336.873085842682    Decay Length         : 87324480.0    Epsilon_decay        : 5.100088114082073e-07    N_signal             : 0.012594031678514129<jupyter_code>mx = 1000
ma = 1
epsilon = 1e-8
alpha = 1/137
alphax = 0.035
tauCross = DP.tauCross

cap1 = DP.cCap(mx,ma,epsilon,alpha,alphax)
kappa0 = DP.kappa_0(mx,alpha)
cap2 = DP.cCapQuick(mx,ma,epsilon,alphax,kappa0)


sommerfeld = DP.thermAvgSommerfeld(mx,ma,alphax)
sigma = DP.sigmaVtree(mx,ma,alphax)
ann = DP.cAnn(mx,sigma,sommerfeld)

tau = DP.tau(cap1,ann)

gammaAnn = DP.gammaAnn(cap1,ann)
L = DP.decayLength(mx,ma,epsilon,1)
Edecay = DP.epsilonDecay(L)

signal = DP.iceCubeSignal(gammaAnn,Edecay,DP.yr2s(10))


print ('Capture_1            :', cap1)
print ('Kappa_0              :', kappa0)
print ('Capture_2            :', cap2)
print ('Therm Avg Sommerfeld :', sommerfeld)
print ('Sigma V              :', sigma)
print ('Annihilation         :', ann)
print ('EQ Time              :', tau)
print ('Gamma_ann            :', gammaAnn)
print ('Decay Length         :', L)
print ('Epsilon_decay        :', Edecay)
print ('N_signal             :', signal)<jupyter_output><empty_output><jupyter_text>Sommerfeld This section creates an $\texttt{Sommerfeld.csv}$ that contains an user-defined number of $m_A$ and $\langle S_S \rangle$ values. $\texttt{Sommerfeld.csv}$ has the following column headers:    Counter, mX[GeV], mA[GeV], ThermAvgSommerfeld, Kappa0[GeV5]- `Counter`: Stores the current row of the csv. This will be used as a loop index later.- `m_X[GeV]`: Dark matter mass in GeV specified at the prompt in [Create Sommerfeld.csv](Create-Sommerfeld.csv).- `m_A[GeV]`: A list of numbers ranging from $0.01$ GeV to $10$ GeV with arbitrary spacing. Since these will each correspond to a single values of $\langle S_S \rangle$ to be interpolated later, we suggest using logarithmic spacing with a high density `num_Somm ~ 2500`.- `ThermAvgSommerfeld`: The corresponding Sommerfeld enhancement $\langle S_S \rangle$ for a given $m_{A'}$ value.- `Kappa0[GeV5]`: The part of the capture rate which only depends on $m_X$ and $\alpha$ after the small recoil energy approximation. Once $m_X$ is fixed, this value is uniquely determined.  Define Sommerfeld Point Resolution This cell defines the variable `num_Somm`, the resolution of the Sommerfeld enhancement. This number should be ~2500 because it is used to create an interpolation of the Sommerfeld enhancement $\langle S_S \rangle$. This interpolation, as opposed to the function `DarkCapPy.DarkPhoton.thermAvgSomm`, is called to calculate the number of signal events.<jupyter_code>num_Somm = 2500<jupyter_output><empty_output><jupyter_text>Initialize Paramter Arrays We assume the following range for $m_{A'}$:$$ 0.01 \ \text{GeV} \leq m_{A'} \leq 10 \ \text{GeV} $$This will use this range for the mediator mass for the rest of the calculations. The lower bound is fixed by requiring the dark photon to decay into electrons. The upper bound is adjusted to allow the dark photons to be relativistic when they are produced from dark matter annihilations.<jupyter_code>m_ALow = 10**-2
m_AHigh= 10

mALogRange = np.logspace(np.log10(m_ALow), np.log10(m_AHigh), num_Somm, base = 10)
mAArray = []

counter = 0
for mATemp in mALogRange:
                 #( Counter, m_X[GeV], m_A[Gev], ThermAvgSommerfeld, Kappa0[GeV5])
    mAArray.append([counter, ' '     , mATemp  , 'None'            , ' '])
    counter += 1

print ('Complete')<jupyter_output><empty_output><jupyter_text>Create Sommerfeld.csv Filenames should follow a consistent convention since they will be used as input later on. A good convention for file names is:    Sommerfeld.csvincluding the ".csv" extension at the prompt.For example, if $m_X = 100$ GeV, a good filename would be something like:    100GeVSommerfeld.csv<jupyter_code>masterSommerfeldDataFrame = \
    pd.DataFrame(mAArray,\
                 columns = \
                 ['Counter','mX[GeV]', 'mA[GeV]', 'ThermAvgSommerfeld', 'Kappa0[GeV5]'])

sommFileName = input('Sommerfeld Filename: ')
if (sommFileName == ''):
    print ('Setting default filename')
    sommFileName = 'Sommerfeld_DEFAULT.csv'
writeFile = sommerfeldPath(sommFileName)

assert (sommFileName[-4:] == '.csv'), 'File must end with ".csv"'


masterSommerfeldDataFrame.to_csv(writeFile, index=False)
dataFrame = pd.read_csv(writeFile , sep = ',')
m_XValue = float(input('m_X [GeV]: '))
kappa0Value = 'None'


dataFrame.at[0, 'mX[GeV]'] = m_XValue
dataFrame.at[0, 'Kappa0[GeV5]'] = kappa0Value

dataFrame.to_csv(writeFile, index=False)


print ('Complete')<jupyter_output><empty_output><jupyter_text>Populating Sommerfeld.csv This cell calculates:1. `Kappa0[GeV5]` = $\kappa_0$, the part of the capture rate which is constant in $(m_{A'},\varepsilon)$ space.2. `ThermAvgSommerfeld` = $\langle S_S(m_{A'}) \rangle$, the thermally-averaged Sommerfeld enhancement for each corresponding value of $m_A$.After each value of $\langle S_S \rangle$ is calculated, the $\texttt{Sommerfeld.csv}$ is updated and overwritten. This cell should take about 4 minutes.<jupyter_code>sommFileName = input('Sommerfeld Filename: ')
sommWriteFile = sommerfeldPath(sommFileName)
sommDataIn = pd.read_csv(sommWriteFile,sep = ',')

# Define the loop range
looprange = len(sommDataIn['Counter'])

# Read in m_X value
Filem_X = float(sommDataIn.at[0,'mX[GeV]'])
    
##################################
# Calculate Kappa0
##################################
testKappa0 = sommDataIn.at[0,'Kappa0[GeV5]']

if (testKappa0 == 'None'):
    print ('Calculating Kappa0...')
    
    kappa0Value = float(DP.kappa_0(Filem_X, 1./137))
    
    sommDataIn.at[0, 'Kappa0[GeV5]'] = kappa0Value
    sommDataIn.to_csv(sommWriteFile, index=False)
    print('Kappa0 value calculated and set')
    
elif (testKappa0 != 'None'):
    print ('Kappa0 value already calculated')


##################################
# Calculate the Sommerfeld enhancements
##################################

finishedCounter = 0
print ('-------------------')
print ('Starting Sommerfeld Calculations...')
for index in range(0,looprange):
    ##################################
    # Initialize Parameters
    ##################################
    m_AValue = sommDataIn.at[index,'mA[GeV]']
    testSomm = sommDataIn.at[index,'ThermAvgSommerfeld']
    
    if (testSomm == 'None'):    
        alpha_X = DP.alphaTherm(Filem_X, m_AValue)
        thermAvgSomm = DP.thermAvgSommerfeld(Filem_X, m_AValue, alpha_X)

        sommDataIn.at[index,'ThermAvgSommerfeld'] = thermAvgSomm
        sommDataIn.to_csv(sommWriteFile, index=False)

        finishedCounter +=1
        if (index%20 == 0):
            print ('Index: {0} recorded'.format(index))
        
    elif(testSomm != 'None'):
        finishedCounter +=1
        
    if (finishedCounter == looprange):
        print ('-------------------')
        print ('All Calculations Complete')
        
    
sommDataIn.to_csv(sommWriteFile, index=False)
print ('Complete')<jupyter_output><empty_output><jupyter_text>Manual Overwrite Should the previous cell take longer than is feasable, the caculation may be interrupted at any time by interrupting the Jupyter kernel (`esc I,I`). If the interruption occurs during the `to_csv` command, $\texttt{Sommerfeld.csv}$ will be blank and it would appear that all the calculations have been lost. However, the dataframe is still stored in Jupyter memory. Manually writing this memory to $\texttt{Sommerfeld.csv}$ will guarantee prevent this data from being lost.<jupyter_code>print ('Working Sommerfeld file: {0}'.format(sommWriteFile))

overwrite = input('Overwrite? (y/n): ')
if ((overwrite == 'y') or (overwrite == 'Y')):
    sommDataIn.to_csv(sommWriteFile, index=False)
    print ('Overwrite complete')
    
else:
    print ('Overwrite aborted')


print ('Complete')<jupyter_output><empty_output><jupyter_text>Plot Sommerfeld Data This cell:1. Reads in a completed $\texttt{Sommerfeld.csv}$ file2. Plots the thermally-averaged Sommerfeld effect against mediator massIf $\texttt{Sommerfeld.csv}$ isn't fully populated, i.e., the calculations have not been finished, Python will throw the following error:```pythonAttributeError: 'str' object has no attribute 'log10'```<jupyter_code>##################################
# Read in Sommerfeld CSV
##################################
sommFile = input('Sommerfeld File: ')
readFile = sommerfeldPath(sommFile)

dataIn = pd.read_csv(readFile, sep = ',')


#####################
# Extract Plot Data
#####################
mAList = dataIn['mA[GeV]']
SommerfeldList = dataIn['ThermAvgSommerfeld']
Filem_X = float(dataIn.loc[0,'mX[GeV]'])

##################################
# Determine the diplay Units of m_X 
##################################
m_XUnit = 'None'
if (Filem_X < 1000):
    m_XDisplay = Filem_X
    m_XUnit = 'GeV'

if (Filem_X >= 1000):
    m_XDisplay = Filem_X*10**-3
    m_XUnit = 'TeV'

#####################
# Plot
#####################
sommFig = plt.figure(figsize = (6,6))
Plot = plt.plot(np.log10(mAList),np.log10(SommerfeldList))
plt.ylabel(r'$\log( \langle S_s \rangle )$', fontsize = 14)
plt.xlabel(r'$\log( m_A ) [GeV]$', fontsize = 14)
plt.suptitle('Sommerfeld Enhancement vs. $m_A$',fontsize = 16)
plt.title(r'$m_X = {0}$ {1}'.format(m_XDisplay, m_XUnit),loc = 'right', fontsize = 14)
plt.grid()

print ('Complete')<jupyter_output><empty_output><jupyter_text>Save Figure<jupyter_code>sommFileName = input('Figure Name: ')
assert (sommFileName != ''), 'No Filename'
sommFig.savefig(sommFileName, dpi = 700)

print ('Complete')<jupyter_output><empty_output><jupyter_text>Equilibrium Plots The recipe for this section is:1. Read in a completed $\texttt{Sommerfeld.csv}$ file2. Extract contour data3. Plot contours of constant $\tau/\tau_\oplus$  Read In Sommerfeld File The following cell reads in a $\texttt{Sommerfeld.csv}$ file and interpolates the discrete set of $\langle S_S (m_{A'})\rangle$ points into a continuous function. To save computation time, we call this interpolation during the calculation of the equilibrium time as opposed to calling the function `DarkCapPy.DarkPhton.thermAvgSommerfeld` explicitly.<jupyter_code>sommFileName = input('Sommerfeld Data File: ')
sommFile = sommerfeldPath(sommFileName)

dataIn = pd.read_csv(sommFile, sep = ',')

Filem_X = float(dataIn.at[0,'mX[GeV]'])
FileKappa0 = float(dataIn.at[0,'Kappa0[GeV5]'])

###############################
# Set the correct units for m_X
###############################
m_XUnit = 'None'

if (Filem_X < 1000):
    m_XDisplay = Filem_X
    m_XUnit = 'GeV'

if (Filem_X >= 1000):
    m_XDisplay = Filem_X*10**-3
    m_XUnit = 'TeV'
    

# Double Check to make sure the file name matches the read-in value
print ('Input m_X: {0} GeV'.format(Filem_X))


###############################
# Interpolate Sommerfeld
###############################
maList = dataIn['mA[GeV]']
sommerfeldList = dataIn['ThermAvgSommerfeld']
SommerfeldInterp = interpolate.interp1d(maList, sommerfeldList)


print ('Compete')<jupyter_output><empty_output><jupyter_text>Extract Equilibrium Contours Included in this package is the function `tau` = $\tau = \sqrt{C_\text{cap}C_\text{ann}}$, which is required to plot contours of equilibrium time. The relevant ratio is is the equilibrium time relative to the age of the Earth, $\tau_\oplus$. However, generating contours of constant $\tau/\tau_\oplus$ using `matplotlib.contour` is computationally inefficient. Instead, it is much better to define a contour level by its order of magnitude, $L$ in $\tau/\tau_\oplus = 10^L$, so that:$$ \varepsilon(m_{A'}) = 2\log(m_{A'}) -\frac{1}{2}\log(\alpha_X C_\text{ann,0} \langle S_S \rangle) - \frac{1}{2}\log(\kappa_0) - \log(\mathrm{L}  \tau_{\oplus}) $$For a constant contour $L$, dark matter mass $m_X$, and dark matter fine structure constant $\alpha_X$, the last two terms are constant in $(\varepsilon, m_{A'})$ space.The following cell initializes `contourDictionary`, a dictionary which stores arrays of $\varepsilon(m_{A'}$) values for different contours. The keys for `contourDictionary` are contour levels $L$ corresponding to the $\tau/\tau_\oplus = 10^\mathrm{L}$ contour. The values of `contourDictionary` are an array of two sub-arrays. The first sub-array stores $\varepsilon(m_{A'})$ with no Sommerfeld enhancements ($\langle S_S \rangle =1$). The second sub-array stores $\varepsilon(m_{A'})$ with Sommerfeld enhancements.<jupyter_code>####################################
# Create contourDictionary
####################################
# The keys of contourDictionary define the 10^key contour level
# the values are an array of plot values: [[],[]] 
# The first array is Sommerfeld off, the second array is sommerfeld on.
contourDictionary={
    '-4':[[],[]],
    '-2':[[],[]],
    '0' :[[],[]],
    '2' :[[],[]],
    '4' :[[],[]]
}

for key in contourDictionary:
    contourDictionary[key] = [[],[]]

####################################
# Initialize the x-axis
# 0.01 GeV < m_A < 10 GeV
####################################
maMin = min(maList)
maMax = max(maList)

maRange = np.logspace(np.log10(maMin), np.log10(maMax), 500, base = 10)

####################################
# Populate contourDictionary with plot values
####################################
maArray = []

for maTemp in maRange:
    maArray.append(maTemp)

for key in contourDictionary:
    contourline = float(key)
    
    for mATemp in maRange:
        Alpha_X    = DP.alphaTherm(Filem_X, mATemp)
        SigmaVTree = DP.sigmaVtree(Filem_X, mATemp, Alpha_X)
        Sommerfeld = SommerfeldInterp(mATemp)
        CannNoSomm = DP.cAnn(Filem_X, SigmaVTree)
    
        EpsilonNoSomm = DP.contourFunction(mATemp, Alpha_X, CannNoSomm, 1, FileKappa0, contourline)
        EpsilonSomm   = DP.contourFunction(mATemp, Alpha_X, CannNoSomm, Sommerfeld, FileKappa0, contourline)
    
        contourDictionary[key][0].append(EpsilonNoSomm)
        contourDictionary[key][1].append(EpsilonSomm)


print ('Complete')<jupyter_output><empty_output><jupyter_text>Generate Equilibrium Plots The following cell produces a single plot of the equilibrium time. The user must specify whether the Sommerfeld enhancement is "on" or "off" by following the prompt. We also define a custom color scheme so that the contours follow a color gradient as opposed to the default `matplotlib` colors which are uncoordinated.<jupyter_code># Define custom Color scheme
colorScheme1 = mpl.cycler(color=['#c7c47e','#94ab72','#428583','#40379f','#3d0b7e']) 

EQFig = plt.figure(figsize = (6,6))

sommParam = int(input('Sommerfeld on? No:0 Yes:1 '))
assert ((sommParam == 0) or (sommParam == 1)), 'Invalid Input, must be 0 or 1.'


########################################
# This uses a custom color scheme defined in Masterfunctions_Graphing
########################################
colors = colorScheme1
mpl.rcParams['axes.prop_cycle'] = colors

########################################
# Produce the contour lines
########################################
testFill = False
for key in contourDictionary:
    if (float(key) == 0):
        testFill = True
    assert (len(maRange) == len(contourDictionary[key][sommParam])), 'ContourDictionary is probably not populated'
    plt.plot(np.log10(maRange), (contourDictionary[key][sommParam]), label = r'10^{0}'.format(key))
    

########################################  
# Shade the valid region of parameter space   
########################################
if (testFill == True):
    xs = np.log10(maArray)
    y1 = contourDictionary['0'][sommParam]
    plt.fill_between(xs, y1, -5, color = '#E6FDFF')

########################################
# Define plot label stuff
########################################
plt.legend(loc = 'lower right', fontsize = 12)
plt.xlabel(r"$\log(m_{A'})$ $[GeV]$", fontsize = 14)
plt.ylabel(r'$\log(\varepsilon)$', fontsize = 14)

plt.title(r'$m_X = {0}$ {1}'.format(m_XDisplay, m_XUnit),\
          fontsize = 16,\
          loc = 'right')

plt.title(r'$\tau/\tau_{\oplus}$ Contours',\
          fontsize = 16,\
          loc = 'left')
plt.axis([np.log10(min(maRange)),np.log10(max(maRange)),-10,-5])
plt.grid()
plt.show()

print ('Complete')<jupyter_output><empty_output><jupyter_text>Save Figure<jupyter_code>filename = input('Figure Name: ')
assert (filename != ''), 'No Filename'
EQFig.savefig(filename, dpi = 700)

print ('Complete')<jupyter_output><empty_output><jupyter_text>IceCube Signal The recipe for generating the $\texttt{Signal.csv}$ is:1. Read in $\texttt{Sommerfeld.csv}$ and $\texttt{Branch.csv}$2. Define the resolution of the signal plot3. Check this resolution  4. Initialize a template dataframe to hold the signal rate calculations5. Generate $\texttt{Signal.csv}$6. **This step is time consuming and computationally intensive.**  Loop through signalData.csv, calculate $C_{Cap}$, $C_{Ann}$, $\Gamma_{Ann}$, and $N_{Sig}$, append them to signalData.csvStep six can be quite lengthy depending on the desired resolution of the data points. Typically, one would begin these calculations and be forced to wait until they are completed in their entirety; perhaps unable to shut down their machine for hours at a time. However, the cell "Signal.csv Calculations" can be interrupted at any time, by interrupting the Jupyter kernel, and resumed later. This allows the user to run the calculation in multiple stages instead of dedicating a large chunk of time and running the calculation all at once. If the calculation is interrupted, run the "Manual Overwrite" cell to guarentee that the dataframe stored in Jupyter memory is written to $\texttt{Signal.csv}$.  Initialize Signal.csv  Read in External Files In this cell, the user inputs two files:1. A completed $\texttt{Sommerfeld.csv}$2. A csv containing the branching ratio for a given dark photon decay process, $\texttt{Branch.csv}$We provide the file `brtoe.csv` for the process $A' \rightarrow e^+e^-$ from [arXiv:1505.07459](https://arxiv.org/abs/1505.07459).<jupyter_code>SommFileName = input('Sommerfeld File: ')
SommFile = pd.read_csv(sommerfeldPath(SommFileName))

branchFileName = (input('Branching File: '))
branchFile = pd.read_csv(branchPath(branchFileName))

print ('Complete')<jupyter_output><empty_output><jupyter_text>Define the Resolution of the Signal Plot This cell defines the resolution of the signal plot and consequently for example the sampling of the Sommerfeld enhancement. This cell initializes the variable `num_Signal` which represents the resolution of the Signal plot. If If `num_Signal = 50`, the signal plot will have a $50 \times 50$ resolution. Consequently, the Sommerfeld enhancement and branching ratio interoplations will also have `num_Signal` sampling points. Ideally, the resolution would be very large, `num_Signal ~ 2000`, but the number of calculations for the signal plot scales as `num_Signal`$^2$. There is a clear tradeoff between resolution and computation time. Therefore, it is useful to tune the resolution as needed in order to minimize calculation time. See [Sommerfeld Data Interpolation](Sommerfeld-Data-Interpolation) for a more detailed discussion of tuning the resolution.For the provided $\texttt{Sommerfeld.csv}$ file titled `100GeVSommerfeld.csv`, the suggested resolution is `num_Signal = 200`. As `m_x` increases, the Sommerfeld resonances become more closely spaced and `num_Signal` must increase to capture these finer resonance peaks.To demonstrate what `num_Signal` controls, read in `100GeVSommerfeld.csv` in the cell above and compare the Sommerfeld plots with `num_Signal = 40` to `num_Signal = 200`.<jupyter_code>num_Signal = 200

print ('Complete')<jupyter_output><empty_output><jupyter_text>External File Interpolations The following cell creates interpolations of the:1. Sommerfeld enhancement2. Branching ratioThe file names for both of these files is written to the $\texttt{Signal.csv}$ file. This facilitates resuming the calculation if it is interrupted. If the main calculation cell is interrupted and the Jupyter kernel is closed, all of the variables loaded into Jupyter memory are wiped. Once the caluclation resumes for a given $\texttt{Signal.csv}$ file, the user must load the particular $\texttt{Sommerfeld.csv}$ and $\texttt{Branch.csv}$ files back into Jupyter memory. Writing both of these file names into the $\texttt{Signal.csv}$ file solves this problem, allowing the user to resume the calculation by running a single cell as opposed to multiple cells.  Sommerfeld Data Interpolation In this cell, we generate two plots of the Sommerfeld enhancement to help inform an appropriate value for `num_Signal`. To tune the resolution parameter, we focus on the Sommerfeld enhancement. This is because the Sommerfeld enhancement contains narrow resonance peaks that are not be accurately captured in the interpolation unless the resolution is adequate. If `num_Signal` is too low, the sampling yields an artifically jagged enhancement. To illustrage this point, we generate two graphs of the Sommerfeld enhancement. The left plot is generated with a sampling of `num_Signal` points, the right plot is generated using all points from the $\texttt{Sommerfeld.csv}$ file.<jupyter_code>####################################
# Interpolate Sommerfeld data
####################################
mAData = SommFile['mA[GeV]']
SommData = SommFile['ThermAvgSommerfeld']
SommInterp = interpolate.interp1d(mAData,SommData)

####################################
# Create Comparison Plots
####################################
m_XSommerfeld = SommFile.loc[0,'mX[GeV]']
kappa0Sommerfeld = SommFile.loc[0,'Kappa0[GeV5]']


vect1 = [] # To store m_A values
vect2 = [] # To store Sommerfeld values
for ma in np.logspace(np.log10(min(mAData)),np.log10(max(mAData)), num_Signal, base = 10):
    vect1.append(ma)
    vect2.append(SommInterp(ma))
        
        

fig1 = plt.figure(figsize = (14,6))
plt.suptitle('Comparison of Sommerfeld Resolutions', fontsize = 18)

ax1 = fig1.add_subplot(1,2,1)
ax1.plot(np.log10(vect1),np.log10(vect2),color = '#1f77b4')
ax1.set_xlabel(r"$\log(m_{A'})$ [GeV]", fontsize = 14)
ax1.set_ylabel(r"$\log(\langle S_S \rangle)$", fontsize = 14)
ax1.set_title('num_Signal = {0} Resolution'.format(num_Signal), fontsize = 14, loc = 'right')
plt.grid()

ax2 = fig1.add_subplot(1,2,2)
ax2.plot(np.log10(mAData), np.log10(SommData), color = '#1f77b4')
ax2.set_xlabel(r"$\log(m_{A'})$ [GeV]", fontsize = 14)
ax2.set_ylabel(r"$\log(\langle S_S \rangle)$", fontsize = 14)
ax2.set_title('num_Somm = 2500 Resolution', fontsize = 14, loc = 'right')
plt.grid()


print ('Complete')<jupyter_output><empty_output><jupyter_text>Branching Ratio Interpolation This cell reads in $\texttt{Branch.csv}$ and create an interpolation of the branching ratio as a function of mediator mass. The branching ratio file provided, `brtoe.csv` was created by taking Figure 2 from [arXiv:1505.07459](https://arxiv.org/abs/1505.07459) and creating a csv file using [Web Plot Digitizer](https://automeris.io/WebPlotDigitizer/).Similar to the sampling of the Sommerfeld effect, the sampling of the branching ratio also depends on `num_Signal`. In the following cell, we provide output similar to the Sommerfeld plots. The left plot is the branching ratio using a sampling of `num_Singal` points. The plot on the right uses all points in the $\texttt{Branch.csv}$ file.<jupyter_code>xListBranch = branchFile['mA[GeV]']
yListBranch = branchFile['BR']

branchRatioInterp = interpolate.interp1d(xListBranch, yListBranch)

############################
# Number of data points
############################
num_Branch = len(xListBranch)


############################
# Set low and high m_A values for plotting
############################
m_AHigh = max(xListBranch)

# Get around Branching ratio starting at m_A = 0 GeV
if (min(xListBranch) == 0):
    m_ALow = 10**-1 # ArXiV Fig 2 starts at m_A = 0.1 GeV so we start there as well

else:
    m_ALow = min(xListBranch)
    
    
############################
# Create data for plots
############################
vect1 = [] # To store m_A values
vect2 = [] # To store Branching ratio values
for ma in np.logspace( np.log10(m_ALow), np.log10(m_AHigh), num_Signal, base = 10):
    vect1.append(ma)
    vect2.append(branchRatioInterp(ma))


fig2 = plt.figure(figsize = (14,6))
plt.suptitle('Comparison of Branching Ratio Resolutions', fontsize = 18)

ax3 = fig2.add_subplot(1,2,1)
ax3.plot((vect1),(vect2), color = '#1f77b4')
ax3.set_xlabel(r"$m_{A'}$ [GeV]", fontsize = 14)
ax3.set_ylabel(r"Branching Ratio: $A' \rightarrow e^+ + e^-$", fontsize = 14)
ax3.set_title('num_Signal = {0} Resolution'.format(num_Signal), fontsize = 14, loc = 'right')
plt.grid()

ax2 = fig2.add_subplot(1,2,2)
ax2.plot((xListBranch), (yListBranch), color = '#1f77b4')
ax2.set_xlabel(r"$m_{A'}$ [GeV]", fontsize = 14)
ax2.set_ylabel(r"Branching Ratio: $A' \rightarrow e^+ + e^-$", fontsize = 14)
ax2.set_title('num_Branch = {0} Resolution'.format(num_Branch), fontsize = 14, loc = 'right')
plt.grid()


print ('Complete')<jupyter_output><empty_output><jupyter_text>Initialize Signal Dataframe This cell:1. Initializes the signal data frame2. Appends `num_Signal` $\varepsilon$ and $m_{A'}$ points. By default, the range for mediator mass and kinetic mixing are dictated by the range of $m_{A'}$ in the Sommerfeld file, and $10^{-11} \leq \varepsilon \leq 10^{-5}$. To change the range of $\varepsilon$, adjust the arguments of the variable `epsilonLogRange`.3. Appends the column headers to the data frameThe columns are as follows. If the column values have units, we have included units in square brackets in the column headers.- `counter`: Explicitly stores the current row of the file. This is used as a loop variable to resume the calculation should it be stopped.- `mX[GeV]`: Stores the value of $m_X$ taken directly from the $\texttt{Sommerfeld.csv}$ file- `mA[GeV]`: Stores `num_Signal` values of $m_A$ in the range $0.01 \ \text{GeV} \leq m_{A'} \leq 10 \ \text{GeV}$- `Epsilon`: Stores `num_Signal` values of $\varepsilon$ in the range $10^{-11} \leq \varepsilon \leq 10^{-5}$- `Capture[s-1]`: Stores the value of $C_\text{cap}$ for the values in the corresponding row- `Annihilation[s-1]`: Stores the value of $C_\text{ann}$ for the values in the corresponding row- `GammaAnn[s-1]`: Stores the value of $\Gamma_\text{ann}$ for the values in the corresponding row- `IceCubeSignal`: Stores the value of $N_{sig}$ for the values in the corresponding row- `LiveTime[s]`: Stores the live time of the experiment in seconds. By default, this is 10 years.- `SommerfeldFile`: Stores the name of the working Sommerfeld file- `BranchingRatioFile`: Stores the name of the working branching ratio file- `Resolution`: Stores the value for the parameter `num_Signal`- `Kappa0[GeV5]`: Stores the value of $\kappa_0$ taken directly from the $\texttt{Sommerfeld.csv}$ file<jupyter_code>#########################################
# Define parameter space Ranges
#########################################
# Logarithmic Spacing because we plot this in log-log space
# 0.01 GeV < mA < 10 GeV
# 10^-11 < Epsilon < 10^-5

ma_Min = (min(mAData))
ma_Max = (max(mAData))

epsilon_Min = 10**-11
epsilon_Max = 10**-5

# Use Logarithmic spacing since we plot the log of everything
mALogRange = np.logspace(np.log10(ma_Min), np.log10(ma_Max), num_Signal, base = 10)
epsilonLogRange = np.logspace(np.log10(epsilon_Min), np.log10(epsilon_Max), num_Signal, base = 10)

#########################################
# Initialize Master Arrays
#########################################
masterSignalArray = []

#########################################
# Populate the Master Arrays with the column headers and 'None' Cells
#########################################
counter = 0
for mATemp in mALogRange:
    for epsilonTemp in epsilonLogRange:
        masterSignalArray.append([counter, ' ', mATemp, epsilonTemp, \
                                  'None', 'None', 'None', 'None', ' ', \
                                  ' ', ' ', ' ', ' '])
        counter +=1
#########################################
# Create a PANDAS DataFrame from the Master Arrays
#########################################
masterSignalDataframe = pd.DataFrame(data = masterSignalArray, \
    columns=['Counter', 'mX[GeV]', 'mA[GeV]', 'Epsilon',\
             'Capture[s-1]', 'Annihilation[s-1]', 'GammaAnn[s-1]', 'IceCubeSignal', 'LiveTime[s]', \
             'SommerfeldFile', 'BranchingRatioFile','Resolution','Kappa0[GeV5]'])

print ('Complete')<jupyter_output><empty_output><jupyter_text>Write to External .csv File This cell writes the signal data frame to an external csv file. The naming convention should follow the convention outlined in the [Create Sommerfeld.csv](Create-Sommerfeld.csv) section, namely:        Signal.csvSo for $m_X = 100$ GeV, the corresponding signal file is named:    100GeVSignal.csvThis is the final step before starting the time-consuming calcuation process to populate $\texttt{Signal.csv}$ with data. As a final check, we prompt the user for input to ensure that all model and experimental parameters are correct before starting the calculations.<jupyter_code>signalFileName = input('Signal Rate filename: ')
if signalFileName == '':
    print ("Using default filename")
    signalFileName = 'Signal_DEFAULT.csv'
signalFile = signalPath(signalFileName)
assert (signalFileName[-4:] == '.csv'), 'Must inclue ".csv" in the file name'

#############################
# Double check that these values are correct
#############################
liveTime = DarkCapPy.Configure.Conversions.yr2s(10)

print ('--------------------')
print ('You are about to write the following to {0}'.format(signalFileName))
print ('mX[GeV]             : {0}'.format(m_XSommerfeld))
print ('mA_Low[GeV]         : {0}'.format(ma_Min))
print ('mA_High[GeV]        : {0}'.format(ma_Max))
print ('Epsilon_Low         : {0}'.format(epsilon_Min))
print ('Epsilon_High        : {0}'.format(epsilon_Max))
print ('Kappa0[GeV5]        : {0}'.format(kappa0Sommerfeld))
print ('Observation Time    : {0}'.format(liveTime))
print ('Sommerfeld File     : {0}'.format(SommFileName))
print ('Branching Ratio File: {0}'.format(branchFileName))
print ('Resolution          : {0}'.format(num_Signal))
print ()

yesNo = input ('Write to {0}.csv? (y/n) '.format(signalFileName))
assert (yesNo == 'y' or yesNo == 'n'), 'Invalid input, must be "y" or "n."'

if ((yesNo == 'y') or (yesNo == 'Y')):
    kappa0Value = 'None'
    m_XValue = 'None'
    
    masterSignalDataframe.at[0, 'mX[GeV]']            = m_XSommerfeld
    masterSignalDataframe.at[0, 'Kappa0[GeV5]']       = kappa0Sommerfeld
    masterSignalDataframe.at[0, 'LiveTime[s]']        = liveTime
    masterSignalDataframe.at[0, 'SommerfeldFile']     = SommFileName
    masterSignalDataframe.at[0, 'BranchingRatioFile'] = branchFileName
    masterSignalDataframe.at[0, 'Resolution']         = num_Signal

    masterSignalDataframe.to_csv(signalFile, index=False)
    
    print ('Write executed')
    
else:
    print ('Write aborted')

print ('Complete')<jupyter_output><empty_output><jupyter_text>----------------------  Signal.csv Calculations ** This cell is the main work-horse of this notebook**This is the cell where the $\texttt{Signal.csv}$ file is populated with $N_\text{sig}$ values by scanning over $m_{A'}$ and $\varepsilon$. To begin or resume calculations for a $\texttt{Signal.csv}$ file, this is the single cell to run. The algorithm is:1. Input an incomplete $\texttt{Signal.csv}$ file2. Create a backup of that file. This ensures that even if the calculation is interrupted and the manual overwrite is not run, that not all data will be lost.3. Generate interpolations of the Sommerfeld effects and branching ratio 4. Iterate through the `counter` column looking at the value of `IceCubeSignal`    4. If `IceCubeSignal` is `None`, compute all values in that row    4. Else, continue to loop5. Append newly calculated values to $\texttt{Signal.csv}$6. Overwrite $\texttt{Signal.csv}$7. Loop steps 2-6 until all rows are calculatedSince this is the main working cell in this notebook, a number of features have been implimented for user-convenience.After user generates a $\texttt{Signal.csv}$ file following the steps above, this cell calculates and populates a signal file. Depending on the resolution of the plot, this calculation may take $\mathcal{O}(10 \ \text{hours})$ to complete. This cell saves each iteration to $\texttt{Signal.csv}$ so that in case the Jupyter kernel is interrupted, the calculation may be resumed at another time. If the calculation is interrupted, one must run the cell titled Manual Overwrite to avoid data loss. To resume this calculation, simply run this cell and input the incomplete $\texttt{Signal.csv}$ file to continue calculations. The algorithm will iterate through the `counter` column until it finds a row which has not been calculated.As a benchmark, with a resolution of `num_Signal = 200`, the upper left plot of Fig. 3 in [arXiv:1509.07525](https://arxiv.org/abs/1509.07525) took about 8 hours of run time on a modern laptop.<jupyter_code>#########################################
# Read in Signal File
#########################################
signalFileName = input('Signal Rate File: ')
signalFile = signalPath(signalFileName)
signalDataIn = pd.read_csv(signalFile, sep = ',')

#########################################
# Create backup of Signal File 
#########################################
now = datetime.now()
timeStamp = now.strftime('%b-%d_%H.%M')
signalFileNameCopy = signalFileName[:-4] + '_' + timeStamp + '.csv'
signalDataIn.to_csv(signalBackupPath(signalFileNameCopy), sep=',', index=False)
print ('Backup Created')


#########################################
# Constants in (mA, epsilon) space:
#########################################
signalm_X = float(signalDataIn.at[0, 'mX[GeV]'])
FileKappa0 = float(signalDataIn.at[0, 'Kappa0[GeV5]'])
Alpha = 1./137
LiveTime = float(signalDataIn.at[0, 'LiveTime[s]'])


#########################################
# Sommerfeld Interpolation
#########################################
sommFileName = signalDataIn.at[0,'SommerfeldFile']
sommDataIn = pd.read_csv(sommerfeldPath(sommFileName))
mAData = sommDataIn['mA[GeV]']
SommDataValues = sommDataIn['ThermAvgSommerfeld']
SommInterp = interpolate.interp1d(mAData,SommDataValues)

#########################################
# Branching Ratio Interpolation
#########################################
branchFileName = signalDataIn.at[0,'BranchingRatioFile']
branchDataIn = pd.read_csv(branchPath(branchFileName))
mABranch = branchDataIn['mA[GeV]']
branchingDataValues = branchDataIn['BR']
branchRatioInterp = interpolate.interp1d(mABranch,branchingDataValues)


#########################################
# Begin iterating through the file
#########################################
loopRange = range(0,len(signalDataIn['Counter']))
print ()
print ('--------------------')
finishedCounter = 0
index = 0
for index in loopRange:
    testValue = signalDataIn.loc[index, 'IceCubeSignal']
    
    if (testValue == 'None'):
        #########################################
        # Initialize iteration-specific Quantities
        #########################################
        signalm_ATemp = float(signalDataIn.at[index, 'mA[GeV]'])
        FileEpsilonTemp = float(signalDataIn.at[index, 'Epsilon'])
        Alpha_X = DP.alphaTherm(signalm_X, signalm_ATemp)      
#         Sommerfeld = SommInterp(signalm_ATemp)
        Sommerfeld = 1
        BranchingRatio = branchRatioInterp(signalm_ATemp)
        
        #########################################
        # Parameter Spce Calculations
        #########################################
        Capture = DP.cCapQuick(signalm_X, signalm_ATemp, FileEpsilonTemp, Alpha_X, FileKappa0)
        SigmaVTree = DP.sigmaVtree(signalm_X, signalm_ATemp, Alpha_X)
        Annihilation = DP.cAnn(signalm_X, SigmaVTree, thermAvgSomm = Sommerfeld)
        GammaAnn = DP.gammaAnn(Capture, Annihilation)
        L = DP.decayLength(signalm_X, signalm_ATemp, FileEpsilonTemp, BranchingRatio)
        EpsilonDecay = DP.epsilonDecay(L)
        
        IceCubeSignal = DP.iceCubeSignal(GammaAnn, EpsilonDecay, LiveTime)

        #########################################
        # Append Calculated Values to SignalFile.csv
        #########################################
        signalDataIn.at[index, 'Capture[s-1]']      = Capture
        signalDataIn.at[index, 'Annihilation[s-1]'] = Annihilation
        signalDataIn.at[index, 'GammaAnn[s-1]']     = GammaAnn
        signalDataIn.at[index, 'IceCubeSignal']     = IceCubeSignal
        
        
        #########################################
        # Overwrite SignalFile.csv
        #########################################
        signalDataIn.to_csv(signalFile, sep=',',index=False) 
        finishedCounter += 1
        
        if (index % 20 == 0):
            print ('Calculations for index: {0} recorded'.format(index))
        
    elif (testValue != 'None'):
        finishedCounter += 1
    
    if (finishedCounter == len(signalDataIn['Counter'])):
        print ('--------------------')
        print ('All Calculations Complete')
        break
        
print ('Complete')<jupyter_output><empty_output><jupyter_text>Manual Overwrite This cell is the manual overwrite of the $\texttt{Signal.csv}$ file.If the previous cell has been interrupted, this cell must be run to write the data stored in Jupyter memory into $\texttt{Signal.csv}$. This is because if the Jupyter kernel happens to be interrupted during the `to_csv` function, there is a chance the entire $\texttt{Signal.csv}$ file will be blank. The manual overwrite cell writes the dataframe stored in Jupyter memory to $\texttt{Signal.csv}$ to ensure that no data will be lost in the interruption process. Failure to run the manual overwrite cell after interruption may result in a loss of the data stored in $\texttt{Signal.csv}$.<jupyter_code>print ('Working Signal File: {0}'.format(signalFileName))

overwrite = input('Overwrite? (y/n): ')
if ((overwrite == 'y') or (overwrite == 'Y')):
    signalDataIn.to_csv(signalFile, sep=',',index=False) 
    print ('Overwrite complete')
    
else:
    print ('Overwrite aborted')

print ('Complete')<jupyter_output><empty_output><jupyter_text>***  Plot Signal Events This cell reads in a completed $\texttt{Signal.csv}$ and plots the mediator mass $m_{A'}$ on the horizontal axis, the kinetic mixing parameter $\varepsilon$ on the vertical axis, and the signal event contours $N_\text{sig} = \{ 1, 10, 100, 1000 \}$ at IceCube.If an incomplete $\texttt{Signal.csv}$ file is read into this cell, Python will throw:```pythonAttributeError: 'str' object has no attribute 'log10'```<jupyter_code>signalFile = input('Signal Rate filename: ')
readFile = signalPath(signalFile)
rawData = pd.read_csv(readFile)
Filem_X = float(rawData.loc[0,'mX[GeV]'])

#Pull out min / max values on each axis
xMin = min(rawData['mA[GeV]'])
xMax = max(rawData['mA[GeV]'])
yMin = min(rawData['Epsilon'])
yMax = max(rawData['Epsilon'])


#########################################
# Define the dimension of the grid. 
# For an n x n grid, this will pull out the number "n"
#########################################
gridDim = int(rawData.loc[0,'Resolution'])


#########################################
# Create the n x n grid
#########################################
# Logarithmic Spacing
xLogRange = np.logspace(np.log10(xMin), np.log10(xMax), gridDim, base = 10)
yLogRange = np.logspace(np.log10(yMin), np.log10(yMax), gridDim, base = 10)


#########################################
# Read in and reshape the Contour data
#########################################
# This reads in contour data as a single column,
#     but we reshape it to a 2D, n x n array to fit the grid

z = [rawData['IceCubeSignal']]
SignalData = np.reshape(z, (len(xLogRange), len(yLogRange)))



###############################
# Set the correct units for m_X
###############################
m_XUnit = 'None'
if (Filem_X < 1000):
    m_XDisplay = Filem_X
    m_XUnit = 'GeV'

if (Filem_X >= 1000):
    m_XDisplay = Filem_X * 10**-3
    m_XUnit = 'TeV'
    
    
###############################
# Plotting
###############################
signalFig = plt.figure(figsize = (6,6))

CP = plt.contour(np.log10(xLogRange), np.log10(yLogRange), \
                 np.log10(SignalData.transpose()),\
                 levels=[0,1,2,3],\
                )

xLabelNums = [-2,-1,0,1]
xLabelText = [r'$10^{-2}$',r'$10^{-1}$',r'$10^{0}$',r'$10^{1}$']

yLabelNums = [-5,-6,-7,-8,-9,-10,-11]
yLabelText = [r'$10^{-5}$', r'$10^{-6}$', r'$10^{-7}$', r'$10^{-8}$', \
              r'$10^{-9}$', r'$10^{-10}$', r'$10^{-11}$']


plt.xticks(xLabelNums, xLabelText, fontsize = 12)
plt.yticks(yLabelNums, yLabelText, fontsize = 12)

plt.xlabel(r"$m_{A'}$ $[GeV]$", fontsize = 14)
plt.ylabel(r'$\varepsilon$', fontsize = 18)
plt.suptitle('IceCube Events', fontsize = 16)
plt.title(r'$m_X$ = {0} {1}'.format(m_XDisplay, m_XUnit), loc = 'right', fontsize = 13)

labels = [r'1', r'$10$',r'$10^2$',r'$10^3$']
for i in range(len(labels)):
    CP.collections[i].set_label(labels[i])

plt.legend(loc='upper right', fontsize = 13)
plt.grid()

print ('Complete')<jupyter_output><empty_output><jupyter_text>Save Figure<jupyter_code>figName = input('Filename: ')

signalFig.savefig((figName), dpi = 700,
             bbox_inches = 'tight', # If this isn't here, it partially cuts off the axis labels
            )
print ('Complete')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.17352106756330776, 'fraction_numerical': 0.022780287905852684, 'mean_word_length': 4.736017357762777, 'pattern_counts': {'":': 0, '<': 104, '<?xml version=': 0, '>': 98, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '23', 'dir': 'jupyter-structured-clean-dedup', 'id': '200316', 'license': 'MIT', 'n_tokens_mistral': 15275, 'n_tokens_neox': 14264, 'n_words': 4655, 'path': 'DarkCapPy/Template_Calculation/.ipynb_checkpoints/DarkCapPy_Template-checkpoint.ipynb', 'repo_name': 'fliptanedo/DarkCapPy', 'size': '47598'}