def write_final_pore_configuration(self): # only do this if we have converged on the correct number of waters water_indices = [] for i in self.solvated.tail_water[ 0]: # tail_water indices are given as the center of mass of each water water_indices += self.solvated.residue_indices[( self.water.natoms * i):self.water.natoms * (i + 1)].tolist() keep = np.full(self.solvated.pos.shape[1], True, dtype=bool) # array of True. Booleans are fast keep[water_indices] = False # set pore indices to False # change all 'HOH' to 'SOL' because mdtraj changed it res = np.array(self.solvated.res) res[np.where(np.array(self.solvated.res) == 'HOH')[0]] = 'SOL' file_rw.write_gro_pos(self.solvated.pos[0, keep, :], 'solvated_pores.gro', ids=np.array(self.solvated.ids)[keep], res=res[keep], box=self.solvated.box) # rewrite topology files self.input_files('solvated_pores.gro', 'nvt')
def place_water(xyz, water_placement_point, ids, res, box, emsteps, rem): water = md.load('%s/../top/solutes/water.gro' % location) # load water structure water_xyz, water_centroid, water_alignment_vector, water_ids, water_res = water_parameters( water) placed_water_coordinates = solvate_tails.random_water_orientation( water_xyz, water_alignment_vector, water_placement_point) new_coordinates = np.concatenate((xyz, placed_water_coordinates), axis=0) # add to full list of coordinates names = ids + water_ids # add water to ids residues = res + water_res # add water residue to res file_rw.write_gro_pos(new_coordinates, 'water.gro', ids=names, res=residues, box=box) # write out config with new water energy_minimize(emsteps, nwater + 1, rem, water_placement_point) # energy minimzed system nrg = subprocess.check_output( ["awk", "/Potential Energy/ {print $4}", "em.log"]) # get Potential energy from em.log try: return float(nrg.decode("utf-8")) except ValueError: return 0 # If the system did not energy minimize, the above statement will not work because nrg will be an
def place_solute(self, solute, placement_point, random=False, freeze=False, rem=.5): """ Place solute at desired point and energy minimze the system :param solute: name of solute object (str) :param placement_point: point to place solute (np.array([3]) :param random: place solute at random point in box (bool) :param freeze: freeze all atoms outside rem during energy minimization (bool) :param rem: radius from placement_point within which atoms will NOT be frozen (float, nm) :return: """ # randomly rotate the molecule and then tranlate it to the placement point solute_positions = transform.random_orientation( solute.t.xyz[0, ...], solute.t.xyz[0, 0, :] - solute.t.xyz[0, 1, :], placement_point) self.positions = np.concatenate( (self.positions, solute_positions)) # add to array of positions self.residues += solute.res # add solute residues to list of all residues self.names += [ solute.names.get(i) for i in range(1, solute.natoms + 1) ] # add solute atom names to all names self.top.add_residue(solute, write=True) # add 1 solute to topology # write new .gro file file_rw.write_gro_pos(self.positions, self.intermediate_fname, box=self.box_gromacs, ids=self.names, res=self.residues) if freeze: self.freeze_ndx(solute_placement_point=placement_point, res=solute.resname) nrg = self.energy_minimize(self.em_steps, freeze=freeze) if nrg >= 0: self.revert(solute) if random: self.place_solute_random(solute) else: #self.remove_water(placement_point, 3) self.place_solute(solute, placement_point, freeze=True) else: p3 = subprocess.Popen( ["cp", "em.gro", "%s" % self.intermediate_fname]) p3.wait() self.positions = md.load( '%s' % self.intermediate_fname).xyz[0, :, :] # update positions
def write_config(self, name='out.gro'): """ Write .gro coordinate file from current positions :param name: name of coordinate file to write (str) """ # write new .gro file file_rw.write_gro_pos(self.positions, name, box=self.box_gromacs, ids=self.names, res=self.residues)
def write_gro(self, out, ucell): """ Write coordinate file in .gro format :param out: name of output .gro file :param ucell: unitcell vectors :type out: str :type ucell: np.ndarray, shape(3,3) """ file_rw.write_gro_pos(self.xyz, out, ids=self.names, res=self.all_residues, ucell=ucell)
def place_solute(self, solute, placement_point, random=False, freeze=False, rem=.5): """ Place solute at desired point and energy minimze the system :param solute: name of solute object (str) :param placement_point: point to place solute (np.array([3]) :param random: place solute at random point in box (bool) :param freeze: freeze all atoms outside rem during energy minimization (bool) :param rem: radius from placement_point within which atoms will NOT be frozen (float, nm) :return: """ # p = placement_point - (solute.com - solute.xyz[0, 0, :]) # want to move com to placement point #solute_positions = random_orientation(solute.xyz[0, ...], solute.xyz[0, 0, :] - solute.xyz[0, 1, :], placement_point) # to be fixed in THE FUTURE solute_positions = transform.translate( solute.xyz[0, :, :], solute.com, placement_point) # translate solute to placement point self.positions = np.concatenate( (self.positions, solute_positions)) # add to array of positions self.residues += solute.residues # add solute residues to list of all residues self.names += solute.names # add solute atom names to list of all names self.top.add_residue(solute, write=True) # add 1 solute to topology # write new .gro file file_rw.write_gro_pos(self.positions, self.intermediate_fname, box=self.box_gromacs, ids=self.names, res=self.residues) if freeze: self.freeze_ndx(solute_placement_point=placement_point, res=solute.resname) nrg = self.energy_minimize(self.em_steps, freeze=freeze) if nrg >= 0: self.revert(solute) if random: self.place_solute_random(solute) else: #self.remove_water(placement_point, 3) self.place_solute(solute, placement_point, freeze=False)
def place_water(self, nwater, steps): """ Place water molecules near a random reference atom and perform a short energy minimization :param placement_options: All of the possible indexes of reference atoms near which a water molecule could be placed (list) :param ref_atom_locations: xyz coordinates of reference atoms (numpy array [number of ref atoms, 3] :param coordinates: Coordinates of full system (numpy array [natoms, 3]) :param rmin: minimimum distance to place water molecules from reference atom (float) :param rmax: maximum distance to place water molecules from reference atom (float) :param ids: all atom names in order they appear in coordinates (list) :param res: all residue atoms in order they appear in coordinates (list) :param nwater: number of water molecules in the system (int) :param steps: number of energy minimization steps to take (int) :return: Potential energy of slightly minimized system, coordinates of minimized system, atom used for placement, new locations of reference atoms """ placement_atom = np.random.choice( self.placement_options ) # choose which atom to place water molecule near placement = random_pt_spherical_shell( self.ref_atom_locations[placement_atom, :], self.rmin, self.rmax) # point near placement atom placed_water_coordinates = transform.random_orientation( self.water, self.water_alignment_vector, placement) new_coordinates = np.concatenate( (self.coordinates, placed_water_coordinates), axis=0) # add to full list of coordinates names = self.ids + self.water_ids # add water to ids residues = self.res + self.water_res # add water residue to res file_rw.write_gro_pos( new_coordinates, 'water.gro', ids=names, res=residues, box=self.box_gromacs) # write out config with new water minimized_coordinates, new_ref_atom_locations = self.energy_minimize( steps, nwater + 1) # energy minimzed system nrg = subprocess.check_output( ["awk", "/Potential Energy/ {print $4}", "em.log"]) # get Potential energy from em.log return float( nrg.decode("utf-8") ), minimized_coordinates, placement_atom, new_ref_atom_locations
def cleanup(self, out='xlinked.gro', dummy_atom_name='hc_d'): """ Remove virtual sites in .gro and topology. :param out: name of output .gro file to be written :param dummy_atom_name: atom type of dummy atom :return: A .gro file and an .itp topology file without dummy atoms """ keep = [ i for i, x in enumerate(self.xlink_residue_atoms.type) if x != dummy_atom_name ] keep += [ a.index for a in self.t.topology.atoms if a.residue.name != self.original_residue_name ] ids = np.array([a.name for a in self.t.topology.atoms], dtype=object)[keep] res = np.array([a.residue.name for a in self.t.topology.atoms], dtype=object)[keep] # mdtraj workaround because SOL gets renamed to HOH, OW1 to O, HW1 to H1 and HW2 to H2 for i, x in enumerate(ids): if res[i] == 'HOH': if x == 'O': ids[i] = 'OW' if x == 'H1': ids[i] = 'HW1' if x == 'H2': ids[i] = 'HW2' res[i] = 'SOL' ucell = self.t.unitcell_vectors[-1, ...] file_rw.write_gro_pos(self.t.xyz[-1, keep, :], out, ids=ids, res=res, ucell=ucell) self.write_assembly_topology(virtual_sites=False, vsite_atom_name=dummy_atom_name)
def build_spline(self, pore_centers, rep='K'): """ Build the spline into the last frame of the trajectory :param pore_centers: output of physical.avg_pore_loc() with spline=True :param rep: name of atom to use to represent spline :return: 'spline.gro' """ pos = self.t.xyz[-1, ...] for i in range(4): pos = np.concatenate((pos, pore_centers[-1, i, ...])) ids = [a.name for a in self.t.topology.atoms] res = [a.residue.name for a in self.t.topology.atoms] ids += [rep] * pore_centers.shape[2] * pore_centers.shape[1] res += [rep] * pore_centers.shape[2] * pore_centers.shape[1] file_rw.write_gro_pos(pos, 'spline.gro', ucell=self.box[-1, ...], ids=ids, res=res)
def build_com(self, rep='K'): """ Build system with COM of residue plotted in order to confirm that this script is working :param rep: name of atom to use to represent spline :type rep: str :return: structure file, 'spline.gro' """ pos = np.concatenate((self.t.xyz[-1, ...], self.com[-1, ...])) ids = [a.name for a in self.t.topology.atoms] res = [a.residue.name for a in self.t.topology.atoms] ids += [rep] * self.com.shape[1] res += [rep] * self.com.shape[1] file_rw.write_gro_pos(pos, 'com.gro', ucell=self.box[-1, ...], ids=ids, res=res)
def insert_all_water(self, nwater, output='solvated.gro', final_topname='topol.top'): trials = 0 # number of water placement tries # start = time.time() for i in tqdm.tqdm(range(nwater), unit='waters'): # randomly place water molecule and do short energy minimization energy, new_coordinates, placement_atom, new_ref_atom_locations = self.place_water( i, 5) trials += 1 count = 0 while energy > -50000: # make sure the potential energy doesn't get too close to exploding energy, new_coordinates, placement_atom, new_ref_atom_locations = self.place_water( i, 5) trials += 1 count += 1 if count > 10: # if the energy is too high for water placement, run a longer energy minimization sys.stdout.write( "\r Running longer energy minimization... " " \r") sys.stdout.flush() file_rw.write_gro_pos(self.coordinates, 'water.gro', box=self.box_gromacs, ids=self.ids, res=self.res) self.coordinates, self.ref_atom_locations = self.energy_minimize( 500, i) # energy minimize and update coordinates count = 0 write_em_mdp(1) # success_rate = 100*((i + 1) / trials) # s = "Waters placed: %s/%s, Placement Success Rate : %3.2f, Potential Energy = %s\r" % (i, nwater, # success_rate, energy) # sys.stdout.write("\r"+s) # sys.stdout.flush() self.ids += self.water_ids self.res += self.water_res self.coordinates = copy.deepcopy(new_coordinates) self.placement_options.remove(placement_atom) for filename in glob.glob("./#*"): os.remove(filename) cp = "cp top_intermediate.top %s" % final_topname p = subprocess.Popen(cp.split()) p.wait() # energy minimize final configuration until convergence write_em_mdp(-1) grompp = "%s grompp -f em.mdp -p %s -c water.gro -o %s" % ( self.gmx, final_topname, output.split('.')[0]) if self.restraints: grompp += " -r water.gro" p = subprocess.Popen(grompp.split()) p.wait() mdrun = "%s mdrun -v -deffnm %s" % (self.gmx, output.split('.')[0]) p = subprocess.Popen(mdrun.split()) p.wait()
def hexagonal_column_grid(self, npores, ncol_per_pore, r, npoints, frames=1, noise=True, thermal_disorder=[0, 0, 0], shift_range=0): self.nframes = frames self.locations = np.zeros( [self.nframes, npores**2 * npoints * ncol_per_pore, 3]) xy_pore_centers = np.zeros([npores**2, 2]) dx = self.box[0] / npores # distance between pores in x direction if r > (dx / 2): print( 'WARNING: The pore radius is such that pores intersect and columns will be placed outside of the ' 'unit cell which will disrupt periodicity. \nSetting r to %.2f. \nEither change the radius, change ' 'the box dimensions, or change the number of pores in the unit cell.' % (dx / 2)) for i in range(npores): row_x = i * self.unit_cell[1, 0] * dx + np.linspace( dx / 2, self.box[0] - (dx / 2), npores) row_y = i * self.unit_cell[1, 1] * dx + (dx / 2) * self.unit_cell[1, 1] xy_pore_centers[i * npores:(i + 1) * npores, 0] = row_x xy_pore_centers[i * npores:(i + 1) * npores, 1] = row_y z_separation = self.box[2] / npoints column = np.linspace(0, z_separation * (npoints - 1), npoints) # For adding noise to quenched disordered configuration columns = np.zeros([npores**2 * ncol_per_pore, column.size]) # radii_noise = np.zeros([npores**2*ncol_per_pore, column.size]) # theta_noise = np.zeros([npores**2*ncol_per_pore, column.size]) # #xy_noise = np.zeros([npores**2*ncol_per_pore, column.size, 2]) # shifts = np.zeros([npores**2*ncol_per_pore]) # shift_range = 0 # fraction of layer to allow random displacement # #thetas = np.random.uniform(0, 2*np.pi, size=npores**2) # randomly rotate each pore about the z-axis # for i in range(npores**2*ncol_per_pore): # #columns[i, :] = z_correlation(column, 10, v=2.074) # columns[i, :] = column + z_separation*np.random.normal(scale=0.322, size=column.size) # radii_noise[i, :] = np.random.normal(loc=r, scale=2.2, size=column.size) # theta_noise[i, :] = np.random.normal(loc=0, scale=.43, size=column.size) # #xy_noise[i, :, :] = np.random.normal(scale=2.3, size=(column.size, 2)) # shifts[i] = shift_range * (z_separation / 2) * np.random.uniform(-1, 1) # shift column by a random amount # thetas = np.random.uniform(0, 2*np.pi, size=npores**2) # randomly rotate each pore about the z-axis # for uncorrelated pores # shifts = shift_range * (z_separation / 2) * np.random.uniform(-1, 1, size=npores**2) print('z-spacing: %.2f' % z_separation) print('Pore center spacing: %.2f' % dx) for t in range(self.nframes): for c in range(npores**2): # for each column, choose a random point on the circle with radius, r, centered at the pore center # place a column on that point. Equally space remaining columns on circle with reference to that point start_theta = np.random.uniform(0, 360) * (np.pi / 180 ) # random angle # For adding noise to quenched disordered configuration # start_theta = thetas[c] # start_theta = 0 theta = 2 * np.pi / ncol_per_pore # angle between columns # pore_shift_x = np.random.normal(scale=10) # pore_shift_y = np.random.normal(scale=10) for a in range(ncol_per_pore): start = ncol_per_pore * c * npoints + a * npoints end = ncol_per_pore * c * npoints + (a + 1) * npoints radii = np.random.normal(loc=r, scale=thermal_disorder[0], size=(end - start)) theta_col = np.random.normal(loc=(start_theta + a * theta), scale=thermal_disorder[1], size=(end - start)) # For adding noise to quenched disordered configuration # radii = radii_noise[c*ncol_per_pore + a, :] # theta_col = theta_noise[c*ncol_per_pore + a, :] + start_theta + a*theta x = radii * np.cos(theta_col) y = radii * np.sin(theta_col) # x = r*np.cos(start_theta + a*theta) # y = r*np.sin(start_theta + a*theta) self.locations[t, start:end, 0] = xy_pore_centers[c, 0] + x #+ pore_shift_x self.locations[t, start:end, 1] = xy_pore_centers[c, 1] + y #+ pore_shift_y if noise: shift = shift_range * ( z_separation / 2) * np.random.uniform( -1, 1) # shift column by a random amount #shift = shifts[c] else: shift = 0 # x_disorder = r*np.random.normal(scale=thermal_disorder[0], size=(end - start)) # y_disorder = r*np.random.normal(scale=thermal_disorder[1], size=(end - start)) # z_disorder = z_separation*np.random.normal(scale=thermal_disorder[2], size=(end - start)) # disorder = np.vstack((x_disorder, y_disorder, z_disorder)).T self.locations[t, start:end, 2] = z_correlation( column, 10, v=thermal_disorder[2]**2) + shift #self.locations[t, start:end, :2] += np.random.normal(scale=2.3, size=(column.size, 2)) #self.locations[t, start:end, 2] = column + shift # for noise about initial configuration #self.locations[t, start:end, 2] = columns[c*ncol_per_pore + a] + shifts[c*ncol_per_pore + a] #self.locations[t, start:end, :2] += xy_noise[c*ncol_per_pore + a, ...] # self.locations[t, start:end, :] += disorder # self.locations[t, start:end, 2] += z_disorder from llcsim.llclib import file_rw gamma = 2 * np.pi / 3 a, b, c = self.box A = np.array([a / 10, 0, 0]) # vector in x direction B = np.array([b / 10 * np.cos(gamma), b / 10 * np.sin(gamma), 0]) # vector in y direction C = np.array([0, 0, c / 10]) unitcell_vectors = np.zeros([self.nframes, 3, 3]) for i in range(frames): # vectors don't change but need them as a trajectory unitcell_vectors[i, 0, :] = A unitcell_vectors[i, 1, :] = B unitcell_vectors[i, 2, :] = C file_rw.write_gro_pos(self.locations[-1, ...] / 10, 'test.gro', ucell=unitcell_vectors[-1, ...]) traj = md.formats.TRRTrajectoryFile( 'test.trr', mode='w', force_overwrite=True) # create mdtraj TRR trajectory object time = np.linspace( 0, 1000, self.nframes) # arbitrary times. Times are required by mdtraj traj.write(self.locations / 10, time=time, box=unitcell_vectors) # write the trajectory in .trr format
y = np.linspace(0, L[1], int(bins[1])) # for converted square box, y box length is same as x z = np.linspace(0, L[2], int(bins[2])) # redefine bins xbin = x[1] - x[0] ybin = y[1] - y[0] zbin = z[1] - z[0] zv = [0.0, 0.0, 0.0] # zero vector # put all atoms inside box - works for single frame and multiframe for it in range(locations.shape[0]): # looped to save memory locations[it, ...] = np.where(locations[it, ...] < L, locations[it, ...], locations[it, ...] - L) # get positions in periodic cell locations[it, ...] = np.where(locations[it, ...] > zv, locations[it, ...], locations[it, ...] + L) from llcsim.llclib import file_rw file_rw.write_gro_pos(center_of_mass[-1, ...], "test1.gro") # fourier transform loop fft = np.zeros([x.size - 1, y.size - 1, z.size - 1]) for frame in tqdm.tqdm(range(frames), unit='Frames'): H, edges = np.histogramdd(locations[frame, ...], bins=(x, y, z)) fft += np.abs(np.fft.fftn(H)) ** 2 fft /= frames # average of all frames # invert the fourier transform back to real space fft_inverse = np.fft.ifftn(fft) # only works for z slice currently centers_x = [edges[0][i] + ((edges[0][i + 1] - edges[0][i]) / 2) for i in range(fft_inverse.shape[0])] centers_y = [edges[1][i] + ((edges[1][i + 1] - edges[1][i]) / 2) for i in range(fft_inverse.shape[1])] centers_z = np.array([edges[2][i] + ((edges[2][i + 1] - edges[2][i]) / 2) for i in range(fft_inverse.shape[2])])
def __init__(self, gro, res, atoms, bcc=False, name='restrained', com=False, xlink=False, vparams=None): """ :param gro: coordinate file where restraints will be placed :param res: name of residue where position restraints are being added :param atoms: name of atoms to be restrained in res :param bcc: whether or not this system is bicontinuous cubic (affects where topology is found) :param name: name of output topology file :param com: restrain center of mass of atoms instead of individual atoms :param xlink : whether or not the system is in the process of being crosslinked :param vparams: A list in the following order : virtual site construction type, atoms to use to build virtual site, required length parameters (i.e. a, b, c or d) in the order specified in GROMACS documentation """ t = md.load(gro) self.all_coords = t.xyz[0, :, :] # all coordinates for system self.atom_numbers = np.array([a.index + 1 for a in t.topology.atoms if a.name in atoms]) self.atoms = t.n_atoms # number of atoms in full system self.nmon = len(self.atom_numbers) // len(atoms) # number of monomer residues self.name = name # name of output files (.itp, .gro if you are using centers of masses) self.residue = res # name of residue to which position restraints are being applied self.LC = lc_class.LC('%s.gro' % self.residue) # everything we can know about the residue self.com = com # self.keep = np.array([a.index for a in t.topology.atoms if a.name in atoms]) # atoms to keep # self.atom_numbers = self.keep + 1 # numbers (not indices) of atoms to keep # self.coords = self.all_coords[self.keep, :] # coordinates of atoms in keep if self.com: # add center of mass virtual site # These things are only needed for center of mass virtual site construction self.ids = [a.name for a in t.topology.atoms] # names of all atoms in system self.res = [a.residue.name for a in t.topology.atoms] # residue names of all atoms in system self.vparams = vparams self.vatoms_numbers = [a.index + 1 for a in t.topology.atoms if a.name in vparams] self.box = t.unitcell_vectors[0, :, :] # box vectors in mdtraj formate self.box_gromacs = [self.box[0, 0], self.box[1, 1], self.box[2, 2], self.box[0, 1], self.box[2, 0], self.box[1, 0], self.box[0, 2], self.box[1, 2], self.box[2, 0]] # gromacs format box vects with open('%s/../top/Monomer_Tops/%s.itp' % (location, self.residue), 'r') as f: residue_top = [] for line in f: residue_top.append(line) atoms_index = 0 while residue_top[atoms_index].count('[ atoms ]') == 0: atoms_index += 1 while residue_top[atoms_index] != '\n': atoms_index += 1 residue_top.insert(atoms_index, '{:>6d}{:>5s}{:>6d}{:>6s}{:>6s}{:>5d}{:>13.6f}{:>13.6f}\n'.format( self.LC.natoms + 1, 'hc_d', 1, self.LC.residues[0], 'HD', self.LC.natoms + 1, 0, 0)) if self.vparams[0] == '3fd': # 'a' = 0.5 and 'b' = 0.14 (aromatic carbon bond length (nm)) puts a vsite in the middle of benzene # if the constructor atoms are 3 non-adjacent carbons from the ring residue_top.append('[ virtual_sites3 ]\n') residue_top.append('{:<6d}{:<6d}{:<6d}{:<6d}{:<6d}{:<8.4f}{:<8.4f}\n'.format(self.LC.natoms + 1, self.vatoms_numbers[0], self.vatoms_numbers[1], self.vatoms_numbers[2], 2, float(self.vparams[-2]), float(self.vparams[-1]))) else: print('Your choice of virtual site has not yet been implemented') exit() # groups = np.reshape(self.coords, (len(self.keep) // len(atoms), len(atoms), 3)) # centers_of_mass = np.mean(groups, axis=1) # self.coords = centers_of_mass # redefine coordinates as centers of mass file_rw.write_assembly(residue_top, '%s.itp' % self.name, self.nmon, bcc=bcc, xlink=xlink) # now the dummies need to be added to the .gro file. They are placed at the end of the residue section # This loop works for a single virtual site per monomer. It will need to be modified if multiple sites # are to be constructed. insert_ndx = self.LC.natoms self.atom_numbers = [] # redefine this since everything is renumbered for i in range(self.nmon): ndx = (i + 1)*insert_ndx + i self.ids.insert(ndx, 'HD') self.res.insert(ndx, 'HII') # should make this more general self.all_coords = np.insert(self.all_coords, ndx, np.array([0, 0, 0]), axis=0) self.atom_numbers.append(ndx + 1) file_rw.write_gro_pos(self.all_coords, '%s.gro' % self.name, ids=self.ids, res=self.res, box=self.box_gromacs) else: file_rw.write_assembly(res, '%s.itp' % self.name, self.nmon, bcc=bcc, xlink=xlink) with open('%s.itp' % self.name, 'r') as f: self.topology = [] for line in f: self.topology.append(line)
ld=ld, nlist=nlist) else: print("Loading saved arrays") arrays = np.load('angles_ld_%s.npz' % args.suffix, encoding='bytes') angles = arrays['angles'] centroids = arrays['centroids'] atoms_per_frame = int(angles.shape[0] / centroids.shape[0]) angles = angles[args.start * atoms_per_frame:args.end * atoms_per_frame] ld = arrays['ld'] nlist = arrays['nlist'] if args.write_gro: file_rw.write_gro_pos(centroids[-1, :, :], 'centroids.gro', name='NA') angles = [value for value in angles if not math.isnan(value)] nbins = 45 (counts, bins) = np.histogram(angles, bins=nbins) # plt.hist(angles, bins=nbins) # plt.show() bin_width = bins[1] - bins[0] db = bin_width bin_centers = [ bins[i - 1] + ((bins[i] - bins[i - 1]) / 2) for i in range(1, len(bins)) ] integrated_area = np.trapz(