import numpy as np import matplotlib.pyplot as plt import glob def Normalize(f, x) : dx = x[1:] - x[:-1] integral = np.sum(f[:-1] * dx) f_norm = f / integral return f_norm # When running in MT and outputting to cvs, each thread outputs to a different file. # Therefore I need to cycle through each thread and append them all together. energy = np.empty([0,1]) length = np.empty([0,1]) ntuple_name = "radioprotection_nt_102" output_name = ntuple_name + "_t" + "*" + ".csv" output_list = glob.glob(output_name) for this_file in output_list : energy_thread, length_thread = np.loadtxt(this_file, delimiter=',', unpack=True, usecols=(0,1)) energy = np.append(energy, energy_thread) length = np.append(length, length_thread) # Experimentally, the mean path length is calculated geometrically as a mean chord length. # Here for convenience it's taken by averaging the effective path lengths. mean_path_length = np.average(length) # A conversion factor can be used to convert the target material to water or tissue equivalent. # Its choice depends on the material and can be calculated in different ways. # It is suggested that the user replace the following value with his own. conversion_factor = 1. # If the previous value is not overriden by the user, this script will attempt to read geometry.mac # and provide a factor accordingly if conversion_factor == 1. : detector = np.array(["Diamond", "MicroDiamond", "Silicon", "SiliconBridge"]) factor = np.array([0.32, 0.32, 0.57, 0.57]) # conversion factor based on material stopping power with open("geometry.mac") as search: for line in search: line = line.rstrip() # remove new line for this in detector : match = "/geometrySetup/selectDetector " + this if match == line: conversion_factor = factor[ detector == this ][0] else: conversion_factor = factor[ detector == "Diamond" ][0] # default detector type (no macro used) y = energy * conversion_factor / mean_path_length # The spectrum is now binned logarithmically, to avoid oscillations at higher energies # (due to fewer counts) that wouldn't much meaning. minimum = np.amin(y) maximum = np.amax(y) exp_start = np.floor(np.log10(minimum)) exp_end = np.ceil(np.log10(maximum)) n_decades = int(exp_end - exp_start) # Number of logarithmic bins per decade: # Higher values give better resolution, but lead to oscillations # (especially at high energy) if your statistic has too few counts. bins_per_dec = 60 n_bins = n_decades * bins_per_dec y_bins = np.zeros(n_bins) y_bins[0] = 10**exp_start for i in range(1, n_bins) : y_bins[i] = y_bins[i-1] * 10**( 1 / bins_per_dec ) # Create the histogram # For now f is a number of counts... f = np.histogram( y, bins=y_bins ) [0] tot_counts = np.sum(f) # ... so now I turn f into a density bin_width = y_bins[1:] - y_bins[:-1] f = f / bin_width f = np.append(f, 0.) # give f and y_bins arrays the same size # Normalize the spectra to unit area under the curve f = Normalize(f, y_bins) d = Normalize(y_bins*f, y_bins) # Save to file output_file = "analysed_spectra.csv" header = "y[keV/um], f(y)[um/keV], d(y)[um/keV]" np.savetxt( output_file, np.c_[ y_bins, f, d ], header=header, delimiter=',' ) # Plot fig, ax1 = plt.subplots() color = 'tab:blue' ax1.semilogx( y_bins, y_bins * f, linewidth=0.5, color=color ) ax1.set_xlabel(r'$y \,\, [keV / \mu m]$') ax1.set_ylabel(r'$y \cdot f(y) $', color=color) ax1.tick_params(axis='y', labelcolor=color) ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis color = 'tab:red' ax2.semilogx( y_bins, y_bins * d, linewidth=0.5, color=color ) ax2.set_ylabel(r'$y \cdot d(y) $', color=color) ax2.tick_params(axis='y', labelcolor=color) title = str(tot_counts) + " counts, " + str(bins_per_dec) + " bins per decade, " + str(conversion_factor) + " conversion factor" fig.suptitle(title) fig.tight_layout() plt.subplots_adjust(top=0.92) plt.show()