def angle_distribution(gsd_file, A_name, B_name, C_name, start=0, stop=-1, degrees=False, histogram=False, theta_min=0.0, theta_max=None, normalize=False, bins="auto"): """Returns the bond angle distribution for a given triplet of particles Parameters ---------- gsdfile : str Filename of the GSD trajectory. A_name, B_name, C_name : str Name(s) of particles that form the angle triplet (found in gsd.hoomd.Snapshot.particles.types) They must be given in the same order as they form the angle start : int Starting frame index for accumulating bond lengths. Negative numbers index from the end. (default 0) stop : int Final frame index for accumulating bond lengths. (default -1) degrees : bool, default=False If True, the angle values are returned in degrees. if False, the angle values are returned in radians. histogram : bool, default=False If set to True, places the resulting angles into a histogram and retrums the histogram's bin centers and heights as opposed to the actual calcualted angles. theta_min : float, default = 0.0 Sets the minimum theta value to be included in the distribution theta_max : float, default = None Sets the maximum theta value to be included in the distribution If left as None, then theta_max will be either pi radians or 180 degrees depending on the value set for the degrees parameter normalize : bool, default=False If set to True, normalizes the angle distribution by the sum of the bin heights, so that the distribution adds up to 1. bins : float, int, or str, default="auto" The number of bins to use when finding the distribution of bond angles. Using "auto" will set the number of bins based on the ideal bin size for the data. See the numpy.histogram docs for more details. Returns ------- 1-D numpy.array or 2-D numpy.array If histogram is False, Array of actual bond angles in degrees If histogram is True, returns a 2D array of bin centers and bin heights. """ if not degrees and theta_max is None: theta_max = np.pi elif degrees and theta_max is None: theta_max = 180 trajectory = gsd.hoomd.open(gsd_file, mode="rb") name = "-".join([A_name, B_name, C_name]) name_rev = "-".join([C_name, B_name, A_name]) angles = [] for snap in trajectory[start:stop]: if name not in snap.angles.types and name_rev not in snap.angles.types: raise ValueError(f"Angles {name} or {name_rev} not found in " " snap.angles.types. " "A_name, B_name, C_name must match the order " "as they appear in snap.angles.types.") for idx, angle_id in enumerate(snap.angles.typeid): angle_name = snap.angles.types[angle_id] if angle_name == name or angle_name == name_rev: pos1 = snap.particles.position[snap.angles.group[idx][0]] img1 = snap.particles.image[snap.angles.group[idx][0]] pos2 = snap.particles.position[snap.angles.group[idx][1]] img2 = snap.particles.image[snap.angles.group[idx][1]] pos3 = snap.particles.position[snap.angles.group[idx][2]] img3 = snap.particles.image[snap.angles.group[idx][2]] pos1_unwrap = pos1 + (img1 * snap.configuration.box[:3]) pos2_unwrap = pos2 + (img2 * snap.configuration.box[:3]) pos3_unwrap = pos3 + (img3 * snap.configuration.box[:3]) u = pos1_unwrap - pos2_unwrap v = pos3_unwrap - pos2_unwrap angles.append( np.round(angle_between_vectors(u, v, False, degrees), 3)) trajectory.close() if histogram: if angles[0] < theta_min or angles[-1] > theta_max: warnings.warn("There are bond angles that fall outside of " "your set theta_min and theta_max range. " "You may want to adjust this range to " "include all bond angles.") bin_centers, bin_heights = get_histogram(np.array(angles), bins=bins, x_range=(theta_min, theta_max)) return np.stack((bin_centers, bin_heights)).T else: return np.array(angles)
def test_histogram_normalize(self): sample = np.random.randn(100) * -1 bin_c, bin_h = get_histogram(sample, normalize=True) assert all(bin_h <= 1)
def bond_distribution(gsd_file, A_name, B_name, start=0, stop=-1, histogram=False, l_min=0.0, l_max=4.0, normalize=True, bins=100): """Returns the bond length distribution for a given bond pair Parameters ---------- gsdfile : str Filename of the GSD trajectory. A_name, B_name : str Name(s) of particles that form the bond pair (found in gsd.hoomd.Snapshot.particles.types) start : int Starting frame index for accumulating bond lengths. Negative numbers index from the end. (default 0) stop : int Final frame index for accumulating bond lengths. (default -1) histogram : bool, default=False If set to True, places the resulting bonds into a histogram and retrums the histogram's bin centers and heights as opposed to the actual calcualted bonds. l_min : float, default = 0.0 Sets the minimum bond length to be included in the distribution l_max : float, default = 5.0 Sets the maximum bond length value to be included in the distribution normalize : bool, default=False If set to True, normalizes the angle distribution by the sum of the bin heights, so that the distribution adds up to 1. bins : float, int, or str, default="auto" The number of bins to use when finding the distribution of bond angles. Using "auto" will set the number of bins based on the ideal bin size for the data. See the numpy.histogram docs for more details. Returns ------- 1-D numpy.array or 2-D numpy.array If histogram is False, Array of actual bond angles in degrees If histogram is True, returns a 2D array of bin centers and bin heights. """ trajectory = gsd.hoomd.open(gsd_file, mode="rb") name = "-".join([A_name, B_name]) name_rev = "-".join([B_name, A_name]) bonds = [] for snap in trajectory[start:stop]: if name not in snap.bonds.types and name_rev not in snap.bonds.types: raise ValueError(f"Bond types {name} or {name_rev} not found " "snap.bonds.types.") for idx, bond in enumerate(snap.bonds.typeid): bond_name = snap.bonds.types[bond] if bond_name in [name, name_rev]: pos1 = snap.particles.position[snap.bonds.group[idx][0]] img1 = snap.particles.image[snap.bonds.group[idx][0]] pos2 = snap.particles.position[snap.bonds.group[idx][1]] img2 = snap.particles.image[snap.bonds.group[idx][1]] pos1_unwrap = pos1 + (img1 * snap.configuration.box[:3]) pos2_unwrap = pos2 + (img2 * snap.configuration.box[:3]) bonds.append( np.round(np.linalg.norm(pos2_unwrap - pos1_unwrap), 3)) trajectory.close() if histogram: if bonds[0] < l_min or bonds[-1] > l_max: warnings.warn( "There are bond lengths that fall outside of " "your set l_min and l_max range. You may want to adjust " "this range to include all bond lengths.") bin_centers, bin_heights = get_histogram(np.array(bonds), bins=bins, x_range=(l_min, l_max)) return np.stack((bin_centers, bin_heights)).T else: return np.array(bonds)
def test_histogram_bins(self): sample = np.random.randn(100) bin_c, bin_h = get_histogram(sample, bins=20) assert len(bin_c) == len(bin_h) == 20