@@ -792,62 +792,79 @@ def compute_dynmat(R, D, M, E, params):
792792 return w , w_inv_cm , U_a
793793
794794
795- def get_xyz (E , R , M , U , mode ):
796- """
797795
798- This function returns a string in the xyz format with X, Y, Z and UX, UY, UZ
799- where X,Y,Z are the coordinates, UX, UY, UZ - vectors coming from those coordinates - e.g. normal modes
796+ def normal2xyz (elements , R , M , U , mode , mass_weighted = True , comment = "Normal mode" ):
797+ """
798+ Generate an XYZ-format string for visualizing a normal mode.
800799
801- Args:
802- E ( list of ndof/3 strings ): atom names (elements) of all atoms
803- R ( MATRIX(ndof x nsteps-1) ): coordinates of all DOFs for all mid-timesteps
804- M ( MATRIX(ndof x 1) ): masses of all DOFs
805- U ( MATRIX(ndof x ndof) ): a matrix containing normal mode vectors
806- mode ( int ): index of the normal mode that we want to visualize
800+ Each atom line contains:
801+ X, Y, Z : Cartesian coordinates
802+ UX, UY, UZ : displacement vector components of the selected normal mode
807803
808- Returns:
809- string: A string representing an xyz file
804+ Parameters
805+ ----------
806+ elements : list of str, length = nat
807+ Atomic symbols (one per atom).
808+ R : np.ndarray, shape (3*nat,) or (nat, 3)
809+ Cartesian coordinates in Angstrom.
810+ M : np.ndarray or None, shape (3*nat,) or (nat, 3)
811+ Masses associated with each Cartesian degree of freedom.
812+ Required only if mass_weighted=True.
813+ U : np.ndarray, shape (3*nat, ndof)
814+ Normal-mode eigenvectors. Each column corresponds to one mode.
815+ mode : int
816+ Index of the normal mode to visualize (column index of U).
817+ mass_weighted : bool, optional
818+ If True, divide displacement vectors by sqrt(mass) for each DOF.
819+ Default is True.
820+ comment : str, optional
821+ Comment line written to the XYZ file.
810822
811- """
823+ Returns
824+ -------
825+ xyz_str : str
826+ String containing the XYZ-formatted data.
812827
813- natoms = len (E )
814- res = "%3i\n Comment\n " % (natoms )
815828
816- for i in range (0 , natoms ):
817- x , y , z = R .get (3 * i , 0 ), R .get (3 * i + 1 , 0 ), R .get (3 * i + 2 , 0 )
818- ux = U .get (3 * i + 0 , mode ) / math .sqrt (M .get (3 * i + 0 ))
819- uy = U .get (3 * i + 1 , mode ) / math .sqrt (M .get (3 * i + 1 ))
820- uz = U .get (3 * i + 2 , mode ) / math .sqrt (M .get (3 * i + 2 ))
829+ Examples
830+ --------
831+ >>> normal2xyz(E, R, M, U, mode, mass_weighted=True)
832+ >>> normal2xyz(E, R, None, U, mode, mass_weighted=False)
821833
822- res = res + "%s %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f \n " % ( E [ i ], x , y , z , ux , uy , uz )
834+ """
823835
824- return res
836+ elements = list (elements )
837+ nat = len (elements )
825838
839+ # --- reshape coordinates if needed ---
840+ R = np .asarray (R )
841+ if R .ndim == 1 :
842+ R = R .reshape (nat , 3 )
826843
827- def get_xyz2 ( E , R , U , mode ):
828- """
844+ # --- extract mode vector ---
845+ mode_vec = U [:, mode ]. reshape ( nat , 3 )
829846
830- This function returns a string in the xyz format with X, Y, Z and UX, UY, UZ
831- where X,Y,Z are the coordinates, UX, UY, UZ - vectors coming from those coordinates - e.g. normal modes
847+ # --- apply mass weighting if requested ---
848+ if mass_weighted :
849+ if M is None :
850+ raise ValueError ("Mass array M must be provided when mass_weighted=True" )
832851
833- Args:
834- E ( list of ndof/3 string ): atom names (elements) of all atoms
835- R ( MATRIX(ndof x nsteps-1) ): coordinates of all DOFs for all mid-timesteps
836- U ( MATRIX(ndof x ndof) ): a matrix containing normal mode vectors
837- mode ( int ): index of the normal mode that we want to visualize
852+ M = np .asarray (M )
853+ if M .ndim == 1 :
854+ M = M .reshape (nat , 3 )
838855
839- Returns:
840- string: A string representing an xyz file
856+ mode_vec = mode_vec / np .sqrt (M )
841857
842- """
858+ # --- build XYZ string ---
859+ lines = [f"{ nat } " , comment ]
843860
844- natoms = len (E )
845- res = "%3i\n Comment\n " % (natoms )
861+ for elt , (x , y , z ), (ux , uy , uz ) in zip (elements , R , mode_vec ):
862+ lines .append (
863+ f"{ elt :2s} "
864+ f"{ x :10.5f} { y :10.5f} { z :10.5f} "
865+ f"{ ux :10.5f} { uy :10.5f} { uz :10.5f} "
866+ )
846867
847- for i in range (0 , natoms ):
848- x , y , z = R .get (3 * i , 0 ), R .get (3 * i + 1 , 0 ), R .get (3 * i + 2 , 0 )
849- ux , uy , uz = U .get (3 * i + 0 , mode ), U .get (3 * i + 1 , mode ), U .get (3 * i + 2 , mode )
868+ return "\n " .join (lines ) + "\n "
850869
851- res = res + "%s %5.3f %5.3f %5.3f %5.3f %5.3f %5.3f\n " % (E [i ], x , y , z , ux , uy , uz )
852870
853- return res
0 commit comments