Source code for grits.utils

"""Utility functions for GRiTS."""

import json
import re
import warnings

import ele
import freud
import numpy as np
import rowan
from ele import element_from_symbol
from mbuild.box import Box
from mbuild.compound import Compound, Particle


[docs] class NumpyEncoder(json.JSONEncoder): """Serializer for numpy objects."""
[docs] def default(self, obj): """Overwrite the default for numpy arrays and data types.""" if isinstance(obj, np.integer): return int(obj) elif isinstance(obj, np.floating): return float(obj) elif isinstance(obj, np.ndarray): return obj.tolist() else: return super(NumpyEncoder, self).default(obj)
[docs] def comp_from_snapshot(snapshot, indices, length_scale=1.0, mass_scale=1.0): """Convert particles by indices from a Snapshot to a Compound. Parameters ---------- snapshot : gsd.hoomd.Frame Snapshot from which to build the mbuild Compound. indices : np.ndarray Indices of the particles to be added to the compound. length_scale : float, default 1.0 Value by which to scale the length values mass_scale : float, default 1.0 Value by which to scale the mass values Returns ------- comp : mbuild.Compound Note ---- GSD snapshots center their boxes on the origin (0,0,0), so the compound is shifted by half the box lengths """ comp = Compound() bond_array = snapshot.bonds.group n_atoms = snapshot.particles.N # gsd / hoomd v3 box = np.asarray(snapshot.configuration.box) comp.box = Box.from_lengths_tilt_factors( lengths=box[:3] * length_scale, tilt_factors=box[3:] ) # GSD and HOOMD snapshots center their boxes on the origin (0,0,0) shift = np.array(comp.box.lengths) / 2 particle_dict = {} # Add particles for i in range(n_atoms): if i in indices: name = snapshot.particles.types[snapshot.particles.typeid[i]] xyz = snapshot.particles.position[i] * length_scale + shift mass = snapshot.particles.mass[i] * mass_scale element = ele.element_from_symbol(name) atom = Particle(name=name, pos=xyz, mass=mass, element=element) comp.add(atom, label=str(i)) particle_dict[i] = atom # Add bonds for i, j in snapshot.bonds.group: if i in indices and j in indices: comp.add_bond([particle_dict[i], particle_dict[j]]) return comp
[docs] def align(compound, particle, towards_compound, around=None): """Spin a compound such that particle points at towards_compound. Parameters ---------- compound : mbuild.Compound or CG_Compound, The compound to align particle : mbuild.Compound or CG_Compound, The particle to point at towards_compound. Child of compound. towards_compound : mbuild.Compound or CG_Compound, The compound to point towards. around : numpy.ndarray, default None The unit vector around which the compound is spun. If None is given, an orthogonal vector is chosen. Returns ------- numpy.ndarray The unit vector about which the compound is rotated """ # Find the unit vector from compound center to particle vec = np.array(particle.pos - compound.center) sep = np.linalg.norm(vec) comp_to_part = vec / sep # Get the unit vector between the two centers # end - start: from 1 -> 2 vec = np.array(towards_compound.center - compound.center) sep = np.linalg.norm(vec) towards_to_comp = vec / sep if around is None: # Next get a vector orthogonal to both vectors, # this is the vector around which the compound is spun around = np.cross(comp_to_part, towards_to_comp) # and the angle between the two vectors (in rad) angle = np.arccos(np.dot(comp_to_part, towards_to_comp)) compound.spin(angle, around) return around
[docs] def get_bonds(compound): """Convert Particle instances in bond_graph.edges to their indices. See :py:meth:`CG_Compound.bond_graph.edges`. Parameters ---------- compound : mbuild.Compound or CG_Compound Compound from which to get the indexed bond graph. Returns ------- list[tuple(int, int)] Sorted list of bonded particle indices """ particle_list = [p for p in compound] bonds = [] for tup in compound.bond_graph.edges(): bonds.append(tuple(sorted(map(particle_list.index, tup)))) # This sorting is required for coarse-graining bonds.sort(key=lambda tup: (tup[0], tup[1])) return bonds
[docs] def get_bonded(compound, particle): """Get particles bonded to the given particle in a compound. Parameters ---------- compound : mbuild.Compound or CG_Compound Compound containing particle particle : mbuild.Particle or Bead Particle to which the directly bonded neighbors are requested Returns ------- list[mbuild.Particle] The bonded particles. """ def is_particle(i, j): if i is particle: return j elif j is particle: return i else: return False xs = [is_particle(i, j) for i, j in compound.bonds() if is_particle(i, j)] # The following logic won't be necessary when bond graph is deterministic # https://github.com/mosdef-hub/mbuild/issues/895 # and instead we can just do: return xs
# return [x for _,x in sorted(zip([get_index(compound,i) for i in xs],xs))]
[docs] def get_index(compound, particle): """Get the index of a Particle in the Compound. The particle can be accessed like compound[index]. Parameters ---------- compound : mbuild.Compound or CG_Compound Compound which contains particle particle : mbuild.Particle or Bead Particle for which to fetch the index Returns ------- int The particle index """ return [p for p in compound].index(particle)
[docs] def get_heavy_atoms(particles): """Filter out hydrogens for axis-angle calculation. Returns arrays of only heavy atoms' positions and masses, given a gsd.frame.particles object. Used in Aniso mapping. Parameters ---------- particles : gsd.frame.particles Particles from all atom gsd frame. Returns ------- heavy_partpos : numpy array Array of all positions of heavy atoms heavy_partmass : numpy array Array of all masses of heavy atoms """ partpos = particles.position partmass = particles.mass partelem = particles.typeid heavy_atom_indicies = np.where(partelem != 1)[0] heavy_partpos = partpos[heavy_atom_indicies] heavy_partmass = partmass[heavy_atom_indicies] return heavy_partpos, heavy_partmass
[docs] def get_major_axis(positions_arr): """Find the major axis for GB CG representation. Used in axis-angle orientation representation. Parameters ---------- positions_arr : numpy array N_particlesx3 numpy array of particle positions to map to one aniso bead. elements_arr : list List of length N_particles containing particle elements Returns ------- major_axis : numpy array array designating vector of major axis of Gay-Berne particle particle_indicies : tuple of ints tuple of two particle indices used to calculate major axis vector """ major_axis = None max_dist = 0 AB_indicies = (None, None) for i, x0 in enumerate(positions_arr): for j, x1 in enumerate(positions_arr[i + 1 :]): vect = x1 - x0 dist = np.linalg.norm(vect) if dist > max_dist: max_dist = dist major_axis = vect # adjust j for loop stride AB_indicies = (i, j + i + 1) return major_axis, AB_indicies
[docs] def get_com(particle_positions, particle_masses): """Calculate center of mass coordinates. Given a set of particle positions and masses, find their center of mass. Arrays must be of same dimension. Parameters ---------- particle_positions : numpy array N_particlesx3 numpy array of particle positions (x,y,z) particle_masses : numpy array N_particlesx0 numpy array of particle masses Returns ------- center_of_mass : numpy array 3x0 numpy array of center of mass coordinates """ M = np.sum(particle_masses) weighted_positions = particle_positions * particle_masses[:, np.newaxis] center_of_mass = np.sum(weighted_positions / M, axis=0) return center_of_mass
[docs] def get_quaternion(n1, n0=np.array([0, 0, 1])): """Calculate rotation quaternion from axis vectors. Calculate axis and angle of rotation given two planes' normal vectors, which is then used to calculate the quaternions for HOOMD. Parameters ---------- n1 : numpy array numpy array that is the major axis vector. n0 : numpy array numpy array that is used to define the default quaternion. Defaults to the Z-axis. Returns ------- quaternion : numpy array numpy array that tells the position of the monomer in units of a quaternion. """ if n1 is None: # one atom in this bead -> default quaternion warnings.warn( "get_quaternion was called with None as input!\n\ Returning default orientation." ) return np.array([0, 0, 0, 1]) V_axis = np.cross(n0, n1) theta_numerator = np.dot(n0, n1) theta_denominator = np.linalg.norm(n0) * np.linalg.norm(n1) theta_rotation = np.arccos(theta_numerator / theta_denominator) quaternion = rowan.from_axis_angle(V_axis, theta_rotation) return quaternion
[docs] def get_hydrogen(compound, particle): """Get the first hydrogen attached to particle. Parameters ---------- compound : mbuild.Compound or CG_Compound Compound which contains particle particle : mbuild.Particle or Bead Particle from which to remove a hydrogen """ hydrogens = [i for i in get_bonded(compound, particle) if i.name == "H"] if hydrogens: for p in compound: if p in hydrogens: return p else: return None
[docs] def has_number(string): """Determine whether a string contains a number. Parameters ---------- string : str String which may contain a number Returns ------- bool Whether the string contains a number. """ return bool(re.search("[0-9]", string))
[docs] def has_common_member(it_a, it_b): """Determine if two iterables share a common member. Parameters ---------- it_a, it_b : iterable object Iterable objects to compare Returns ------- bool Whether the object share a common member. """ return set(it_a) & set(it_b)
[docs] def num2str(num): """Convert a number to a string. Convert positive integers up to 701 into a capital letter (or letters). Parameters ---------- num : int Number to convert Examples -------- >>> num2str(0) 'A' >>> num2str(25) 'Z' >>> num2str(25) 'AA' Returns ------- str The string conversion of the number """ if num < 26: return chr(num + 65) return "".join([chr(num // 26 + 64), chr(num % 26 + 65)])
amber_dict = { "c": ele.element_from_symbol("C"), "c1": ele.element_from_symbol("C"), "c2": ele.element_from_symbol("C"), "c3": ele.element_from_symbol("C"), "ca": ele.element_from_symbol("C"), "cp": ele.element_from_symbol("C"), "cq": ele.element_from_symbol("C"), "cc": ele.element_from_symbol("C"), "cd": ele.element_from_symbol("C"), "ce": ele.element_from_symbol("C"), "cf": ele.element_from_symbol("C"), "cg": ele.element_from_symbol("C"), "ch": ele.element_from_symbol("C"), "cx": ele.element_from_symbol("C"), "cy": ele.element_from_symbol("C"), "cu": ele.element_from_symbol("C"), "cv": ele.element_from_symbol("C"), "h1": ele.element_from_symbol("H"), "h2": ele.element_from_symbol("H"), "h3": ele.element_from_symbol("H"), "h4": ele.element_from_symbol("H"), "h5": ele.element_from_symbol("H"), "ha": ele.element_from_symbol("H"), "hc": ele.element_from_symbol("H"), "hn": ele.element_from_symbol("H"), "ho": ele.element_from_symbol("H"), "hp": ele.element_from_symbol("H"), "hs": ele.element_from_symbol("H"), "hw": ele.element_from_symbol("H"), "hx": ele.element_from_symbol("H"), "f": ele.element_from_symbol("F"), "cl": ele.element_from_symbol("Cl"), "br": ele.element_from_symbol("Br"), "i": ele.element_from_symbol("I"), "n": ele.element_from_symbol("N"), "n1": ele.element_from_symbol("N"), "n2": ele.element_from_symbol("N"), "n3": ele.element_from_symbol("N"), "n4": ele.element_from_symbol("N"), "na": ele.element_from_symbol("N"), "nb": ele.element_from_symbol("N"), "nc": ele.element_from_symbol("N"), "nd": ele.element_from_symbol("N"), "ne": ele.element_from_symbol("N"), "nf": ele.element_from_symbol("N"), "nh": ele.element_from_symbol("N"), "no": ele.element_from_symbol("N"), "o": ele.element_from_symbol("O"), "oh": ele.element_from_symbol("O"), "os": ele.element_from_symbol("O"), "ow": ele.element_from_symbol("O"), "p2": ele.element_from_symbol("P"), "p3": ele.element_from_symbol("P"), "p4": ele.element_from_symbol("P"), "p5": ele.element_from_symbol("P"), "pb": ele.element_from_symbol("P"), "pc": ele.element_from_symbol("P"), "pd": ele.element_from_symbol("P"), "pe": ele.element_from_symbol("P"), "pf": ele.element_from_symbol("P"), "px": ele.element_from_symbol("P"), "py": ele.element_from_symbol("P"), "s": ele.element_from_symbol("S"), "s2": ele.element_from_symbol("S"), "s4": ele.element_from_symbol("S"), "s6": ele.element_from_symbol("S"), "sh": ele.element_from_symbol("S"), "ss": ele.element_from_symbol("S"), "sx": ele.element_from_symbol("S"), "sy": ele.element_from_symbol("S"), }