def LabZ(molecule, sel1=None, sel2=None, Nuc=None, resids=None, segids=None, filter_str=None): """Motion projected to the Z-axis of the Lab frame. Use only for systems that remain aligned along z """ if Nuc is not None: sel1, sel2 = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel1 = selt.sel_simple(molecule, sel1, resids, segids, filter_str) sel2 = selt.sel_simple(molecule, sel2, resids, segids, filter_str) uni = molecule.mda_object def sub(): box = uni.dimensions[0:3] v = sel1.positions - sel2.positions v = vft.pbc_corr(v.T, box) v[:2] = 0 return v return sub
def bond(molecule, sel1=None, sel2=None, sel3=None, Nuc=None, resids=None, segids=None, filter_str=None): """Bond defines the frame. sel1/sel2 : Defines the z-axis of the frame (the bond itself). Follows the argument rules of sel_simple (sel2 should usually be the heteroatom) Nuc : Automatically sets sel1 and sel2 for a given nucleus definition sel3 : sel2 and sel3 will define the xz-plane of the bond frame. This is optional: however, if this frame is the PAS of the bond responsible for relaxation, then frames may not function correctly if this is not provided. By default, sel3 is set to None and is omitted. However, if called from within mol. resids, segids, filter_str apply additional filters to sel1, sel2, and sel3 if defined. """ if Nuc is not None: sel1, sel2 = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel1 = selt.sel_simple(molecule, sel1, resids, segids, filter_str) sel2 = selt.sel_simple(molecule, sel2, resids, segids, filter_str) if isinstance(sel3, str) and sel3 == 'auto': uni = sel1.universe resids = np.unique(sel1.resids) sel0 = uni.residues[np.isin(uni.residues.resids, resids)].atoms sel3 = selt.find_bonded(sel2, sel0, exclude=sel1, n=1, sort='cchain')[0] elif sel3 is not None: sel3 = selt.sel_simple(molecule, sel3, resids, segids, filter_str) uni = molecule.mda_object if sel3 is None: def sub(): box = uni.dimensions[0:3] v = sel1.positions - sel2.positions v = vft.pbc_corr(v.T, box) return v else: def sub(): box = uni.dimensions[0:3] vZ = sel1.positions - sel2.positions vXZ = sel3.positions - sel2.positions vZ = vft.pbc_corr(vZ.T, box) vXZ = vft.pbc_corr(vXZ.T, box) return vZ, vXZ return sub
def bond_rotate(molecule, sel1=None, sel2=None, sel3=None, Nuc=None, resids=None, segids=None, filter_str=None): """ Rotation around a given bond, defined by sel1 and sel2. Has a very similar effect to simply using bond with the same sel1 and sel2. However, an addition selection is created to a third atom. Then, the vector between sel1 and sel2 defines the rotation axis. However, rotation around this axis caused by more distant motions is removed, because a third selection (sel3) is used with sel2 to create a second vector, which then remains in the xz plane (if only sel1 and sel2 are specified for rotation, then some rotation further up a carbon chain, for example, may not move the vector between sel1 and sel2, but does cause rotation of the inner bonds- in most cases it is not clear if this is happening, but becomes particularly apparent when rotation appears on double bonds, where rotation should be highly restricted) sel3 may be defined, but is not required. If it is not provided, a third atom will be found that is bound to sel2 (this frame won't work if sel2 is not bound to any other atom). """ if Nuc is not None: sel1, sel2 = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel1 = selt.sel_simple(molecule, sel1, resids, segids, filter_str) sel2 = selt.sel_simple(molecule, sel2, resids, segids, filter_str) if sel3 is not None: sel3 = selt.sel_simple(molecule, sel3, resids, segids, filter_str) else: resids = np.unique(sel1.resids) i = np.isin(sel1.universe.residues.resids, resids) #Filter for atoms in the same residues sel0 = sel1.universe.residues[i].atoms sel3 = selt.find_bonded(sel2, sel0, sel1, n=1, sort='cchain')[0] uni = molecule.mda_object def sub(): box = uni.dimensions[0:3] v1 = sel1.positions - sel2.positions v2 = sel2.positions - sel3.positions v1 = vft.pbc_corr(v1.T, box) v2 = vft.pbc_corr(v2.T, box) return v1, v2 return sub
def chain_rotate(molecule, sel=None, Nuc=None, resids=None, segids=None, filter_str=None): """ Creates a frame for which a chain of atoms (usually carbons) is aligned such that the vector formed by the previous and next heteroatom (not 1H) are aligned along z. Note that the frame is selected with a single initial selection, and the function automatically searches for the surrounding atoms. In case a methyl carbon is included, the rotation is defined by the carbon itself and its nearest neighbor, instead of the surrounding two atoms (which would then have to include a methyl proton) """ uni = molecule.mda_object "Get the initial selection" if Nuc is not None: sel, _ = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel = selt.sel_simple(molecule, sel, resids, segids, filter_str) "Get all atoms in the residues included in the initial selection" resids = np.unique(sel.resids) sel0 = uni.residues[np.isin(uni.residues.resids, resids)].atoms "Get bonded" sel1, sel2 = selt.find_bonded(sel, sel0=sel0, n=2, sort='cchain') "Replace 1H with the original selection" i = sel2.types == 'H' sel20 = sel2 sel2 = uni.atoms[:0] for s2, s, i0 in zip(sel20, sel, i): if i0: sel2 += s else: sel2 += s2 def sub(): box = uni.dimensions[0:3] v = sel2.positions - sel1.positions v = vft.pbc_corr(v.T, box) return v return sub
def select_atoms(self, sel1=None, sel2=None, sel1in=None, sel2in=None, Nuc=None, resids=None, segids=None, filter_str=None): """ Selects the atoms to be used for bond definitions. sel1/sel2 : A string or an atom group (MDanalysis) defining the first/second atom in the bond sel1in/sel2in : Index to re-assign sel1/sel2 possibly to multiple bonds (example: if using string assignment, maybe to calculate for multiple H's bonded to the same C) Nuc : Keyword argument for selecting a particular type of bond (for example, N, C, CA would selection NH, C=O bonds, or CA-HA bonds, respectively) resids : Filter the selection defined by the above arguments for only certain residues segids : Filter the selection defined by the above arguments for only certain segments filter_str : Filter the selection by a string (MDAnalysis string selection) """ if Nuc is None: "Apply sel1 and sel2 selections directly" if sel1 is not None: self.sel1 = selt.sel_simple(self, sel1, resids, segids, filter_str) if sel1in is not None: self.sel1 = self.sel1[sel1in] if sel2 is not None: self.sel2 = selt.sel_simple(self, sel2, resids, segids, filter_str) if sel2in is not None: self.sel2 = self.sel2[sel2in] else: self.sel1, self.sel2 = selt.protein_defaults( Nuc, self, resids, segids, filter_str) if self.sel1 is not None and self.sel2 is not None and self.sel1.n_atoms == self.sel2.n_atoms: self.set_selection() "An attempt to generate a unique label under various conditions" count, cont = 0, True while cont: if count == 0: #One bond per residue- just take the residue number label = self.sel1.resids elif count == 1: #Multiple segments with same residue numbers label = np.array([ '{0}_{1}'.format(s.segid, s.resid) for s in self.sel1 ]) elif count == 2: #Same segment, but multiple bonds on the same residue (include names) label = np.array([ '{0}_{1}_{2}'.format(s1.resid, s1.name, s2.name) for s1, s2 in zip(self.sel1, self.sel2) ]) elif count == 3: #Multiple bonds per residue, and multiple segments label=np.array(['{0}_{1}_{2}_{3}'.format(s1.segid,s1.resid,s1.name,s2.name) \ for s1,s2 in zip(self.sel1,self.sel2)]) "We give up after this" count = count + 1 if np.unique(label).size == label.size or count == 4: cont = False self.label = label
def MOIbeta(molecule, sel, sel1=None, sel2=None, Nuc=None, index=None, resids=None, segids=None, filter_str=None): """ Separates out rotation within the moment of inertia frame (should be used in conjunction with MOIz). That is, we identify rotational motion, where the z-axis is the direction of the Moment of Inertia vector. The user must provide one or more selections to define the moment of inertia (sel). The user must also provide the selections to which the MOI is applied (sel1 and sel2, or Nuc). Additional filters will be used as normal, applied to all selections (resids,segids,filter_str). In case multiple MOI selections are provided (in a list), the user must provide an index, to specifify which bond goes with which MOI selection. This should usually be the same variable as provided for the frame_index when using MOIz (and one will usually not use a frame_index when setting up this frame) MOIxy(sel,sel1=None,sel2=None,Nuc=None,index=None,resids=None,segids=None,filter_str=None) """ sel = selt.sel_lists(molecule, sel, resids, segids, filter_str) if Nuc is not None: sel1, sel2 = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel1 = selt.sel_simple(molecule, sel1, resids, segids, filter_str) sel2 = selt.sel_simple(molecule, sel2, resids, segids, filter_str) uni = molecule.mda_object uni.trajectory[0] sel = selt.sel_lists(molecule, sel, resids, segids, filter_str) uni = molecule.mda_object uni.trajectory[0] box = uni.dimensions[:3] for k, s in enumerate(sel): vr = s.positions i0 = vft.sort_by_dist(vr) sel[k] = sel[k][i0] vref = list() for s in sel: v0 = vft.pbc_pos(s.positions.T, box) vref.append(vft.principle_axis_MOI(v0)[:, 0]) def MOIsub(): v = list() box = uni.dimensions[:3] for s, vr in zip(sel, vref): v0 = vft.pbc_pos(s.positions.T, box) v1 = vft.principle_axis_MOI(v0)[:, 0] v.append(v1 * np.sign(np.dot(v1, vr))) return np.array(v).T # for k,s in enumerate(sel): # vr=s.positions # i0=vft.sort_by_dist(vr) # sel[k]=sel[k][i0] if index is None: if len(sel) == 1: index = np.zeros(sel1.n_atoms, dtype=int) elif len(sel) == sel1.n_atoms: index = np.arange(sel1.n_atoms, dtype=int) else: print('index must be defined') return # vref=list() # box=uni.dimensions[:3] # for s in sel: # v0=vft.pbc_pos(s.positions.T,box) # vref.append(vft.principle_axis_MOI(v0)[:,0]) def sub(): vnorm = MOIsub() #Pre-allocate output vector, to point along z vZ = np.zeros([3, sel1.n_atoms]) vZ[2] = 1 #If a bond not in index, then vZ just on Z # vXZ=np.zeros([3,sel1.n_atoms]) # vXZ[0]=1 #If a bond not in index, then vXZ just along x sc = np.array(vft.getFrame(vnorm)).T v00 = vft.norm(vft.pbc_corr((sel1.positions - sel2.positions).T, box)) for k, (vn, sc0) in enumerate(zip(vnorm.T, sc)): v0 = v00[:, k == index] cb = v0[0] * vn[0] + v0[1] * vn[1] + v0[2] * vn[ 2] #Angle between MOI and bond cb[cb > 1] = 1.0 sb = np.sqrt(1 - cb**2) v0 = np.concatenate(([sb], [np.zeros(sb.shape)], [cb]), axis=0) "Here, we keep the vector fixed in the xz plane of the MOI frame" vZ[:, k == index] = vft.R(v0, *sc0) # vZ[:,k==index]=v0 # vXZ[:,k==index]=np.atleast_2d(vn).T.repeat(v0.shape[1],1) return vZ return sub
def MOIxy(molecule, sel, sel1=None, sel2=None, Nuc=None, index=None, resids=None, segids=None, filter_str=None): """ Separates out rotation within the moment of inertia frame (should be used in conjunction with MOIz). That is, we identify rotational motion, where the z-axis is the direction of the Moment of Inertia vector. The user must provide one or more selections to define the moment of inertia (sel). The user must also provide the selections to which the MOI is applied (sel1 and sel2, or Nuc). Additional filters will be used as normal, applied to all selections (resids,segids,filter_str). In case multiple MOI selections are provided (in a list), the user must provide an index, to specifify which bond goes with which MOI selection. This should usually be the same variable as provided for the frame_index when using MOIz (and one will usually not use a frame_index when setting up this frame) MOIxy(sel,sel1=None,sel2=None,Nuc=None,index=None,resids=None,segids=None,filter_str=None) """ sel = selt.sel_lists(molecule, sel, resids, segids, filter_str) if Nuc is not None: sel1, sel2 = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel1 = selt.sel_simple(molecule, sel1, resids, segids, filter_str) sel2 = selt.sel_simple(molecule, sel2, resids, segids, filter_str) uni = molecule.mda_object uni.trajectory[0] for k, s in enumerate(sel): vr = s.positions i0 = vft.sort_by_dist(vr) sel[k] = sel[k][i0] if index is None: if len(sel) == 1: index = np.zeros(sel1.n_atoms, dtype=int) elif len(sel) == sel1.n_atoms: index = np.arange(sel1.n_atoms, dtype=int) else: print('index must be defined') return def sub(): vnorm = list() box = uni.dimensions[:3] for s in sel: v0 = vft.pbc_pos(s.positions.T, box) vnorm.append(vft.principle_axis_MOI(v0)[:, 0]) vnorm = np.array(vnorm) #Pre-allocate output vector, to point along z v1 = np.zeros([3, sel1.n_atoms]) v1[2] = 1 v2 = v1.copy() v0 = vft.pbc_corr((sel1.positions - sel2.positions).T, box) for k, vn in enumerate(vnorm): v1[:, k == index] = vft.projXY(v0[:, k == index], vn) v2[:, k == index] = np.array([vn]).T.repeat((k == index).sum(), axis=1) return v1, v2 return sub
def librations0(molecule, sel1=None, sel2=None, Nuc=None, resids=None, segids=None, filter_str=None): """ Defines a frame for which librations are visible. That is, for a given bond, defined by sel1 and sel2, we search for two other atoms bound to the heteroatom (by distance). The reference frame is then defined by the heteroatom and the additional two atoms, leaving primarily librational motion of the bond. We preferentially select the other two atoms for larger masses, but they may also be protons (for example, a methyl H–C bond will be referenced to the next carbon but also another one of the protons of the methyl group) In case the heteroatom only has two bound partners, the second atom in the bond will also be used for alignment, reducing the effect motion (not very common in biomolecules) librations(sel1,sel2,Nuc,resids,segids,filter_str) """ if Nuc is not None: sel1, sel2 = selt.protein_defaults(Nuc, molecule, resids, segids, filter_str) else: sel1 = selt.sel_simple(molecule, sel1, resids, segids, filter_str) sel2 = selt.sel_simple(molecule, sel2, resids, segids, filter_str) if sel1.masses.sum() < sel2.masses.sum(): sel1, sel2 = sel2, sel1 #Make sel1 the heteroatom resids = np.unique(sel1.resids) i = np.isin(sel1.universe.residues.resids, resids) #Filter for atoms in the same residues sel0 = sel1.universe.residues[i].atoms sel2, sel3, sel4, sel5 = selt.find_bonded(sel1, sel0, n=4, sort='mass') def vfun(): v = list() for v1, v2, v3, v4, v5 in zip(sel1, sel2, sel3, sel4, sel5): v0 = np.array([ v2.position - v1.position, v3.position - v1.position, v4.position - v1.position, v5.position - v1.position ]) box = uni.dimensions[:3] v.append(vft.pbc_corr(v0.T, box)) return v uni = molecule.mda_object uni.trajectory.rewind() vref = vfun() def sub(): R = list() vecs = vfun() R = [vft.RMSalign(vr, v) for v, vr in zip(vecs, vref)] return vft.R2vec(R) return sub
def membrane_grid(molecule, grid_pts, sigma=25, sel0=None, sel='type P', resids=None, segids=None, filter_str=None): """ Calculates motion of the membrane normal, defined by a grid of points spread about the simulation. For each grid point, a normal vector is returned. The grid is spread uniformly around some initial selection (sel0 is a single atom!) in the xy dimensions (currently, if z is not approximately the membrane normal, this function will fail). The membrane normal is defined by a set of atoms (determined with some combination of the arguments sel, resids, segids, filter_str, with sel_simple) At each grid point, atoms in the selection will be fit to a plane. However, the positions will be weighted depending on how far they are away from that grid point in the xy dimensions. Weighting is performed with a normal distribution. sigma, by default, has a width approximately equal to the grid spacing (if x and y box lengths are different, we have to round off the spacing) The number of points is given by grid_pts. These points will be distributed automatically in the xy dimensions, to have approximately the same spacing in both dimensions. grid_pts will be changed to be the product of the exact number of points used (we will always distribute an odd number of points in each dimension, so the reference point is in the center of the grid) if sel0, defining the reference atom, is omitted, then the center of the box will be used. Otherwise, the grid will move around with the reference atom membrane_grid(molecule,grid_pts,sigma,sel0,sel,resids,segids,filter_str) """ uni = molecule.mda_object X, Y, Z = uni.dimensions[:3] nX, nY = 1 + 2 * np.round( (np.sqrt(grid_pts) - 1) / 2 * np.array([X / Y, Y / X])) dX, dY = X / nX, Y / nY print( '{0:.0f} pts in X, {1:.0f} pts in Y, for {2:.0f} total points'.format( nX, nY, nX * nY)) print('Spacing is {0:.2f} A in X, {0:.2f} A in Y'.format(dX, dY)) print('Center of grid is found at index {0:.0f}'.format(nX * (nY - 1) / 2 + (nX - 1) / 2)) print('sigma = {0:.2f} A'.format(sigma)) if sel0 is not None: sel0 = selt.sel_simple(molecule, sel0) #Make sure this is an atom group if hasattr(sel0, 'n_atoms'): if sel0.n_atoms != 1: print( 'Only one atom should be selected as the membrane grid reference point' ) print('Setup failed') return else: sel0 = sel0[0] #Make sure we have an atom, not an atom group tophalf = sel0.position[2] > Z / 2 #Which side of the membrane is this? else: tophalf = True "Atoms defining the membrance surface" sel = selt.sel_simple(molecule, sel, resids, segids, filter_str) "Filter for only atoms on the same side of the membrane" sel = sel[sel.positions[:, 2] > Z / 2] if tophalf else sel[sel.positions[:, 2] < Z / 2] def grid(): "Subfunction, calculates the grid" X0, Y0 = ( X / 2, Y / 2 ) if sel0 is None else sel0.position[: 2] #Grid at center, or at position of sel0 Xout = np.transpose([X0 + (np.arange(nX) - (nX - 1) / 2) * dX ]).repeat(nY, axis=1).reshape(int(nX * nY)) Yout = np.array([Y0 + (np.arange(nY) - (nY - 1) / 2) * dY ]).repeat(nY, axis=0).reshape(int(nX * nY)) return Xout, Yout def sub(): "Calculate planes for each element in grid" X, Y = grid() v = list() box = uni.dimensions[:3] for x, y in zip(X, Y): v0 = vft.pbc_corr(np.transpose(sel.positions - [x, y, 0]), box) d2 = v0[0]**2 + v0[1]**2 i = d2 > 3 * sigma weight = np.exp(-d2[i] / (2 * sigma**2)) v.append(vft.RMSplane(v0[:, i], np.sqrt(weight))) v = np.transpose(v) return v / np.sign(v[2]) return sub